Merge commit '609cd310be
' into clippy-subtree-update
This commit is contained in:
commit
8a7d8ece32
208 changed files with 6014 additions and 1427 deletions
|
@ -5380,6 +5380,7 @@ Released 2018-09-13
|
|||
[`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
|
||||
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
|
||||
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
|
||||
[`as_pointer_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_pointer_underscore
|
||||
[`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut
|
||||
[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
|
||||
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
|
||||
|
@ -5490,6 +5491,7 @@ Released 2018-09-13
|
|||
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
|
||||
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
|
||||
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
|
||||
[`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs
|
||||
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
|
||||
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
|
||||
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
|
||||
|
@ -5685,6 +5687,7 @@ Released 2018-09-13
|
|||
[`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
|
||||
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
|
||||
[`literal_string_with_formatting_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#literal_string_with_formatting_args
|
||||
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
|
||||
[`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
|
||||
|
@ -5966,6 +5969,7 @@ Released 2018-09-13
|
|||
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
|
||||
[`repeat_vec_with_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity
|
||||
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
|
||||
[`repr_packed_without_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#repr_packed_without_abi
|
||||
[`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization
|
||||
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
|
||||
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
|
||||
|
@ -6210,6 +6214,7 @@ Released 2018-09-13
|
|||
[`allow-comparison-to-zero`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-comparison-to-zero
|
||||
[`allow-dbg-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-dbg-in-tests
|
||||
[`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests
|
||||
[`allow-indexing-slicing-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-indexing-slicing-in-tests
|
||||
[`allow-mixed-uninlined-format-args`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-mixed-uninlined-format-args
|
||||
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
|
||||
[`allow-panic-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-panic-in-tests
|
||||
|
|
|
@ -37,7 +37,7 @@ impl LateLintPass<'_> for MyStructLint {
|
|||
// Get type of `expr`
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
// Match its kind to enter its type
|
||||
match ty.kind {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
|
||||
_ => ()
|
||||
}
|
||||
|
|
|
@ -5,68 +5,108 @@ Backports in Clippy are rare and should be approved by the Clippy team. For
|
|||
example, a backport is done, if a crucial ICE was fixed or a lint is broken to a
|
||||
point, that it has to be disabled, before landing on stable.
|
||||
|
||||
Backports are done to the `beta` branch of Clippy. Backports to stable Clippy
|
||||
releases basically don't exist, since this would require a Rust point release,
|
||||
which is almost never justifiable for a Clippy fix.
|
||||
> Note: If you think a PR should be backported you can label it with
|
||||
> `beta-nominated`. This has to be done before the Thursday the week before the
|
||||
> release.
|
||||
|
||||
## Filtering PRs to backport
|
||||
|
||||
First, find all labeled PRs using [this filter][beta-accepted-prs].
|
||||
|
||||
Next, look at each PR individually. There are a few things to check. Those need
|
||||
some explanation and are quite subjective. Good judgement is required.
|
||||
|
||||
1. **Is the fix worth a backport?**
|
||||
|
||||
This is really subjective. An ICE fix usually is. Moving a lint to a _lower_
|
||||
group (from warn- to allow-by-default) usually as well. An FP fix usually not
|
||||
(on its own). If a backport is done anyway, FP fixes might also be included.
|
||||
If the PR has a lot of changes, backports must be considered more carefully.
|
||||
|
||||
2. **Is the problem that was fixed by the PR already in `beta`?**
|
||||
|
||||
It could be that the problem that was fixed by the PR hasn't made it to the
|
||||
`beta` branch of the Rust repo yet. If that's the case, and the fix is
|
||||
already synced to the Rust repo, the fix doesn't need to be backported, as it
|
||||
will hit stable together with the commit that introduced the problem. If the
|
||||
fix PR is not synced yet, the fix PR either needs to be "backported" to the
|
||||
Rust `master` branch or to `beta` in the next backport cycle.
|
||||
|
||||
3. **Make sure that the fix is on `master` before porting to `beta`**
|
||||
|
||||
The fix must already be synced to the Rust `master` branch. Otherwise, the
|
||||
next `beta` will be missing this fix again. If it is not yet in `master` it
|
||||
should probably not be backported. If the backport is really important, do an
|
||||
out-of-cycle sync first. However, the out-of-cycle sync should be small,
|
||||
because the changes in that sync will get right into `beta`, without being
|
||||
tested in `nightly` first.
|
||||
|
||||
[beta-accepted-prs]: https://github.com/rust-lang/rust-clippy/issues?q=label%3Abeta-nominated
|
||||
|
||||
## Preparation
|
||||
|
||||
> Note: All commands in this chapter will be run in the Rust clone.
|
||||
|
||||
Follow the instructions in [defining remotes] to define the `clippy-upstream`
|
||||
remote in the Rust repository.
|
||||
|
||||
After that, fetch the remote with
|
||||
|
||||
```bash
|
||||
git fetch clippy-upstream master
|
||||
```
|
||||
|
||||
Then, switch to the `beta` branch:
|
||||
|
||||
```bash
|
||||
git switch beta
|
||||
git fetch upstream
|
||||
git reset --hard upstream/beta
|
||||
```
|
||||
|
||||
[defining remotes]: release.md#defining-remotes
|
||||
|
||||
## Backport the changes
|
||||
|
||||
Backports are done on the beta branch of the Clippy repository.
|
||||
When a PR is merged with the GitHub merge queue, the PR is closed with the message
|
||||
|
||||
> \<PR title\> (#\<PR number\>)
|
||||
|
||||
This commit needs to be backported. To do that, find the `<sha1>` of that commit
|
||||
and run the following command in the clone of the **Rust repository**:
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Clippy repository
|
||||
$ git checkout beta
|
||||
$ git checkout -b backport
|
||||
$ git cherry-pick <SHA> # `<SHA>` is the commit hash of the commit(s), that should be backported
|
||||
$ git push origin backport
|
||||
git cherry-pick -m 1 `<sha1>`
|
||||
```
|
||||
|
||||
Now you should test that the backport passes all the tests in the Rust
|
||||
repository. You can do this with:
|
||||
Do this for all PRs that should be backported.
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Rust repository
|
||||
$ git checkout beta
|
||||
# Make sure to change `your-github-name` to your github name in the following command
|
||||
$ git subtree pull -p src/tools/clippy https://github.com/<your-github-name>/rust-clippy backport
|
||||
$ ./x.py test src/tools/clippy
|
||||
## Open PR in the Rust repository
|
||||
|
||||
Next, open the PR for the backport. Make sure, the PR is opened towards the
|
||||
`beta` branch and not the `master` branch. The PR description should look like
|
||||
this:
|
||||
|
||||
```
|
||||
[beta] Clippy backports
|
||||
|
||||
r? @Mark-Simulacrum
|
||||
|
||||
Backports:
|
||||
- <Link to the Clippy PR>
|
||||
- ...
|
||||
|
||||
<Short summary of what is backported and why>
|
||||
```
|
||||
|
||||
Should the test fail, you can fix Clippy directly in the Rust repository. This
|
||||
has to be first applied to the Clippy beta branch and then again synced to the
|
||||
Rust repository, though. The easiest way to do this is:
|
||||
Mark is from the release team and they ultimately have to merge the PR before
|
||||
branching a new `beta` version. Tag them to take care of the backport. Next,
|
||||
list all the backports and give a short summary what's backported and why it is
|
||||
worth backporting this.
|
||||
|
||||
```bash
|
||||
# In the Rust repository
|
||||
$ git diff --patch --relative=src/tools/clippy > clippy.patch
|
||||
# In the Clippy repository
|
||||
$ git apply /path/to/clippy.patch
|
||||
$ git add -u
|
||||
$ git commit -m "Fix rustup fallout"
|
||||
$ git push origin backport
|
||||
```
|
||||
## Relabel backported PRs
|
||||
|
||||
After this, you can open a PR to the `beta` branch of the Clippy repository.
|
||||
When a PR is backported to Rust `beta`, label the PR with `beta-accepted`. This
|
||||
will then get picked up when [writing the changelog].
|
||||
|
||||
|
||||
## Update Clippy in the Rust Repository
|
||||
|
||||
This step must be done, **after** the PR of the previous step was merged.
|
||||
|
||||
After the backport landed in the Clippy repository, the branch has to be synced
|
||||
back to the beta branch of the Rust repository.
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Rust repository
|
||||
$ git checkout beta
|
||||
$ git checkout -b clippy_backport
|
||||
$ git subtree pull -p src/tools/clippy https://github.com/rust-lang/rust-clippy beta
|
||||
$ git push origin clippy_backport
|
||||
```
|
||||
|
||||
Make sure to test the backport in the Rust repository before opening a PR. This
|
||||
is done with `./x.py test src/tools/clippy`. If that passes all tests, open a PR
|
||||
to the `beta` branch of the Rust repository. In this PR you should tag the
|
||||
Clippy team member, that agreed to the backport or the `@rust-lang/clippy` team.
|
||||
Make sure to add `[beta]` to the title of the PR.
|
||||
[writing the changelog]: changelog_update.md#31-include-beta-accepted-prs
|
||||
|
|
|
@ -7,112 +7,114 @@ Clippy is released together with stable Rust releases. The dates for these
|
|||
releases can be found at the [Rust Forge]. This document explains the necessary
|
||||
steps to create a Clippy release.
|
||||
|
||||
1. [Remerge the `beta` branch](#remerge-the-beta-branch)
|
||||
2. [Update the `beta` branch](#update-the-beta-branch)
|
||||
3. [Find the Clippy commit](#find-the-clippy-commit)
|
||||
4. [Tag the stable commit](#tag-the-stable-commit)
|
||||
5. [Update `CHANGELOG.md`](#update-changelogmd)
|
||||
|
||||
> _NOTE:_ This document is for stable Rust releases, not for point releases. For
|
||||
> point releases, step 1. and 2. should be enough.
|
||||
1. [Defining Remotes](#defining-remotes)
|
||||
1. [Bump Version](#bump-version)
|
||||
1. [Find the Clippy commit](#find-the-clippy-commit)
|
||||
1. [Update the `beta` branch](#update-the-beta-branch)
|
||||
1. [Update the `stable` branch](#update-the-stable-branch)
|
||||
1. [Tag the stable commit](#tag-the-stable-commit)
|
||||
1. [Update `CHANGELOG.md`](#update-changelogmd)
|
||||
|
||||
[Rust Forge]: https://forge.rust-lang.org/
|
||||
|
||||
## Remerge the `beta` branch
|
||||
## Defining Remotes
|
||||
|
||||
This step is only necessary, if since the last release something was backported
|
||||
to the beta Rust release. The remerge is then necessary, to make sure that the
|
||||
Clippy commit, that was used by the now stable Rust release, persists in the
|
||||
tree of the Clippy repository.
|
||||
|
||||
To find out if this step is necessary run
|
||||
You may want to define the `upstream` remote of the Clippy project to simplify
|
||||
the following steps. However, this is optional and you can replace `upstream`
|
||||
with the full URL instead.
|
||||
|
||||
```bash
|
||||
# Assumes that the local master branch of rust-lang/rust-clippy is up-to-date
|
||||
$ git fetch upstream
|
||||
$ git branch master --contains upstream/beta
|
||||
git remote add upstream git@github.com:rust-lang/rust-clippy
|
||||
```
|
||||
|
||||
If this command outputs `master`, this step is **not** necessary.
|
||||
## Bump Version
|
||||
|
||||
When a release needs to be done, `cargo test` will fail, if the versions in the
|
||||
`Cargo.toml` are not correct. During that sync, the versions need to be bumped.
|
||||
This is done by running:
|
||||
|
||||
```bash
|
||||
# Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy
|
||||
$ git checkout -b backport_remerge
|
||||
$ git merge upstream/beta
|
||||
$ git diff # This diff has to be empty, otherwise something with the remerge failed
|
||||
$ git push origin backport_remerge # This can be pushed to your fork
|
||||
cargo dev release bump_version
|
||||
```
|
||||
|
||||
After this, open a PR to the master branch. In this PR, the commit hash of the
|
||||
`HEAD` of the `beta` branch must exist. In addition to that, no files should be
|
||||
changed by this PR.
|
||||
|
||||
## Update the `beta` branch
|
||||
|
||||
This step must be done **after** the PR of the previous step was merged.
|
||||
|
||||
First, the Clippy commit of the `beta` branch of the Rust repository has to be
|
||||
determined.
|
||||
This will increase the version number of each relevant `Cargo.toml` file. After
|
||||
that, just commit the updated files with:
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Rust repository
|
||||
$ git fetch upstream
|
||||
$ git checkout upstream/beta
|
||||
$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g")
|
||||
git commit -m "Bump Clippy version -> 0.1.XY" **/*Cargo.toml
|
||||
```
|
||||
|
||||
After finding the Clippy commit, the `beta` branch in the Clippy repository can
|
||||
be updated.
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Clippy repository
|
||||
$ git checkout beta
|
||||
$ git reset --hard $BETA_SHA
|
||||
$ git push upstream beta
|
||||
```
|
||||
`XY` should be exchanged with the corresponding version
|
||||
|
||||
## Find the Clippy commit
|
||||
|
||||
The first step is to tag the Clippy commit, that is included in the stable Rust
|
||||
release. This commit can be found in the Rust repository.
|
||||
For both updating the `beta` and the `stable` branch, the first step is to find
|
||||
the Clippy commit of the last Clippy sync done in the respective Rust branch.
|
||||
|
||||
Running the following commands _in the Rust repo_ will get the commit for the
|
||||
specified `<branch>`:
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Rust repository
|
||||
$ git fetch upstream # `upstream` is the `rust-lang/rust` remote
|
||||
$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version
|
||||
$ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g")
|
||||
git switch <branch>
|
||||
SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g")
|
||||
```
|
||||
|
||||
## Tag the stable commit
|
||||
Where `<branch>` is one of `stable`, `beta`, or `master`.
|
||||
|
||||
After finding the Clippy commit, it can be tagged with the release number.
|
||||
## Update the `beta` branch
|
||||
|
||||
After getting the commit of the `beta` branch, the `beta` branch in the Clippy
|
||||
repository can be updated.
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Clippy repository
|
||||
$ git checkout $SHA
|
||||
$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version
|
||||
$ git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote
|
||||
git checkout beta
|
||||
git reset --hard $SHA
|
||||
git push upstream beta
|
||||
```
|
||||
|
||||
## Update the `stable` branch
|
||||
|
||||
After getting the commit of the `stable` branch, the `stable` branch in the
|
||||
Clippy repository can be updated.
|
||||
|
||||
```bash
|
||||
git checkout stable
|
||||
git reset --hard $SHA
|
||||
git push upstream stable
|
||||
```
|
||||
|
||||
## Tag the `stable` commit
|
||||
|
||||
After updating the `stable` branch, tag the HEAD commit and push it to the
|
||||
Clippy repo.
|
||||
|
||||
> Note: Only push the tag once the Deploy GitHub action of the `beta` branch is
|
||||
> finished. Otherwise the deploy for the tag might fail.
|
||||
|
||||
```bash
|
||||
git tag rust-1.XX.0 # XX should be exchanged with the corresponding version
|
||||
git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote
|
||||
```
|
||||
|
||||
After this, the release should be available on the Clippy [release page].
|
||||
|
||||
[release page]: https://github.com/rust-lang/rust-clippy/releases
|
||||
|
||||
## Update the `stable` branch
|
||||
## Publish `clippy_utils`
|
||||
|
||||
At this step you should have already checked out the commit of the `rust-1.XX.0`
|
||||
tag. Updating the stable branch from here is as easy as:
|
||||
The `clippy_utils` crate is published to `crates.io` without any stability
|
||||
guarantees. To do this, after the [sync] and the release is done, switch back to
|
||||
the `upstream/master` branch and publish `clippy_utils`:
|
||||
|
||||
> Note: The Rustup PR bumping the nightly and Clippy version **must** be merged
|
||||
> before doing this.
|
||||
|
||||
```bash
|
||||
# Assuming the current directory corresponds to the Clippy repository and the
|
||||
# commit of the just created rust-1.XX.0 tag is checked out.
|
||||
$ git push upstream rust-1.XX.0:stable # `upstream` is the `rust-lang/rust-clippy` remote
|
||||
git switch master && git pull upstream master
|
||||
cargo publish --manifest-path clippy_utils/Cargo.toml
|
||||
```
|
||||
|
||||
> _NOTE:_ Usually there are no stable backports for Clippy, so this update
|
||||
> should be possible without force pushing or anything like this. If there
|
||||
> should have happened a stable backport, make sure to re-merge those changes
|
||||
> just as with the `beta` branch.
|
||||
[sync]: sync.md
|
||||
|
||||
## Update `CHANGELOG.md`
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ to beta. For reference, the first sync following this cadence was performed the
|
|||
This process is described in detail in the following sections. For general
|
||||
information about `subtree`s in the Rust repository see [the rustc-dev-guide][subtree].
|
||||
|
||||
[subtree]: https://rustc-dev-guide.rust-lang.org/external-repos.html#external-dependencies-subtree
|
||||
|
||||
## Patching git-subtree to work with big repos
|
||||
|
||||
Currently, there's a bug in `git-subtree` that prevents it from working properly
|
||||
|
@ -50,23 +52,11 @@ sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subt
|
|||
> `bash` instead. You can do this by editing the first line of the `git-subtree`
|
||||
> script and changing `sh` to `bash`.
|
||||
|
||||
## Defining remotes
|
||||
> Note: The following sections assume that you have set up remotes following the
|
||||
> instructions in [defining remotes].
|
||||
|
||||
You may want to define remotes, so you don't have to type out the remote
|
||||
addresses on every sync. You can do this with the following commands (these
|
||||
commands still have to be run inside the `rust` directory):
|
||||
|
||||
```bash
|
||||
# Set clippy-upstream remote for pulls
|
||||
$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy
|
||||
# Make sure to not push to the upstream repo
|
||||
$ git remote set-url --push clippy-upstream DISABLED
|
||||
# Set a local remote
|
||||
$ git remote add clippy-local /path/to/rust-clippy
|
||||
```
|
||||
|
||||
> Note: The following sections assume that you have set those remotes with the
|
||||
> above remote names.
|
||||
[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
|
||||
[defining remotes]: release.md#defining-remotes
|
||||
|
||||
## Performing the sync from [`rust-lang/rust`] to Clippy
|
||||
|
||||
|
@ -78,9 +68,9 @@ to be run inside the `rust` directory):
|
|||
`rustup check`.
|
||||
3. Sync the changes to the rust-copy of Clippy to your Clippy fork:
|
||||
```bash
|
||||
# Be sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand
|
||||
# Be sure to either use a net-new branch, e.g. `rustup`, or delete the branch beforehand
|
||||
# because changes cannot be fast forwarded and you have to run this command again.
|
||||
git subtree push -P src/tools/clippy clippy-local sync-from-rust
|
||||
git subtree push -P src/tools/clippy clippy-local rustup
|
||||
```
|
||||
|
||||
> _Note:_ Most of the time you have to create a merge commit in the
|
||||
|
@ -88,21 +78,22 @@ to be run inside the `rust` directory):
|
|||
> rust-copy of Clippy):
|
||||
```bash
|
||||
git fetch upstream # assuming upstream is the rust-lang/rust remote
|
||||
git checkout sync-from-rust
|
||||
git switch rustup
|
||||
git merge upstream/master --no-ff
|
||||
```
|
||||
> Note: This is one of the few instances where a merge commit is allowed in
|
||||
> a PR.
|
||||
4. Bump the nightly version in the Clippy repository by changing the date in the
|
||||
rust-toolchain file to the current date and committing it with the message:
|
||||
4. Bump the nightly version in the Clippy repository by running these commands:
|
||||
```bash
|
||||
git commit -m "Bump nightly version -> YYYY-MM-DD"
|
||||
cargo dev sync update_nightly
|
||||
git commit -m "Bump nightly version -> YYYY-MM-DD" rust-toolchain clippy_utils/README.md
|
||||
```
|
||||
5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to
|
||||
accelerate the process ping the `@rust-lang/clippy` team in your PR and/or
|
||||
ask them in the [Zulip] stream.)
|
||||
|
||||
[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
|
||||
[`rust-lang/rust`]: https://github.com/rust-lang/rust
|
||||
|
||||
## Performing the sync from Clippy to [`rust-lang/rust`]
|
||||
|
||||
|
@ -111,11 +102,7 @@ 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:
|
||||
```bash
|
||||
git checkout -b sync-from-clippy
|
||||
git switch -c clippy-subtree-update
|
||||
git subtree pull -P src/tools/clippy clippy-upstream master
|
||||
```
|
||||
3. Open a PR to [`rust-lang/rust`]
|
||||
|
||||
[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
|
||||
[subtree]: https://rustc-dev-guide.rust-lang.org/external-repos.html#external-dependencies-subtree
|
||||
[`rust-lang/rust`]: https://github.com/rust-lang/rust
|
||||
|
|
|
@ -72,7 +72,7 @@ you to the alumni group. You're always welcome to come back.
|
|||
|
||||
## The Clippy Team
|
||||
|
||||
[The Clippy team](https://www.rust-lang.org/governance/teams/dev-tools#Clippy%20team)
|
||||
[The Clippy team](https://www.rust-lang.org/governance/teams/dev-tools#team-clippy)
|
||||
is responsible for maintaining Clippy.
|
||||
|
||||
### Duties
|
||||
|
|
|
@ -94,7 +94,7 @@ impl LateLintPass<'_> for MyStructLint {
|
|||
// Get type of `expr`
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
// Match its kind to enter the type
|
||||
match ty.kind {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
|
||||
_ => ()
|
||||
}
|
||||
|
|
|
@ -81,6 +81,16 @@ Whether `expect` should be allowed in test functions or `#[cfg(test)]`
|
|||
* [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#expect_used)
|
||||
|
||||
|
||||
## `allow-indexing-slicing-in-tests`
|
||||
Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
|
||||
|
||||
**Default Value:** `false`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`indexing_slicing`](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing)
|
||||
|
||||
|
||||
## `allow-mixed-uninlined-format-args`
|
||||
Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
|
||||
|
||||
|
|
|
@ -291,6 +291,9 @@ define_Conf! {
|
|||
/// Whether `expect` should be allowed in test functions or `#[cfg(test)]`
|
||||
#[lints(expect_used)]
|
||||
allow_expect_in_tests: bool = false,
|
||||
/// Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
|
||||
#[lints(indexing_slicing)]
|
||||
allow_indexing_slicing_in_tests: bool = false,
|
||||
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
|
||||
#[lints(uninlined_format_args)]
|
||||
allow_mixed_uninlined_format_args: bool = true,
|
||||
|
|
|
@ -126,7 +126,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md
|
||||
///
|
||||
#[clippy::version = "1.82.0"]
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
restriction,
|
||||
"arbitrary source item ordering"
|
||||
|
|
|
@ -7,6 +7,7 @@ mod duplicated_attributes;
|
|||
mod inline_always;
|
||||
mod mixed_attributes_style;
|
||||
mod non_minimal_cfg;
|
||||
mod repr_attributes;
|
||||
mod should_panic_without_expect;
|
||||
mod unnecessary_clippy_cfg;
|
||||
mod useless_attribute;
|
||||
|
@ -272,6 +273,44 @@ declare_clippy_lint! {
|
|||
"ensures that all `should_panic` attributes specify its expected panic message"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for items with `#[repr(packed)]`-attribute without ABI qualification
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable.
|
||||
/// While this is fine as long as the type is accessed correctly within Rust-code, most uses
|
||||
/// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or
|
||||
/// other external specifications. In such situations, the unstable Rust-ABI implied in
|
||||
/// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change.
|
||||
///
|
||||
/// In case you are relying on a well defined and stable memory layout, qualify the type's
|
||||
/// representation using the `C`-ABI. Otherwise, if the type in question is only ever
|
||||
/// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[repr(packed)]
|
||||
/// struct NetworkPacketHeader {
|
||||
/// header_length: u8,
|
||||
/// header_version: u16
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[repr(C, packed)]
|
||||
/// struct NetworkPacketHeader {
|
||||
/// header_length: u8,
|
||||
/// header_version: u16
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub REPR_PACKED_WITHOUT_ABI,
|
||||
suspicious,
|
||||
"ensures that `repr(packed)` always comes with a qualified ABI"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `any` and `all` combinators in `cfg` with only one condition.
|
||||
|
@ -415,6 +454,7 @@ pub struct Attributes {
|
|||
|
||||
impl_lint_pass!(Attributes => [
|
||||
INLINE_ALWAYS,
|
||||
REPR_PACKED_WITHOUT_ABI,
|
||||
]);
|
||||
|
||||
impl Attributes {
|
||||
|
@ -431,6 +471,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|||
if is_relevant_item(cx, item) {
|
||||
inline_always::check(cx, item.span, item.ident.name, attrs);
|
||||
}
|
||||
repr_attributes::check(cx, item.span, attrs, &self.msrv);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
|
||||
|
|
43
src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs
Normal file
43
src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use rustc_hir::Attribute;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs;
|
||||
|
||||
use super::REPR_PACKED_WITHOUT_ABI;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute], msrv: &msrvs::Msrv) {
|
||||
if msrv.meets(msrvs::REPR_RUST) {
|
||||
check_packed(cx, item_span, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_packed(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) {
|
||||
if let Some(items) = attrs.iter().find_map(|attr| {
|
||||
if attr.ident().is_some_and(|ident| matches!(ident.name, sym::repr)) {
|
||||
attr.meta_item_list()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) && let Some(packed) = items
|
||||
.iter()
|
||||
.find(|item| item.ident().is_some_and(|ident| matches!(ident.name, sym::packed)))
|
||||
&& !items.iter().any(|item| {
|
||||
item.ident()
|
||||
.is_some_and(|ident| matches!(ident.name, sym::C | sym::Rust))
|
||||
})
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
REPR_PACKED_WITHOUT_ABI,
|
||||
item_span,
|
||||
"item uses `packed` representation without ABI-qualification",
|
||||
|diag| {
|
||||
diag.warn("unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI")
|
||||
.help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
|
||||
.span_label(packed.span(), "`packed` representation set here");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use super::USELESS_ATTRIBUTE;
|
||||
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
|
||||
use super::utils::{is_lint_level, is_word, namespace_and_lint};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{SpanRangeExt, first_line_of_span};
|
||||
use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner};
|
||||
use rustc_ast::{Attribute, Item, ItemKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
@ -20,11 +20,13 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
for lint in lint_list {
|
||||
match item.kind {
|
||||
ItemKind::Use(..) => {
|
||||
if let MetaItemInner::MetaItem(meta_item) = lint
|
||||
&& meta_item.is_word()
|
||||
&& let Some(ident) = meta_item.ident()
|
||||
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if namespace.is_none()
|
||||
&& matches!(
|
||||
ident.name.as_str(),
|
||||
name.as_str(),
|
||||
"ambiguous_glob_reexports"
|
||||
| "dead_code"
|
||||
| "deprecated"
|
||||
|
@ -39,9 +41,9 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
return;
|
||||
}
|
||||
|
||||
if extract_clippy_lint(lint).is_some_and(|symbol| {
|
||||
matches!(
|
||||
symbol.as_str(),
|
||||
if namespace == Some(sym::clippy)
|
||||
&& matches!(
|
||||
name.as_str(),
|
||||
"wildcard_imports"
|
||||
| "enum_glob_use"
|
||||
| "redundant_pub_crate"
|
||||
|
@ -52,7 +54,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
| "disallowed_types"
|
||||
| "unused_trait_names"
|
||||
)
|
||||
}) {
|
||||
{
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -75,13 +75,18 @@ fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>
|
|||
|
||||
/// Returns the lint name if it is clippy lint.
|
||||
pub(super) fn extract_clippy_lint(lint: &MetaItemInner) -> Option<Symbol> {
|
||||
if let Some(meta_item) = lint.meta_item()
|
||||
&& meta_item.path.segments.len() > 1
|
||||
&& let tool_name = meta_item.path.segments[0].ident
|
||||
&& tool_name.name == sym::clippy
|
||||
{
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
return Some(lint_name);
|
||||
match namespace_and_lint(lint) {
|
||||
(Some(sym::clippy), name) => name,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lint namespace, if any, as well as the lint name. (`None`, `None`) means
|
||||
/// the lint had less than 1 or more than 2 segments.
|
||||
pub(super) fn namespace_and_lint(lint: &MetaItemInner) -> (Option<Symbol>, Option<Symbol>) {
|
||||
match lint.meta_item().map(|m| m.path.segments.as_slice()).unwrap_or_default() {
|
||||
[name] => (None, Some(name.ident.name)),
|
||||
[namespace, name] => (Some(namespace.ident.name), Some(name.ident.name)),
|
||||
_ => (None, None),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ use clippy_utils::msrvs::{self, Msrv};
|
|||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_attr_parsing::RustcVersion;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
|
||||
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass, Level};
|
||||
use rustc_attr_parsing::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, sym};
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
pub fn check<'tcx>(cx: &LateContext<'tcx>, ty_into: Ty<'_>, cast_to_hir: &'tcx rustc_hir::Ty<'tcx>) {
|
||||
if let rustc_hir::TyKind::Ptr(rustc_hir::MutTy { ty, .. }) = cast_to_hir.kind
|
||||
&& matches!(ty.kind, rustc_hir::TyKind::Infer)
|
||||
{
|
||||
clippy_utils::diagnostics::span_lint_and_sugg(
|
||||
cx,
|
||||
super::AS_POINTER_UNDERSCORE,
|
||||
cast_to_hir.span,
|
||||
"using inferred pointer cast",
|
||||
"use explicit type",
|
||||
ty_into.to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::std_or_core;
|
||||
use clippy_utils::{is_lint_allowed, msrvs, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -13,15 +14,12 @@ pub(super) fn check<'tcx>(
|
|||
expr: &'tcx Expr<'_>,
|
||||
cast_expr: &'tcx Expr<'_>,
|
||||
cast_to: &'tcx Ty<'_>,
|
||||
) {
|
||||
msrv: &Msrv,
|
||||
) -> bool {
|
||||
if matches!(cast_to.kind, TyKind::Ptr(_))
|
||||
&& let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
|
||||
&& let Some(std_or_core) = std_or_core(cx)
|
||||
&& !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id)
|
||||
{
|
||||
let macro_name = match mutability {
|
||||
Mutability::Not => "addr_of",
|
||||
Mutability::Mut => "addr_of_mut",
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
|
||||
// Fix #9884
|
||||
|
@ -31,17 +29,36 @@ pub(super) fn check<'tcx>(
|
|||
.get(base.hir_id)
|
||||
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
|
||||
}) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let suggestion = if msrv.meets(msrvs::RAW_REF_OP) {
|
||||
let operator_kind = match mutability {
|
||||
Mutability::Not => "const",
|
||||
Mutability::Mut => "mut",
|
||||
};
|
||||
format!("&raw {operator_kind} {snip}")
|
||||
} else {
|
||||
let Some(std_or_core) = std_or_core(cx) else {
|
||||
return false;
|
||||
};
|
||||
let macro_name = match mutability {
|
||||
Mutability::Not => "addr_of",
|
||||
Mutability::Mut => "addr_of_mut",
|
||||
};
|
||||
format!("{std_or_core}::ptr::{macro_name}!({snip})")
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
BORROW_AS_PTR,
|
||||
expr.span,
|
||||
"borrow as raw pointer",
|
||||
"try",
|
||||
format!("{std_or_core}::ptr::{macro_name}!({snip})"),
|
||||
suggestion,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod as_pointer_underscore;
|
||||
mod as_ptr_cast_mut;
|
||||
mod as_underscore;
|
||||
mod borrow_as_ptr;
|
||||
|
@ -574,13 +575,13 @@ declare_clippy_lint! {
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the usage of `&expr as *const T` or
|
||||
/// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or
|
||||
/// `ptr::addr_of_mut` instead.
|
||||
/// `&mut expr as *mut T`, and suggest using `&raw const` or
|
||||
/// `&raw mut` instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This would improve readability and avoid creating a reference
|
||||
/// that points to an uninitialized value or unaligned place.
|
||||
/// Read the `ptr::addr_of` docs for more information.
|
||||
/// Read the `&raw` explanation in the Reference for more information.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
@ -593,10 +594,10 @@ declare_clippy_lint! {
|
|||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let val = 1;
|
||||
/// let p = std::ptr::addr_of!(val);
|
||||
/// let p = &raw const val;
|
||||
///
|
||||
/// let mut val_mut = 1;
|
||||
/// let p_mut = std::ptr::addr_of_mut!(val_mut);
|
||||
/// let p_mut = &raw mut val_mut;
|
||||
/// ```
|
||||
#[clippy::version = "1.60.0"]
|
||||
pub BORROW_AS_PTR,
|
||||
|
@ -726,6 +727,33 @@ declare_clippy_lint! {
|
|||
"using `as` to cast a reference to pointer"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// The conversion might include a dangerous cast that might go undetected due to the type being inferred.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn as_usize<T>(t: &T) -> usize {
|
||||
/// // BUG: `t` is already a reference, so we will here
|
||||
/// // return a dangling pointer to a temporary value instead
|
||||
/// &t as *const _ as usize
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn as_usize<T>(t: &T) -> usize {
|
||||
/// t as *const T as usize
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.81.0"]
|
||||
pub AS_POINTER_UNDERSCORE,
|
||||
restriction,
|
||||
"detects `as *mut _` and `as *const _` conversion"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
@ -763,6 +791,7 @@ impl_lint_pass!(Casts => [
|
|||
CAST_NAN_TO_INT,
|
||||
ZERO_PTR,
|
||||
REF_AS_PTR,
|
||||
AS_POINTER_UNDERSCORE,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
@ -805,11 +834,15 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
}
|
||||
|
||||
as_underscore::check(cx, expr, cast_to_hir);
|
||||
as_pointer_underscore::check(cx, cast_to, cast_to_hir);
|
||||
|
||||
if self.msrv.meets(msrvs::PTR_FROM_REF) {
|
||||
let was_borrow_as_ptr_emitted = if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, &self.msrv)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if self.msrv.meets(msrvs::PTR_FROM_REF) && !was_borrow_as_ptr_emitted {
|
||||
ref_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{SpanlessEq, if_sequence, is_else_clause, is_in_const_context};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
@ -120,13 +122,19 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
|
|||
return;
|
||||
}
|
||||
}
|
||||
span_lint_and_help(
|
||||
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
|
||||
unreachable!();
|
||||
};
|
||||
let lhs = Sugg::hir(cx, lhs, "..").maybe_par();
|
||||
let rhs = Sugg::hir(cx, rhs, "..").addr();
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COMPARISON_CHAIN,
|
||||
expr.span,
|
||||
"`if` chain can be rewritten with `match`",
|
||||
None,
|
||||
"consider rewriting the `if` chain to use `cmp` and `match`",
|
||||
"consider rewriting the `if` chain with `match`",
|
||||
format!("match {lhs}.cmp({rhs}) {{...}}"),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::attrs::INLINE_ALWAYS_INFO,
|
||||
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
|
||||
crate::attrs::NON_MINIMAL_CFG_INFO,
|
||||
crate::attrs::REPR_PACKED_WITHOUT_ABI_INFO,
|
||||
crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO,
|
||||
crate::attrs::UNNECESSARY_CLIPPY_CFG_INFO,
|
||||
crate::attrs::USELESS_ATTRIBUTE_INFO,
|
||||
|
@ -75,6 +76,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
|
||||
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
|
||||
crate::cargo::WILDCARD_DEPENDENCIES_INFO,
|
||||
crate::casts::AS_POINTER_UNDERSCORE_INFO,
|
||||
crate::casts::AS_PTR_CAST_MUT_INFO,
|
||||
crate::casts::AS_UNDERSCORE_INFO,
|
||||
crate::casts::BORROW_AS_PTR_INFO,
|
||||
|
@ -139,6 +141,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::doc::DOC_LAZY_CONTINUATION_INFO,
|
||||
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
|
||||
crate::doc::DOC_MARKDOWN_INFO,
|
||||
crate::doc::DOC_NESTED_REFDEFS_INFO,
|
||||
crate::doc::EMPTY_DOCS_INFO,
|
||||
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
|
||||
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
|
||||
|
@ -277,6 +280,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
|
||||
crate::literal_representation::UNREADABLE_LITERAL_INFO,
|
||||
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
|
||||
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
|
||||
crate::loops::EMPTY_LOOP_INFO,
|
||||
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
|
||||
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
|
||||
|
|
|
@ -26,7 +26,8 @@ declare_clippy_lint! {
|
|||
/// To ensure that every numeric type is chosen explicitly rather than implicitly.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint can only be allowed at the function level or above.
|
||||
/// This lint is implemented using a custom algorithm independent of rustc's inference,
|
||||
/// which results in many false positives and false negatives.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
@ -36,8 +37,8 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let i = 10i32;
|
||||
/// let f = 1.23f64;
|
||||
/// let i = 10_i32;
|
||||
/// let f = 1.23_f64;
|
||||
/// ```
|
||||
#[clippy::version = "1.52.0"]
|
||||
pub DEFAULT_NUMERIC_FALLBACK,
|
||||
|
|
|
@ -1004,7 +1004,10 @@ fn report<'tcx>(
|
|||
let needs_paren = match cx.tcx.parent_hir_node(data.first_expr.hir_id) {
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => false,
|
||||
ExprKind::Call(..) => expr.precedence() < ExprPrecedence::Unambiguous || matches!(expr.kind, ExprKind::Field(..)),
|
||||
ExprKind::Call(..) => {
|
||||
expr.precedence() < ExprPrecedence::Unambiguous
|
||||
|| matches!(expr.kind, ExprKind::Field(..))
|
||||
},
|
||||
_ => expr.precedence() < e.precedence(),
|
||||
},
|
||||
_ => false,
|
||||
|
@ -1017,11 +1020,7 @@ fn report<'tcx>(
|
|||
})
|
||||
);
|
||||
|
||||
let sugg = if !snip_is_macro
|
||||
&& needs_paren
|
||||
&& !has_enclosing_paren(&snip)
|
||||
&& !is_in_tuple
|
||||
{
|
||||
let sugg = if !snip_is_macro && needs_paren && !has_enclosing_paren(&snip) && !is_in_tuple {
|
||||
format!("({snip})")
|
||||
} else {
|
||||
snip.into()
|
||||
|
|
|
@ -6,9 +6,7 @@ use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, pat
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
|
||||
use rustc_hir::{
|
||||
self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource,
|
||||
};
|
||||
use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{
|
||||
|
@ -453,7 +451,7 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r
|
|||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
||||
&& !has_non_exhaustive_attr(cx.tcx, *adt)
|
||||
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
|
||||
&& let typing_env = typing_env_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& let Some(local_def_id) = adt.did().as_local()
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
&& adt
|
||||
|
@ -484,7 +482,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
|
|||
}
|
||||
|
||||
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
|
||||
fn typing_env_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
|
||||
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
|
||||
// Initial map from generic index to param def.
|
||||
// Vec<(param_def, needs_eq)>
|
||||
let mut params = tcx
|
||||
|
|
|
@ -43,7 +43,6 @@ declare_clippy_lint! {
|
|||
/// ```no_run
|
||||
/// use serde::Serialize;
|
||||
///
|
||||
/// // Example code where clippy issues a warning
|
||||
/// println!("warns");
|
||||
///
|
||||
/// // The diagnostic will contain the message "no serializing"
|
||||
|
|
|
@ -35,7 +35,6 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Example code where clippy issues a warning
|
||||
/// let xs = vec![1, 2, 3, 4];
|
||||
/// xs.leak(); // Vec::leak is disallowed in the config.
|
||||
/// // The diagnostic contains the message "no leaking memory".
|
||||
|
@ -47,7 +46,6 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// Use instead:
|
||||
/// ```rust,ignore
|
||||
/// // Example code which does not raise clippy warning
|
||||
/// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.
|
||||
/// xs.push(123); // Vec::push is _not_ disallowed in the config.
|
||||
/// ```
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::{AttrStyle};
|
||||
use rustc_ast::AttrStyle;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{AttrArgs, AttrKind, Attribute};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_hir::{Attribute, AttrKind, AttrArgs};
|
||||
|
||||
use super::DOC_INCLUDE_WITHOUT_CFG;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ mod too_long_first_doc_paragraph;
|
|||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::Visitable;
|
||||
|
@ -17,6 +17,7 @@ use pulldown_cmark::Event::{
|
|||
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph};
|
||||
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
@ -563,6 +564,32 @@ declare_clippy_lint! {
|
|||
"check if files included in documentation are behind `cfg(doc)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Warns if a link reference definition appears at the start of a
|
||||
/// list item or quote.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably intended as an intra-doc link. If it is really
|
||||
/// supposed to be a reference definition, it can be written outside
|
||||
/// of the list item or quote.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// //! - [link]: description
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// //! - [link][]: description (for intra-doc link)
|
||||
/// //!
|
||||
/// //! [link]: destination (for link reference definition)
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub DOC_NESTED_REFDEFS,
|
||||
suspicious,
|
||||
"link reference defined in list item or quote"
|
||||
}
|
||||
|
||||
pub struct Documentation {
|
||||
valid_idents: FxHashSet<String>,
|
||||
check_private_items: bool,
|
||||
|
@ -580,6 +607,7 @@ impl Documentation {
|
|||
impl_lint_pass!(Documentation => [
|
||||
DOC_LINK_WITH_QUOTES,
|
||||
DOC_MARKDOWN,
|
||||
DOC_NESTED_REFDEFS,
|
||||
MISSING_SAFETY_DOC,
|
||||
MISSING_ERRORS_DOC,
|
||||
MISSING_PANICS_DOC,
|
||||
|
@ -831,6 +859,31 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
Start(BlockQuote(_)) => {
|
||||
blockquote_level += 1;
|
||||
containers.push(Container::Blockquote);
|
||||
if let Some((next_event, next_range)) = events.peek() {
|
||||
let next_start = match next_event {
|
||||
End(TagEnd::BlockQuote) => next_range.end,
|
||||
_ => next_range.start,
|
||||
};
|
||||
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
|
||||
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DOC_NESTED_REFDEFS,
|
||||
refdefspan,
|
||||
"link reference defined in quote",
|
||||
|diag| {
|
||||
diag.span_suggestion_short(
|
||||
refdefspan.shrink_to_hi(),
|
||||
"for an intra-doc link, add `[]` between the label and the colon",
|
||||
"[]",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("link definitions are not shown in rendered documentation");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
End(TagEnd::BlockQuote) => {
|
||||
blockquote_level -= 1;
|
||||
|
@ -869,11 +922,42 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
in_heading = true;
|
||||
}
|
||||
if let Start(Item) = event {
|
||||
if let Some((_next_event, next_range)) = events.peek() {
|
||||
containers.push(Container::List(next_range.start - range.start));
|
||||
let indent = if let Some((next_event, next_range)) = events.peek() {
|
||||
let next_start = match next_event {
|
||||
End(TagEnd::Item) => next_range.end,
|
||||
_ => next_range.start,
|
||||
};
|
||||
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
|
||||
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DOC_NESTED_REFDEFS,
|
||||
refdefspan,
|
||||
"link reference defined in list item",
|
||||
|diag| {
|
||||
diag.span_suggestion_short(
|
||||
refdefspan.shrink_to_hi(),
|
||||
"for an intra-doc link, add `[]` between the label and the colon",
|
||||
"[]",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("link definitions are not shown in rendered documentation");
|
||||
}
|
||||
);
|
||||
refdefrange.start - range.start
|
||||
} else {
|
||||
let mut start = next_range.start;
|
||||
if start > 0 && doc.as_bytes().get(start - 1) == Some(&b'\\') {
|
||||
// backslashes aren't in the event stream...
|
||||
start -= 1;
|
||||
}
|
||||
start - range.start
|
||||
}
|
||||
} else {
|
||||
containers.push(Container::List(0));
|
||||
}
|
||||
0
|
||||
};
|
||||
containers.push(Container::List(indent));
|
||||
}
|
||||
ticks_unbalanced = false;
|
||||
paragraph_range = range;
|
||||
|
@ -1045,3 +1129,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
|
|||
self.cx.tcx.hir()
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
|
||||
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
|
||||
let offset = range.start;
|
||||
let mut iterator = doc.as_bytes()[range].iter().copied().enumerate();
|
||||
let mut start = None;
|
||||
while let Some((i, byte)) = iterator.next() {
|
||||
match byte {
|
||||
b'\\' => {
|
||||
iterator.next();
|
||||
},
|
||||
b'[' => {
|
||||
start = Some(i + offset);
|
||||
},
|
||||
b']' if let Some(start) = start => {
|
||||
return Some(start..i + offset + 1);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
// match call to write_fmt
|
||||
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind)
|
||||
&& let ExprKind::Call(write_recv_path, []) = write_recv.kind
|
||||
&& write_fun.ident.name.as_str() == "write_fmt"
|
||||
&& write_fun.ident.name == sym::write_fmt
|
||||
&& let Some(def_id) = path_def_id(cx, write_recv_path)
|
||||
{
|
||||
// match calls to std::io::stdout() / std::io::stderr ()
|
||||
|
|
|
@ -10,6 +10,7 @@ mod too_many_lines;
|
|||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::def_path_def_ids;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
@ -455,6 +456,7 @@ pub struct Functions {
|
|||
/// A set of resolved `def_id` of traits that are configured to allow
|
||||
/// function params renaming.
|
||||
trait_ids: DefIdSet,
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl Functions {
|
||||
|
@ -469,6 +471,7 @@ impl Functions {
|
|||
.iter()
|
||||
.flat_map(|p| def_path_def_ids(tcx, &p.split("::").collect::<Vec<_>>()))
|
||||
.collect(),
|
||||
msrv: conf.msrv.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -518,12 +521,12 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
|
|||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
must_use::check_item(cx, item);
|
||||
result::check_item(cx, item, self.large_error_threshold);
|
||||
result::check_item(cx, item, self.large_error_threshold, &self.msrv);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
must_use::check_impl_item(cx, item);
|
||||
result::check_impl_item(cx, item, self.large_error_threshold);
|
||||
result::check_impl_item(cx, item, self.large_error_threshold, &self.msrv);
|
||||
impl_trait_in_params::check_impl_item(cx, item);
|
||||
renamed_function_params::check_impl_item(cx, item, &self.trait_ids);
|
||||
}
|
||||
|
@ -532,8 +535,10 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
|
|||
too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
|
||||
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
|
||||
must_use::check_trait_item(cx, item);
|
||||
result::check_trait_item(cx, item, self.large_error_threshold);
|
||||
result::check_trait_item(cx, item, self.large_error_threshold, &self.msrv);
|
||||
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
|
||||
ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api);
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,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, sig);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, sig);
|
||||
} else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
|
@ -50,7 +50,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, sig);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, 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,
|
||||
|
@ -73,7 +73,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, sig);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, 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) {
|
||||
|
@ -91,6 +91,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_needless_must_use(
|
||||
cx: &LateContext<'_>,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
|
@ -98,21 +99,54 @@ fn check_needless_must_use(
|
|||
item_span: Span,
|
||||
fn_header_span: Span,
|
||||
attr: &Attribute,
|
||||
attrs: &[Attribute],
|
||||
sig: &FnSig<'_>,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), item_span) {
|
||||
return;
|
||||
}
|
||||
if returns_unit(decl) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MUST_USE_UNIT,
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
if attrs.len() == 1 {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MUST_USE_UNIT,
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// When there are multiple attributes, it is not sufficient to simply make `must_use` empty, see
|
||||
// issue #12320.
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MUST_USE_UNIT,
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
let mut attrs_without_must_use = attrs.to_vec();
|
||||
attrs_without_must_use.retain(|a| a.id != attr.id);
|
||||
let sugg_str = attrs_without_must_use
|
||||
.iter()
|
||||
.map(|a| {
|
||||
if a.value_str().is_none() {
|
||||
return a.name_or_empty().to_string();
|
||||
}
|
||||
format!("{} = \"{}\"", a.name_or_empty(), a.value_str().unwrap())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
diag.span_suggestion(
|
||||
attrs[0].span.with_hi(attrs[attrs.len() - 1].span.hi()),
|
||||
"change these attributes to",
|
||||
sugg_str,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} 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() {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use rustc_errors::Diag;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
|
@ -6,8 +7,8 @@ use rustc_middle::ty::{self, Ty};
|
|||
use rustc_span::{Span, sym};
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::trait_ref_of_method;
|
||||
use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_no_std_crate, trait_ref_of_method};
|
||||
|
||||
use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR};
|
||||
|
||||
|
@ -34,19 +35,24 @@ fn result_err_ty<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) {
|
||||
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64, msrv: &Msrv) {
|
||||
if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind
|
||||
&& let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
|
||||
{
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
check_result_unit_err(cx, err_ty, fn_header_span);
|
||||
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
|
||||
}
|
||||
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) {
|
||||
pub(super) fn check_impl_item<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
item: &hir::ImplItem<'tcx>,
|
||||
large_err_threshold: u64,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
// Don't lint if method is a trait's implementation, we can't do anything about those
|
||||
if let hir::ImplItemKind::Fn(ref sig, _) = item.kind
|
||||
&& let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
|
||||
|
@ -54,26 +60,31 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem
|
|||
{
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
check_result_unit_err(cx, err_ty, fn_header_span);
|
||||
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
|
||||
}
|
||||
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) {
|
||||
pub(super) fn check_trait_item<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
item: &hir::TraitItem<'tcx>,
|
||||
large_err_threshold: u64,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span) {
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
|
||||
check_result_unit_err(cx, err_ty, fn_header_span);
|
||||
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
|
||||
}
|
||||
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) {
|
||||
if err_ty.is_unit() {
|
||||
fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span, msrv: &Msrv) {
|
||||
if err_ty.is_unit() && (!is_no_std_crate(cx) || msrv.meets(msrvs::ERROR_IN_CORE)) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
RESULT_UNIT_ERR,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::is_else_clause;
|
||||
use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use std::borrow::Cow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -54,7 +58,7 @@ fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
|
|||
|
||||
impl LateLintPass<'_> for IfNotElse {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
|
||||
if let ExprKind::If(cond, _, Some(els)) = e.kind
|
||||
if let ExprKind::If(cond, cond_inner, Some(els)) = e.kind
|
||||
&& let ExprKind::DropTemps(cond) = cond.kind
|
||||
&& let ExprKind::Block(..) = els.kind
|
||||
{
|
||||
|
@ -79,8 +83,52 @@ impl LateLintPass<'_> for IfNotElse {
|
|||
// }
|
||||
// ```
|
||||
if !e.span.from_expansion() && !is_else_clause(cx.tcx, e) {
|
||||
span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help);
|
||||
match cond.kind {
|
||||
ExprKind::Unary(UnOp::Not, _) | ExprKind::Binary(_, _, _) => span_lint_and_sugg(
|
||||
cx,
|
||||
IF_NOT_ELSE,
|
||||
e.span,
|
||||
msg,
|
||||
"try",
|
||||
make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)).to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
),
|
||||
_ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_sugg<'a>(
|
||||
sess: &impl HasSession,
|
||||
cond_kind: &'a ExprKind<'a>,
|
||||
cond_inner: Span,
|
||||
els_span: Span,
|
||||
default: &'a str,
|
||||
indent_relative_to: Option<Span>,
|
||||
) -> Cow<'a, str> {
|
||||
let cond_inner_snip = snippet(sess, cond_inner, default);
|
||||
let els_snip = snippet(sess, els_span, default);
|
||||
let indent = indent_relative_to.and_then(|s| indent_of(sess, s));
|
||||
|
||||
let suggestion = match cond_kind {
|
||||
ExprKind::Unary(UnOp::Not, cond_rest) => {
|
||||
format!(
|
||||
"if {} {} else {}",
|
||||
snippet(sess, cond_rest.span, default),
|
||||
els_snip,
|
||||
cond_inner_snip
|
||||
)
|
||||
},
|
||||
ExprKind::Binary(_, lhs, rhs) => {
|
||||
let lhs_snip = snippet(sess, lhs.span, default);
|
||||
let rhs_snip = snippet(sess, rhs.span, default);
|
||||
|
||||
format!("if {lhs_snip} == {rhs_snip} {els_snip} else {cond_inner_snip}")
|
||||
},
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
reindent_multiline(suggestion.into(), true, indent)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use clippy_config::Conf;
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_in_test;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use rustc_attr_parsing::{StabilityLevel, StableSince, RustcVersion};
|
||||
use rustc_attr_parsing::{RustcVersion, StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
@ -135,7 +135,7 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
|
|||
.map(|(index, _)| *index)
|
||||
.collect::<FxIndexSet<_>>();
|
||||
|
||||
let value_name = |index| format!("{}_{index}", slice.ident.name);
|
||||
let value_name = |index| format!("{}_{}", slice.ident.name, index);
|
||||
|
||||
if let Some(max_index) = used_indices.iter().max() {
|
||||
let opt_ref = if slice.needs_ref { "ref " } else { "" };
|
||||
|
@ -150,6 +150,18 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
|
|||
.collect::<Vec<_>>();
|
||||
let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", "));
|
||||
|
||||
let mut suggestions = Vec::new();
|
||||
|
||||
// Add the binding pattern suggestion
|
||||
if !slice.pattern_spans.is_empty() {
|
||||
suggestions.extend(slice.pattern_spans.iter().map(|span| (*span, pat_sugg.clone())));
|
||||
}
|
||||
|
||||
// Add the index replacement suggestions
|
||||
if !slice.index_use.is_empty() {
|
||||
suggestions.extend(slice.index_use.iter().map(|(index, span)| (*span, value_name(*index))));
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
INDEX_REFUTABLE_SLICE,
|
||||
|
@ -157,28 +169,10 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
|
|||
"this binding can be a slice pattern to avoid indexing",
|
||||
|diag| {
|
||||
diag.multipart_suggestion(
|
||||
"try using a slice pattern here",
|
||||
slice
|
||||
.pattern_spans
|
||||
.iter()
|
||||
.map(|span| (*span, pat_sugg.clone()))
|
||||
.collect(),
|
||||
"replace the binding and indexed access with a slice pattern",
|
||||
suggestions,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
diag.multipart_suggestion(
|
||||
"and replace the index expressions here",
|
||||
slice
|
||||
.index_use
|
||||
.iter()
|
||||
.map(|(index, span)| (*span, value_name(*index)))
|
||||
.collect(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
// The lint message doesn't contain a warning about the removed index expression,
|
||||
// since `filter_lintable_slices` will only return slices where all access indices
|
||||
// are known at compile time. Therefore, they can be removed without side effects.
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use clippy_config::Conf;
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
|
||||
use clippy_utils::{higher, is_from_proc_macro};
|
||||
use clippy_utils::{higher, is_from_proc_macro, is_in_test};
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
@ -42,39 +42,50 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of indexing or slicing. Arrays are special cases, this lint
|
||||
/// does report on arrays if we can tell that slicing operations are in bounds and does not
|
||||
/// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint.
|
||||
/// Checks for usage of indexing or slicing that may panic at runtime.
|
||||
///
|
||||
/// This lint does not report on indexing or slicing operations
|
||||
/// that always panic, clippy's `out_of_bound_indexing` already
|
||||
/// handles those cases.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// To avoid implicit panics from indexing and slicing.
|
||||
///
|
||||
/// There are “checked” alternatives which do not panic, and can be used with `unwrap()` to make
|
||||
/// an explicit panic when it is desired.
|
||||
///
|
||||
/// ### Limitations
|
||||
/// This lint does not check for the usage of indexing or slicing on strings. These are covered
|
||||
/// by the more specific `string_slice` lint.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,no_run
|
||||
/// // Vector
|
||||
/// let x = vec![0; 5];
|
||||
/// let x = vec![0, 1, 2, 3];
|
||||
///
|
||||
/// x[2];
|
||||
/// x[100];
|
||||
/// &x[2..100];
|
||||
///
|
||||
/// // Array
|
||||
/// let y = [0, 1, 2, 3];
|
||||
///
|
||||
/// &y[10..100];
|
||||
/// &y[10..];
|
||||
/// let i = 10; // Could be a runtime value
|
||||
/// let j = 20;
|
||||
/// &y[i..j];
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # let x = vec![0; 5];
|
||||
/// # let y = [0, 1, 2, 3];
|
||||
/// # let x = vec![0, 1, 2, 3];
|
||||
/// x.get(2);
|
||||
/// x.get(100);
|
||||
/// x.get(2..100);
|
||||
///
|
||||
/// y.get(10);
|
||||
/// y.get(10..100);
|
||||
/// # let y = [0, 1, 2, 3];
|
||||
/// let i = 10;
|
||||
/// let j = 20;
|
||||
/// y.get(i..j);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INDEXING_SLICING,
|
||||
|
@ -85,12 +96,14 @@ declare_clippy_lint! {
|
|||
impl_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]);
|
||||
|
||||
pub struct IndexingSlicing {
|
||||
allow_indexing_slicing_in_tests: bool,
|
||||
suppress_restriction_lint_in_const: bool,
|
||||
}
|
||||
|
||||
impl IndexingSlicing {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
allow_indexing_slicing_in_tests: conf.allow_indexing_slicing_in_tests,
|
||||
suppress_restriction_lint_in_const: conf.suppress_restriction_lint_in_const,
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
{
|
||||
let note = "the suggestion might not be applicable in constant blocks";
|
||||
let ty = cx.typeck_results().expr_ty(array).peel_refs();
|
||||
let allowed_in_tests = self.allow_indexing_slicing_in_tests && is_in_test(cx.tcx, expr.hir_id);
|
||||
if let Some(range) = higher::Range::hir(index) {
|
||||
// Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
|
||||
if let ty::Array(_, s) = ty.kind() {
|
||||
|
@ -160,6 +174,10 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
(None, None) => return, // [..] is ok.
|
||||
};
|
||||
|
||||
if allowed_in_tests {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(cx, INDEXING_SLICING, expr.span, "slicing may panic", |diag| {
|
||||
diag.help(help_msg);
|
||||
|
||||
|
@ -198,6 +216,10 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
}
|
||||
}
|
||||
|
||||
if allowed_in_tests {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(cx, INDEXING_SLICING, expr.span, "indexing may panic", |diag| {
|
||||
diag.help("consider using `.get(n)` or `.get_mut(n)` instead");
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ use clippy_config::Conf;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::{LitKind};
|
||||
use rustc_hir::{Expr, ExprKind, Attribute, AttrArgs, AttrKind};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_hir::{AttrArgs, AttrKind, Attribute, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{Span, sym};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#![feature(iter_partition_in_place)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(never_type)]
|
||||
#![feature(round_char_boundary)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![feature(unwrap_infallible)]
|
||||
|
@ -17,7 +18,8 @@
|
|||
clippy::missing_docs_in_private_items,
|
||||
clippy::must_use_candidate,
|
||||
rustc::diagnostic_outside_of_impl,
|
||||
rustc::untranslatable_diagnostic
|
||||
rustc::untranslatable_diagnostic,
|
||||
clippy::literal_string_with_formatting_args
|
||||
)]
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
|
@ -49,6 +51,7 @@ extern crate rustc_lexer;
|
|||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_parse;
|
||||
extern crate rustc_parse_format;
|
||||
extern crate rustc_resolve;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
|
@ -196,6 +199,7 @@ mod let_with_type_underscore;
|
|||
mod lifetimes;
|
||||
mod lines_filter_map_ok;
|
||||
mod literal_representation;
|
||||
mod literal_string_with_formatting_args;
|
||||
mod loops;
|
||||
mod macro_metavars_in_unsafe;
|
||||
mod macro_use;
|
||||
|
@ -957,6 +961,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
|
||||
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
|
||||
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
|
||||
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
|
||||
|
|
|
@ -643,8 +643,7 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'
|
|||
|
||||
// An `impl` lifetime is elidable if it satisfies the following conditions:
|
||||
// - It is used exactly once.
|
||||
// - That single use is not in a bounded type or `GenericArgs` in a `WherePredicate`. (Note that
|
||||
// `GenericArgs` are different from `GenericParam`s.)
|
||||
// - That single use is not in a `WherePredicate`.
|
||||
fn report_elidable_impl_lifetimes<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
impl_: &'tcx Impl<'_>,
|
||||
|
@ -658,12 +657,6 @@ fn report_elidable_impl_lifetimes<'tcx>(
|
|||
lifetime,
|
||||
in_where_predicate: false,
|
||||
..
|
||||
}
|
||||
| Usage {
|
||||
lifetime,
|
||||
in_bounded_ty: false,
|
||||
in_generics_arg: false,
|
||||
..
|
||||
},
|
||||
] = usages.as_slice()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
use rustc_ast::{LitKind, StrStyle};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lexer::is_ident;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_parse_format::{ParseMode, Parser, Piece};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::mir::enclosing_mir;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if string literals have formatting arguments outside of macros
|
||||
/// using them (like `format!`).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It will likely not generate the expected content.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: Option<usize> = None;
|
||||
/// let y = "hello";
|
||||
/// x.expect("{y:?}");
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let x: Option<usize> = None;
|
||||
/// let y = "hello";
|
||||
/// x.expect(&format!("{y:?}"));
|
||||
/// ```
|
||||
#[clippy::version = "1.83.0"]
|
||||
pub LITERAL_STRING_WITH_FORMATTING_ARGS,
|
||||
suspicious,
|
||||
"Checks if string literals have formatting arguments"
|
||||
}
|
||||
|
||||
declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARGS]);
|
||||
|
||||
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<String>)]) {
|
||||
if !spans.is_empty()
|
||||
&& let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id)
|
||||
{
|
||||
let spans = spans
|
||||
.iter()
|
||||
.filter_map(|(span, name)| {
|
||||
if let Some(name) = name {
|
||||
// We need to check that the name is a local.
|
||||
if !mir
|
||||
.var_debug_info
|
||||
.iter()
|
||||
.any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(*span)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
match spans.len() {
|
||||
0 => {},
|
||||
1 => {
|
||||
span_lint(
|
||||
cx,
|
||||
LITERAL_STRING_WITH_FORMATTING_ARGS,
|
||||
spans,
|
||||
"this looks like a formatting argument but it is not part of a formatting macro",
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
span_lint(
|
||||
cx,
|
||||
LITERAL_STRING_WITH_FORMATTING_ARGS,
|
||||
spans,
|
||||
"these look like formatting arguments but are not part of a formatting macro",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for LiteralStringWithFormattingArg {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Lit(lit) = expr.kind {
|
||||
let (add, symbol) = match lit.node {
|
||||
LitKind::Str(symbol, style) => {
|
||||
let add = match style {
|
||||
StrStyle::Cooked => 1,
|
||||
StrStyle::Raw(nb) => nb as usize + 2,
|
||||
};
|
||||
(add, symbol)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let fmt_str = symbol.as_str();
|
||||
let lo = expr.span.lo();
|
||||
let mut current = fmt_str;
|
||||
let mut diff_len = 0;
|
||||
|
||||
let mut parser = Parser::new(current, None, None, false, ParseMode::Format);
|
||||
let mut spans = Vec::new();
|
||||
while let Some(piece) = parser.next() {
|
||||
if let Some(error) = parser.errors.last() {
|
||||
// We simply ignore the errors and move after them.
|
||||
if error.span.end >= current.len() {
|
||||
break;
|
||||
}
|
||||
// We find the closest char to where the error location ends.
|
||||
let pos = current.floor_char_boundary(error.span.end);
|
||||
// We get the next character.
|
||||
current = if let Some((next_char_pos, _)) = current[pos..].char_indices().nth(1) {
|
||||
// We make the parser start from this new location.
|
||||
¤t[pos + next_char_pos..]
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
diff_len = fmt_str.len() - current.len();
|
||||
parser = Parser::new(current, None, None, false, ParseMode::Format);
|
||||
} else if let Piece::NextArgument(arg) = piece {
|
||||
let mut pos = arg.position_span;
|
||||
pos.start += diff_len;
|
||||
pos.end += diff_len;
|
||||
|
||||
let start = fmt_str[..pos.start].rfind('{').unwrap_or(pos.start);
|
||||
// If this is a unicode character escape, we don't want to lint.
|
||||
if start > 1 && fmt_str[..start].ends_with("\\u") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fmt_str[start + 1..].trim_start().starts_with('}') {
|
||||
// We ignore `{}`.
|
||||
continue;
|
||||
}
|
||||
|
||||
let end = fmt_str[start + 1..]
|
||||
.find('}')
|
||||
.map_or(pos.end, |found| start + 1 + found)
|
||||
+ 1;
|
||||
let ident_start = start + 1;
|
||||
let colon_pos = fmt_str[ident_start..end].find(':');
|
||||
let ident_end = colon_pos.unwrap_or(end - 1);
|
||||
let mut name = None;
|
||||
if ident_start < ident_end
|
||||
&& let arg = &fmt_str[ident_start..ident_end]
|
||||
&& !arg.is_empty()
|
||||
&& is_ident(arg)
|
||||
{
|
||||
name = Some(arg.to_string());
|
||||
} else if colon_pos.is_none() {
|
||||
// Not a `{:?}`.
|
||||
continue;
|
||||
}
|
||||
spans.push((
|
||||
expr.span
|
||||
.with_hi(lo + BytePos((start + add).try_into().unwrap()))
|
||||
.with_lo(lo + BytePos((end + add).try_into().unwrap())),
|
||||
name,
|
||||
));
|
||||
}
|
||||
}
|
||||
emit_lint(cx, expr, &spans);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
|
|||
if let Some(vis_snip) = vis_span.get_source_text(cx)
|
||||
&& let Some(header_snip) = header_span.get_source_text(cx)
|
||||
&& let Some(ret_pos) = position_before_rarrow(&header_snip)
|
||||
&& let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
|
||||
&& let Some((_, ret_snip)) = suggested_ret(cx, output)
|
||||
{
|
||||
let header_snip = if vis_snip.is_empty() {
|
||||
format!("async {}", &header_snip[..ret_pos])
|
||||
|
@ -82,19 +82,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
|
|||
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
|
||||
};
|
||||
|
||||
let help = format!("make the function `async` and {ret_sugg}");
|
||||
diag.span_suggestion(
|
||||
header_span,
|
||||
help,
|
||||
format!("{header_snip}{ret_snip}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)).to_string();
|
||||
|
||||
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
|
||||
diag.span_suggestion(
|
||||
block.span,
|
||||
"move the body of the async block to the enclosing function",
|
||||
body_snip,
|
||||
diag.multipart_suggestion(
|
||||
"make the function `async` and return the output of the future directly",
|
||||
vec![
|
||||
(header_span, format!("{header_snip}{ret_snip}")),
|
||||
(block.span, body_snip),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ use clippy_utils::msrvs::{self, Msrv};
|
|||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::{is_from_proc_macro, path_to_local};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
@ -129,9 +129,7 @@ fn is_not_const(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
|
|||
| DefKind::Ctor(..)
|
||||
| DefKind::AssocConst => false,
|
||||
|
||||
DefKind::Fn
|
||||
| DefKind::AssocFn
|
||||
| DefKind::Closure => tcx.constness(def_id) == Constness::NotConst,
|
||||
DefKind::Fn | DefKind::AssocFn | DefKind::Closure => tcx.constness(def_id) == Constness::NotConst,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,21 +43,21 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
|
|||
&& bin_op.node == BinOpKind::Eq
|
||||
{
|
||||
// a.count_ones() == 1
|
||||
if let ExprKind::MethodCall(method_name, reciever, [], _) = left.kind
|
||||
if let ExprKind::MethodCall(method_name, receiver, [], _) = left.kind
|
||||
&& method_name.ident.as_str() == "count_ones"
|
||||
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
|
||||
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
|
||||
&& check_lit(right, 1)
|
||||
{
|
||||
build_sugg(cx, expr, reciever, &mut applicability);
|
||||
build_sugg(cx, expr, receiver, &mut applicability);
|
||||
}
|
||||
|
||||
// 1 == a.count_ones()
|
||||
if let ExprKind::MethodCall(method_name, reciever, [], _) = right.kind
|
||||
if let ExprKind::MethodCall(method_name, receiver, [], _) = right.kind
|
||||
&& method_name.ident.as_str() == "count_ones"
|
||||
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
|
||||
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
|
||||
&& check_lit(left, 1)
|
||||
{
|
||||
build_sugg(cx, expr, reciever, &mut applicability);
|
||||
build_sugg(cx, expr, receiver, &mut applicability);
|
||||
}
|
||||
|
||||
// a & (a - 1) == 0
|
||||
|
@ -115,8 +115,8 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, reciever: &Expr<'_>, applicability: &mut Applicability) {
|
||||
let snippet = snippet_with_applicability(cx, reciever.span, "..", applicability);
|
||||
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, applicability: &mut Applicability) {
|
||||
let snippet = snippet_with_applicability(cx, receiver.span, "..", applicability);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
|
@ -74,8 +74,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
// check if using the same bindings as before
|
||||
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
|
||||
}
|
||||
// the names technically don't have to match; this makes the lint more conservative
|
||||
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
|
||||
// the names technically don't have to match; this makes the lint more conservative
|
||||
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
|
||||
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
|
||||
&& pat_contains_local(lhs.pat, a_id)
|
||||
&& pat_contains_local(rhs.pat, b_id)
|
||||
|
@ -149,16 +149,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
|
||||
let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
|
||||
|
||||
diag.span_suggestion(
|
||||
keep_arm.pat.span,
|
||||
"or try merging the arm patterns",
|
||||
format!("{keep_pat_snip} | {move_pat_snip}"),
|
||||
appl,
|
||||
)
|
||||
.span_suggestion(
|
||||
adjusted_arm_span(cx, move_arm.span),
|
||||
"and remove this obsolete arm",
|
||||
"",
|
||||
diag.multipart_suggestion(
|
||||
"or try merging the arm patterns and removing the obsolete arm",
|
||||
vec![
|
||||
(keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")),
|
||||
(adjusted_arm_span(cx, move_arm.span), String::new()),
|
||||
],
|
||||
appl,
|
||||
)
|
||||
.help("try changing either arm body");
|
||||
|
|
|
@ -27,7 +27,9 @@ mod wild_in_or_pats;
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::walk_span_to_context;
|
||||
use clippy_utils::{higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg};
|
||||
use clippy_utils::{
|
||||
higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments,
|
||||
};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
@ -1059,7 +1061,28 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
|
|||
}
|
||||
|
||||
redundant_pattern_match::check_match(cx, expr, ex, arms);
|
||||
single_match::check(cx, ex, arms, expr);
|
||||
let source_map = cx.tcx.sess.source_map();
|
||||
let mut match_comments = span_extract_comments(source_map, expr.span);
|
||||
// We remove comments from inside arms block.
|
||||
if !match_comments.is_empty() {
|
||||
for arm in arms {
|
||||
for comment in span_extract_comments(source_map, arm.body.span) {
|
||||
if let Some(index) = match_comments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, cm)| **cm == comment)
|
||||
.map(|(index, _)| index)
|
||||
{
|
||||
match_comments.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are still comments, it means they are outside of the arms, therefore
|
||||
// we should not lint.
|
||||
if match_comments.is_empty() {
|
||||
single_match::check(cx, ex, arms, expr);
|
||||
}
|
||||
match_bool::check(cx, ex, arms, expr);
|
||||
overlapping_arms::check(cx, ex, arms);
|
||||
match_wild_enum::check(cx, ex, arms);
|
||||
|
|
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
|
||||
SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
|
||||
peel_blocks_with_stmt,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -90,7 +90,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
|
|||
}
|
||||
|
||||
// Recursively check for each `else if let` phrase,
|
||||
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
|
||||
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else)
|
||||
&& SpanlessEq::new(cx).eq_expr(nested_if_let.let_expr, if_let.let_expr)
|
||||
{
|
||||
return check_if_let_inner(cx, nested_if_let);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
|
|||
PatKind::Lit(Expr {
|
||||
kind: ExprKind::Lit(lit),
|
||||
..
|
||||
}) if lit.node.is_str() => pat_ref_count + 1,
|
||||
}) if lit.node.is_str() || lit.node.is_bytestr() => pat_ref_count + 1,
|
||||
_ => pat_ref_count,
|
||||
};
|
||||
// References are only implicitly added to the pattern, so no overflow here.
|
||||
|
|
|
@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::{is_expr_identity_function, is_expr_untyped_identity_function, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::ExprKind;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
|
@ -21,6 +22,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg:
|
|||
if is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let Some(applicability) = is_identity(cx, filter_map_arg)
|
||||
{
|
||||
// check if the iterator is from an empty array, see issue #12653
|
||||
if let ExprKind::MethodCall(_, recv, ..) = expr.kind
|
||||
&& let ExprKind::MethodCall(_, recv2, ..) = recv.kind
|
||||
&& let ExprKind::Array(arr) = recv2.kind
|
||||
&& arr.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FILTER_MAP_IDENTITY,
|
||||
|
|
|
@ -19,7 +19,7 @@ fn extract_count_with_applicability(
|
|||
) -> Option<String> {
|
||||
let start = range.start?;
|
||||
let end = range.end?;
|
||||
// TODO: This doens't handle if either the start or end are negative literals, or if the start is
|
||||
// TODO: This doesn't handle if either the start or end are negative literals, or if the start is
|
||||
// not a literal. In the first case, we need to be careful about how we handle computing the
|
||||
// count to avoid overflows. In the second, we may need to add parenthesis to make the
|
||||
// suggestion correct.
|
||||
|
|
|
@ -1864,7 +1864,6 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// let opt: Option<u32> = None;
|
||||
///
|
||||
/// opt.unwrap_or_else(|| 42);
|
||||
|
@ -3839,13 +3838,11 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// vec![Some(1)].into_iter().filter(Option::is_some);
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// vec![Some(1)].into_iter().flatten();
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
|
@ -3865,13 +3862,11 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// vec![Ok::<i32, String>(1)].into_iter().filter(Result::is_ok);
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// vec![Ok::<i32, String>(1)].into_iter().flatten();
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
|
@ -3969,7 +3964,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// In the aformentioned cases it is not necessary to call `min()` or `max()`
|
||||
/// In the aforementioned cases it is not necessary to call `min()` or `max()`
|
||||
/// to compare values, it may even cause confusion.
|
||||
///
|
||||
/// ### Example
|
||||
|
@ -4982,6 +4977,10 @@ impl Methods {
|
|||
}
|
||||
map_identity::check(cx, expr, recv, m_arg, name, span);
|
||||
manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv);
|
||||
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
|
||||
},
|
||||
("map_break" | "map_continue", [m_arg]) => {
|
||||
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
|
||||
},
|
||||
("map_or", [def, map]) => {
|
||||
option_map_or_none::check(cx, expr, recv, def, map);
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::match_def_path;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
|
@ -11,20 +8,17 @@ use super::NEEDLESS_OPTION_TAKE;
|
|||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
|
||||
// Checks if expression type is equal to sym::Option and if the expr is not a syntactic place
|
||||
if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_OPTION_TAKE,
|
||||
expr.span,
|
||||
"called `Option::take()` on a temporary value",
|
||||
"try",
|
||||
format!(
|
||||
"{}",
|
||||
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) {
|
||||
if let Some(function_name) = source_of_temporary_value(recv) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
NEEDLESS_OPTION_TAKE,
|
||||
expr.span,
|
||||
"called `Option::take()` on a temporary value",
|
||||
None,
|
||||
format!("`{function_name}` creates a temporary value, so calling take() has no effect"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,9 +27,24 @@ fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
is_type_diagnostic_item(cx, expr_type, sym::Option)
|
||||
}
|
||||
|
||||
fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]);
|
||||
/// Returns the string of the function call that creates the temporary.
|
||||
/// When this function is called, we are reasonably certain that the `ExprKind` is either
|
||||
/// `Call` or `MethodCall` because we already checked that the expression is not
|
||||
/// `is_syntactic_place_expr()`.
|
||||
fn source_of_temporary_value<'a>(expr: &'a Expr<'_>) -> Option<&'a str> {
|
||||
match expr.peel_borrows().kind {
|
||||
ExprKind::Call(function, _) => {
|
||||
if let ExprKind::Path(QPath::Resolved(_, func_path)) = function.kind {
|
||||
if !func_path.segments.is_empty() {
|
||||
return Some(func_path.segments[0].ident.name.as_str());
|
||||
}
|
||||
}
|
||||
if let ExprKind::Path(QPath::TypeRelative(_, func_path_segment)) = function.kind {
|
||||
return Some(func_path_segment.ident.name.as_str());
|
||||
}
|
||||
None
|
||||
},
|
||||
ExprKind::MethodCall(path_segment, ..) => Some(path_segment.ident.name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ fn check_manual_split_once_indirect(
|
|||
let ctxt = expr.span.ctxt();
|
||||
let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
|
||||
if let (_, Node::LetStmt(local)) = parents.next()?
|
||||
&& let PatKind::Binding(BindingMode::MUT, iter_binding_id, iter_ident, None) = local.pat.kind
|
||||
&& let PatKind::Binding(BindingMode::MUT, iter_binding_id, _, None) = local.pat.kind
|
||||
&& let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
|
||||
&& let (_, Node::Block(enclosing_block)) = parents.next()?
|
||||
&& let mut stmts = enclosing_block
|
||||
|
@ -162,16 +162,20 @@ fn check_manual_split_once_indirect(
|
|||
UnwrapKind::Unwrap => ".unwrap()",
|
||||
UnwrapKind::QuestionMark => "?",
|
||||
};
|
||||
diag.span_suggestion_verbose(
|
||||
local.span,
|
||||
format!("try `{r}split_once`"),
|
||||
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
|
||||
|
||||
// Add a multipart suggestion
|
||||
diag.multipart_suggestion(
|
||||
format!("replace with `{r}split_once`"),
|
||||
vec![
|
||||
(
|
||||
local.span,
|
||||
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
|
||||
),
|
||||
(first.span, String::new()), // Remove the first usage
|
||||
(second.span, String::new()), // Remove the second usage
|
||||
],
|
||||
app,
|
||||
);
|
||||
|
||||
let remove_msg = format!("remove the `{iter_ident}` usages");
|
||||
diag.span_suggestion(first.span, remove_msg.clone(), "", app);
|
||||
diag.span_suggestion(second.span, remove_msg, "", app);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use clippy_utils::ty::{get_iterator_item_ty, implements_trait};
|
|||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local};
|
||||
use core::ops::ControlFlow;
|
||||
use itertools::Itertools;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind};
|
||||
|
@ -122,14 +123,13 @@ pub fn check_for_loop_iter(
|
|||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
diag.span_suggestion(expr.span, "use", snippet.to_owned(), applicability);
|
||||
if !references_to_binding.is_empty() {
|
||||
diag.multipart_suggestion(
|
||||
"remove any references to the binding",
|
||||
references_to_binding,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
let combined = references_to_binding
|
||||
.into_iter()
|
||||
.chain(vec![(expr.span, snippet.to_owned())])
|
||||
.collect_vec();
|
||||
|
||||
diag.multipart_suggestion("remove any references to the binding", combined, applicability);
|
||||
},
|
||||
);
|
||||
return true;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_trait_method;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_trait_method, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -211,8 +211,10 @@ pub(super) fn check<'tcx>(
|
|||
trigger.vec_name,
|
||||
if is_unstable { "_unstable" } else { "" },
|
||||
trigger.closure_arg,
|
||||
if trigger.reverse {
|
||||
format!("std::cmp::Reverse({})", trigger.closure_body)
|
||||
if let Some(std_or_core) = std_or_core(cx)
|
||||
&& trigger.reverse
|
||||
{
|
||||
format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body)
|
||||
} else {
|
||||
trigger.closure_body.to_string()
|
||||
},
|
||||
|
|
|
@ -27,14 +27,12 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// thread_local! {
|
||||
/// static BUF: String = String::new();
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// thread_local! {
|
||||
/// static BUF: String = const { String::new() };
|
||||
/// }
|
||||
|
|
|
@ -8,7 +8,7 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind, Stmt, StmtKind,
|
||||
UnsafeSource, StructTailExpr, is_range_literal,
|
||||
StructTailExpr, UnsafeSource, is_range_literal,
|
||||
};
|
||||
use rustc_infer::infer::TyCtxtInferExt as _;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
|
@ -43,8 +43,8 @@ impl Context {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
let (_, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
|
||||
if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
|
||||
let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
|
||||
if l_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
|
||||
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
|
||||
use rustc_ast::ast::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
|
@ -12,6 +13,7 @@ declare_clippy_lint! {
|
|||
/// and suggests to add parentheses. Currently it catches the following:
|
||||
/// * mixed usage of arithmetic and bit shifting/combining operators without
|
||||
/// parentheses
|
||||
/// * mixed usage of bitmasking and bit shifting operators without parentheses
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Not everyone knows the precedence of those operators by
|
||||
|
@ -20,6 +22,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
|
||||
/// * `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRECEDENCE,
|
||||
complexity,
|
||||
|
@ -51,8 +54,13 @@ impl EarlyLintPass for Precedence {
|
|||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
match (is_arith_expr(left), is_arith_expr(right)) {
|
||||
(true, true) => {
|
||||
match (op, get_bin_opt(left), get_bin_opt(right)) {
|
||||
(
|
||||
BitAnd | BitOr | BitXor,
|
||||
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
|
||||
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
|
||||
)
|
||||
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), Some(Add | Div | Mul | Rem | Sub)) => {
|
||||
let sugg = format!(
|
||||
"({}) {} ({})",
|
||||
snippet_with_applicability(cx, left.span, "..", &mut applicability),
|
||||
|
@ -61,7 +69,8 @@ impl EarlyLintPass for Precedence {
|
|||
);
|
||||
span_sugg(expr, sugg, applicability);
|
||||
},
|
||||
(true, false) => {
|
||||
(BitAnd | BitOr | BitXor, Some(Shl | Shr | Add | Div | Mul | Rem | Sub), _)
|
||||
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), _) => {
|
||||
let sugg = format!(
|
||||
"({}) {} {}",
|
||||
snippet_with_applicability(cx, left.span, "..", &mut applicability),
|
||||
|
@ -70,7 +79,8 @@ impl EarlyLintPass for Precedence {
|
|||
);
|
||||
span_sugg(expr, sugg, applicability);
|
||||
},
|
||||
(false, true) => {
|
||||
(BitAnd | BitOr | BitXor, _, Some(Shl | Shr | Add | Div | Mul | Rem | Sub))
|
||||
| (Shl | Shr, _, Some(Add | Div | Mul | Rem | Sub)) => {
|
||||
let sugg = format!(
|
||||
"{} {} ({})",
|
||||
snippet_with_applicability(cx, left.span, "..", &mut applicability),
|
||||
|
@ -79,27 +89,20 @@ impl EarlyLintPass for Precedence {
|
|||
);
|
||||
span_sugg(expr, sugg, applicability);
|
||||
},
|
||||
(false, false) => (),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_arith_expr(expr: &Expr) -> bool {
|
||||
fn get_bin_opt(expr: &Expr) -> Option<BinOpKind> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
|
||||
_ => false,
|
||||
ExprKind::Binary(Spanned { node: op, .. }, _, _) => Some(op),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn is_bit_op(op: BinOpKind) -> bool {
|
||||
use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
|
||||
matches!(op, BitXor | BitAnd | BitOr | Shl | Shr)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn is_arith_op(op: BinOpKind) -> bool {
|
||||
use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub};
|
||||
matches!(op, Add | Sub | Mul | Div | Rem)
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ pub struct QuestionMark {
|
|||
/// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
|
||||
try_block_depth_stack: Vec<u32>,
|
||||
/// Keeps track of the number of inferred return type closures we are inside, to avoid problems
|
||||
/// with the `Err(x.into())` expansion being ambiguious.
|
||||
/// with the `Err(x.into())` expansion being ambiguous.
|
||||
inferred_ret_closure_stack: u16,
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
|
|||
let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr));
|
||||
let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
|
||||
let parent_expr = get_parent_expr(cx, expr);
|
||||
let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix);
|
||||
let needs_parens_for_prefix =
|
||||
parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix);
|
||||
|
||||
if expr_ty == indexed_ty {
|
||||
if expr_ref_count > indexed_ref_count {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::path_to_local_id;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::visitors::{Descend, Visitable, for_each_expr};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
|
@ -175,9 +178,31 @@ fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second
|
|||
false
|
||||
}
|
||||
|
||||
/// Checks if the given local is used, except for in child expression of `except`.
|
||||
///
|
||||
/// This is a version of [`is_local_used`](clippy_utils::visitors::is_local_used), used to
|
||||
/// implement the fix for <https://github.com/rust-lang/rust-clippy/issues/10780>.
|
||||
pub fn is_local_used_except<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
visitable: impl Visitable<'tcx>,
|
||||
id: HirId,
|
||||
except: Option<HirId>,
|
||||
) -> bool {
|
||||
for_each_expr(cx, visitable, |e| {
|
||||
if except.is_some_and(|it| it == e.hir_id) {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
} else if path_to_local_id(e, id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span) {
|
||||
let (lint, msg) = match find_init(cx, pat.hir_id) {
|
||||
Some(expr) if is_self_shadow(cx, pat, expr, shadowed) => {
|
||||
Some((expr, _)) if is_self_shadow(cx, pat, expr, shadowed) => {
|
||||
let msg = format!(
|
||||
"`{}` is shadowed by itself in `{}`",
|
||||
snippet(cx, pat.span, "_"),
|
||||
|
@ -185,7 +210,7 @@ fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span)
|
|||
);
|
||||
(SHADOW_SAME, msg)
|
||||
},
|
||||
Some(expr) if is_local_used(cx, expr, shadowed) => {
|
||||
Some((expr, except)) if is_local_used_except(cx, expr, shadowed, except) => {
|
||||
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
|
||||
(SHADOW_REUSE, msg)
|
||||
},
|
||||
|
@ -232,15 +257,32 @@ fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_
|
|||
|
||||
/// Finds the "init" expression for a pattern: `let <pat> = <init>;` (or `if let`) or
|
||||
/// `match <init> { .., <pat> => .., .. }`
|
||||
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
|
||||
for (_, node) in cx.tcx.hir().parent_iter(hir_id) {
|
||||
///
|
||||
/// For closure arguments passed to a method call, returns the method call, and the `HirId` of the
|
||||
/// closure (which will later be skipped). This is for <https://github.com/rust-lang/rust-clippy/issues/10780>
|
||||
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<(&'tcx Expr<'tcx>, Option<HirId>)> {
|
||||
for (hir_id, node) in cx.tcx.hir().parent_iter(hir_id) {
|
||||
let init = match node {
|
||||
Node::Arm(_) | Node::Pat(_) => continue,
|
||||
Node::Arm(_) | Node::Pat(_) | Node::PatField(_) | Node::Param(_) => continue,
|
||||
Node::Expr(expr) => match expr.kind {
|
||||
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some(e),
|
||||
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some((e, None)),
|
||||
// If we're a closure argument, then a parent call is also an associated item.
|
||||
ExprKind::Closure(_) => {
|
||||
if let Some((_, node)) = cx.tcx.hir().parent_iter(hir_id).next() {
|
||||
match node {
|
||||
Node::Expr(expr) => match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, _) | ExprKind::Call(_, _) => Some((expr, Some(hir_id))),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
Node::LetStmt(local) => local.init,
|
||||
Node::LetStmt(local) => local.init.map(|init| (init, None)),
|
||||
_ => None,
|
||||
};
|
||||
return init;
|
||||
|
|
|
@ -99,16 +99,10 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> {
|
|||
snippet(cx, apa.last_bind_ident.span, ".."),
|
||||
)
|
||||
};
|
||||
diag.span_suggestion_verbose(
|
||||
apa.first_stmt_span,
|
||||
|
||||
diag.multipart_suggestion_verbose(
|
||||
"merge the temporary construction with its single usage",
|
||||
stmt,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.span_suggestion(
|
||||
apa.last_stmt_span,
|
||||
"remove separated single usage",
|
||||
"",
|
||||
vec![(apa.first_stmt_span, stmt), (apa.last_stmt_span, String::new())],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -86,7 +86,8 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
|
|||
return;
|
||||
};
|
||||
|
||||
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind else {
|
||||
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
@ -370,12 +370,10 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// let _ = "str".to_string();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// let _ = "str".to_owned();
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
|
@ -424,13 +422,11 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// let msg = String::from("Hello World");
|
||||
/// let _ = msg.to_string();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// let msg = String::from("Hello World");
|
||||
/// let _ = msg.clone();
|
||||
/// ```
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source, is_local_used};
|
||||
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
@ -71,25 +71,38 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
|
|||
local.span,
|
||||
"this let-binding has unit value",
|
||||
|diag| {
|
||||
let mut suggestions = Vec::new();
|
||||
|
||||
// Suggest omitting the `let` binding
|
||||
if let Some(expr) = &local.init {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0;
|
||||
diag.span_suggestion(local.span, "omit the `let` binding", format!("{snip};"), app);
|
||||
suggestions.push((local.span, format!("{snip};")));
|
||||
}
|
||||
|
||||
if let PatKind::Binding(_, binding_hir_id, ident, ..) = local.pat.kind
|
||||
// If this is a binding pattern, we need to add suggestions to remove any usages
|
||||
// of the variable
|
||||
if let PatKind::Binding(_, binding_hir_id, ..) = local.pat.kind
|
||||
&& let Some(body_id) = cx.enclosing_body.as_ref()
|
||||
&& let body = cx.tcx.hir().body(*body_id)
|
||||
&& is_local_used(cx, body, binding_hir_id)
|
||||
{
|
||||
let identifier = ident.as_str();
|
||||
let body = cx.tcx.hir().body(*body_id);
|
||||
|
||||
// Collect variable usages
|
||||
let mut visitor = UnitVariableCollector::new(binding_hir_id);
|
||||
walk_body(&mut visitor, body);
|
||||
visitor.spans.into_iter().for_each(|span| {
|
||||
let msg =
|
||||
format!("variable `{identifier}` of type `()` can be replaced with explicit `()`");
|
||||
diag.span_suggestion(span, msg, "()", Applicability::MachineApplicable);
|
||||
});
|
||||
|
||||
// Add suggestions for replacing variable usages
|
||||
suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string())));
|
||||
}
|
||||
|
||||
// Emit appropriate diagnostic based on whether there are usages of the let binding
|
||||
if !suggestions.is_empty() {
|
||||
let message = if suggestions.len() == 1 {
|
||||
"omit the `let` binding"
|
||||
} else {
|
||||
"omit the `let` binding and replace variable usages with `()`"
|
||||
};
|
||||
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -25,13 +25,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
|||
return;
|
||||
}
|
||||
|
||||
let (reciever, args) = match expr.kind {
|
||||
let (receiver, args) = match expr.kind {
|
||||
ExprKind::Call(_, args) => (None, args),
|
||||
ExprKind::MethodCall(_, receiver, args, _) => (Some(receiver), args),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let args_to_recover = reciever
|
||||
let args_to_recover = receiver
|
||||
.into_iter()
|
||||
.chain(args)
|
||||
.filter(|arg| {
|
||||
|
|
|
@ -17,7 +17,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unneccessary allocations or confusion.
|
||||
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unnecessary allocations or confusion.
|
||||
/// This is also most likely what you meant to write.
|
||||
///
|
||||
/// ### Example
|
||||
|
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{get_parent_expr, path_to_local};
|
||||
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp, StructTailExpr};
|
||||
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, StructTailExpr, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
|
@ -63,7 +63,9 @@ impl LateLintPass<'_> for UnnecessaryStruct {
|
|||
// all fields match, no base given
|
||||
path.span
|
||||
},
|
||||
(Some(path), StructTailExpr::Base(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
|
||||
(Some(path), StructTailExpr::Base(base))
|
||||
if base_is_suitable(cx, expr, base) && path_matches_base(path, base) =>
|
||||
{
|
||||
// all fields match, has base: ensure that the path of the base matches
|
||||
base.span
|
||||
},
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::sugg::{DiagExt as _, Sugg};
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
|
||||
use clippy_utils::{get_parent_expr, is_trait_method, is_ty_alias, path_to_local};
|
||||
use clippy_utils::{
|
||||
get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
|
||||
|
@ -10,7 +12,7 @@ use rustc_infer::infer::TyCtxtInferExt;
|
|||
use rustc_infer::traits::Obligation;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::traits::ObligationCause;
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
|
||||
use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
|
@ -382,3 +384,50 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a
|
||||
/// higher-order mapping function.
|
||||
pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if has_eligible_receiver(cx, recv, expr)
|
||||
&& (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From))
|
||||
&& let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind()
|
||||
&& let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice()
|
||||
&& same_type_and_consts(from_ty, to_ty)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
USELESS_CONVERSION,
|
||||
expr.span.with_lo(recv.span.hi()),
|
||||
format!("useless conversion to the same type: `{from_ty}`"),
|
||||
|diag| {
|
||||
diag.suggest_remove_item(
|
||||
cx,
|
||||
expr.span.with_lo(recv.span.hi()),
|
||||
"consider removing",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool {
|
||||
let recv_ty = cx.typeck_results().expr_ty(recv);
|
||||
if is_inherent_method_call(cx, expr)
|
||||
&& let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
|
||||
{
|
||||
if let Some(diag_name) = cx.tcx.get_diagnostic_name(recv_ty_defid)
|
||||
&& matches!(diag_name, sym::Option | sym::Result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if cx.tcx.is_diagnostic_item(sym::ControlFlow, recv_ty_defid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if is_trait_method(cx, expr, sym::Iterator) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use rustc_ast::LitIntType;
|
|||
use rustc_ast::ast::{LitFloatType, LitKind};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{
|
||||
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind,
|
||||
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind, StructTailExpr,
|
||||
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind, ExprKind,
|
||||
FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, StructTailExpr, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
@ -625,7 +625,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
},
|
||||
ExprKind::UnsafeBinderCast(..) => {
|
||||
unimplemented!("unsafe binders are not implemented yet");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::utils::internal_lints::lint_without_lint_pass::is_lint_ref_type;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use regex::Regex;
|
||||
use rustc_ast as ast;
|
||||
use rustc_hir::{Item, ItemKind, Mutability};
|
||||
use rustc_hir::{Attribute, Item, ItemKind, Mutability};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
|
@ -51,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
|
|||
.hir()
|
||||
.attrs(item.hir_id())
|
||||
.iter()
|
||||
.filter_map(|attr| ast::Attribute::doc_str(attr).map(|sym| (sym, attr)));
|
||||
.filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr)));
|
||||
if is_lint_ref_type(cx, ty) {
|
||||
for (line, attr) in lines {
|
||||
let cur_line = line.as_str().trim();
|
||||
|
|
|
@ -92,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Call(func, [arg]) = &expr.kind
|
||||
&& let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind()
|
||||
&& match_def_path(cx, *def_id, &paths::SYMBOL_INTERN)
|
||||
&& cx.tcx.is_diagnostic_item(sym::SymbolIntern, *def_id)
|
||||
&& let Some(Constant::Str(arg)) = ConstEvalCtxt::new(cx).eval_simple(arg)
|
||||
&& let value = Symbol::intern(&arg).as_u32()
|
||||
&& let Some(&def_id) = self.symbol_map.get(&value)
|
||||
|
|
|
@ -3,6 +3,7 @@ use clippy_utils::macros::root_macro_call_first_node;
|
|||
use clippy_utils::{is_lint_allowed, match_def_path, paths};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::hir_id::CRATE_HIR_ID;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
|
@ -13,7 +14,6 @@ use rustc_session::impl_lint_pass;
|
|||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{Span, sym};
|
||||
use {rustc_ast as ast, rustc_hir as hir};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -249,11 +249,11 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'
|
|||
pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
attrs.iter().find_map(|attr| {
|
||||
if let ast::AttrKind::Normal(attr_kind) = &attr.kind
|
||||
if let hir::AttrKind::Normal(attr_kind) = &attr.kind
|
||||
// Identify attribute
|
||||
&& let [tool_name, attr_name] = &attr_kind.item.path.segments[..]
|
||||
&& tool_name.ident.name == sym::clippy
|
||||
&& attr_name.ident.name == sym::version
|
||||
&& let [tool_name, attr_name] = &attr_kind.path.segments[..]
|
||||
&& tool_name.name == sym::clippy
|
||||
&& attr_name.name == sym::version
|
||||
&& let Some(version) = attr.value_str()
|
||||
{
|
||||
Some(version)
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::paths;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::match_type;
|
||||
use clippy_utils::{match_function_call, paths};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Detects symbol comparision using `Symbol::intern`.
|
||||
/// Detects symbol comparison using `Symbol::intern`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Comparision via `Symbol::as_str()` is faster if the interned symbols are not reused.
|
||||
/// Comparison via `Symbol::as_str()` is faster if the interned symbols are not reused.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// None, see suggestion.
|
||||
pub SLOW_SYMBOL_COMPARISONS,
|
||||
internal,
|
||||
"detects slow comparisions of symbol"
|
||||
"detects slow comparisons of symbol"
|
||||
}
|
||||
|
||||
declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]);
|
||||
|
@ -34,7 +34,12 @@ fn check_slow_comparison<'tcx>(
|
|||
op2: &'tcx Expr<'tcx>,
|
||||
) -> Option<(Span, String)> {
|
||||
if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL)
|
||||
&& let Some([symbol_name_expr]) = match_function_call(cx, op2, &paths::SYMBOL_INTERN)
|
||||
&& let ExprKind::Call(fun, args) = op2.kind
|
||||
&& let ExprKind::Path(ref qpath) = fun.kind
|
||||
&& cx
|
||||
.tcx
|
||||
.is_diagnostic_item(sym::SymbolIntern, cx.qpath_res(qpath, fun.hir_id).opt_def_id()?)
|
||||
&& let [symbol_name_expr] = args
|
||||
&& let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr)
|
||||
{
|
||||
Some((op1.span, symbol_name))
|
||||
|
|
|
@ -2,13 +2,14 @@ use ControlFlow::{Break, Continue};
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{fn_def_id, get_enclosing_block, match_any_def_paths, match_def_path, path_to_local_id, paths};
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_ast::visit::visit_opt;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_local};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -22,6 +23,17 @@ declare_clippy_lint! {
|
|||
/// which can eventually lead to resource exhaustion, so it's recommended to call `wait()` in long-running applications.
|
||||
/// Such processes are called "zombie processes".
|
||||
///
|
||||
/// To reduce the rate of false positives, if the spawned process is assigned to a binding, the lint actually works the other way around; it
|
||||
/// conservatively checks that all uses of a variable definitely don't call `wait()` and only then emits a warning.
|
||||
/// For that reason, a seemingly unrelated use can get called out as calling `wait()` in help messages.
|
||||
///
|
||||
/// ### Control flow
|
||||
/// If a `wait()` call exists in an if/then block but not in the else block (or there is no else block),
|
||||
/// then this still gets linted as not calling `wait()` in all code paths.
|
||||
/// Likewise, when early-returning from the function, `wait()` calls that appear after the return expression
|
||||
/// are also not accepted.
|
||||
/// In other words, the `wait()` call must be unconditionally reachable after the spawn expression.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use std::process::Command;
|
||||
|
@ -53,26 +65,47 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
|
|||
if let PatKind::Binding(_, local_id, ..) = local.pat.kind
|
||||
&& let Some(enclosing_block) = get_enclosing_block(cx, expr.hir_id) =>
|
||||
{
|
||||
let mut vis = WaitFinder::WalkUpTo(cx, local_id);
|
||||
let mut vis = WaitFinder {
|
||||
cx,
|
||||
local_id,
|
||||
state: VisitorState::WalkUpToLocal,
|
||||
early_return: None,
|
||||
missing_wait_branch: None,
|
||||
};
|
||||
|
||||
// If it does have a `wait()` call, we're done. Don't lint.
|
||||
if let Break(BreakReason::WaitFound) = walk_block(&mut vis, enclosing_block) {
|
||||
return;
|
||||
}
|
||||
let res = (
|
||||
walk_block(&mut vis, enclosing_block),
|
||||
vis.missing_wait_branch,
|
||||
vis.early_return,
|
||||
);
|
||||
|
||||
let cause = match res {
|
||||
(Break(MaybeWait(wait_span)), _, Some(return_span)) => {
|
||||
Cause::EarlyReturn { wait_span, return_span }
|
||||
},
|
||||
(Break(MaybeWait(_)), _, None) => return,
|
||||
(Continue(()), None, _) => Cause::NeverWait,
|
||||
(Continue(()), Some(MissingWaitBranch::MissingElse { if_span, wait_span }), _) => {
|
||||
Cause::MissingElse { wait_span, if_span }
|
||||
},
|
||||
(Continue(()), Some(MissingWaitBranch::MissingWaitInBranch { branch_span, wait_span }), _) => {
|
||||
Cause::MissingWaitInBranch { wait_span, branch_span }
|
||||
},
|
||||
};
|
||||
|
||||
// Don't emit a suggestion since the binding is used later
|
||||
check(cx, expr, false);
|
||||
check(cx, expr, cause, false);
|
||||
},
|
||||
Node::LetStmt(&LetStmt { pat, .. }) if let PatKind::Wild = pat.kind => {
|
||||
// `let _ = child;`, also dropped immediately without `wait()`ing
|
||||
check(cx, expr, true);
|
||||
check(cx, expr, Cause::NeverWait, true);
|
||||
},
|
||||
Node::Stmt(&Stmt {
|
||||
kind: StmtKind::Semi(_),
|
||||
..
|
||||
}) => {
|
||||
// Immediately dropped. E.g. `std::process::Command::new("echo").spawn().unwrap();`
|
||||
check(cx, expr, true);
|
||||
check(cx, expr, Cause::NeverWait, true);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
@ -80,21 +113,10 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
|
|||
}
|
||||
}
|
||||
|
||||
enum BreakReason {
|
||||
WaitFound,
|
||||
EarlyReturn,
|
||||
}
|
||||
struct MaybeWait(Span);
|
||||
|
||||
/// A visitor responsible for finding a `wait()` call on a local variable.
|
||||
///
|
||||
/// Conditional `wait()` calls are assumed to not call wait:
|
||||
/// ```ignore
|
||||
/// let mut c = Command::new("").spawn().unwrap();
|
||||
/// if true {
|
||||
/// c.wait();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that this visitor does NOT explicitly look for `wait()` calls directly, but rather does the
|
||||
/// inverse -- checking if all uses of the local are either:
|
||||
/// - a field access (`child.{stderr,stdin,stdout}`)
|
||||
|
@ -104,43 +126,50 @@ enum BreakReason {
|
|||
///
|
||||
/// None of these are sufficient to prevent zombie processes.
|
||||
/// Doing it like this means more FNs, but FNs are better than FPs.
|
||||
///
|
||||
/// `return` expressions, conditional or not, short-circuit the visitor because
|
||||
/// if a `wait()` call hadn't been found at that point, it might never reach one at a later point:
|
||||
/// ```ignore
|
||||
/// let mut c = Command::new("").spawn().unwrap();
|
||||
/// if true {
|
||||
/// return; // Break(BreakReason::EarlyReturn)
|
||||
/// }
|
||||
/// c.wait(); // this might not be reachable
|
||||
/// ```
|
||||
enum WaitFinder<'a, 'tcx> {
|
||||
WalkUpTo(&'a LateContext<'tcx>, HirId),
|
||||
Found(&'a LateContext<'tcx>, HirId),
|
||||
struct WaitFinder<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
local_id: HirId,
|
||||
state: VisitorState,
|
||||
early_return: Option<Span>,
|
||||
// When joining two if branches where one of them doesn't call `wait()`, stores its span for more targetted help
|
||||
// messages
|
||||
missing_wait_branch: Option<MissingWaitBranch>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum VisitorState {
|
||||
WalkUpToLocal,
|
||||
LocalFound,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum MissingWaitBranch {
|
||||
MissingElse { if_span: Span, wait_span: Span },
|
||||
MissingWaitInBranch { branch_span: Span, wait_span: Span },
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::OnlyBodies;
|
||||
type Result = ControlFlow<BreakReason>;
|
||||
type Result = ControlFlow<MaybeWait>;
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result {
|
||||
if let Self::WalkUpTo(cx, local_id) = *self
|
||||
if self.state == VisitorState::WalkUpToLocal
|
||||
&& let PatKind::Binding(_, pat_id, ..) = l.pat.kind
|
||||
&& local_id == pat_id
|
||||
&& self.local_id == pat_id
|
||||
{
|
||||
*self = Self::Found(cx, local_id);
|
||||
self.state = VisitorState::LocalFound;
|
||||
}
|
||||
|
||||
walk_local(self, l)
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
|
||||
let Self::Found(cx, local_id) = *self else {
|
||||
if self.state != VisitorState::LocalFound {
|
||||
return walk_expr(self, ex);
|
||||
};
|
||||
}
|
||||
|
||||
if path_to_local_id(ex, local_id) {
|
||||
match cx.tcx.parent_hir_node(ex.hir_id) {
|
||||
if path_to_local_id(ex, self.local_id) {
|
||||
match self.cx.tcx.parent_hir_node(ex.hir_id) {
|
||||
Node::Stmt(Stmt {
|
||||
kind: StmtKind::Semi(_),
|
||||
..
|
||||
|
@ -148,29 +177,33 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
|
|||
Node::Expr(expr) if let ExprKind::Field(..) = expr.kind => {},
|
||||
Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {},
|
||||
Node::Expr(expr)
|
||||
if let Some(fn_did) = fn_def_id(cx, expr)
|
||||
&& match_any_def_paths(cx, fn_did, &[&paths::CHILD_ID, &paths::CHILD_KILL]).is_some() => {},
|
||||
if let Some(fn_did) = fn_def_id(self.cx, expr)
|
||||
&& match_any_def_paths(self.cx, fn_did, &[&paths::CHILD_ID, &paths::CHILD_KILL]).is_some() => {
|
||||
},
|
||||
|
||||
// Conservatively assume that all other kinds of nodes call `.wait()` somehow.
|
||||
_ => return Break(BreakReason::WaitFound),
|
||||
_ => return Break(MaybeWait(ex.span)),
|
||||
}
|
||||
} else {
|
||||
match ex.kind {
|
||||
ExprKind::Ret(..) => return Break(BreakReason::EarlyReturn),
|
||||
ExprKind::Ret(e) => {
|
||||
visit_opt!(self, visit_expr, e);
|
||||
if self.early_return.is_none() {
|
||||
self.early_return = Some(ex.span);
|
||||
}
|
||||
|
||||
return Continue(());
|
||||
},
|
||||
ExprKind::If(cond, then, None) => {
|
||||
walk_expr(self, cond)?;
|
||||
|
||||
// A `wait()` call in an if expression with no else is not enough:
|
||||
//
|
||||
// let c = spawn();
|
||||
// if true {
|
||||
// c.wait();
|
||||
// }
|
||||
//
|
||||
// This might not call wait(). However, early returns are propagated,
|
||||
// because they might lead to a later wait() not being called.
|
||||
if let Break(BreakReason::EarlyReturn) = walk_expr(self, then) {
|
||||
return Break(BreakReason::EarlyReturn);
|
||||
if let Break(MaybeWait(wait_span)) = walk_expr(self, then)
|
||||
&& self.missing_wait_branch.is_none()
|
||||
{
|
||||
self.missing_wait_branch = Some(MissingWaitBranch::MissingElse {
|
||||
if_span: ex.span,
|
||||
wait_span,
|
||||
});
|
||||
}
|
||||
|
||||
return Continue(());
|
||||
|
@ -179,22 +212,31 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
|
|||
ExprKind::If(cond, then, Some(else_)) => {
|
||||
walk_expr(self, cond)?;
|
||||
|
||||
#[expect(clippy::unnested_or_patterns)]
|
||||
match (walk_expr(self, then), walk_expr(self, else_)) {
|
||||
(Continue(()), Continue(()))
|
||||
(Continue(()), Continue(())) => {},
|
||||
|
||||
// `wait()` in one branch but nothing in the other does not count
|
||||
| (Continue(()), Break(BreakReason::WaitFound))
|
||||
| (Break(BreakReason::WaitFound), Continue(())) => {},
|
||||
|
||||
// `wait()` in both branches is ok
|
||||
(Break(BreakReason::WaitFound), Break(BreakReason::WaitFound)) => {
|
||||
return Break(BreakReason::WaitFound);
|
||||
(Continue(()), Break(MaybeWait(wait_span))) => {
|
||||
if self.missing_wait_branch.is_none() {
|
||||
self.missing_wait_branch = Some(MissingWaitBranch::MissingWaitInBranch {
|
||||
branch_span: ex.span.shrink_to_lo().to(then.span),
|
||||
wait_span,
|
||||
});
|
||||
}
|
||||
},
|
||||
(Break(MaybeWait(wait_span)), Continue(())) => {
|
||||
if self.missing_wait_branch.is_none() {
|
||||
self.missing_wait_branch = Some(MissingWaitBranch::MissingWaitInBranch {
|
||||
branch_span: then.span.shrink_to_hi().to(else_.span),
|
||||
wait_span,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Propagate early returns in either branch
|
||||
(Break(BreakReason::EarlyReturn), _) | (_, Break(BreakReason::EarlyReturn)) => {
|
||||
return Break(BreakReason::EarlyReturn);
|
||||
// `wait()` in both branches is ok
|
||||
(Break(MaybeWait(wait_span)), Break(MaybeWait(_))) => {
|
||||
self.missing_wait_branch = None;
|
||||
return Break(MaybeWait(wait_span));
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -208,8 +250,40 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
|
|||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
let (Self::Found(cx, _) | Self::WalkUpTo(cx, _)) = self;
|
||||
cx.tcx.hir()
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Cause {
|
||||
/// No call to `wait()` at all
|
||||
NeverWait,
|
||||
/// `wait()` call exists, but not all code paths definitely lead to one due to
|
||||
/// an early return
|
||||
EarlyReturn { wait_span: Span, return_span: Span },
|
||||
/// `wait()` call exists in some if branches but not this one
|
||||
MissingWaitInBranch { wait_span: Span, branch_span: Span },
|
||||
/// `wait()` call exists in an if/then branch but it is missing an else block
|
||||
MissingElse { wait_span: Span, if_span: Span },
|
||||
}
|
||||
|
||||
impl Cause {
|
||||
fn message(self) -> &'static str {
|
||||
match self {
|
||||
Cause::NeverWait => "spawned process is never `wait()`ed on",
|
||||
Cause::EarlyReturn { .. } | Cause::MissingWaitInBranch { .. } | Cause::MissingElse { .. } => {
|
||||
"spawned process is not `wait()`ed on in all code paths"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_help(self) -> &'static str {
|
||||
match self {
|
||||
Cause::NeverWait => "consider calling `.wait()`",
|
||||
Cause::EarlyReturn { .. } | Cause::MissingWaitInBranch { .. } | Cause::MissingElse { .. } => {
|
||||
"consider calling `.wait()` in all code paths"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +294,7 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
|
|||
/// `let _ = <expr that spawns child>;`.
|
||||
///
|
||||
/// This checks if the program doesn't unconditionally exit after the spawn expression.
|
||||
fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, emit_suggestion: bool) {
|
||||
fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Cause, emit_suggestion: bool) {
|
||||
let Some(block) = get_enclosing_block(cx, spawn_expr.hir_id) else {
|
||||
return;
|
||||
};
|
||||
|
@ -234,27 +308,46 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, emit_sugges
|
|||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
ZOMBIE_PROCESSES,
|
||||
spawn_expr.span,
|
||||
"spawned process is never `wait()`ed on",
|
||||
|diag| {
|
||||
if emit_suggestion {
|
||||
diag.span_suggestion(
|
||||
spawn_expr.span.shrink_to_hi(),
|
||||
"try",
|
||||
".wait()",
|
||||
Applicability::MaybeIncorrect,
|
||||
span_lint_and_then(cx, ZOMBIE_PROCESSES, spawn_expr.span, cause.message(), |diag| {
|
||||
match cause {
|
||||
Cause::EarlyReturn { wait_span, return_span } => {
|
||||
diag.span_note(
|
||||
return_span,
|
||||
"no `wait()` call exists on the code path to this early return",
|
||||
);
|
||||
} else {
|
||||
diag.note("consider calling `.wait()`");
|
||||
}
|
||||
diag.span_note(
|
||||
wait_span,
|
||||
"`wait()` call exists, but it is unreachable due to the early return",
|
||||
);
|
||||
},
|
||||
Cause::MissingWaitInBranch { wait_span, branch_span } => {
|
||||
diag.span_note(branch_span, "`wait()` is not called in this if branch");
|
||||
diag.span_note(wait_span, "`wait()` is called in the other branch");
|
||||
},
|
||||
Cause::MissingElse { if_span, wait_span } => {
|
||||
diag.span_note(
|
||||
if_span,
|
||||
"this if expression has a `wait()` call, but it is missing an else block",
|
||||
);
|
||||
diag.span_note(wait_span, "`wait()` called here");
|
||||
},
|
||||
Cause::NeverWait => {},
|
||||
}
|
||||
|
||||
diag.note("not doing so might leave behind zombie processes")
|
||||
.note("see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning");
|
||||
},
|
||||
);
|
||||
if emit_suggestion {
|
||||
diag.span_suggestion(
|
||||
spawn_expr.span.shrink_to_hi(),
|
||||
"try",
|
||||
".wait()",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help(cause.fallback_help());
|
||||
}
|
||||
|
||||
diag.note("not doing so might leave behind zombie processes")
|
||||
.note("see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning");
|
||||
});
|
||||
}
|
||||
|
||||
/// Checks if the given expression exits the process.
|
||||
|
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2024-11-28
|
||||
nightly-2024-12-26
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
@ -133,11 +133,7 @@ fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name:
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_unique_attr<'a, A: AttributeExt>(
|
||||
sess: &'a Session,
|
||||
attrs: &'a [A],
|
||||
name: &'static str,
|
||||
) -> Option<&'a A> {
|
||||
pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: &'static str) -> Option<&'a A> {
|
||||
let mut unique_attr: Option<&A> = None;
|
||||
for attr in get_attr(sess, attrs, name) {
|
||||
if let Some(duplicate) = unique_attr {
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::ty::is_type_diagnostic_item;
|
|||
|
||||
use rustc_ast::ast;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Arm, Block, Expr, ExprKind, StructTailExpr, HirId, LoopSource, MatchSource, Node, Pat, QPath};
|
||||
use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StructTailExpr};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{Span, sym, symbol};
|
||||
|
||||
|
|
|
@ -7,10 +7,10 @@ use rustc_data_structures::fx::FxHasher;
|
|||
use rustc_hir::MatchSource::TryDesugar;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr,
|
||||
ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime,
|
||||
LifetimeName, Pat, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitBoundModifiers, Ty,
|
||||
TyKind, StructTailExpr,
|
||||
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField,
|
||||
ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName,
|
||||
Pat, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, TraitBoundModifiers, Ty,
|
||||
TyKind,
|
||||
};
|
||||
use rustc_lexer::{TokenKind, tokenize};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -386,7 +386,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
|||
self.eq_qpath(l_path, r_path)
|
||||
&& match (lo, ro) {
|
||||
(StructTailExpr::Base(l),StructTailExpr::Base(r)) => self.eq_expr(l, r),
|
||||
(StructTailExpr::None, StructTailExpr::None) => true,
|
||||
(StructTailExpr::None, StructTailExpr::None) |
|
||||
(StructTailExpr::DefaultFields(_), StructTailExpr::DefaultFields(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
@ -473,10 +473,10 @@ impl HirEqInterExpr<'_, '_, '_> {
|
|||
(ConstArgKind::Anon(l_an), ConstArgKind::Anon(r_an)) => self.eq_body(l_an.body, r_an.body),
|
||||
(ConstArgKind::Infer(..), ConstArgKind::Infer(..)) => true,
|
||||
// Use explicit match for now since ConstArg is undergoing flux.
|
||||
(ConstArgKind::Path(..), ConstArgKind::Anon(..)) | (ConstArgKind::Anon(..), ConstArgKind::Path(..))
|
||||
| (ConstArgKind::Infer(..), _) | (_, ConstArgKind::Infer(..)) => {
|
||||
false
|
||||
},
|
||||
(ConstArgKind::Path(..), ConstArgKind::Anon(..))
|
||||
| (ConstArgKind::Anon(..), ConstArgKind::Path(..))
|
||||
| (ConstArgKind::Infer(..), _)
|
||||
| (_, ConstArgKind::Infer(..)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1043,7 +1043,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
if let Some(ty) = ty {
|
||||
self.hash_ty(ty);
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::Err(_) => {},
|
||||
}
|
||||
}
|
||||
|
@ -1255,7 +1255,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
},
|
||||
TyKind::UnsafeBinder(binder) => {
|
||||
self.hash_ty(binder.inner_ty);
|
||||
}
|
||||
},
|
||||
TyKind::Err(_)
|
||||
| TyKind::Infer
|
||||
| TyKind::Never
|
||||
|
|
|
@ -1960,43 +1960,6 @@ pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
/// Matches a function call with the given path and returns the arguments.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
|
||||
/// ```
|
||||
/// This function is deprecated. Use [`match_function_call_with_def_id`].
|
||||
pub fn match_function_call<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
path: &[&str],
|
||||
) -> Option<&'tcx [Expr<'tcx>]> {
|
||||
if let ExprKind::Call(fun, args) = expr.kind
|
||||
&& let ExprKind::Path(ref qpath) = fun.kind
|
||||
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
|
||||
&& match_def_path(cx, fun_def_id, path)
|
||||
{
|
||||
return Some(args);
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
pub fn match_function_call_with_def_id<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
fun_def_id: DefId,
|
||||
) -> Option<&'tcx [Expr<'tcx>]> {
|
||||
if let ExprKind::Call(fun, args) = expr.kind
|
||||
&& let ExprKind::Path(ref qpath) = fun.kind
|
||||
&& cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id)
|
||||
{
|
||||
return Some(args);
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
|
||||
/// any.
|
||||
///
|
||||
|
@ -2273,15 +2236,19 @@ pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
|
|||
}
|
||||
|
||||
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
|
||||
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
|
||||
attr.name_or_empty() == sym::no_std
|
||||
})
|
||||
cx.tcx
|
||||
.hir()
|
||||
.attrs(hir::CRATE_HIR_ID)
|
||||
.iter()
|
||||
.any(|attr| attr.name_or_empty() == sym::no_std)
|
||||
}
|
||||
|
||||
pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
|
||||
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
|
||||
attr.name_or_empty() == sym::no_core
|
||||
})
|
||||
cx.tcx
|
||||
.hir()
|
||||
.attrs(hir::CRATE_HIR_ID)
|
||||
.iter()
|
||||
.any(|attr| attr.name_or_empty() == sym::no_core)
|
||||
}
|
||||
|
||||
/// Check if parent of a hir node is a trait implementation block.
|
||||
|
@ -2980,12 +2947,18 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
|
|||
///
|
||||
/// Comments are returned wrapped with their relevant delimiters
|
||||
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
|
||||
span_extract_comments(sm, span).join("\n")
|
||||
}
|
||||
|
||||
/// Returns all the comments a given span contains.
|
||||
///
|
||||
/// Comments are returned wrapped with their relevant delimiters.
|
||||
pub fn span_extract_comments(sm: &SourceMap, span: Span) -> Vec<String> {
|
||||
let snippet = sm.span_to_snippet(span).unwrap_or_default();
|
||||
let res = tokenize_with_text(&snippet)
|
||||
tokenize_with_text(&snippet)
|
||||
.filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
|
||||
.map(|(_, s, _)| s)
|
||||
.join("\n");
|
||||
res
|
||||
.map(|(_, s, _)| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use rustc_ast::attr::AttributeExt;
|
||||
use rustc_attr_parsing::{parse_version, RustcVersion};
|
||||
use rustc_attr_parsing::{RustcVersion, parse_version};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::{Symbol, sym};
|
||||
use serde::Deserialize;
|
||||
|
@ -19,11 +19,12 @@ macro_rules! msrv_aliases {
|
|||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY }
|
||||
1,82,0 { IS_NONE_OR, REPEAT_N }
|
||||
1,81,0 { LINT_REASONS_STABILIZATION }
|
||||
1,80,0 { BOX_INTO_ITER}
|
||||
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }
|
||||
1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE }
|
||||
1,80,0 { BOX_INTO_ITER }
|
||||
1,77,0 { C_STR_LITERALS }
|
||||
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
|
||||
1,74,0 { REPR_RUST }
|
||||
1,73,0 { MANUAL_DIV_CEIL }
|
||||
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
|
||||
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
|
||||
|
|
|
@ -23,7 +23,6 @@ pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
|
|||
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
|
||||
pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
|
||||
pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
|
||||
pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
|
||||
pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
|
||||
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
|
||||
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
|
||||
|
|
|
@ -171,7 +171,7 @@ pub fn should_call_clone_as_function(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns true if ty has `iter` or `iter_mut` methods
|
||||
/// If `ty` is known to have a `iter` or `iter_mut` method, returns a symbol representing the type.
|
||||
pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
|
||||
// FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
|
||||
// exists and has the desired signature. Unfortunately FnCtxt is not exported
|
|
@ -7,7 +7,7 @@ use rustc_hir::def::{CtorKind, DefKind, Res};
|
|||
use rustc_hir::intravisit::{self, Visitor, walk_block, walk_expr};
|
||||
use rustc_hir::{
|
||||
AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, LetExpr, Pat, QPath,
|
||||
Stmt, UnOp, UnsafeSource, StructTailExpr,
|
||||
Stmt, StructTailExpr, UnOp, UnsafeSource,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::needless_borrows_for_generic_args,
|
||||
clippy::module_name_repetitions
|
||||
clippy::module_name_repetitions,
|
||||
clippy::literal_string_with_formatting_args
|
||||
)]
|
||||
|
||||
mod config;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[toolchain]
|
||||
# begin autogenerated nightly
|
||||
channel = "nightly-2024-11-28"
|
||||
channel = "nightly-2024-12-26"
|
||||
# end autogenerated nightly
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
profile = "minimal"
|
||||
|
|
|
@ -285,7 +285,7 @@ pub fn main() {
|
|||
let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some()
|
||||
&& arg_value(&orig_args, "--force-warn", |val| val.contains("clippy::")).is_none();
|
||||
|
||||
// If `--no-deps` is enabled only lint the primary pacakge
|
||||
// If `--no-deps` is enabled only lint the primary package
|
||||
let relevant_package = !no_deps || env::var("CARGO_PRIMARY_PACKAGE").is_ok();
|
||||
|
||||
// Do not run Clippy for Cargo's info queries so that invalid CLIPPY_ARGS are not cached
|
||||
|
@ -303,7 +303,7 @@ pub fn main() {
|
|||
.set_using_internal_features(using_internal_features)
|
||||
.run();
|
||||
}
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ fn main() {
|
|||
let _ = rustc_span::sym::proc_dash_macro;
|
||||
|
||||
// interning a keyword
|
||||
let _ = rustc_span::symbol::kw::SelfLower;
|
||||
let _ = rustc_span::kw::SelfLower;
|
||||
|
||||
// Interning a symbol that is not defined
|
||||
let _ = Symbol::intern("xyz123");
|
||||
|
|
|
@ -27,7 +27,7 @@ error: interning a defined symbol
|
|||
--> tests/ui-internal/interning_defined_symbol.rs:26:13
|
||||
|
|
||||
LL | let _ = Symbol::intern("self");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::kw::SelfLower`
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ use rustc_span::symbol::{Ident, Symbol};
|
|||
|
||||
fn main() {
|
||||
Symbol::intern("foo") == rustc_span::sym::clippy;
|
||||
Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower;
|
||||
Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper;
|
||||
Symbol::intern("foo") == rustc_span::kw::SelfLower;
|
||||
Symbol::intern("foo") != rustc_span::kw::SelfUpper;
|
||||
Ident::empty().name == rustc_span::sym::clippy;
|
||||
rustc_span::sym::clippy == Ident::empty().name;
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ error: unnecessary `Symbol` to string conversion
|
|||
--> tests/ui-internal/unnecessary_symbol_str.rs:17:5
|
||||
|
|
||||
LL | Symbol::intern("foo").to_string() == "self";
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::kw::SelfLower`
|
||||
|
||||
error: unnecessary `Symbol` to string conversion
|
||||
--> tests/ui-internal/unnecessary_symbol_str.rs:18:5
|
||||
|
|
||||
LL | Symbol::intern("foo").to_ident_string() != "Self";
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::kw::SelfUpper`
|
||||
|
||||
error: unnecessary `Symbol` to string conversion
|
||||
--> tests/ui-internal/unnecessary_symbol_str.rs:19:5
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
allow-indexing-slicing-in-tests = true
|
|
@ -0,0 +1,19 @@
|
|||
//@compile-flags: --test
|
||||
#![warn(clippy::indexing_slicing)]
|
||||
#![allow(clippy::no_effect)]
|
||||
|
||||
fn main() {
|
||||
let x = [1, 2, 3, 4];
|
||||
let index: usize = 1;
|
||||
&x[index..];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_fn() {
|
||||
let x = [1, 2, 3, 4];
|
||||
let index: usize = 1;
|
||||
&x[index..];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
error: slicing may panic
|
||||
--> tests/ui-toml/indexing_slicing/indexing_slicing.rs:8:6
|
||||
|
|
||||
LL | &x[index..];
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= help: consider using `.get(n..)` or .get_mut(n..)` instead
|
||||
= note: `-D clippy::indexing-slicing` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#![warn(clippy::large_include_file)]
|
||||
#![allow(clippy::literal_string_with_formatting_args)]
|
||||
|
||||
// Good
|
||||
const GOOD_INCLUDE_BYTES: &[u8; 68] = include_bytes!("../../ui/author.rs");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: attempted to include a large file
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:13:43
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:14:43
|
||||
|
|
||||
LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -9,7 +9,7 @@ LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
|
|||
= help: to override `-D warnings` add `#[allow(clippy::large_include_file)]`
|
||||
|
||||
error: attempted to include a large file
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:15:35
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:16:35
|
||||
|
|
||||
LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -17,7 +17,7 @@ LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt");
|
|||
= note: the configuration allows a maximum size of 600 bytes
|
||||
|
||||
error: attempted to include a large file
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:18:1
|
||||
--> tests/ui-toml/large_include_file/large_include_file.rs:19:1
|
||||
|
|
||||
LL | #[doc = include_str!("too_big.txt")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#![deny(clippy::index_refutable_slice)]
|
||||
|
||||
fn below_limit() {
|
||||
let slice: Option<&[u32]> = Some(&[1, 2, 3]);
|
||||
if let Some([_, _, _, _, _, _, _, slice_7, ..]) = slice {
|
||||
//~^ ERROR: binding can be a slice pattern
|
||||
// This would usually not be linted but is included now due to the
|
||||
// index limit in the config file
|
||||
println!("{}", slice_7);
|
||||
}
|
||||
}
|
||||
|
||||
fn above_limit() {
|
||||
let slice: Option<&[u32]> = Some(&[1, 2, 3]);
|
||||
if let Some(slice) = slice {
|
||||
// This will not be linted as 8 is above the limit
|
||||
println!("{}", slice[8]);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
below_limit();
|
||||
above_limit();
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
#![deny(clippy::index_refutable_slice)]
|
||||
|
||||
//@no-rustfix: need to change the suggestion to a multipart suggestion
|
||||
|
||||
fn below_limit() {
|
||||
let slice: Option<&[u32]> = Some(&[1, 2, 3]);
|
||||
if let Some(slice) = slice {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: this binding can be a slice pattern to avoid indexing
|
||||
--> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:7:17
|
||||
--> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:5:17
|
||||
|
|
||||
LL | if let Some(slice) = slice {
|
||||
| ^^^^^
|
||||
|
@ -9,14 +9,14 @@ note: the lint level is defined here
|
|||
|
|
||||
LL | #![deny(clippy::index_refutable_slice)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
help: try using a slice pattern here
|
||||
help: replace the binding and indexed access with a slice pattern
|
||||
|
|
||||
LL | if let Some([_, _, _, _, _, _, _, slice_7, ..]) = slice {
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
help: and replace the index expressions here
|
||||
LL ~ if let Some([_, _, _, _, _, _, _, slice_7, ..]) = slice {
|
||||
LL |
|
||||
LL | // This would usually not be linted but is included now due to the
|
||||
LL | // index limit in the config file
|
||||
LL ~ println!("{}", slice_7);
|
||||
|
|
||||
LL | println!("{}", slice_7);
|
||||
| ~~~~~~~
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
|||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-indexing-slicing-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
allow-one-hash-in-raw-strings
|
||||
allow-panic-in-tests
|
||||
|
@ -93,6 +94,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
|||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-indexing-slicing-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
allow-one-hash-in-raw-strings
|
||||
allow-panic-in-tests
|
||||
|
@ -180,6 +182,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
|
|||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-indexing-slicing-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
allow-one-hash-in-raw-strings
|
||||
allow-panic-in-tests
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue