1
Fork 0

Merge commit '609cd310be' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-12-26 15:01:07 +01:00
commit 8a7d8ece32
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
208 changed files with 6014 additions and 1427 deletions

View file

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

View file

@ -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!"),
_ => ()
}

View file

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

View file

@ -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`

View file

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

View file

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

View file

@ -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!"),
_ => ()
}

View file

@ -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)`

View file

@ -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,

View file

@ -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"

View file

@ -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<'_>) {

View 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");
},
);
}
}

View file

@ -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;
}
},

View file

@ -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
}

View file

@ -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};

View file

@ -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,
);
}
}

View file

@ -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
}

View file

@ -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);
}
}

View file

@ -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,
);
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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()

View file

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

View file

@ -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"

View file

@ -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.
/// ```

View file

@ -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;

View file

@ -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
}

View file

@ -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 ()

View file

@ -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);
}

View file

@ -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() {

View file

@ -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,

View file

@ -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)
}

View file

@ -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};

View file

@ -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.
},
);
}

View file

@ -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");

View file

@ -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};

View file

@ -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));

View file

@ -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()
{

View file

@ -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.
&current[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);
}
}
}

View file

@ -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,
);
}

View file

@ -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,
}
}

View file

@ -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,

View file

@ -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");

View file

@ -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);

View file

@ -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);
}

View file

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

View file

@ -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,

View file

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

View file

@ -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);

View file

@ -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
}

View file

@ -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);
});
}

View file

@ -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;

View file

@ -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()
},

View file

@ -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() };
/// }

View file

@ -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};

View file

@ -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);
}

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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 {

View file

@ -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;

View file

@ -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,
);
},

View file

@ -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;
};

View file

@ -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();
/// ```

View file

@ -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);
}
},
);

View file

@ -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| {

View file

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

View file

@ -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
},

View file

@ -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
}

View file

@ -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");
}
},
}
}

View file

@ -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();

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

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

View file

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

View file

@ -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 {

View file

@ -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};

View file

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

View file

@ -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 {

View file

@ -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 }

View file

@ -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"];

View file

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

View file

@ -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;

View file

@ -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;

View file

@ -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"

View file

@ -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(())
}))
}

View file

@ -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");

View file

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

View file

@ -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;
}

View file

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

View file

@ -0,0 +1 @@
allow-indexing-slicing-in-tests = true

View file

@ -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..];
}
}

View file

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

View file

@ -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");

View file

@ -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")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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();
}

View file

@ -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 {

View file

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

View file

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