mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 16:24:46 +00:00
Auto merge of #132746 - flip1995:clippy-subtree-update, r=Manishearth
Clippy subtree update r? `@Manishearth`
This commit is contained in:
commit
59cec72a57
@ -2861,7 +2861,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"getopts",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape 0.11.0",
|
||||
"unicase",
|
||||
|
@ -1,17 +1,8 @@
|
||||
name: Clippy Dev Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- auto
|
||||
- try
|
||||
merge_group:
|
||||
pull_request:
|
||||
# Only run on paths, that get checked by the clippy_dev tool
|
||||
paths:
|
||||
- 'CHANGELOG.md'
|
||||
- 'README.md'
|
||||
- '**.stderr'
|
||||
- '**.rs'
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
@ -47,28 +38,21 @@ jobs:
|
||||
cargo check
|
||||
git reset --hard HEAD
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
# workflow is successful listening to webhooks only.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
|
||||
end-success:
|
||||
name: bors dev test finished
|
||||
if: github.event.pusher.name == 'bors' && success()
|
||||
conclusion_dev:
|
||||
needs: [ clippy_dev ]
|
||||
# We need to ensure this job does *not* get skipped if its dependencies fail,
|
||||
# because a skipped job is considered a success by GitHub. So we have to
|
||||
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
|
||||
# when the workflow is canceled manually.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [clippy_dev]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as successful
|
||||
run: exit 0
|
||||
|
||||
end-failure:
|
||||
name: bors dev test finished
|
||||
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
|
||||
runs-on: ubuntu-latest
|
||||
needs: [clippy_dev]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as a failure
|
||||
run: exit 1
|
||||
# Manually check the status of all dependencies. `if: failure()` does not work.
|
||||
- name: Conclusion
|
||||
run: |
|
||||
# Print the dependent jobs to see them in the CI log
|
||||
jq -C <<< '${{ toJson(needs) }}'
|
||||
# Check if all jobs that we depend on (in the needs array) were successful.
|
||||
jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
|
||||
|
@ -1,10 +1,7 @@
|
||||
name: Clippy Test (bors)
|
||||
name: Clippy Test (merge queue)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- auto
|
||||
- try
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
@ -13,11 +10,6 @@ env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
concurrency:
|
||||
# For a given workflow, if we push to the same branch, cancel all previous builds on that branch.
|
||||
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@ -218,28 +210,21 @@ jobs:
|
||||
env:
|
||||
INTEGRATION: ${{ matrix.integration }}
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
# workflow is successful listening to webhooks only.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
|
||||
end-success:
|
||||
name: bors test finished
|
||||
if: github.event.pusher.name == 'bors' && success()
|
||||
conclusion:
|
||||
needs: [ changelog, base, metadata_collection, integration_build, integration ]
|
||||
# We need to ensure this job does *not* get skipped if its dependencies fail,
|
||||
# because a skipped job is considered a success by GitHub. So we have to
|
||||
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
|
||||
# when the workflow is canceled manually.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changelog, base, metadata_collection, integration_build, integration]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as successful
|
||||
run: exit 0
|
||||
|
||||
end-failure:
|
||||
name: bors test finished
|
||||
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changelog, base, metadata_collection, integration_build, integration]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as a failure
|
||||
run: exit 1
|
||||
# Manually check the status of all dependencies. `if: failure()` does not work.
|
||||
- name: Conclusion
|
||||
run: |
|
||||
# Print the dependent jobs to see them in the CI log
|
||||
jq -C <<< '${{ toJson(needs) }}'
|
||||
# Check if all jobs that we depend on (in the needs array) were successful.
|
||||
jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
|
@ -1,24 +1,7 @@
|
||||
name: Clippy Test
|
||||
|
||||
on:
|
||||
push:
|
||||
# Ignore bors branches, since they are covered by `clippy_bors.yml`
|
||||
branches-ignore:
|
||||
- auto
|
||||
- try
|
||||
# Don't run Clippy tests, when only text files were modified
|
||||
paths-ignore:
|
||||
- 'COPYRIGHT'
|
||||
- 'LICENSE-*'
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
pull_request:
|
||||
# Don't run Clippy tests, when only text files were modified
|
||||
paths-ignore:
|
||||
- 'COPYRIGHT'
|
||||
- 'LICENSE-*'
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
@ -35,7 +18,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
base:
|
||||
# NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml
|
||||
# NOTE: If you modify this job, make sure you copy the changes to clippy_mq.yml
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -73,3 +56,24 @@ jobs:
|
||||
run: .github/driver.sh
|
||||
env:
|
||||
OS: ${{ runner.os }}
|
||||
|
||||
# We need to have the "conclusion" job also on PR CI, to make it possible
|
||||
# to add PRs to a merge queue.
|
||||
conclusion:
|
||||
needs: [ base ]
|
||||
# We need to ensure this job does *not* get skipped if its dependencies fail,
|
||||
# because a skipped job is considered a success by GitHub. So we have to
|
||||
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
|
||||
# when the workflow is canceled manually.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Manually check the status of all dependencies. `if: failure()` does not work.
|
||||
- name: Conclusion
|
||||
run: |
|
||||
# Print the dependent jobs to see them in the CI log
|
||||
jq -C <<< '${{ toJson(needs) }}'
|
||||
# Check if all jobs that we depend on (in the needs array) were successful.
|
||||
jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
|
@ -52,7 +52,7 @@ jobs:
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Cache
|
||||
uses: Swatinem/rust-cache@v2.7.0
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||
|
||||
|
46
src/tools/clippy/.github/workflows/remark.yml
vendored
46
src/tools/clippy/.github/workflows/remark.yml
vendored
@ -1,13 +1,8 @@
|
||||
name: Remark
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- auto
|
||||
- try
|
||||
merge_group:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
remark:
|
||||
@ -45,28 +40,21 @@ jobs:
|
||||
- name: Build mdbook
|
||||
run: mdbook build book
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
# workflow is successful listening to webhooks only.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
|
||||
end-success:
|
||||
name: bors remark test finished
|
||||
if: github.event.pusher.name == 'bors' && success()
|
||||
conclusion_remark:
|
||||
needs: [ remark ]
|
||||
# We need to ensure this job does *not* get skipped if its dependencies fail,
|
||||
# because a skipped job is considered a success by GitHub. So we have to
|
||||
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
|
||||
# when the workflow is canceled manually.
|
||||
#
|
||||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [remark]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as successful
|
||||
run: exit 0
|
||||
|
||||
end-failure:
|
||||
name: bors remark test finished
|
||||
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
|
||||
runs-on: ubuntu-latest
|
||||
needs: [remark]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as a failure
|
||||
run: exit 1
|
||||
# Manually check the status of all dependencies. `if: failure()` does not work.
|
||||
- name: Conclusion
|
||||
run: |
|
||||
# Print the dependent jobs to see them in the CI log
|
||||
jq -C <<< '${{ toJson(needs) }}'
|
||||
# Check if all jobs that we depend on (in the needs array) were successful.
|
||||
jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
|
||||
|
@ -5331,6 +5331,7 @@ Released 2018-09-13
|
||||
[`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range
|
||||
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
|
||||
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
|
||||
[`arbitrary_source_item_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering
|
||||
[`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
|
||||
@ -5689,6 +5690,7 @@ Released 2018-09-13
|
||||
[`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
|
||||
[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some
|
||||
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
|
||||
[`map_all_any_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity
|
||||
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
|
||||
[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
|
||||
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
|
||||
@ -5696,6 +5698,7 @@ Released 2018-09-13
|
||||
[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
|
||||
[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
|
||||
[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
|
||||
[`map_with_unused_argument_over_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges
|
||||
[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
|
||||
[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool
|
||||
[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
|
||||
@ -5761,6 +5764,7 @@ Released 2018-09-13
|
||||
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
|
||||
[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
|
||||
[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
|
||||
[`needless_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes
|
||||
[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
|
||||
[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
|
||||
[`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
|
||||
@ -6205,12 +6209,14 @@ Released 2018-09-13
|
||||
[`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds
|
||||
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
|
||||
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
|
||||
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
|
||||
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
|
||||
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
|
||||
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
||||
[`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline
|
||||
[`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline
|
||||
[`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold
|
||||
[`source-item-ordering`]: https://doc.rust-lang.org/clippy/lint_configuration.html#source-item-ordering
|
||||
[`stack-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#stack-size-threshold
|
||||
[`standard-macro-braces`]: https://doc.rust-lang.org/clippy/lint_configuration.html#standard-macro-braces
|
||||
[`struct-field-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#struct-field-name-threshold
|
||||
@ -6218,6 +6224,7 @@ Released 2018-09-13
|
||||
[`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack
|
||||
[`too-many-arguments-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-arguments-threshold
|
||||
[`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold
|
||||
[`trait-assoc-item-kinds-order`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trait-assoc-item-kinds-order
|
||||
[`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit
|
||||
[`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold
|
||||
[`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size
|
||||
|
@ -21,7 +21,6 @@ All contributors are expected to follow the [Rust Code of Conduct].
|
||||
- [Rust Analyzer](#rust-analyzer)
|
||||
- [How Clippy works](#how-clippy-works)
|
||||
- [Issue and PR triage](#issue-and-pr-triage)
|
||||
- [Bors and Homu](#bors-and-homu)
|
||||
- [Contributions](#contributions)
|
||||
- [License](#license)
|
||||
|
||||
@ -213,16 +212,6 @@ We have prioritization labels and a sync-blocker label, which are described belo
|
||||
Or rather: before the sync this should be addressed,
|
||||
e.g. by removing a lint again, so it doesn't hit beta/stable.
|
||||
|
||||
## Bors and Homu
|
||||
|
||||
We use a bot powered by [Homu][homu] to help automate testing and landing of pull
|
||||
requests in Clippy. The bot's username is @bors.
|
||||
|
||||
You can find the Clippy bors queue [here][homu_queue].
|
||||
|
||||
If you have @bors permissions, you can find an overview of the available
|
||||
commands [here][homu_instructions].
|
||||
|
||||
[triage]: https://forge.rust-lang.org/release/triage-procedure.html
|
||||
[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
|
||||
[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
|
||||
@ -230,9 +219,6 @@ commands [here][homu_instructions].
|
||||
[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
|
||||
[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
|
||||
[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
|
||||
[homu]: https://github.com/rust-lang/homu
|
||||
[homu_instructions]: https://bors.rust-lang.org/
|
||||
[homu_queue]: https://bors.rust-lang.org/queue/clippy
|
||||
|
||||
## Contributions
|
||||
|
||||
@ -244,7 +230,7 @@ All PRs should include a `changelog` entry with a short comment explaining the c
|
||||
"what do you believe is important from an outsider's perspective?" Often, PRs are only related to a single property of a
|
||||
lint, and then it's good to mention that one. Otherwise, it's better to include too much detail than too little.
|
||||
|
||||
Clippy's [changelog] is created from these comments. Every release, someone gets all commits from bors with a
|
||||
Clippy's [changelog] is created from these comments. Every release, someone gets all merge commits with a
|
||||
`changelog: XYZ` entry and combines them into the changelog. This is a manual process.
|
||||
|
||||
Examples:
|
||||
|
@ -39,7 +39,7 @@ toml = "0.7.3"
|
||||
walkdir = "2.3"
|
||||
filetime = "0.2.9"
|
||||
itertools = "0.12"
|
||||
pulldown-cmark = "0.11"
|
||||
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
|
||||
rinja = { version = "0.3", default-features = false, features = ["config"] }
|
||||
|
||||
# UI test dependencies
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Clippy
|
||||
|
||||
[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto)
|
||||
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license)
|
||||
|
||||
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
|
||||
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
|
||||
|
@ -1,12 +1,11 @@
|
||||
# Clippy
|
||||
|
||||
[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto)
|
||||
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license)
|
||||
|
||||
A collection of lints to catch common mistakes and improve your
|
||||
[Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint
|
||||
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
|
||||
|
@ -53,7 +53,6 @@ book](../lints.md).
|
||||
> - IDE setup
|
||||
> - High level overview on how Clippy works
|
||||
> - Triage procedure
|
||||
> - Bors and Homu
|
||||
|
||||
[ast]: https://rustc-dev-guide.rust-lang.org/syntax-intro.html
|
||||
[hir]: https://rustc-dev-guide.rust-lang.org/hir.html
|
||||
|
@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint {
|
||||
// Check our expr is calling a method
|
||||
if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind
|
||||
// Check the name of this method is `some_method`
|
||||
&& path.ident.name == sym!(some_method)
|
||||
&& path.ident.name.as_str() == "some_method"
|
||||
// Optionally, check the type of the self argument.
|
||||
// - See "Checking for a specific type"
|
||||
{
|
||||
@ -167,7 +167,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
|
||||
// Check if item is a method/function
|
||||
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
|
||||
// Check the method is named `some_method`
|
||||
&& impl_item.ident.name == sym!(some_method)
|
||||
&& impl_item.ident.name.as_str() == "some_method"
|
||||
// We can also check it has a parameter `self`
|
||||
&& signature.decl.implicit_self.has_implicit_self()
|
||||
// We can go further and even check if its return type is `String`
|
||||
|
@ -3,8 +3,8 @@
|
||||
In some scenarios we might want to check for methods when developing
|
||||
a lint. There are two kinds of questions that we might be curious about:
|
||||
|
||||
- Invocation: Does an expression call a specific method?
|
||||
- Definition: Does an `impl` define a method?
|
||||
- Invocation: Does an expression call a specific method?
|
||||
- Definition: Does an `impl` define a method?
|
||||
|
||||
## Checking if an `expr` is calling a specific method
|
||||
|
||||
@ -23,7 +23,7 @@ impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint {
|
||||
// Check our expr is calling a method with pattern matching
|
||||
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind
|
||||
// Check if the name of this method is `our_fancy_method`
|
||||
&& path.ident.name == sym!(our_fancy_method)
|
||||
&& path.ident.name.as_str() == "our_fancy_method"
|
||||
// We can check the type of the self argument whenever necessary.
|
||||
// (It's necessary if we want to check that method is specifically belonging to a specific trait,
|
||||
// for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
|
||||
@ -41,10 +41,6 @@ information on the pattern matching. As mentioned in [Define
|
||||
Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern
|
||||
matching with `MethodCall` in case the reader wishes to explore more.
|
||||
|
||||
Additionally, we use the [`clippy_utils::sym!`][sym] macro to conveniently
|
||||
convert an input `our_fancy_method` into a `Symbol` and compare that symbol to
|
||||
the [`Ident`]'s name in the [`PathSegment`] in the [`MethodCall`].
|
||||
|
||||
## Checking if a `impl` block implements a method
|
||||
|
||||
While sometimes we want to check whether a method is being called or not, other
|
||||
@ -71,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
|
||||
// Check if item is a method/function
|
||||
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
|
||||
// Check the method is named `our_fancy_method`
|
||||
&& impl_item.ident.name == sym!(our_fancy_method)
|
||||
&& impl_item.ident.name.as_str() == "our_fancy_method"
|
||||
// We can also check it has a parameter `self`
|
||||
&& signature.decl.implicit_self.has_implicit_self()
|
||||
// We can go even further and even check if its return type is `String`
|
||||
@ -85,9 +81,6 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
|
||||
|
||||
[`check_impl_item`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_impl_item
|
||||
[`ExprKind`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html
|
||||
[`Ident`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/struct.Ident.html
|
||||
[`ImplItem`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_hir/hir/struct.ImplItem.html
|
||||
[`LateLintPass`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html
|
||||
[`MethodCall`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.MethodCall
|
||||
[`PathSegment`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/struct.PathSegment.html
|
||||
[sym]: https://doc.rust-lang.org/stable/nightly-rustc/clippy_utils/macro.sym.html
|
||||
|
@ -456,7 +456,7 @@ default configuration of Clippy. By default, any configuration will replace the
|
||||
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
|
||||
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
|
||||
|
||||
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "AccessKit", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
|
||||
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
@ -666,6 +666,16 @@ crate. For example, `pub(crate)` items.
|
||||
* [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
|
||||
|
||||
|
||||
## `module-item-order-groupings`
|
||||
The named groupings of different source item kinds within modules.
|
||||
|
||||
**Default Value:** `[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
|
||||
|
||||
|
||||
## `msrv`
|
||||
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
|
||||
|
||||
@ -710,6 +720,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
||||
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
|
||||
* [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone)
|
||||
* [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or)
|
||||
* [`map_with_unused_argument_over_ranges`](https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges)
|
||||
* [`match_like_matches_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro)
|
||||
* [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default)
|
||||
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
|
||||
@ -783,6 +794,16 @@ The maximum number of single char bindings a scope may have
|
||||
* [`many_single_char_names`](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names)
|
||||
|
||||
|
||||
## `source-item-ordering`
|
||||
Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
|
||||
|
||||
**Default Value:** `["enum", "impl", "module", "struct", "trait"]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
|
||||
|
||||
|
||||
## `stack-size-threshold`
|
||||
The maximum allowed stack size for functions in bytes
|
||||
|
||||
@ -862,6 +883,16 @@ The maximum number of lines a function or method can have
|
||||
* [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
|
||||
|
||||
|
||||
## `trait-assoc-item-kinds-order`
|
||||
The order of associated items in traits.
|
||||
|
||||
**Default Value:** `["const", "type", "fn"]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
|
||||
|
||||
|
||||
## `trivial-copy-size-limit`
|
||||
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
|
||||
reference. By default there is no limit
|
||||
|
@ -1,6 +1,10 @@
|
||||
use crate::ClippyConfiguration;
|
||||
use crate::msrvs::Msrv;
|
||||
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
|
||||
use crate::types::{
|
||||
DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
|
||||
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
|
||||
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::edit_distance::edit_distance;
|
||||
@ -17,8 +21,9 @@ use std::{cmp, env, fmt, fs, io};
|
||||
#[rustfmt::skip]
|
||||
const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
|
||||
"KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
|
||||
"MHz", "GHz", "THz",
|
||||
"AccessKit",
|
||||
"CoreFoundation", "CoreGraphics", "CoreText",
|
||||
"CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
|
||||
"DevOps",
|
||||
"Direct2D", "Direct3D", "DirectWrite", "DirectX",
|
||||
"ECMAScript",
|
||||
@ -46,6 +51,29 @@ const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z
|
||||
const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
|
||||
const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
|
||||
&["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
|
||||
const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingModuleItemKind::*;
|
||||
&[
|
||||
("modules", &[ExternCrate, Mod, ForeignMod]),
|
||||
("use", &[Use]),
|
||||
("macros", &[Macro]),
|
||||
("global_asm", &[GlobalAsm]),
|
||||
("UPPER_SNAKE_CASE", &[Static, Const]),
|
||||
("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
|
||||
("lower_snake_case", &[Fn]),
|
||||
]
|
||||
};
|
||||
const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingTraitAssocItemKind::*;
|
||||
&[Const, Type, Fn]
|
||||
};
|
||||
const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingCategory::*;
|
||||
&[Enum, Impl, Module, Struct, Trait]
|
||||
};
|
||||
|
||||
/// Conf with parse errors
|
||||
#[derive(Default)]
|
||||
@ -102,7 +130,9 @@ pub fn sanitize_explanation(raw_docs: &str) -> String {
|
||||
// Remove tags and hidden code:
|
||||
let mut explanation = String::with_capacity(128);
|
||||
let mut in_code = false;
|
||||
for line in raw_docs.lines().map(str::trim) {
|
||||
for line in raw_docs.lines() {
|
||||
let line = line.strip_prefix(' ').unwrap_or(line);
|
||||
|
||||
if let Some(lang) = line.strip_prefix("```") {
|
||||
let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
|
||||
if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
|
||||
@ -530,6 +560,9 @@ define_Conf! {
|
||||
/// crate. For example, `pub(crate)` items.
|
||||
#[lints(missing_docs_in_private_items)]
|
||||
missing_docs_in_crate_items: bool = false,
|
||||
/// The named groupings of different source item kinds within modules.
|
||||
#[lints(arbitrary_source_item_ordering)]
|
||||
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
|
||||
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
|
||||
#[default_text = "current version"]
|
||||
#[lints(
|
||||
@ -570,6 +603,7 @@ define_Conf! {
|
||||
manual_try_fold,
|
||||
map_clone,
|
||||
map_unwrap_or,
|
||||
map_with_unused_argument_over_ranges,
|
||||
match_like_matches_macro,
|
||||
mem_replace_with_default,
|
||||
missing_const_for_fn,
|
||||
@ -608,6 +642,9 @@ define_Conf! {
|
||||
/// The maximum number of single char bindings a scope may have
|
||||
#[lints(many_single_char_names)]
|
||||
single_char_binding_names_threshold: u64 = 4,
|
||||
/// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
|
||||
#[lints(arbitrary_source_item_ordering)]
|
||||
source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
|
||||
/// The maximum allowed stack size for functions in bytes
|
||||
#[lints(large_stack_frames)]
|
||||
stack_size_threshold: u64 = 512_000,
|
||||
@ -637,6 +674,9 @@ define_Conf! {
|
||||
/// The maximum number of lines a function or method can have
|
||||
#[lints(too_many_lines)]
|
||||
too_many_lines_threshold: u64 = 100,
|
||||
/// The order of associated items in traits.
|
||||
#[lints(arbitrary_source_item_ordering)]
|
||||
trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
|
||||
/// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
|
||||
/// reference. By default there is no limit
|
||||
#[default_text = "target_pointer_width * 2"]
|
||||
|
@ -20,6 +20,7 @@ extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
extern crate smallvec;
|
||||
|
||||
mod conf;
|
||||
mod metadata;
|
||||
|
@ -3,6 +3,7 @@ use rustc_attr::parse_version;
|
||||
use rustc_session::{RustcVersion, Session};
|
||||
use rustc_span::{Symbol, sym};
|
||||
use serde::Deserialize;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! msrv_aliases {
|
||||
@ -18,7 +19,7 @@ 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 }
|
||||
1,82,0 { IS_NONE_OR, REPEAT_N }
|
||||
1,81,0 { LINT_REASONS_STABILIZATION }
|
||||
1,80,0 { BOX_INTO_ITER}
|
||||
1,77,0 { C_STR_LITERALS }
|
||||
@ -54,7 +55,7 @@ msrv_aliases! {
|
||||
1,33,0 { UNDERSCORE_IMPORTS }
|
||||
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
|
||||
1,29,0 { ITER_FLATTEN }
|
||||
1,28,0 { FROM_BOOL }
|
||||
1,28,0 { FROM_BOOL, REPEAT_WITH }
|
||||
1,27,0 { ITERATOR_TRY_FOLD }
|
||||
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
|
||||
1,24,0 { IS_ASCII_DIGIT }
|
||||
@ -67,7 +68,7 @@ msrv_aliases! {
|
||||
/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Msrv {
|
||||
stack: Vec<RustcVersion>,
|
||||
stack: SmallVec<[RustcVersion; 2]>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Msrv {
|
||||
@ -87,14 +88,14 @@ impl<'de> Deserialize<'de> for Msrv {
|
||||
{
|
||||
let v = String::deserialize(deserializer)?;
|
||||
parse_version(Symbol::intern(&v))
|
||||
.map(|v| Msrv { stack: vec![v] })
|
||||
.map(|v| Msrv { stack: smallvec![v] })
|
||||
.ok_or_else(|| serde::de::Error::custom("not a valid Rust version"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Msrv {
|
||||
pub fn empty() -> Msrv {
|
||||
Msrv { stack: Vec::new() }
|
||||
Msrv { stack: SmallVec::new() }
|
||||
}
|
||||
|
||||
pub fn read_cargo(&mut self, sess: &Session) {
|
||||
@ -103,7 +104,7 @@ impl Msrv {
|
||||
.and_then(|v| parse_version(Symbol::intern(&v)));
|
||||
|
||||
match (self.current(), cargo_msrv) {
|
||||
(None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv],
|
||||
(None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv],
|
||||
(Some(clippy_msrv), Some(cargo_msrv)) => {
|
||||
if clippy_msrv != cargo_msrv {
|
||||
sess.dcx().warn(format!(
|
||||
|
@ -1,5 +1,6 @@
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::{Deserialize, Serialize, ser};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -102,6 +103,306 @@ impl<'de> Deserialize<'de> for MacroMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the item categories that can be ordered by the source ordering lint.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SourceItemOrderingCategory {
|
||||
Enum,
|
||||
Impl,
|
||||
Module,
|
||||
Struct,
|
||||
Trait,
|
||||
}
|
||||
|
||||
/// Represents which item categories are enabled for ordering.
|
||||
///
|
||||
/// The [`Deserialize`] implementation checks that there are no duplicates in
|
||||
/// the user configuration.
|
||||
pub struct SourceItemOrdering(Vec<SourceItemOrderingCategory>);
|
||||
|
||||
impl SourceItemOrdering {
|
||||
pub fn contains(&self, category: &SourceItemOrderingCategory) -> bool {
|
||||
self.0.contains(category)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for SourceItemOrdering
|
||||
where
|
||||
T: Into<Vec<SourceItemOrderingCategory>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for SourceItemOrdering {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SourceItemOrdering {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let items = Vec::<SourceItemOrderingCategory>::deserialize(deserializer)?;
|
||||
let mut items_set = std::collections::HashSet::new();
|
||||
|
||||
for item in &items {
|
||||
if items_set.contains(item) {
|
||||
return Err(de::Error::custom(format!(
|
||||
"The category \"{item:?}\" was enabled more than once in the source ordering configuration."
|
||||
)));
|
||||
}
|
||||
items_set.insert(item);
|
||||
}
|
||||
|
||||
Ok(Self(items))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SourceItemOrdering {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the items that can occur within a module.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SourceItemOrderingModuleItemKind {
|
||||
ExternCrate,
|
||||
Mod,
|
||||
ForeignMod,
|
||||
Use,
|
||||
Macro,
|
||||
GlobalAsm,
|
||||
Static,
|
||||
Const,
|
||||
TyAlias,
|
||||
Enum,
|
||||
Struct,
|
||||
Union,
|
||||
Trait,
|
||||
TraitAlias,
|
||||
Impl,
|
||||
Fn,
|
||||
}
|
||||
|
||||
impl SourceItemOrderingModuleItemKind {
|
||||
pub fn all_variants() -> Vec<Self> {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingModuleItemKind::*;
|
||||
vec![
|
||||
ExternCrate,
|
||||
Mod,
|
||||
ForeignMod,
|
||||
Use,
|
||||
Macro,
|
||||
GlobalAsm,
|
||||
Static,
|
||||
Const,
|
||||
TyAlias,
|
||||
Enum,
|
||||
Struct,
|
||||
Union,
|
||||
Trait,
|
||||
TraitAlias,
|
||||
Impl,
|
||||
Fn,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the configured ordering of items within a module.
|
||||
///
|
||||
/// The [`Deserialize`] implementation checks that no item kinds have been
|
||||
/// omitted and that there are no duplicates in the user configuration.
|
||||
#[derive(Clone)]
|
||||
pub struct SourceItemOrderingModuleItemGroupings {
|
||||
groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
|
||||
lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
|
||||
}
|
||||
|
||||
impl SourceItemOrderingModuleItemGroupings {
|
||||
fn build_lut(
|
||||
groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
|
||||
) -> HashMap<SourceItemOrderingModuleItemKind, usize> {
|
||||
let mut lut = HashMap::new();
|
||||
for (group_index, (_, items)) in groups.iter().enumerate() {
|
||||
for item in items {
|
||||
lut.insert(item.clone(), group_index);
|
||||
}
|
||||
}
|
||||
lut
|
||||
}
|
||||
|
||||
pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<usize> {
|
||||
self.lut.get(item).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrderingModuleItemGroupings {
|
||||
fn from(value: &[(&str, &[SourceItemOrderingModuleItemKind])]) -> Self {
|
||||
let groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)> =
|
||||
value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect();
|
||||
let lut = Self::build_lut(&groups);
|
||||
Self { groups, lut }
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for SourceItemOrderingModuleItemGroupings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.groups.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let groups = Vec::<(String, Vec<SourceItemOrderingModuleItemKind>)>::deserialize(deserializer)?;
|
||||
let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
|
||||
let lut = Self::build_lut(&groups);
|
||||
|
||||
let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
|
||||
for item in lut.keys() {
|
||||
expected_items.retain(|i| i != item);
|
||||
}
|
||||
|
||||
let all_items = SourceItemOrderingModuleItemKind::all_variants();
|
||||
if expected_items.is_empty() && items_total == all_items.len() {
|
||||
let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else {
|
||||
return Err(de::Error::custom("Error in internal LUT."));
|
||||
};
|
||||
let Some((_, use_group_items)) = groups.get(*use_group_index) else {
|
||||
return Err(de::Error::custom("Error in internal LUT."));
|
||||
};
|
||||
if use_group_items.len() > 1 {
|
||||
return Err(de::Error::custom(
|
||||
"The group containing the \"use\" item kind may not contain any other item kinds. \
|
||||
The \"use\" items will (generally) be sorted by rustfmt already. \
|
||||
Therefore it makes no sense to implement linting rules that may conflict with rustfmt.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self { groups, lut })
|
||||
} else if items_total != all_items.len() {
|
||||
Err(de::Error::custom(format!(
|
||||
"Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
|
||||
The module item kinds are: {all_items:?}"
|
||||
)))
|
||||
} else {
|
||||
Err(de::Error::custom(format!(
|
||||
"Not all module item kinds were part of the configured source ordering rule. \
|
||||
All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
|
||||
The module item kinds are: {all_items:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SourceItemOrderingModuleItemGroupings {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.groups.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents all kinds of trait associated items.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SourceItemOrderingTraitAssocItemKind {
|
||||
Const,
|
||||
Fn,
|
||||
Type,
|
||||
}
|
||||
|
||||
impl SourceItemOrderingTraitAssocItemKind {
|
||||
pub fn all_variants() -> Vec<Self> {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingTraitAssocItemKind::*;
|
||||
vec![Const, Fn, Type]
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the order in which associated trait items should be ordered.
|
||||
///
|
||||
/// The reason to wrap a `Vec` in a newtype is to be able to implement
|
||||
/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks
|
||||
/// on configuration completeness at the time of loading the clippy config,
|
||||
/// letting the user know if there's any issues with the config (e.g. not
|
||||
/// listing all item kinds that should be sorted).
|
||||
#[derive(Clone)]
|
||||
pub struct SourceItemOrderingTraitAssocItemKinds(Vec<SourceItemOrderingTraitAssocItemKind>);
|
||||
|
||||
impl SourceItemOrderingTraitAssocItemKinds {
|
||||
pub fn index_of(&self, item: &SourceItemOrderingTraitAssocItemKind) -> Option<usize> {
|
||||
self.0.iter().position(|i| i == item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for SourceItemOrderingTraitAssocItemKinds
|
||||
where
|
||||
T: Into<Vec<SourceItemOrderingTraitAssocItemKind>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for SourceItemOrderingTraitAssocItemKinds {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SourceItemOrderingTraitAssocItemKinds {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let items = Vec::<SourceItemOrderingTraitAssocItemKind>::deserialize(deserializer)?;
|
||||
|
||||
let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants();
|
||||
for item in &items {
|
||||
expected_items.retain(|i| i != item);
|
||||
}
|
||||
|
||||
let all_items = SourceItemOrderingTraitAssocItemKind::all_variants();
|
||||
if expected_items.is_empty() && items.len() == all_items.len() {
|
||||
Ok(Self(items))
|
||||
} else if items.len() != all_items.len() {
|
||||
Err(de::Error::custom(format!(
|
||||
"Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \
|
||||
The trait associated item kinds are: {all_items:?}",
|
||||
)))
|
||||
} else {
|
||||
Err(de::Error::custom(format!(
|
||||
"Not all trait associated item kinds were part of the configured source ordering rule. \
|
||||
All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
|
||||
The trait associated item kinds are: {all_items:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SourceItemOrderingTraitAssocItemKinds {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
// these impls are never actually called but are used by the various config options that default to
|
||||
// empty lists
|
||||
macro_rules! unimplemented_serialize {
|
||||
|
@ -20,8 +20,14 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
|
||||
|
||||
loop {
|
||||
let index_time = mtime("util/gh-pages/index.html");
|
||||
let times = [
|
||||
"clippy_lints/src",
|
||||
"util/gh-pages/index_template.html",
|
||||
"tests/compile-test.rs",
|
||||
]
|
||||
.map(mtime);
|
||||
|
||||
if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") {
|
||||
if times.iter().any(|&time| index_time < time) {
|
||||
Command::new(env::var("CARGO").unwrap_or("cargo".into()))
|
||||
.arg("collect-metadata")
|
||||
.spawn()
|
||||
|
@ -762,13 +762,19 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
|
||||
Literal{..}(desc)
|
||||
);
|
||||
|
||||
if let Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::CloseBrace,
|
||||
range,
|
||||
..
|
||||
}) = iter.next()
|
||||
{
|
||||
lints.push(Lint::new(name, group, desc, module, start..range.end));
|
||||
if let Some(end) = iter.find_map(|t| {
|
||||
if let LintDeclSearchResult {
|
||||
token_kind: TokenKind::CloseBrace,
|
||||
range,
|
||||
..
|
||||
} = t
|
||||
{
|
||||
Some(range.end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
lints.push(Lint::new(name, group, desc, module, start..end));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,531 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_config::types::{
|
||||
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
|
||||
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
|
||||
};
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use rustc_hir::{
|
||||
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, UseKind,
|
||||
Variant, VariantData,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Confirms that items are sorted in source files as per configuration.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
///
|
||||
/// Keeping a consistent ordering throughout the codebase helps with working
|
||||
/// as a team, and possibly improves maintainability of the codebase. The
|
||||
/// idea is that by defining a consistent and enforceable rule for how
|
||||
/// source files are structured, less time will be wasted during reviews on
|
||||
/// a topic that is (under most circumstances) not relevant to the logic
|
||||
/// implemented in the code. Sometimes this will be referred to as
|
||||
/// "bikeshedding".
|
||||
///
|
||||
/// ### Default Ordering and Configuration
|
||||
///
|
||||
/// As there is no generally applicable rule, and each project may have
|
||||
/// different requirements, the lint can be configured with high
|
||||
/// granularity. The configuration is split into two stages:
|
||||
///
|
||||
/// 1. Which item kinds that should have an internal order enforced.
|
||||
/// 2. Individual ordering rules per item kind.
|
||||
///
|
||||
/// The item kinds that can be linted are:
|
||||
/// - Module (with customized groupings, alphabetical within)
|
||||
/// - Trait (with customized order of associated items, alphabetical within)
|
||||
/// - Enum, Impl, Struct (purely alphabetical)
|
||||
///
|
||||
/// #### Module Item Order
|
||||
///
|
||||
/// Due to the large variation of items within modules, the ordering can be
|
||||
/// configured on a very granular level. Item kinds can be grouped together
|
||||
/// arbitrarily, items within groups will be ordered alphabetically. The
|
||||
/// following table shows the default groupings:
|
||||
///
|
||||
/// | Group | Item Kinds |
|
||||
/// |--------------------|----------------------|
|
||||
/// | `modules` | "mod", "foreign_mod" |
|
||||
/// | `use` | "use" |
|
||||
/// | `macros` | "macro" |
|
||||
/// | `global_asm` | "global_asm" |
|
||||
/// | `UPPER_SNAKE_CASE` | "static", "const" |
|
||||
/// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" |
|
||||
/// | `lower_snake_case` | "fn" |
|
||||
///
|
||||
/// All item kinds must be accounted for to create an enforceable linting
|
||||
/// rule set.
|
||||
///
|
||||
/// ### Known Problems
|
||||
///
|
||||
/// #### Performance Impact
|
||||
///
|
||||
/// Keep in mind, that ordering source code alphabetically can lead to
|
||||
/// reduced performance in cases where the most commonly used enum variant
|
||||
/// isn't the first entry anymore, and similar optimizations that can reduce
|
||||
/// branch misses, cache locality and such. Either don't use this lint if
|
||||
/// that's relevant, or disable the lint in modules or items specifically
|
||||
/// where it matters. Other solutions can be to use profile guided
|
||||
/// optimization (PGO), post-link optimization (e.g. using BOLT for LLVM),
|
||||
/// or other advanced optimization methods. A good starting point to dig
|
||||
/// into optimization is [cargo-pgo][cargo-pgo].
|
||||
///
|
||||
/// #### Lints on a Contains basis
|
||||
///
|
||||
/// The lint can be disabled only on a "contains" basis, but not per element
|
||||
/// within a "container", e.g. the lint works per-module, per-struct,
|
||||
/// per-enum, etc. but not for "don't order this particular enum variant".
|
||||
///
|
||||
/// #### Module documentation
|
||||
///
|
||||
/// Module level rustdoc comments are not part of the resulting syntax tree
|
||||
/// and as such cannot be linted from within `check_mod`. Instead, the
|
||||
/// `rustdoc::missing_documentation` lint may be used.
|
||||
///
|
||||
/// #### Module Tests
|
||||
///
|
||||
/// This lint does not implement detection of module tests (or other feature
|
||||
/// dependent elements for that matter). To lint the location of mod tests,
|
||||
/// the lint `items_after_test_module` can be used instead.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// trait TraitUnordered {
|
||||
/// const A: bool;
|
||||
/// const C: bool;
|
||||
/// const B: bool;
|
||||
///
|
||||
/// type SomeType;
|
||||
///
|
||||
/// fn a();
|
||||
/// fn c();
|
||||
/// fn b();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// trait TraitOrdered {
|
||||
/// const A: bool;
|
||||
/// const B: bool;
|
||||
/// const C: bool;
|
||||
///
|
||||
/// type SomeType;
|
||||
///
|
||||
/// fn a();
|
||||
/// fn b();
|
||||
/// fn c();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md
|
||||
///
|
||||
#[clippy::version = "1.82.0"]
|
||||
pub ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
restriction,
|
||||
"arbitrary source item ordering"
|
||||
}
|
||||
|
||||
impl_lint_pass!(ArbitrarySourceItemOrdering => [ARBITRARY_SOURCE_ITEM_ORDERING]);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::struct_excessive_bools)] // Bools are cached feature flags.
|
||||
pub struct ArbitrarySourceItemOrdering {
|
||||
assoc_types_order: SourceItemOrderingTraitAssocItemKinds,
|
||||
enable_ordering_for_enum: bool,
|
||||
enable_ordering_for_impl: bool,
|
||||
enable_ordering_for_module: bool,
|
||||
enable_ordering_for_struct: bool,
|
||||
enable_ordering_for_trait: bool,
|
||||
module_item_order_groupings: SourceItemOrderingModuleItemGroupings,
|
||||
}
|
||||
|
||||
impl ArbitrarySourceItemOrdering {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingCategory::*;
|
||||
Self {
|
||||
assoc_types_order: conf.trait_assoc_item_kinds_order.clone(),
|
||||
enable_ordering_for_enum: conf.source_item_ordering.contains(&Enum),
|
||||
enable_ordering_for_impl: conf.source_item_ordering.contains(&Impl),
|
||||
enable_ordering_for_module: conf.source_item_ordering.contains(&Module),
|
||||
enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct),
|
||||
enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait),
|
||||
module_item_order_groupings: conf.module_item_order_groupings.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a linting warning for incorrectly ordered impl items.
|
||||
fn lint_impl_item<T: LintContext>(&self, cx: &T, item: &ImplItemRef, before_item: &ImplItemRef) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
item.span,
|
||||
format!(
|
||||
"incorrect ordering of impl items (defined order: {:?})",
|
||||
self.assoc_types_order
|
||||
),
|
||||
Some(before_item.span),
|
||||
format!("should be placed before `{}`", before_item.ident.as_str(),),
|
||||
);
|
||||
}
|
||||
|
||||
/// Produces a linting warning for incorrectly ordered item members.
|
||||
fn lint_member_name<T: LintContext>(
|
||||
cx: &T,
|
||||
ident: &rustc_span::symbol::Ident,
|
||||
before_ident: &rustc_span::symbol::Ident,
|
||||
) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
ident.span,
|
||||
"incorrect ordering of items (must be alphabetically ordered)",
|
||||
Some(before_ident.span),
|
||||
format!("should be placed before `{}`", before_ident.as_str(),),
|
||||
);
|
||||
}
|
||||
|
||||
fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>) {
|
||||
let span = if item.ident.as_str().is_empty() {
|
||||
&item.span
|
||||
} else {
|
||||
&item.ident.span
|
||||
};
|
||||
|
||||
let (before_span, note) = if before_item.ident.as_str().is_empty() {
|
||||
(
|
||||
&before_item.span,
|
||||
"should be placed before the following item".to_owned(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
&before_item.ident.span,
|
||||
format!("should be placed before `{}`", before_item.ident.as_str(),),
|
||||
)
|
||||
};
|
||||
|
||||
// This catches false positives where generated code gets linted.
|
||||
if span == before_span {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
*span,
|
||||
"incorrect ordering of items (must be alphabetically ordered)",
|
||||
Some(*before_span),
|
||||
note,
|
||||
);
|
||||
}
|
||||
|
||||
/// Produces a linting warning for incorrectly ordered trait items.
|
||||
fn lint_trait_item<T: LintContext>(&self, cx: &T, item: &TraitItemRef, before_item: &TraitItemRef) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
ARBITRARY_SOURCE_ITEM_ORDERING,
|
||||
item.span,
|
||||
format!(
|
||||
"incorrect ordering of trait items (defined order: {:?})",
|
||||
self.assoc_types_order
|
||||
),
|
||||
Some(before_item.span),
|
||||
format!("should be placed before `{}`", before_item.ident.as_str(),),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
match &item.kind {
|
||||
ItemKind::Enum(enum_def, _generics) if self.enable_ordering_for_enum => {
|
||||
let mut cur_v: Option<&Variant<'_>> = None;
|
||||
for variant in enum_def.variants {
|
||||
if in_external_macro(cx.sess(), variant.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(cur_v) = cur_v {
|
||||
if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span {
|
||||
Self::lint_member_name(cx, &variant.ident, &cur_v.ident);
|
||||
}
|
||||
}
|
||||
cur_v = Some(variant);
|
||||
}
|
||||
},
|
||||
ItemKind::Struct(VariantData::Struct { fields, .. }, _generics) if self.enable_ordering_for_struct => {
|
||||
let mut cur_f: Option<&FieldDef<'_>> = None;
|
||||
for field in *fields {
|
||||
if in_external_macro(cx.sess(), field.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(cur_f) = cur_f {
|
||||
if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span {
|
||||
Self::lint_member_name(cx, &field.ident, &cur_f.ident);
|
||||
}
|
||||
}
|
||||
cur_f = Some(field);
|
||||
}
|
||||
},
|
||||
ItemKind::Trait(is_auto, _safety, _generics, _generic_bounds, item_ref)
|
||||
if self.enable_ordering_for_trait && *is_auto == IsAuto::No =>
|
||||
{
|
||||
let mut cur_t: Option<&TraitItemRef> = None;
|
||||
|
||||
for item in *item_ref {
|
||||
if in_external_macro(cx.sess(), item.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(cur_t) = cur_t {
|
||||
let cur_t_kind = convert_assoc_item_kind(cur_t.kind);
|
||||
let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind);
|
||||
let item_kind = convert_assoc_item_kind(item.kind);
|
||||
let item_kind_index = self.assoc_types_order.index_of(&item_kind);
|
||||
|
||||
if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() {
|
||||
Self::lint_member_name(cx, &item.ident, &cur_t.ident);
|
||||
} else if cur_t_kind_index > item_kind_index {
|
||||
self.lint_trait_item(cx, item, cur_t);
|
||||
}
|
||||
}
|
||||
cur_t = Some(item);
|
||||
}
|
||||
},
|
||||
ItemKind::Impl(trait_impl) if self.enable_ordering_for_impl => {
|
||||
let mut cur_t: Option<&ImplItemRef> = None;
|
||||
|
||||
for item in trait_impl.items {
|
||||
if in_external_macro(cx.sess(), item.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(cur_t) = cur_t {
|
||||
let cur_t_kind = convert_assoc_item_kind(cur_t.kind);
|
||||
let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind);
|
||||
let item_kind = convert_assoc_item_kind(item.kind);
|
||||
let item_kind_index = self.assoc_types_order.index_of(&item_kind);
|
||||
|
||||
if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() {
|
||||
Self::lint_member_name(cx, &item.ident, &cur_t.ident);
|
||||
} else if cur_t_kind_index > item_kind_index {
|
||||
self.lint_impl_item(cx, item, cur_t);
|
||||
}
|
||||
}
|
||||
cur_t = Some(item);
|
||||
}
|
||||
},
|
||||
_ => {}, // Catch-all for `ItemKinds` that don't have fields.
|
||||
}
|
||||
}
|
||||
|
||||
fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) {
|
||||
struct CurItem<'a> {
|
||||
item: &'a Item<'a>,
|
||||
order: usize,
|
||||
name: String,
|
||||
}
|
||||
let mut cur_t: Option<CurItem<'_>> = None;
|
||||
|
||||
if !self.enable_ordering_for_module {
|
||||
return;
|
||||
}
|
||||
|
||||
let items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id));
|
||||
|
||||
// Iterates over the items within a module.
|
||||
//
|
||||
// As of 2023-05-09, the Rust compiler will hold the entries in the same
|
||||
// order as they appear in the source code, which is convenient for us,
|
||||
// as no sorting by source map/line of code has to be applied.
|
||||
//
|
||||
for item in items {
|
||||
if in_external_macro(cx.sess(), item.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The following exceptions (skipping with `continue;`) may not be
|
||||
// complete, edge cases have not been explored further than what
|
||||
// appears in the existing code base.
|
||||
if item.ident.name == rustc_span::symbol::kw::Empty {
|
||||
if let ItemKind::Impl(_) = item.kind {
|
||||
// Sorting trait impls for unnamed types makes no sense.
|
||||
if get_item_name(item).is_empty() {
|
||||
continue;
|
||||
}
|
||||
} else if let ItemKind::ForeignMod { .. } = item.kind {
|
||||
continue;
|
||||
} else if let ItemKind::GlobalAsm(_) = item.kind {
|
||||
continue;
|
||||
} else if let ItemKind::Use(path, use_kind) = item.kind {
|
||||
if path.segments.is_empty() {
|
||||
// Use statements that contain braces get caught here.
|
||||
// They will still be linted internally.
|
||||
continue;
|
||||
} else if path.segments.len() >= 2
|
||||
&& (path.segments[0].ident.name == rustc_span::sym::std
|
||||
|| path.segments[0].ident.name == rustc_span::sym::core)
|
||||
&& path.segments[1].ident.name == rustc_span::sym::prelude
|
||||
{
|
||||
// Filters the autogenerated prelude use statement.
|
||||
// e.g. `use std::prelude::rustc_2021`
|
||||
} else if use_kind == UseKind::Glob {
|
||||
// Filters glob kinds of uses.
|
||||
// e.g. `use std::sync::*`
|
||||
} else {
|
||||
// This can be used for debugging.
|
||||
// println!("Unknown autogenerated use statement: {:?}", item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if item.ident.name.as_str().starts_with('_') {
|
||||
// Filters out unnamed macro-like impls for various derives,
|
||||
// e.g. serde::Serialize or num_derive::FromPrimitive.
|
||||
continue;
|
||||
}
|
||||
|
||||
if item.ident.name == rustc_span::sym::std && item.span.is_dummy() {
|
||||
if let ItemKind::ExternCrate(None) = item.kind {
|
||||
// Filters the auto-included Rust standard library.
|
||||
continue;
|
||||
}
|
||||
println!("Unknown item: {item:?}");
|
||||
}
|
||||
|
||||
let item_kind = convert_module_item_kind(&item.kind);
|
||||
let module_level_order = self
|
||||
.module_item_order_groupings
|
||||
.module_level_order_of(&item_kind)
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(cur_t) = cur_t.as_ref() {
|
||||
use std::cmp::Ordering; // Better legibility.
|
||||
match module_level_order.cmp(&cur_t.order) {
|
||||
Ordering::Less => {
|
||||
Self::lint_member_item(cx, item, cur_t.item);
|
||||
},
|
||||
Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => {
|
||||
// Skip ordering use statements, as these should be ordered by rustfmt.
|
||||
},
|
||||
Ordering::Equal if cur_t.name > get_item_name(item) => {
|
||||
Self::lint_member_item(cx, item, cur_t.item);
|
||||
},
|
||||
Ordering::Equal | Ordering::Greater => {
|
||||
// Nothing to do in this case, they're already in the right order.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Makes a note of the current item for comparison with the next.
|
||||
cur_t = Some(CurItem {
|
||||
order: module_level_order,
|
||||
item,
|
||||
name: get_item_name(item),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`rustc_hir::AssocItemKind`] to a
|
||||
/// [`SourceItemOrderingTraitAssocItemKind`].
|
||||
///
|
||||
/// This is implemented here because `rustc_hir` is not a dependency of
|
||||
/// `clippy_config`.
|
||||
fn convert_assoc_item_kind(value: AssocItemKind) -> SourceItemOrderingTraitAssocItemKind {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingTraitAssocItemKind::*;
|
||||
match value {
|
||||
AssocItemKind::Const { .. } => Const,
|
||||
AssocItemKind::Type { .. } => Type,
|
||||
AssocItemKind::Fn { .. } => Fn,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`rustc_hir::ItemKind`] to a
|
||||
/// [`SourceItemOrderingModuleItemKind`].
|
||||
///
|
||||
/// This is implemented here because `rustc_hir` is not a dependency of
|
||||
/// `clippy_config`.
|
||||
fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleItemKind {
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingModuleItemKind::*;
|
||||
match value {
|
||||
ItemKind::ExternCrate(..) => ExternCrate,
|
||||
ItemKind::Use(..) => Use,
|
||||
ItemKind::Static(..) => Static,
|
||||
ItemKind::Const(..) => Const,
|
||||
ItemKind::Fn(..) => Fn,
|
||||
ItemKind::Macro(..) => Macro,
|
||||
ItemKind::Mod(..) => Mod,
|
||||
ItemKind::ForeignMod { .. } => ForeignMod,
|
||||
ItemKind::GlobalAsm(..) => GlobalAsm,
|
||||
ItemKind::TyAlias(..) => TyAlias,
|
||||
ItemKind::Enum(..) => Enum,
|
||||
ItemKind::Struct(..) => Struct,
|
||||
ItemKind::Union(..) => Union,
|
||||
ItemKind::Trait(..) => Trait,
|
||||
ItemKind::TraitAlias(..) => TraitAlias,
|
||||
ItemKind::Impl(..) => Impl,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the item name for sorting purposes, which in the general case is
|
||||
/// `item.ident.name`.
|
||||
///
|
||||
/// For trait impls, the name used for sorting will be the written path of
|
||||
/// `item.self_ty` plus the written path of `item.of_trait`, joined with
|
||||
/// exclamation marks. Exclamation marks are used because they are the first
|
||||
/// printable ASCII character.
|
||||
///
|
||||
/// Trait impls generated using a derive-macro will have their path rewritten,
|
||||
/// such that for example `Default` is `$crate::default::Default`, and
|
||||
/// `std::clone::Clone` is `$crate::clone::Clone`. This behaviour is described
|
||||
/// further in the [Rust Reference, Paths Chapter][rust_ref].
|
||||
///
|
||||
/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1
|
||||
fn get_item_name(item: &Item<'_>) -> String {
|
||||
match item.kind {
|
||||
ItemKind::Impl(im) => {
|
||||
if let TyKind::Path(path) = im.self_ty.kind {
|
||||
match path {
|
||||
QPath::Resolved(_, path) => {
|
||||
let segs = path.segments.iter();
|
||||
let mut segs: Vec<String> = segs.map(|s| s.ident.name.as_str().to_owned()).collect();
|
||||
|
||||
if let Some(of_trait) = im.of_trait {
|
||||
let mut trait_segs: Vec<String> = of_trait
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| s.ident.name.as_str().to_owned())
|
||||
.collect();
|
||||
segs.append(&mut trait_segs);
|
||||
}
|
||||
|
||||
segs.push(String::new());
|
||||
segs.join("!!")
|
||||
},
|
||||
QPath::TypeRelative(_, _path_seg) => {
|
||||
// This case doesn't exist in the clippy tests codebase.
|
||||
String::new()
|
||||
},
|
||||
QPath::LangItem(_, _) => String::new(),
|
||||
}
|
||||
} else {
|
||||
// Impls for anything that isn't a named type can be skipped.
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
_ => item.ident.name.as_str().to_owned(),
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ mod utils;
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast};
|
||||
use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
|
||||
use rustc_hir::{ImplItem, Item, TraitItem};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
@ -1,10 +1,9 @@
|
||||
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
|
||||
use super::USELESS_ATTRIBUTE;
|
||||
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{SpanRangeExt, first_line_of_span};
|
||||
use rustc_ast::MetaItemInner;
|
||||
use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_ast::{Item, ItemKind, Attribute};
|
||||
use rustc_lint::{EarlyContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::sym;
|
||||
|
@ -15,7 +15,7 @@ declare_clippy_lint! {
|
||||
/// `MutexGuard`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and
|
||||
/// The Mutex types found in [`std::sync`](https://doc.rust-lang.org/stable/std/sync/) and
|
||||
/// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are
|
||||
/// not designed to operate in an async context across await points.
|
||||
///
|
||||
|
@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
|
||||
&& let Some(suggestion) = simplify_not(cx, msrv, inner)
|
||||
&& cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
|
||||
{
|
||||
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
|
||||
let maybe_par = if let Some(sug) = Sugg::hir_opt(cx, inner) {
|
||||
match sug {
|
||||
Sugg::BinOp(..) => true,
|
||||
Sugg::MaybeParen(sug) if !has_enclosing_paren(&sug) => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let suggestion = if maybe_par {
|
||||
format!("({suggestion})")
|
||||
} else {
|
||||
suggestion
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NONMINIMAL_BOOL,
|
||||
|
@ -4,7 +4,7 @@ use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{ExprKind, UnOp};
|
||||
use rustc_hir::{BorrowKind, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty;
|
||||
@ -49,7 +49,7 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
|
||||
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind
|
||||
&& let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
|
||||
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
|
||||
&& !e.span.from_expansion()
|
||||
|
@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
);
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind {
|
||||
if method_path.ident.name == sym!(cast)
|
||||
if method_path.ident.name.as_str() == "cast"
|
||||
&& let Some(generic_args) = method_path.args
|
||||
&& let [GenericArg::Type(cast_to)] = generic_args.args
|
||||
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
|
||||
|
@ -268,8 +268,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx
|
||||
if !snippet
|
||||
.split("->")
|
||||
.skip(1)
|
||||
.map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from)))
|
||||
.any(|a| a)
|
||||
.any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from)))
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ fn get_types_from_cast<'a>(
|
||||
// or `to_type::MAX as from_type`
|
||||
let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind
|
||||
// to_type::max_value(), from_type
|
||||
&& let TyKind::Path(ref from_type_path) = &from_type.kind
|
||||
&& let TyKind::Path(from_type_path) = &from_type.kind
|
||||
&& let Some(from_sym) = int_ty_to_sym(from_type_path)
|
||||
{
|
||||
Some((limit, from_sym))
|
||||
@ -245,7 +245,7 @@ fn get_types_from_cast<'a>(
|
||||
if let ExprKind::Call(from_func, [limit]) = &expr.kind
|
||||
// `from_type::from, to_type::max_value()`
|
||||
// `from_type::from`
|
||||
&& let ExprKind::Path(ref path) = &from_func.kind
|
||||
&& let ExprKind::Path(path) = &from_func.kind
|
||||
&& let Some(from_sym) = get_implementing_type(path, INTS, "from")
|
||||
{
|
||||
Some((limit, from_sym))
|
||||
|
@ -30,7 +30,7 @@ declare_clippy_lint! {
|
||||
#[clippy::version = "1.35.0"]
|
||||
pub COGNITIVE_COMPLEXITY,
|
||||
nursery,
|
||||
"functions that should be split up into multiple functions"
|
||||
"functions that should be split up into multiple functions",
|
||||
@eval_always = true
|
||||
}
|
||||
|
||||
|
@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
|
||||
.iter()
|
||||
.filter(|&&(_, name)| !name.as_str().starts_with('_'))
|
||||
.any(|&(_, name)| {
|
||||
let mut walker = ContainsName {
|
||||
name,
|
||||
result: false,
|
||||
cx,
|
||||
};
|
||||
let mut walker = ContainsName { name, cx };
|
||||
|
||||
// Scan block
|
||||
block
|
||||
let mut res = block
|
||||
.stmts
|
||||
.iter()
|
||||
.filter(|stmt| !ignore_span.overlaps(stmt.span))
|
||||
.for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
|
||||
.try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
|
||||
|
||||
if let Some(expr) = block.expr {
|
||||
intravisit::walk_expr(&mut walker, expr);
|
||||
if res.is_continue() {
|
||||
res = intravisit::walk_expr(&mut walker, expr);
|
||||
}
|
||||
}
|
||||
|
||||
walker.result
|
||||
res.is_break()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
|
||||
impl<'tcx> LateLintPass<'tcx> for CopyIterator {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = item.kind
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
|
@ -5,7 +5,6 @@ use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
|
||||
declare_lint_pass! {
|
||||
/// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes).
|
||||
/// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran.
|
||||
|
@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint {
|
||||
(@
|
||||
$(#[doc = $lit:literal])*
|
||||
pub $lint_name:ident,
|
||||
$category:ident,
|
||||
$level:ident,
|
||||
$lintcategory:expr,
|
||||
$desc:literal,
|
||||
$version_expr:expr,
|
||||
@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint {
|
||||
$(#[doc = $lit])*
|
||||
#[clippy::version = $version_lit]
|
||||
pub clippy::$lint_name,
|
||||
$category,
|
||||
$level,
|
||||
$desc,
|
||||
report_in_external_macro:true
|
||||
$(, @eval_always = $eval_always)?
|
||||
@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
restriction,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Allow, crate::LintCategory::Restriction, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -49,12 +50,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
style,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Warn, crate::LintCategory::Style, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -63,12 +65,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
correctness,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Deny, crate::LintCategory::Correctness, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
|
||||
}
|
||||
};
|
||||
@ -78,12 +81,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
perf,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Warn, crate::LintCategory::Perf, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -92,12 +96,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
complexity,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Warn, crate::LintCategory::Complexity, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -106,12 +111,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
suspicious,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -120,12 +126,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
nursery,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Allow, crate::LintCategory::Nursery, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -134,12 +141,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
pedantic,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
(
|
||||
@ -148,12 +156,13 @@ macro_rules! declare_clippy_lint {
|
||||
pub $lint_name:ident,
|
||||
cargo,
|
||||
$desc:literal
|
||||
$(@eval_always = $eval_always: literal)?
|
||||
$(, @eval_always = $eval_always: literal)?
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Allow, crate::LintCategory::Cargo, $desc,
|
||||
Some($version), $version $(, $eval_always)?
|
||||
Some($version), $version
|
||||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -28,12 +28,15 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
|
||||
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
|
||||
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
|
||||
crate::approx_const::APPROX_CONSTANT_INFO,
|
||||
crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO,
|
||||
crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
|
||||
crate::as_conversions::AS_CONVERSIONS_INFO,
|
||||
crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO,
|
||||
@ -414,14 +417,17 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::methods::MANUAL_SPLIT_ONCE_INFO,
|
||||
crate::methods::MANUAL_STR_REPEAT_INFO,
|
||||
crate::methods::MANUAL_TRY_FOLD_INFO,
|
||||
crate::methods::MAP_ALL_ANY_IDENTITY_INFO,
|
||||
crate::methods::MAP_CLONE_INFO,
|
||||
crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
|
||||
crate::methods::MAP_ERR_IGNORE_INFO,
|
||||
crate::methods::MAP_FLATTEN_INFO,
|
||||
crate::methods::MAP_IDENTITY_INFO,
|
||||
crate::methods::MAP_UNWRAP_OR_INFO,
|
||||
crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO,
|
||||
crate::methods::MUT_MUTEX_LOCK_INFO,
|
||||
crate::methods::NAIVE_BYTECOUNT_INFO,
|
||||
crate::methods::NEEDLESS_AS_BYTES_INFO,
|
||||
crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO,
|
||||
crate::methods::NEEDLESS_COLLECT_INFO,
|
||||
crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,
|
||||
|
@ -187,7 +187,7 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex
|
||||
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
items: [child],
|
||||
self_ty,
|
||||
..
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
|
||||
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths};
|
||||
@ -202,7 +204,7 @@ declare_lint_pass!(Derive => [
|
||||
impl<'tcx> LateLintPass<'tcx> for Derive {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = item.kind
|
||||
{
|
||||
@ -371,9 +373,8 @@ fn check_unsafe_derive_deserialize<'tcx>(
|
||||
ty: Ty<'tcx>,
|
||||
) {
|
||||
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
|
||||
let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
|
||||
walk_item(&mut visitor, item);
|
||||
visitor.has_unsafe
|
||||
let mut visitor = UnsafeVisitor { cx };
|
||||
walk_item(&mut visitor, item).is_break()
|
||||
}
|
||||
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id()
|
||||
@ -406,38 +407,37 @@ fn check_unsafe_derive_deserialize<'tcx>(
|
||||
|
||||
struct UnsafeVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
has_unsafe: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
type NestedFilter = nested_filter::All;
|
||||
|
||||
fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) {
|
||||
if self.has_unsafe {
|
||||
return;
|
||||
}
|
||||
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
kind: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl<'_>,
|
||||
body_id: BodyId,
|
||||
_: Span,
|
||||
id: LocalDefId,
|
||||
) -> Self::Result {
|
||||
if let Some(header) = kind.header()
|
||||
&& header.safety == Safety::Unsafe
|
||||
{
|
||||
self.has_unsafe = true;
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
walk_fn(self, kind, decl, body_id, id)
|
||||
}
|
||||
|
||||
walk_fn(self, kind, decl, body_id, id);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if self.has_unsafe {
|
||||
return;
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::Block(block, _) = expr.kind {
|
||||
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
|
||||
self.has_unsafe = true;
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
walk_expr(self, expr)
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
|
@ -2,7 +2,6 @@ use clippy_config::Conf;
|
||||
use clippy_utils::create_disallowed_map;
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use rustc_ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Diag;
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
@ -14,6 +13,8 @@ use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{ExpnId, MacroKind, Span};
|
||||
|
||||
use crate::utils::attr_collector::AttrStorage;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Denies the configured macros in clippy.toml
|
||||
@ -64,14 +65,19 @@ pub struct DisallowedMacros {
|
||||
// Track the most recently seen node that can have a `derive` attribute.
|
||||
// Needed to use the correct lint level.
|
||||
derive_src: Option<OwnerId>,
|
||||
|
||||
// When a macro is disallowed in an early pass, it's stored
|
||||
// and emitted during the late pass. This happens for attributes.
|
||||
earlies: AttrStorage,
|
||||
}
|
||||
|
||||
impl DisallowedMacros {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
|
||||
Self {
|
||||
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
|
||||
seen: FxHashSet::default(),
|
||||
derive_src: None,
|
||||
earlies,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,6 +120,15 @@ impl DisallowedMacros {
|
||||
impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
|
||||
|
||||
impl LateLintPass<'_> for DisallowedMacros {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
// once we check a crate in the late pass we can emit the early pass lints
|
||||
if let Some(attr_spans) = self.earlies.clone().0.get() {
|
||||
for span in attr_spans {
|
||||
self.check(cx, *span, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
self.check(cx, expr.span, None);
|
||||
// `$t + $t` can have the context of $t, check also the span of the binary operator
|
||||
@ -164,8 +179,4 @@ impl LateLintPass<'_> for DisallowedMacros {
|
||||
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
|
||||
self.check(cx, path.span, None);
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
|
||||
self.check(cx, attr.span, self.derive_src);
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ impl EarlyLintPass for DisallowedScriptIdents {
|
||||
let mut symbols: Vec<_> = symbols.iter().collect();
|
||||
symbols.sort_unstable_by_key(|k| k.1);
|
||||
|
||||
for (symbol, &span) in &symbols {
|
||||
for &(symbol, &span) in &symbols {
|
||||
// Note: `symbol.as_str()` is an expensive operation, thus should not be called
|
||||
// more than once for a single symbol.
|
||||
let symbol_str = symbol.as_str();
|
||||
|
@ -427,11 +427,11 @@ declare_clippy_lint! {
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if the first line in the documentation of items listed in module page is too long.
|
||||
/// Checks if the first paragraph in the documentation of items listed in the module page is too long.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Documentation will show the first paragraph of the doscstring in the summary page of a
|
||||
/// module, so having a nice, short summary in the first paragraph is part of writing good docs.
|
||||
/// Documentation will show the first paragraph of the docstring in the summary page of a
|
||||
/// module. Having a nice, short summary in the first paragraph is part of writing good docs.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@ -453,7 +453,7 @@ declare_clippy_lint! {
|
||||
#[clippy::version = "1.82.0"]
|
||||
pub TOO_LONG_FIRST_DOC_PARAGRAPH,
|
||||
nursery,
|
||||
"ensure that the first line of a documentation paragraph isn't too long"
|
||||
"ensure the first documentation paragraph is short"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -36,7 +36,7 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
|
||||
impl LateLintPass<'_> for EmptyDrop {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
items: [child],
|
||||
..
|
||||
}) = item.kind
|
||||
|
@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt,
|
||||
TypeckResults,
|
||||
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
@ -204,11 +203,16 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc
|
||||
// 'cuz currently nothing changes after deleting this check.
|
||||
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
|
||||
}) {
|
||||
match cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
) {
|
||||
match cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
) {
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
|
||||
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
|
||||
|
@ -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 == sym!(write_fmt)
|
||||
&& write_fun.ident.name.as_str() == "write_fmt"
|
||||
&& let Some(def_id) = path_def_id(cx, write_recv_path)
|
||||
{
|
||||
// match calls to std::io::stdout() / std::io::stderr ()
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
@ -115,25 +117,26 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto {
|
||||
}
|
||||
|
||||
/// Finds the occurrences of `Self` and `self`
|
||||
///
|
||||
/// Returns `ControlFlow::break` if any of the `self`/`Self` usages were from an expansion, or the
|
||||
/// body contained a binding already named `val`.
|
||||
struct SelfFinder<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
/// Occurrences of `Self`
|
||||
upper: Vec<Span>,
|
||||
/// Occurrences of `self`
|
||||
lower: Vec<Span>,
|
||||
/// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding
|
||||
/// already named `val`
|
||||
invalid: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
type NestedFilter = OnlyBodies;
|
||||
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
|
||||
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result {
|
||||
for segment in path.segments {
|
||||
match segment.ident.name {
|
||||
kw::SelfLower => self.lower.push(segment.ident.span),
|
||||
@ -141,17 +144,19 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> {
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
self.invalid |= segment.ident.span.from_expansion();
|
||||
if segment.ident.span.from_expansion() {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
if !self.invalid {
|
||||
walk_path(self, path);
|
||||
}
|
||||
walk_path(self, path)
|
||||
}
|
||||
|
||||
fn visit_name(&mut self, name: Symbol) {
|
||||
fn visit_name(&mut self, name: Symbol) -> Self::Result {
|
||||
if name == sym::val {
|
||||
self.invalid = true;
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,11 +214,9 @@ fn convert_to_from(
|
||||
cx,
|
||||
upper: Vec::new(),
|
||||
lower: Vec::new(),
|
||||
invalid: false,
|
||||
};
|
||||
finder.visit_expr(body.value);
|
||||
|
||||
if finder.invalid {
|
||||
if finder.visit_expr(body.value).is_break() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::Call(box_from_raw, [arg]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind
|
||||
&& seg.ident.name == sym!(from_raw)
|
||||
&& seg.ident.name.as_str() == "from_raw"
|
||||
&& let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id))
|
||||
&& let arg_kind = cx.typeck_results().expr_ty(arg).kind()
|
||||
&& let ty::RawPtr(ty, _) = arg_kind
|
||||
|
@ -441,7 +441,7 @@ declare_clippy_lint! {
|
||||
/// fn bar(&self) -> Option<&String> { None }
|
||||
/// # }
|
||||
/// ```
|
||||
#[clippy::version = "1.82.0"]
|
||||
#[clippy::version = "1.83.0"]
|
||||
pub REF_OPTION,
|
||||
pedantic,
|
||||
"function signature uses `&Option<T>` instead of `Option<&T>`"
|
||||
|
@ -15,9 +15,9 @@ use rustc_span::{Span, sym};
|
||||
fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) {
|
||||
if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind()
|
||||
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option)
|
||||
&& let ty::Adt(_, opt_gen) = opt_ty.kind()
|
||||
&& let [gen] = opt_gen.as_slice()
|
||||
&& let GenericArgKind::Type(gen_ty) = gen.unpack()
|
||||
&& let ty::Adt(_, opt_gen_args) = opt_ty.kind()
|
||||
&& let [gen_arg] = opt_gen_args.as_slice()
|
||||
&& let GenericArgKind::Type(gen_ty) = gen_arg.unpack()
|
||||
&& !gen_ty.is_ref()
|
||||
// Need to gen the original spans, so first parsing mid, and hir parsing afterward
|
||||
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind
|
||||
|
@ -340,7 +340,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> {
|
||||
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions.insert(e.span, "HashMap::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
} else if method.ident.name.as_str() == "with_capacity" {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
@ -352,7 +352,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> {
|
||||
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions.insert(e.span, "HashSet::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
} else if method.ident.name.as_str() == "with_capacity" {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
|
@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
||||
.and(cap);
|
||||
}
|
||||
}
|
||||
if method.ident.name == sym!(flat_map) && args.len() == 1 {
|
||||
if method.ident.name.as_str() == "flat_map" && args.len() == 1 {
|
||||
if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
return is_infinite(cx, body.value);
|
||||
@ -224,7 +224,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
||||
return MaybeInfinite.and(is_infinite(cx, receiver));
|
||||
}
|
||||
}
|
||||
if method.ident.name == sym!(last) && args.is_empty() {
|
||||
if method.ident.name.as_str() == "last" && args.is_empty() {
|
||||
let not_double_ended = cx
|
||||
.tcx
|
||||
.get_diagnostic_item(sym::DoubleEndedIterator)
|
||||
@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
||||
if not_double_ended {
|
||||
return is_infinite(cx, receiver);
|
||||
}
|
||||
} else if method.ident.name == sym!(collect) {
|
||||
} else if method.ident.name.as_str() == "collect" {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if matches!(
|
||||
get_type_diagnostic_name(cx, ty),
|
||||
|
@ -142,7 +142,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
|
||||
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some()
|
||||
})
|
||||
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
|
||||
if item.ident.name == sym!(IntoIter) {
|
||||
if item.ident.name.as_str() == "IntoIter" {
|
||||
Some(cx.tcx.hir().impl_item(item.id).expect_type().span)
|
||||
} else {
|
||||
None
|
||||
@ -247,8 +247,8 @@ impl {self_ty_without_ref} {{
|
||||
let sugg = format!(
|
||||
"
|
||||
impl IntoIterator for {self_ty_snippet} {{
|
||||
type IntoIter = {ret_ty};
|
||||
type Item = {iter_ty};
|
||||
type IntoIter = {ret_ty};
|
||||
fn into_iter(self) -> Self::IntoIter {{
|
||||
self.iter()
|
||||
}}
|
||||
|
@ -4,7 +4,7 @@ use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, ConstKind};
|
||||
use rustc_middle::ty::{self, ParamEnv};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
|
||||
@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
|
||||
&& !item.span.from_expansion()
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& let ty::Array(element_type, cst) = ty.kind()
|
||||
&& let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind()
|
||||
&& let Ok((_, ty::ValTree::Leaf(element_count))) = cst.eval_valtree(cx.tcx, ParamEnv::empty(), item.span)
|
||||
&& let element_count = element_count.to_target_usize(cx.tcx)
|
||||
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes())
|
||||
&& u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size)
|
||||
|
@ -1,11 +1,12 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use rustc_ast::LitKind;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind, Attribute, LitKind};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -51,6 +52,24 @@ impl LargeIncludeFile {
|
||||
|
||||
impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
|
||||
|
||||
impl LargeIncludeFile {
|
||||
fn emit_lint(&self, cx: &LateContext<'_>, span: Span) {
|
||||
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_INCLUDE_FILE,
|
||||
span,
|
||||
"attempted to include a large file",
|
||||
|diag| {
|
||||
diag.note(format!(
|
||||
"the configuration allows a maximum size of {} bytes",
|
||||
self.max_file_size
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for LargeIncludeFile {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
|
||||
if let ExprKind::Lit(lit) = &expr.kind
|
||||
@ -66,19 +85,33 @@ impl LateLintPass<'_> for LargeIncludeFile {
|
||||
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|
||||
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
|
||||
{
|
||||
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_INCLUDE_FILE,
|
||||
expr.span.source_callsite(),
|
||||
"attempted to include a large file",
|
||||
|diag| {
|
||||
diag.note(format!(
|
||||
"the configuration allows a maximum size of {} bytes",
|
||||
self.max_file_size
|
||||
));
|
||||
},
|
||||
);
|
||||
self.emit_lint(cx, expr.span.source_callsite());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
|
||||
if !attr.span.from_expansion()
|
||||
// Currently, rustc limits the usage of macro at the top-level of attributes,
|
||||
// so we don't need to recurse into each level.
|
||||
&& let AttrKind::Normal(ref normal) = attr.kind
|
||||
&& let AttrArgs::Eq(_, AttrArgsEq::Hir(ref meta)) = normal.item.args
|
||||
&& !attr.span.contains(meta.span)
|
||||
// Since the `include_str` is already expanded at this point, we can only take the
|
||||
// whole attribute snippet and then modify for our suggestion.
|
||||
&& let Some(snippet) = snippet_opt(cx, attr.span)
|
||||
// We cannot remove this because a `#[doc = include_str!("...")]` attribute can
|
||||
// occupy several lines.
|
||||
&& let Some(start) = snippet.find('[')
|
||||
&& let Some(end) = snippet.rfind(']')
|
||||
&& let snippet = &snippet[start + 1..end]
|
||||
// We check that the expansion actually comes from `include_str!` and not just from
|
||||
// another macro.
|
||||
&& let Some(sub_snippet) = snippet.trim().strip_prefix("doc")
|
||||
&& let Some(sub_snippet) = sub_snippet.trim().strip_prefix("=")
|
||||
&& let sub_snippet = sub_snippet.trim()
|
||||
&& (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!"))
|
||||
{
|
||||
self.emit_lint(cx, attr.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -629,7 +629,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
.filter_by_name_unhygienic(is_empty)
|
||||
.any(|item| is_is_empty(cx, item))
|
||||
}),
|
||||
ty::Alias(ty::Projection, ref proj) => has_is_empty_impl(cx, proj.def_id),
|
||||
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),
|
||||
ty::Adt(id, _) => has_is_empty_impl(cx, id.did()),
|
||||
ty::Array(..) | ty::Slice(..) | ty::Str => true,
|
||||
_ => false,
|
||||
|
@ -73,6 +73,7 @@ pub mod deprecated_lints;
|
||||
mod absolute_paths;
|
||||
mod almost_complete_range;
|
||||
mod approx_const;
|
||||
mod arbitrary_source_item_ordering;
|
||||
mod arc_with_non_send_sync;
|
||||
mod as_conversions;
|
||||
mod asm_syntax;
|
||||
@ -400,6 +401,7 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
|
||||
use clippy_utils::macros::FormatArgsStorage;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_lint::{Lint, LintId};
|
||||
use utils::attr_collector::{AttrCollector, AttrStorage};
|
||||
|
||||
/// Register all pre expansion lints
|
||||
///
|
||||
@ -464,6 +466,7 @@ pub(crate) enum LintCategory {
|
||||
#[cfg(feature = "internal")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use LintCategory::*;
|
||||
|
||||
@ -585,6 +588,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
))
|
||||
});
|
||||
|
||||
let attr_storage = AttrStorage::default();
|
||||
let attrs = attr_storage.clone();
|
||||
store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone())));
|
||||
|
||||
// all the internal lints
|
||||
#[cfg(feature = "internal")]
|
||||
{
|
||||
@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
store.register_late_pass(|_| {
|
||||
Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new())
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons));
|
||||
}
|
||||
|
||||
store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe));
|
||||
@ -795,7 +803,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
|
||||
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
|
||||
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
|
||||
store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf)));
|
||||
let attrs = attr_storage.clone();
|
||||
store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone())));
|
||||
store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf)));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
|
||||
@ -953,5 +962,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
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));
|
||||
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
@ -29,11 +29,7 @@ pub(super) fn check(
|
||||
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
|
||||
return;
|
||||
}
|
||||
} else if count
|
||||
.try_to_target_usize(cx.tcx)
|
||||
.map_or(true, |x| x > 32)
|
||||
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
|
||||
{
|
||||
} else if count.try_to_target_usize(cx.tcx).map_or(true, |x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
|
||||
use hir::intravisit::{Visitor, walk_expr};
|
||||
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node};
|
||||
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
|
||||
use rustc_ast::Label;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::INFINITE_LOOP;
|
||||
|
||||
@ -25,13 +26,7 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
};
|
||||
// Or, its parent function is already returning `Never`
|
||||
if matches!(
|
||||
parent_fn_ret,
|
||||
FnRetTy::Return(hir::Ty {
|
||||
kind: hir::TyKind::Never,
|
||||
..
|
||||
})
|
||||
) {
|
||||
if is_never_return(parent_fn_ret) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,6 +64,16 @@ pub(super) fn check<'tcx>(
|
||||
fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
|
||||
for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
match parent_node {
|
||||
// Skip `Coroutine` closures, these are the body of `async fn`, not async closures.
|
||||
// This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
|
||||
Node::Expr(Expr {
|
||||
kind:
|
||||
ExprKind::Closure(hir::Closure {
|
||||
kind: hir::ClosureKind::Coroutine(_),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => (),
|
||||
Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _),
|
||||
..
|
||||
@ -143,3 +148,41 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the given [`FnRetTy`] is never (!).
|
||||
///
|
||||
/// Note: This function also take care of return type of async fn,
|
||||
/// as the actual type is behind an [`OpaqueDef`](TyKind::OpaqueDef).
|
||||
fn is_never_return(ret_ty: FnRetTy<'_>) -> bool {
|
||||
let FnRetTy::Return(hir_ty) = ret_ty else { return false };
|
||||
|
||||
match hir_ty.kind {
|
||||
TyKind::Never => true,
|
||||
TyKind::OpaqueDef(hir::OpaqueTy {
|
||||
origin: hir::OpaqueTyOrigin::AsyncFn { .. },
|
||||
bounds,
|
||||
..
|
||||
}) => {
|
||||
if let Some(trait_ref) = bounds.iter().find_map(|b| b.trait_ref())
|
||||
&& let Some(segment) = trait_ref
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.find(|seg| seg.ident.name == sym::future_trait)
|
||||
&& let Some(args) = segment.args
|
||||
&& let Some(cst_kind) = args
|
||||
.constraints
|
||||
.iter()
|
||||
.find_map(|cst| (cst.ident.name == sym::Output).then_some(cst.kind))
|
||||
&& let hir::AssocItemConstraintKind::Equality {
|
||||
term: hir::Term::Ty(ty),
|
||||
} = cst_kind
|
||||
{
|
||||
matches!(ty.kind, TyKind::Never)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'
|
||||
cx,
|
||||
ids: HirIdSet::default(),
|
||||
def_ids: DefIdMap::default(),
|
||||
skip: false,
|
||||
};
|
||||
var_visitor.visit_expr(cond);
|
||||
if var_visitor.skip {
|
||||
if var_visitor.visit_expr(cond).is_break() {
|
||||
return;
|
||||
}
|
||||
let used_in_condition = &var_visitor.ids;
|
||||
@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
ids: HirIdSet,
|
||||
def_ids: DefIdMap<bool>,
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> VarCollectorVisitor<'_, 'tcx> {
|
||||
@ -104,11 +101,15 @@ impl<'tcx> VarCollectorVisitor<'_, 'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> {
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
type Result = ControlFlow<()>;
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result {
|
||||
match ex.kind {
|
||||
ExprKind::Path(_) => self.insert_def_id(ex),
|
||||
ExprKind::Path(_) => {
|
||||
self.insert_def_id(ex);
|
||||
ControlFlow::Continue(())
|
||||
},
|
||||
// If there is any function/method call… we just stop analysis
|
||||
ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
|
||||
ExprKind::Call(..) | ExprKind::MethodCall(..) => ControlFlow::Break(()),
|
||||
|
||||
_ => walk_expr(self, ex),
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::WHILE_LET_ON_ITERATOR;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
@ -204,35 +206,32 @@ fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tc
|
||||
struct V<'a, 'b, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
iter_expr: &'b IterExpr,
|
||||
uses_iter: bool,
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if self.uses_iter {
|
||||
// return
|
||||
} else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
|
||||
self.uses_iter = true;
|
||||
type Result = ControlFlow<()>;
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result {
|
||||
if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
|
||||
ControlFlow::Break(())
|
||||
} else if let (e, true) = skip_fields_and_path(e) {
|
||||
if let Some(e) = e {
|
||||
self.visit_expr(e);
|
||||
self.visit_expr(e)
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
} else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
|
||||
if is_res_used(self.cx, self.iter_expr.path, id) {
|
||||
self.uses_iter = true;
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
} else {
|
||||
walk_expr(self, e);
|
||||
walk_expr(self, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut v = V {
|
||||
cx,
|
||||
iter_expr,
|
||||
uses_iter: false,
|
||||
};
|
||||
v.visit_expr(container);
|
||||
v.uses_iter
|
||||
let mut v = V { cx, iter_expr };
|
||||
v.visit_expr(container).is_break()
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
@ -242,34 +241,38 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
|
||||
iter_expr: &'b IterExpr,
|
||||
loop_id: HirId,
|
||||
after_loop: bool,
|
||||
used_iter: bool,
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
|
||||
type NestedFilter = OnlyBodies;
|
||||
type Result = ControlFlow<()>;
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if self.used_iter {
|
||||
return;
|
||||
}
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result {
|
||||
if self.after_loop {
|
||||
if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
|
||||
self.used_iter = true;
|
||||
ControlFlow::Break(())
|
||||
} else if let (e, true) = skip_fields_and_path(e) {
|
||||
if let Some(e) = e {
|
||||
self.visit_expr(e);
|
||||
self.visit_expr(e)
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
} else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
|
||||
self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
|
||||
if is_res_used(self.cx, self.iter_expr.path, id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
} else {
|
||||
walk_expr(self, e);
|
||||
walk_expr(self, e)
|
||||
}
|
||||
} else if self.loop_id == e.hir_id {
|
||||
self.after_loop = true;
|
||||
ControlFlow::Continue(())
|
||||
} else {
|
||||
walk_expr(self, e);
|
||||
walk_expr(self, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,9 +350,8 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
|
||||
iter_expr,
|
||||
loop_id: loop_expr.hir_id,
|
||||
after_loop: false,
|
||||
used_iter: false,
|
||||
};
|
||||
v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
|
||||
v.used_iter
|
||||
v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value)
|
||||
.is_break()
|
||||
}
|
||||
}
|
||||
|
@ -106,13 +106,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
|
||||
|
||||
fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>) -> Option<&'tcx TraitRef<'tcx>> {
|
||||
if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
|
||||
if let GenericBound::Trait(poly) = bound {
|
||||
Some(&poly.trait_ref)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
&& trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
|
||||
if let GenericBound::Trait(poly) = bound {
|
||||
Some(&poly.trait_ref)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
|
||||
{
|
||||
return Some(trait_ref);
|
||||
}
|
||||
@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de
|
||||
.tcx
|
||||
.opaque_captured_lifetimes(opaque_def_id)
|
||||
.iter()
|
||||
.filter(|&(lifetime, _)| match *lifetime {
|
||||
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true,
|
||||
_ => false,
|
||||
.filter(|&(lifetime, _)| {
|
||||
matches!(
|
||||
*lifetime,
|
||||
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _)
|
||||
)
|
||||
})
|
||||
.count();
|
||||
num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes
|
||||
}
|
||||
|
||||
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
|
||||
if let Some(Expr {
|
||||
if let Some(&Expr {
|
||||
kind: ExprKind::Closure(&Closure { kind, body, .. }),
|
||||
..
|
||||
}) = block.expr
|
||||
|
@ -7,8 +7,7 @@ use rustc_ast::ast::LitKind;
|
||||
use rustc_data_structures::packed::Pu128;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym;
|
||||
@ -53,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
|
||||
&& let BinOpKind::Mul = &bin_op.node
|
||||
&& !in_external_macro(cx.sess(), expr.span)
|
||||
&& !expr.span.from_expansion()
|
||||
&& self.msrv.meets(msrvs::MANUAL_BITS)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& left_expr.span.ctxt() == ctxt
|
||||
|
@ -68,7 +68,7 @@ impl LateLintPass<'_> for ManualHashOne {
|
||||
&& let Some(init) = local.init
|
||||
&& !init.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind
|
||||
&& seg.ident.name == sym!(build_hasher)
|
||||
&& seg.ident.name.as_str() == "build_hasher"
|
||||
|
||||
&& let Node::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id)
|
||||
&& let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.hir_id)
|
||||
@ -96,7 +96,7 @@ impl LateLintPass<'_> for ManualHashOne {
|
||||
&& let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id)
|
||||
&& !finish_expr.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind
|
||||
&& seg.ident.name == sym!(finish)
|
||||
&& seg.ident.name.as_str() == "finish"
|
||||
|
||||
&& self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
|
||||
{
|
||||
|
@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
|
||||
check_is_ascii(cx, macro_call.span, recv, &range, None);
|
||||
}
|
||||
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
|
||||
&& path.ident.name == sym!(contains)
|
||||
&& path.ident.name.as_str() == "contains"
|
||||
&& let Some(higher::Range {
|
||||
start: Some(start),
|
||||
end: Some(end),
|
||||
|
@ -148,7 +148,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
|
||||
}) => Some(*b),
|
||||
ExprKind::Block(
|
||||
rustc_hir::Block {
|
||||
stmts: &[],
|
||||
stmts: [],
|
||||
expr: Some(exp),
|
||||
..
|
||||
},
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use rustc_ast::ast::LitKind;
|
||||
@ -23,11 +25,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm
|
||||
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind()
|
||||
&& let ty::Str = ty.kind()
|
||||
{
|
||||
let mut visitor = MatchExprVisitor { cx, case_method: None };
|
||||
|
||||
visitor.visit_expr(scrutinee);
|
||||
|
||||
if let Some(case_method) = visitor.case_method {
|
||||
let mut visitor = MatchExprVisitor { cx };
|
||||
if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) {
|
||||
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
|
||||
lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
|
||||
}
|
||||
@ -37,30 +36,33 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm
|
||||
|
||||
struct MatchExprVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
case_method: Option<CaseMethod>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> {
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
match ex.kind {
|
||||
ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {},
|
||||
_ => walk_expr(self, ex),
|
||||
type Result = ControlFlow<CaseMethod>;
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind {
|
||||
let result = self.case_altered(segment.ident.as_str(), receiver);
|
||||
if result.is_break() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
walk_expr(self, ex)
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchExprVisitor<'_, '_> {
|
||||
fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool {
|
||||
fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> {
|
||||
if let Some(case_method) = get_case_method(segment_ident) {
|
||||
let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
|
||||
|
||||
if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str {
|
||||
self.case_method = Some(case_method);
|
||||
return true;
|
||||
return ControlFlow::Break(case_method);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{Span, Symbol, sym};
|
||||
use rustc_span::{Span, sym};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
@ -95,7 +95,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
|
||||
} else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind
|
||||
&& let Some(binding) = get_pat_binding(cx, recv, outer_arm)
|
||||
{
|
||||
check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding);
|
||||
check_method_calls(cx, outer_arm, path.ident.name.as_str(), recv, args, guard, &binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +103,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
|
||||
fn check_method_calls<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
arm: &Arm<'tcx>,
|
||||
method: Symbol,
|
||||
method: &str,
|
||||
recv: &Expr<'_>,
|
||||
args: &[Expr<'_>],
|
||||
if_expr: &Expr<'_>,
|
||||
@ -112,7 +112,7 @@ fn check_method_calls<'tcx>(
|
||||
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
let slice_like = ty.is_slice() || ty.is_array();
|
||||
|
||||
let sugg = if method == sym!(is_empty) {
|
||||
let sugg = if method == "is_empty" {
|
||||
// `s if s.is_empty()` becomes ""
|
||||
// `arr if arr.is_empty()` becomes []
|
||||
|
||||
@ -137,9 +137,9 @@ fn check_method_calls<'tcx>(
|
||||
|
||||
if needles.is_empty() {
|
||||
sugg.insert_str(1, "..");
|
||||
} else if method == sym!(starts_with) {
|
||||
} else if method == "starts_with" {
|
||||
sugg.insert_str(sugg.len() - 1, ", ..");
|
||||
} else if method == sym!(ends_with) {
|
||||
} else if method == "ends_with" {
|
||||
sugg.insert_str(1, ".., ");
|
||||
} else {
|
||||
return;
|
||||
|
@ -319,10 +319,9 @@ fn found_good_method<'tcx>(
|
||||
node: (&PatKind<'_>, &PatKind<'_>),
|
||||
) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> {
|
||||
match node {
|
||||
(
|
||||
PatKind::TupleStruct(ref path_left, patterns_left, _),
|
||||
PatKind::TupleStruct(ref path_right, patterns_right, _),
|
||||
) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
|
||||
(PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _))
|
||||
if patterns_left.len() == 1 && patterns_right.len() == 1 =>
|
||||
{
|
||||
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
@ -350,8 +349,8 @@ fn found_good_method<'tcx>(
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
|
||||
| (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
|
||||
(PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right))
|
||||
| (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _))
|
||||
if patterns.len() == 1 =>
|
||||
{
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
@ -381,14 +380,14 @@ fn found_good_method<'tcx>(
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => {
|
||||
(PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => {
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
get_good_method(cx, arms, path_left)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left),
|
||||
(PatKind::Path(path_left), PatKind::Wild) => get_good_method(cx, arms, path_left),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,10 @@ impl<'a> PatState<'a> {
|
||||
let states = match self {
|
||||
Self::Wild => return None,
|
||||
Self::Other => {
|
||||
*self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other)));
|
||||
*self = Self::StdEnum(
|
||||
cx.arena
|
||||
.alloc_from_iter(std::iter::repeat_with(|| Self::Other).take(adt.variants().len())),
|
||||
);
|
||||
let Self::StdEnum(x) = self else {
|
||||
unreachable!();
|
||||
};
|
||||
|
@ -21,8 +21,8 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool
|
||||
ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
|
||||
ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name,
|
||||
ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
|
||||
ExprKind::Closure(&Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
ExprKind::Closure(Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(*body);
|
||||
let closure_expr = peel_blocks(body.value);
|
||||
match closure_expr.kind {
|
||||
ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => {
|
||||
@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
|
||||
// the latter only calls `effect` once
|
||||
let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span);
|
||||
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) {
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" {
|
||||
Some(Self::IsSome {
|
||||
receiver,
|
||||
side_effect_expr_span,
|
||||
})
|
||||
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) {
|
||||
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" {
|
||||
Some(Self::IsOk {
|
||||
receiver,
|
||||
side_effect_expr_span,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use clippy_utils::consts::ConstEvalCtxt;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{find_binding_init, path_to_local};
|
||||
use clippy_utils::macros::{is_assert_macro, root_macro_call};
|
||||
use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local};
|
||||
use rustc_hir::{Expr, HirId};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_
|
||||
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
|
||||
return;
|
||||
}
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
if let Some(parent) = get_parent_expr(cx, parent) {
|
||||
if is_inside_always_const_context(cx.tcx, expr.hir_id)
|
||||
&& let Some(macro_call) = root_macro_call(parent.span)
|
||||
&& is_assert_macro(cx, macro_call.def_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let init_expr = expr_or_init(cx, receiver);
|
||||
if !receiver.span.eq_ctxt(init_expr.span) {
|
||||
return;
|
||||
|
@ -29,10 +29,7 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) ->
|
||||
if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) {
|
||||
// For array::IntoIter<T, const N: usize>, the length is the second generic
|
||||
// parameter.
|
||||
substs
|
||||
.const_at(1)
|
||||
.try_to_target_usize(cx.tcx)
|
||||
.map(u128::from)
|
||||
substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from)
|
||||
} else if cx.tcx.is_diagnostic_item(sym::SliceIter, did)
|
||||
&& let ExprKind::MethodCall(_, recv, ..) = iter.kind
|
||||
{
|
||||
|
@ -0,0 +1,43 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::{is_expr_identity_function, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::MAP_ALL_ANY_IDENTITY;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
map_call_span: Span,
|
||||
map_arg: &Expr<'_>,
|
||||
any_call_span: Span,
|
||||
any_arg: &Expr<'_>,
|
||||
method: &str,
|
||||
) {
|
||||
if is_trait_method(cx, expr, sym::Iterator)
|
||||
&& is_trait_method(cx, recv, sym::Iterator)
|
||||
&& is_expr_identity_function(cx, any_arg)
|
||||
&& let map_any_call_span = map_call_span.with_hi(any_call_span.hi())
|
||||
&& let Some(map_arg) = map_arg.span.get_source_text(cx)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MAP_ALL_ANY_IDENTITY,
|
||||
map_any_call_span,
|
||||
format!("usage of `.map(...).{method}(identity)`"),
|
||||
|diag| {
|
||||
diag.span_suggestion_verbose(
|
||||
map_any_call_span,
|
||||
format!("use `.{method}(...)` instead"),
|
||||
format!("{method}({map_arg})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES;
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{eager_or_lazy, higher, usage};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use rustc_data_structures::packed::Pu128;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
fn extract_count_with_applicability(
|
||||
cx: &LateContext<'_>,
|
||||
range: higher::Range<'_>,
|
||||
applicability: &mut 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
|
||||
// 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.
|
||||
if let ExprKind::Lit(lit) = start.kind
|
||||
&& let LitKind::Int(Pu128(lower_bound), _) = lit.node
|
||||
{
|
||||
if let ExprKind::Lit(lit) = end.kind
|
||||
&& let LitKind::Int(Pu128(upper_bound), _) = lit.node
|
||||
{
|
||||
// Here we can explicitly calculate the number of iterations
|
||||
let count = if upper_bound >= lower_bound {
|
||||
match range.limits {
|
||||
RangeLimits::HalfOpen => upper_bound - lower_bound,
|
||||
RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
return Some(format!("{count}"));
|
||||
}
|
||||
let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability)
|
||||
.maybe_par()
|
||||
.into_string();
|
||||
if lower_bound == 0 {
|
||||
if range.limits == RangeLimits::Closed {
|
||||
return Some(format!("{end_snippet} + 1"));
|
||||
}
|
||||
return Some(end_snippet);
|
||||
}
|
||||
if range.limits == RangeLimits::Closed {
|
||||
return Some(format!("{end_snippet} - {}", lower_bound - 1));
|
||||
}
|
||||
return Some(format!("{end_snippet} - {lower_bound}"));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
ex: &Expr<'_>,
|
||||
receiver: &Expr<'_>,
|
||||
arg: &Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
method_call_span: Span,
|
||||
) {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
if let Some(range) = higher::Range::hir(receiver)
|
||||
&& let ExprKind::Closure(Closure { body, .. }) = arg.kind
|
||||
&& let body_hir = cx.tcx.hir().body(*body)
|
||||
&& let Body {
|
||||
params: [param],
|
||||
value: body_expr,
|
||||
} = body_hir
|
||||
&& !usage::BindingUsageFinder::are_params_used(cx, body_hir)
|
||||
&& let Some(count) = extract_count_with_applicability(cx, range, &mut applicability)
|
||||
{
|
||||
let method_to_use_name;
|
||||
let new_span;
|
||||
let use_take;
|
||||
|
||||
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
|
||||
if msrv.meets(msrvs::REPEAT_N) {
|
||||
method_to_use_name = "repeat_n";
|
||||
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
|
||||
new_span = (arg.span, format!("{body_snippet}, {count}"));
|
||||
use_take = false;
|
||||
} else {
|
||||
method_to_use_name = "repeat";
|
||||
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
|
||||
new_span = (arg.span, body_snippet.to_string());
|
||||
use_take = true;
|
||||
}
|
||||
} else if msrv.meets(msrvs::REPEAT_WITH) {
|
||||
method_to_use_name = "repeat_with";
|
||||
new_span = (param.span, String::new());
|
||||
use_take = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to provide nonempty parts to diag.multipart_suggestion so we
|
||||
// collate all our parts here and then remove those that are empty.
|
||||
let mut parts = vec![
|
||||
(
|
||||
receiver.span.to(method_call_span),
|
||||
format!("std::iter::{method_to_use_name}"),
|
||||
),
|
||||
new_span,
|
||||
];
|
||||
if use_take {
|
||||
parts.push((ex.span.shrink_to_hi(), format!(".take({count})")));
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
|
||||
ex.span,
|
||||
"map of a closure that does not depend on its parameter over a range",
|
||||
|diag| {
|
||||
diag.multipart_suggestion(
|
||||
if use_take {
|
||||
format!("remove the explicit range and use `{method_to_use_name}` and `take`")
|
||||
} else {
|
||||
format!("remove the explicit range and use `{method_to_use_name}`")
|
||||
},
|
||||
parts,
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -60,13 +60,16 @@ mod manual_ok_or;
|
||||
mod manual_saturating_arithmetic;
|
||||
mod manual_str_repeat;
|
||||
mod manual_try_fold;
|
||||
mod map_all_any_identity;
|
||||
mod map_clone;
|
||||
mod map_collect_result_unit;
|
||||
mod map_err_ignore;
|
||||
mod map_flatten;
|
||||
mod map_identity;
|
||||
mod map_unwrap_or;
|
||||
mod map_with_unused_argument_over_ranges;
|
||||
mod mut_mutex_lock;
|
||||
mod needless_as_bytes;
|
||||
mod needless_character_iteration;
|
||||
mod needless_collect;
|
||||
mod needless_option_as_deref;
|
||||
@ -4166,6 +4169,90 @@ declare_clippy_lint! {
|
||||
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `len()` and `is_empty()` methods are also directly available on strings, and they
|
||||
/// return identical results. In particular, `len()` on a string returns the number of
|
||||
/// bytes.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// let len = "some string".as_bytes().len();
|
||||
/// let b = "some string".as_bytes().is_empty();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```
|
||||
/// let len = "some string".len();
|
||||
/// let b = "some string".is_empty();
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub NEEDLESS_AS_BYTES,
|
||||
complexity,
|
||||
"detect useless calls to `as_bytes()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # let mut v = [""];
|
||||
/// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a);
|
||||
/// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```
|
||||
/// # let mut v = [""];
|
||||
/// let e1 = v.iter().all(|s| s.is_empty());
|
||||
/// let e2 = v.iter().any(|s| s.is_empty());
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub MAP_ALL_ANY_IDENTITY,
|
||||
complexity,
|
||||
"combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks for `Iterator::map` over ranges without using the parameter which
|
||||
/// could be more clearly expressed using `std::iter::repeat(...).take(...)`
|
||||
/// or `std::iter::repeat_n`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It expresses the intent more clearly to `take` the correct number of times
|
||||
/// from a generating function than to apply a closure to each number in a
|
||||
/// range only to discard them.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect();
|
||||
/// ```
|
||||
///
|
||||
/// ### Known Issues
|
||||
///
|
||||
/// This lint may suggest replacing a `Map<Range>` with a `Take<RepeatWith>`.
|
||||
/// The former implements some traits that the latter does not, such as
|
||||
/// `DoubleEndedIterator`.
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
|
||||
restriction,
|
||||
"map of a trivial closure (not dependent on parameter) over a range"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [
|
||||
NEEDLESS_CHARACTER_ITERATION,
|
||||
MANUAL_INSPECT,
|
||||
UNNECESSARY_MIN_OR_MAX,
|
||||
NEEDLESS_AS_BYTES,
|
||||
MAP_ALL_ANY_IDENTITY,
|
||||
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
@ -4534,15 +4624,21 @@ impl Methods {
|
||||
("all", [arg]) => {
|
||||
unused_enumerate_index::check(cx, expr, recv, arg);
|
||||
needless_character_iteration::check(cx, expr, recv, arg, true);
|
||||
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
|
||||
iter_overeager_cloned::check(
|
||||
cx,
|
||||
expr,
|
||||
recv,
|
||||
recv2,
|
||||
iter_overeager_cloned::Op::NeedlessMove(arg),
|
||||
false,
|
||||
);
|
||||
match method_call(recv) {
|
||||
Some(("cloned", recv2, [], _, _)) => {
|
||||
iter_overeager_cloned::check(
|
||||
cx,
|
||||
expr,
|
||||
recv,
|
||||
recv2,
|
||||
iter_overeager_cloned::Op::NeedlessMove(arg),
|
||||
false,
|
||||
);
|
||||
},
|
||||
Some(("map", _, [map_arg], _, map_call_span)) => {
|
||||
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all");
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
("and_then", [arg]) => {
|
||||
@ -4571,6 +4667,9 @@ impl Methods {
|
||||
{
|
||||
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
|
||||
},
|
||||
Some(("map", _, [map_arg], _, map_call_span)) => {
|
||||
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any");
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
@ -4764,8 +4863,14 @@ impl Methods {
|
||||
unit_hash::check(cx, expr, recv, arg);
|
||||
},
|
||||
("is_empty", []) => {
|
||||
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
|
||||
redundant_as_str::check(cx, expr, recv, as_str_span, span);
|
||||
match method_call(recv) {
|
||||
Some(("as_bytes", prev_recv, [], _, _)) => {
|
||||
needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span);
|
||||
},
|
||||
Some(("as_str", recv, [], as_str_span, _)) => {
|
||||
redundant_as_str::check(cx, expr, recv, as_str_span, span);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
is_empty::check(cx, expr, recv);
|
||||
},
|
||||
@ -4795,6 +4900,11 @@ impl Methods {
|
||||
);
|
||||
}
|
||||
},
|
||||
("len", []) => {
|
||||
if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {
|
||||
needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span);
|
||||
}
|
||||
},
|
||||
("lock", []) => {
|
||||
mut_mutex_lock::check(cx, expr, recv, span);
|
||||
},
|
||||
@ -4802,6 +4912,7 @@ impl Methods {
|
||||
if name == "map" {
|
||||
unused_enumerate_index::check(cx, expr, recv, m_arg);
|
||||
map_clone::check(cx, expr, recv, m_arg, &self.msrv);
|
||||
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span);
|
||||
match method_call(recv) {
|
||||
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
|
||||
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv);
|
||||
@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase {
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")]
|
||||
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
|
||||
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
|
@ -0,0 +1,28 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, LangItem};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::NEEDLESS_AS_BYTES;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) {
|
||||
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
|
||||
&& let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs()
|
||||
&& (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str())
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_AS_BYTES,
|
||||
span,
|
||||
"needless call to `as_bytes()`",
|
||||
format!("`{method}()` can be called directly on strings"),
|
||||
format!("{sugg}.{method}()"),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
|
||||
// Check function calls on our collection
|
||||
if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
|
||||
if args.is_empty()
|
||||
&& method_name.ident.name == sym!(collect)
|
||||
&& method_name.ident.name.as_str() == "collect"
|
||||
&& is_trait_method(self.cx, expr, sym::Iterator)
|
||||
{
|
||||
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
|
||||
|
@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
|
||||
if args.is_empty()
|
||||
&& segment.ident.name == sym!(parse)
|
||||
&& segment.ident.name.as_str() == "parse"
|
||||
&& let parse_result_ty = cx.typeck_results().expr_ty(parent)
|
||||
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
|
||||
&& let ty::Adt(_, substs) = parse_result_ty.kind()
|
||||
@ -58,7 +58,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
|
||||
"calling `.parse()` on a string without trimming the trailing newline character",
|
||||
"checking",
|
||||
))
|
||||
} else if segment.ident.name == sym!(ends_with)
|
||||
} else if segment.ident.name.as_str() == "ends_with"
|
||||
&& recv.span == expr.span
|
||||
&& let [arg] = args
|
||||
&& expr_is_string_literal_without_trailing_newline(arg)
|
||||
|
@ -1,13 +1,15 @@
|
||||
use super::utils::clone_or_copy_needed;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
|
||||
use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::query::Key;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
|
||||
@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
});
|
||||
|
||||
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
|
||||
let sugg = if !found_filtering {
|
||||
// Check if the closure is .filter_map(|x| Some(x))
|
||||
if name == "filter_map"
|
||||
&& let hir::ExprKind::Call(expr, args) = body.value.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, expr), OptionSome)
|
||||
&& arg_id.ty_def_id() == args[0].hir_id().ty_def_id()
|
||||
&& let hir::ExprKind::Path(_) = args[0].kind
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_FILTER_MAP,
|
||||
expr.span,
|
||||
format!("{name} is unnecessary"),
|
||||
"try removing the filter_map",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
if name == "filter_map" { "map" } else { "map(..).next()" }
|
||||
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
|
||||
match cx.typeck_results().expr_ty(body.value).kind() {
|
||||
@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
span_lint(
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
if name == "filter_map" {
|
||||
UNNECESSARY_FILTER_MAP
|
||||
@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
|
||||
UNNECESSARY_FIND_MAP
|
||||
},
|
||||
expr.span,
|
||||
format!("this `.{name}` can be written more simply using `.{sugg}`"),
|
||||
format!("this `.{name}` can be written more simply"),
|
||||
"try instead",
|
||||
sugg.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
|
||||
(true, true)
|
||||
},
|
||||
hir::ExprKind::MethodCall(segment, recv, [arg], _) => {
|
||||
if segment.ident.name == sym!(then_some)
|
||||
if segment.ident.name.as_str() == "then_some"
|
||||
&& cx.typeck_results().expr_ty(recv).is_bool()
|
||||
&& path_to_local_id(arg, arg_id)
|
||||
{
|
||||
|
@ -79,9 +79,9 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
|
||||
},
|
||||
ExprKind::MethodCall(path, receiver, args @ [_], _) => {
|
||||
if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) {
|
||||
if path.ident.name == sym!(max) {
|
||||
if path.ident.name.as_str() == "max" {
|
||||
fetch_const(cx, Some(receiver), args, MinMax::Max)
|
||||
} else if path.ident.name == sym!(min) {
|
||||
} else if path.ident.name.as_str() == "min" {
|
||||
fetch_const(cx, Some(receiver), args, MinMax::Min)
|
||||
} else {
|
||||
None
|
||||
|
@ -12,11 +12,13 @@ use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::Visibility;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::CRATE_DEF_ID;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -110,6 +112,21 @@ impl MissingDoc {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(parent_def_id) = cx.tcx.opt_parent(def_id.to_def_id())
|
||||
&& let DefKind::AnonConst
|
||||
| DefKind::AssocConst
|
||||
| DefKind::AssocFn
|
||||
| DefKind::Closure
|
||||
| DefKind::Const
|
||||
| DefKind::Fn
|
||||
| DefKind::InlineConst
|
||||
| DefKind::Static { .. }
|
||||
| DefKind::SyntheticCoroutineBody = cx.tcx.def_kind(parent_def_id)
|
||||
{
|
||||
// Nested item has no generated documentation, so it doesn't need to be documented.
|
||||
return;
|
||||
}
|
||||
|
||||
let has_doc = attrs
|
||||
.iter()
|
||||
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()))
|
||||
@ -184,8 +201,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ItemKind::Const(..)
|
||||
| hir::ItemKind::Enum(..)
|
||||
hir::ItemKind::Const(..) => {
|
||||
if it.ident.name == kw::Underscore {
|
||||
note_prev_span_then_ret!(self.prev_span, it.span);
|
||||
}
|
||||
},
|
||||
hir::ItemKind::Enum(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::Static(..)
|
||||
|
@ -116,7 +116,7 @@ fn should_lint<'tcx>(
|
||||
|
||||
if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) {
|
||||
has_debug_struct = true;
|
||||
} else if path.ident.name == sym!(finish_non_exhaustive)
|
||||
} else if path.ident.name.as_str() == "finish_non_exhaustive"
|
||||
&& is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct)
|
||||
{
|
||||
has_finish_non_exhaustive = true;
|
||||
|
@ -330,10 +330,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
|
||||
}
|
||||
|
||||
fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
||||
if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind
|
||||
if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind
|
||||
&& let Some(last_stmt) = loop_block.stmts.last()
|
||||
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind
|
||||
&& let ast::ExprKind::Continue(_) = inner_expr.kind
|
||||
&& let ast::ExprKind::Continue(continue_label) = inner_expr.kind
|
||||
&& compare_labels(loop_label.as_ref(), continue_label.as_ref())
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -347,7 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
|
||||
if let LetStmt {
|
||||
init: None,
|
||||
pat:
|
||||
&Pat {
|
||||
Pat {
|
||||
kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None),
|
||||
..
|
||||
},
|
||||
@ -357,7 +357,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
|
||||
&& let Some((_, Node::Stmt(local_stmt))) = parents.next()
|
||||
&& let Some((_, Node::Block(block))) = parents.next()
|
||||
{
|
||||
check(cx, local, local_stmt, block, binding_id);
|
||||
check(cx, local, local_stmt, block, *binding_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::{DefId, DefIdMap};
|
||||
use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, BoundPolarity, WherePredicate};
|
||||
use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicate};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{ClauseKind, PredicatePolarity};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
@ -183,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
||||
.iter()
|
||||
.zip(fn_sig.inputs())
|
||||
.zip(body.params)
|
||||
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
|
||||
.filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
|
||||
.peekable();
|
||||
if it.peek().is_none() {
|
||||
return;
|
||||
|
@ -159,8 +159,12 @@ impl NoEffect {
|
||||
|
||||
// Remove `impl Future<Output = T>` to get `T`
|
||||
if cx.tcx.ty_is_opaque_future(ret_ty)
|
||||
&& let Some(true_ret_ty) =
|
||||
cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty)
|
||||
&& let Some(true_ret_ty) = cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.get_impl_future_output_ty(ret_ty)
|
||||
{
|
||||
ret_ty = true_ret_ty;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -18,13 +18,13 @@ declare_clippy_lint! {
|
||||
/// Rust ABI can break this at any point.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// ```rust,ignore
|
||||
/// #[no_mangle]
|
||||
/// fn example(arg_one: u32, arg_two: usize) {}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// ```rust,ignore
|
||||
/// #[no_mangle]
|
||||
/// extern "C" fn example(arg_one: u32, arg_two: usize) {}
|
||||
/// ```
|
||||
@ -40,24 +40,25 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi {
|
||||
if let ItemKind::Fn(fn_sig, _, _) = &item.kind {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app);
|
||||
let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(item.ident.span.lo()), "..", &mut app);
|
||||
for attr in attrs {
|
||||
if let Some(ident) = attr.ident()
|
||||
&& ident.name == rustc_span::sym::no_mangle
|
||||
&& fn_sig.header.abi == Abi::Rust
|
||||
&& let Some((fn_attrs, _)) = snippet.split_once("fn")
|
||||
&& let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn")
|
||||
&& !fn_attrs.contains("extern")
|
||||
{
|
||||
let sugg_span = fn_sig
|
||||
.span
|
||||
.with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len()))
|
||||
.shrink_to_lo();
|
||||
let attr_snippet = snippet(cx, attr.span, "..");
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NO_MANGLE_WITH_RUST_ABI,
|
||||
fn_sig.span,
|
||||
"`#[no_mangle]` set on a function with the default (`Rust`) ABI",
|
||||
format!("`{attr_snippet}` set on a function with the default (`Rust`) ABI"),
|
||||
|diag| {
|
||||
diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app)
|
||||
.span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app);
|
||||
|
@ -43,12 +43,12 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
|
||||
match &expr.kind {
|
||||
ExprKind::MethodCall(path, func, [param], _) => {
|
||||
if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def()
|
||||
&& ((path.ident.name == sym!(mode)
|
||||
&& ((path.ident.name.as_str() == "mode"
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::FsOpenOptions | sym::DirBuilder)
|
||||
))
|
||||
|| (path.ident.name == sym!(set_mode)
|
||||
|| (path.ident.name.as_str() == "set_mode"
|
||||
&& cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
|
||||
&& let ExprKind::Lit(_) = param.kind
|
||||
&& param.span.eq_ctxt(expr.span)
|
||||
|
@ -107,7 +107,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
}
|
||||
|
||||
if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind
|
||||
&& sym!(signum) == method_name.ident.name
|
||||
&& method_name.ident.name.as_str() == "signum"
|
||||
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
|
||||
// the method call)
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]);
|
||||
impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
items: impl_items,
|
||||
..
|
||||
}) = item.kind
|
||||
|
@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>(
|
||||
if path_segment.ident.name == sym::offset {
|
||||
return Some((arg_0, arg_1, Method::Offset));
|
||||
}
|
||||
if path_segment.ident.name == sym!(wrapping_offset) {
|
||||
if path_segment.ident.name.as_str() == "wrapping_offset" {
|
||||
return Some((arg_0, arg_1, Method::WrappingOffset));
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ enum IfBlockType<'hir> {
|
||||
|
||||
fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> {
|
||||
if let Block {
|
||||
stmts: &[],
|
||||
stmts: [],
|
||||
expr: Some(els),
|
||||
..
|
||||
} = block
|
||||
@ -163,8 +163,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
|
||||
is_type_diagnostic_item(cx, caller_ty, smbl)
|
||||
&& expr_return_none_or_err(smbl, cx, if_then, caller, None)
|
||||
&& match smbl {
|
||||
sym::Option => call_sym == sym!(is_none),
|
||||
sym::Result => call_sym == sym!(is_err),
|
||||
sym::Option => call_sym.as_str() == "is_none",
|
||||
sym::Result => call_sym.as_str() == "is_err",
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
|
@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef {
|
||||
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
|
||||
if let TyKind::Ref(_, ref mut_ty) = ty.kind
|
||||
&& mut_ty.mutbl == Mutability::Not
|
||||
&& let TyKind::Path(ref qpath) = &mut_ty.ty.kind
|
||||
&& let TyKind::Path(qpath) = &mut_ty.ty.kind
|
||||
&& let last = last_path_segment(qpath)
|
||||
&& let Some(def_id) = last.res.opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Option, def_id)
|
||||
|
@ -347,7 +347,7 @@ fn check_final_expr<'tcx>(
|
||||
let peeled_drop_expr = expr.peel_drop_temps();
|
||||
match &peeled_drop_expr.kind {
|
||||
// simple return is always "bad"
|
||||
ExprKind::Ret(ref inner) => {
|
||||
ExprKind::Ret(inner) => {
|
||||
// check if expr return nothing
|
||||
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
|
||||
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
|
||||
|
@ -101,17 +101,21 @@ impl SemicolonBlock {
|
||||
);
|
||||
}
|
||||
|
||||
fn semicolon_outside_block(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
block: &Block<'_>,
|
||||
tail_stmt_expr: &Expr<'_>,
|
||||
semi_span: Span,
|
||||
) {
|
||||
fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) {
|
||||
let insert_span = block.span.with_lo(block.span.hi());
|
||||
// account for macro calls
|
||||
let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span);
|
||||
let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi());
|
||||
|
||||
// For macro call semicolon statements (`mac!();`), the statement's span does not actually
|
||||
// include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon
|
||||
// based on a source snippet.
|
||||
// (Does not use `stmt_span` as that requires `.from_expansion()` to return true,
|
||||
// which is not the case for e.g. `line!();` and `asm!();`)
|
||||
let Some(remove_span) = cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) {
|
||||
return;
|
||||
@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock {
|
||||
};
|
||||
let &Stmt {
|
||||
kind: StmtKind::Semi(expr),
|
||||
span,
|
||||
..
|
||||
} = stmt
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.semicolon_outside_block(cx, block, expr, span);
|
||||
self.semicolon_outside_block(cx, block, expr);
|
||||
},
|
||||
StmtKind::Semi(Expr {
|
||||
kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),
|
||||
|
@ -26,7 +26,7 @@ declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]);
|
||||
impl<'tcx> LateLintPass<'tcx> for SerdeApi {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
of_trait: Some(trait_ref),
|
||||
items,
|
||||
..
|
||||
}) = item.kind
|
||||
|
@ -174,7 +174,7 @@ impl SingleComponentPathImports {
|
||||
}
|
||||
|
||||
match &item.kind {
|
||||
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
|
||||
ItemKind::Mod(_, ModKind::Loaded(items, ..)) => {
|
||||
self.check_mod(items);
|
||||
},
|
||||
ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
|
||||
|
@ -235,7 +235,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
|
||||
if self.initialization_found
|
||||
&& let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind
|
||||
&& path_to_local_id(self_arg, self.vec_alloc.local_id)
|
||||
&& path.ident.name == sym!(extend)
|
||||
&& path.ident.name.as_str() == "extend"
|
||||
&& self.is_repeat_take(extend_arg)
|
||||
{
|
||||
self.slow_expression = Some(InitializationType::Extend(expr));
|
||||
@ -247,7 +247,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
|
||||
if self.initialization_found
|
||||
&& let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind
|
||||
&& path_to_local_id(self_arg, self.vec_alloc.local_id)
|
||||
&& path.ident.name == sym!(resize)
|
||||
&& path.ident.name.as_str() == "resize"
|
||||
// Check that is filled with 0
|
||||
&& is_integer_literal(fill_arg, 0)
|
||||
{
|
||||
@ -269,7 +269,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
|
||||
/// Returns `true` if give expression is `repeat(0).take(...)`
|
||||
fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind
|
||||
&& take_path.ident.name == sym!(take)
|
||||
&& take_path.ident.name.as_str() == "take"
|
||||
// Check that take is applied to `repeat(0)`
|
||||
&& self.is_repeat_zero(recv)
|
||||
{
|
||||
|
@ -286,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
|
||||
|
||||
if !in_external_macro(cx.sess(), e.span)
|
||||
&& let ExprKind::MethodCall(path, receiver, ..) = &e.kind
|
||||
&& path.ident.name == sym!(as_bytes)
|
||||
&& path.ident.name.as_str() == "as_bytes"
|
||||
&& let ExprKind::Lit(lit) = &receiver.kind
|
||||
&& let LitKind::Str(lit_content, _) = &lit.node
|
||||
{
|
||||
@ -332,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
|
||||
}
|
||||
|
||||
if let ExprKind::MethodCall(path, recv, [], _) = &e.kind
|
||||
&& path.ident.name == sym!(into_bytes)
|
||||
&& path.ident.name.as_str() == "into_bytes"
|
||||
&& let ExprKind::MethodCall(path, recv, [], _) = &recv.kind
|
||||
&& matches!(path.ident.name.as_str(), "to_owned" | "to_string")
|
||||
&& let ExprKind::Lit(lit) = &recv.kind
|
||||
@ -493,7 +493,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
|
||||
let tyckres = cx.typeck_results();
|
||||
if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind
|
||||
&& path.ident.name == sym!(split_whitespace)
|
||||
&& path.ident.name.as_str() == "split_whitespace"
|
||||
&& let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id)
|
||||
&& let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind
|
||||
|
@ -334,7 +334,7 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
|
||||
let mut output = expr;
|
||||
loop {
|
||||
output = match &output.kind {
|
||||
ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner,
|
||||
ExprKind::Paren(inner) | ExprKind::Unary(_, inner) => inner,
|
||||
_ => {
|
||||
return output;
|
||||
},
|
||||
@ -348,13 +348,13 @@ fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
|
||||
|
||||
fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
|
||||
match kind {
|
||||
ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
|
||||
ExprKind::Paren(ref e) => if_statement_binops(&e.kind),
|
||||
ExprKind::Block(ref block, _) => {
|
||||
ExprKind::If(condition, _, _) => chained_binops(&condition.kind),
|
||||
ExprKind::Paren(e) => if_statement_binops(&e.kind),
|
||||
ExprKind::Block(block, _) => {
|
||||
let mut output = None;
|
||||
for stmt in &block.stmts {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
|
||||
match &stmt.kind {
|
||||
StmtKind::Expr(e) | StmtKind::Semi(e) => {
|
||||
output = append_opt_vecs(output, if_statement_binops(&e.kind));
|
||||
},
|
||||
_ => {},
|
||||
@ -383,7 +383,7 @@ fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) ->
|
||||
fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
|
||||
match kind {
|
||||
ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer),
|
||||
ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind),
|
||||
ExprKind::Paren(e) | ExprKind::Unary(_, e) => chained_binops(&e.kind),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -391,16 +391,14 @@ fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
|
||||
fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> {
|
||||
match (&left_outer.kind, &right_outer.kind) {
|
||||
(
|
||||
ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e),
|
||||
ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e),
|
||||
ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e),
|
||||
ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e),
|
||||
) => chained_binops_helper(left_e, right_e),
|
||||
(ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer),
|
||||
(_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => {
|
||||
chained_binops_helper(left_outer, right_e)
|
||||
},
|
||||
(ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), _) => chained_binops_helper(left_e, right_outer),
|
||||
(_, ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e)) => chained_binops_helper(left_outer, right_e),
|
||||
(
|
||||
ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right),
|
||||
ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right),
|
||||
ExprKind::Binary(Spanned { node: left_op, .. }, left_left, left_right),
|
||||
ExprKind::Binary(Spanned { node: right_op, .. }, right_left, right_right),
|
||||
) => match (
|
||||
chained_binops_helper(left_left, left_right),
|
||||
chained_binops_helper(right_left, right_right),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user