Merge commit 'f712eb5cdccd121d0569af12f20e6a0fabe4364d' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-11-07 22:31:20 +01:00
parent 4847c40c8b
commit 6ced8c33c0
248 changed files with 5023 additions and 900 deletions

View file

@ -1,17 +1,8 @@
name: Clippy Dev Test name: Clippy Dev Test
on: on:
push: merge_group:
branches:
- auto
- try
pull_request: pull_request:
# Only run on paths, that get checked by the clippy_dev tool
paths:
- 'CHANGELOG.md'
- 'README.md'
- '**.stderr'
- '**.rs'
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
@ -47,28 +38,21 @@ jobs:
cargo check cargo check
git reset --hard HEAD git reset --hard HEAD
# These jobs doesn't actually test anything, but they're only used to tell conclusion_dev:
# bors the build completed, as there is no practical way to detect when a needs: [ clippy_dev ]
# workflow is successful listening to webhooks only. # 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
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
# when the workflow is canceled manually.
end-success: #
name: bors dev test finished # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
if: github.event.pusher.name == 'bors' && success() if: ${{ !cancelled() }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [clippy_dev]
steps: steps:
- name: Mark the job as successful # Manually check the status of all dependencies. `if: failure()` does not work.
run: exit 0 - name: Conclusion
run: |
end-failure: # Print the dependent jobs to see them in the CI log
name: bors dev test finished jq -C <<< '${{ toJson(needs) }}'
if: github.event.pusher.name == 'bors' && (failure() || cancelled()) # Check if all jobs that we depend on (in the needs array) were successful.
runs-on: ubuntu-latest jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
needs: [clippy_dev]
steps:
- name: Mark the job as a failure
run: exit 1

View file

@ -1,10 +1,7 @@
name: Clippy Test (bors) name: Clippy Test (merge queue)
on: on:
push: merge_group:
branches:
- auto
- try
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
@ -13,11 +10,6 @@ env:
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings 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: defaults:
run: run:
shell: bash shell: bash
@ -218,28 +210,21 @@ jobs:
env: env:
INTEGRATION: ${{ matrix.integration }} INTEGRATION: ${{ matrix.integration }}
# These jobs doesn't actually test anything, but they're only used to tell conclusion:
# bors the build completed, as there is no practical way to detect when a needs: [ changelog, base, metadata_collection, integration_build, integration ]
# workflow is successful listening to webhooks only. # 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
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
# when the workflow is canceled manually.
end-success: #
name: bors test finished # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
if: github.event.pusher.name == 'bors' && success() if: ${{ !cancelled() }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [changelog, base, metadata_collection, integration_build, integration]
steps: steps:
- name: Mark the job as successful # Manually check the status of all dependencies. `if: failure()` does not work.
run: exit 0 - name: Conclusion
run: |
end-failure: # Print the dependent jobs to see them in the CI log
name: bors test finished jq -C <<< '${{ toJson(needs) }}'
if: github.event.pusher.name == 'bors' && (failure() || cancelled()) # Check if all jobs that we depend on (in the needs array) were successful.
runs-on: ubuntu-latest jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
needs: [changelog, base, metadata_collection, integration_build, integration]
steps:
- name: Mark the job as a failure
run: exit 1

View file

@ -1,24 +1,7 @@
name: Clippy Test name: Clippy Test
on: 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: pull_request:
# Don't run Clippy tests, when only text files were modified
paths-ignore:
- 'COPYRIGHT'
- 'LICENSE-*'
- '**.md'
- '**.txt'
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
@ -35,7 +18,7 @@ concurrency:
jobs: jobs:
base: 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 runs-on: ubuntu-latest
steps: steps:
@ -73,3 +56,24 @@ jobs:
run: .github/driver.sh run: .github/driver.sh
env: env:
OS: ${{ runner.os }} 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) }}'

View file

@ -52,7 +52,7 @@ jobs:
run: cargo generate-lockfile run: cargo generate-lockfile
- name: Cache - name: Cache
uses: Swatinem/rust-cache@v2.7.0 uses: Swatinem/rust-cache@v2
with: with:
save-if: ${{ github.ref == 'refs/heads/master' }} save-if: ${{ github.ref == 'refs/heads/master' }}

View file

@ -1,13 +1,8 @@
name: Remark name: Remark
on: on:
push: merge_group:
branches:
- auto
- try
pull_request: pull_request:
paths:
- '**.md'
jobs: jobs:
remark: remark:
@ -45,28 +40,21 @@ jobs:
- name: Build mdbook - name: Build mdbook
run: mdbook build book run: mdbook build book
# These jobs doesn't actually test anything, but they're only used to tell conclusion_remark:
# bors the build completed, as there is no practical way to detect when a needs: [ remark ]
# workflow is successful listening to webhooks only. # 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
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
# when the workflow is canceled manually.
end-success: #
name: bors remark test finished # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
if: github.event.pusher.name == 'bors' && success() if: ${{ !cancelled() }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [remark]
steps: steps:
- name: Mark the job as successful # Manually check the status of all dependencies. `if: failure()` does not work.
run: exit 0 - name: Conclusion
run: |
end-failure: # Print the dependent jobs to see them in the CI log
name: bors remark test finished jq -C <<< '${{ toJson(needs) }}'
if: github.event.pusher.name == 'bors' && (failure() || cancelled()) # Check if all jobs that we depend on (in the needs array) were successful.
runs-on: ubuntu-latest jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
needs: [remark]
steps:
- name: Mark the job as a failure
run: exit 1

View file

@ -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_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 [`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 [`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 [`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 [`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions [`as_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_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 [`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 [`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_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_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 [`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_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_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_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_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_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 [`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 [`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 [`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_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_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`]: 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 [`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 [`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 [`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 [`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 [`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 [`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 [`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-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 [`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 [`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 [`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 [`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 [`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-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-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 [`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 [`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 [`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 [`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size

View file

@ -21,7 +21,6 @@ All contributors are expected to follow the [Rust Code of Conduct].
- [Rust Analyzer](#rust-analyzer) - [Rust Analyzer](#rust-analyzer)
- [How Clippy works](#how-clippy-works) - [How Clippy works](#how-clippy-works)
- [Issue and PR triage](#issue-and-pr-triage) - [Issue and PR triage](#issue-and-pr-triage)
- [Bors and Homu](#bors-and-homu)
- [Contributions](#contributions) - [Contributions](#contributions)
- [License](#license) - [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, Or rather: before the sync this should be addressed,
e.g. by removing a lint again, so it doesn't hit beta/stable. 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 [triage]: https://forge.rust-lang.org/release/triage-procedure.html
[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash [l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug [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-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high [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 [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 ## 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 "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. 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. `changelog: XYZ` entry and combines them into the changelog. This is a manual process.
Examples: Examples:

View file

@ -39,7 +39,7 @@ toml = "0.7.3"
walkdir = "2.3" walkdir = "2.3"
filetime = "0.2.9" filetime = "0.2.9"
itertools = "0.12" 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"] } rinja = { version = "0.3", default-features = false, features = ["config"] }
# UI test dependencies # UI test dependencies

View file

@ -1,11 +1,10 @@
# Clippy # 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) [![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. 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). 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. You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View file

@ -1,12 +1,11 @@
# Clippy # 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) [![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 A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code. [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 Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View file

@ -53,7 +53,6 @@ book](../lints.md).
> - IDE setup > - IDE setup
> - High level overview on how Clippy works > - High level overview on how Clippy works
> - Triage procedure > - Triage procedure
> - Bors and Homu
[ast]: https://rustc-dev-guide.rust-lang.org/syntax-intro.html [ast]: https://rustc-dev-guide.rust-lang.org/syntax-intro.html
[hir]: https://rustc-dev-guide.rust-lang.org/hir.html [hir]: https://rustc-dev-guide.rust-lang.org/hir.html

View file

@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint {
// Check our expr is calling a method // Check our expr is calling a method
if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind
// Check the name of this method is `some_method` // 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. // Optionally, check the type of the self argument.
// - See "Checking for a specific type" // - See "Checking for a specific type"
{ {
@ -167,7 +167,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
// Check if item is a method/function // Check if item is a method/function
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// Check the method is named `some_method` // 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` // We can also check it has a parameter `self`
&& signature.decl.implicit_self.has_implicit_self() && signature.decl.implicit_self.has_implicit_self()
// We can go further and even check if its return type is `String` // We can go further and even check if its return type is `String`

View file

@ -3,8 +3,8 @@
In some scenarios we might want to check for methods when developing 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: a lint. There are two kinds of questions that we might be curious about:
- Invocation: Does an expression call a specific method? - Invocation: Does an expression call a specific method?
- Definition: Does an `impl` define a method? - Definition: Does an `impl` define a method?
## Checking if an `expr` is calling a specific 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 // Check our expr is calling a method with pattern matching
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind
// Check if the name of this method is `our_fancy_method` // 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. // 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, // (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`) // 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 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. 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 ## Checking if a `impl` block implements a method
While sometimes we want to check whether a method is being called or not, other 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 // Check if item is a method/function
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// Check the method is named `our_fancy_method` // 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` // We can also check it has a parameter `self`
&& signature.decl.implicit_self.has_implicit_self() && signature.decl.implicit_self.has_implicit_self()
// We can go even further and even check if its return type is `String` // 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 [`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 [`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 [`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 [`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 [`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

View file

@ -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 replace the default list with `["ClipPy"]`.
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. * `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:** **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) * [`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` ## `msrv`
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` 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) * [`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_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_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) * [`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) * [`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) * [`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) * [`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` ## `stack-size-threshold`
The maximum allowed stack size for functions in bytes 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) * [`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` ## `trivial-copy-size-limit`
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
reference. By default there is no limit reference. By default there is no limit

View file

@ -1,6 +1,10 @@
use crate::ClippyConfiguration; use crate::ClippyConfiguration;
use crate::msrvs::Msrv; 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_errors::Applicability;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::edit_distance::edit_distance; use rustc_span::edit_distance::edit_distance;
@ -17,8 +21,9 @@ use std::{cmp, env, fmt, fs, io};
#[rustfmt::skip] #[rustfmt::skip]
const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
"MHz", "GHz", "THz",
"AccessKit", "AccessKit",
"CoreFoundation", "CoreGraphics", "CoreText", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
"DevOps", "DevOps",
"Direct2D", "Direct3D", "DirectWrite", "DirectX", "Direct2D", "Direct3D", "DirectWrite", "DirectX",
"ECMAScript", "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_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] = const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
&["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]; &["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 /// Conf with parse errors
#[derive(Default)] #[derive(Default)]
@ -102,7 +130,9 @@ pub fn sanitize_explanation(raw_docs: &str) -> String {
// Remove tags and hidden code: // Remove tags and hidden code:
let mut explanation = String::with_capacity(128); let mut explanation = String::with_capacity(128);
let mut in_code = false; 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("```") { if let Some(lang) = line.strip_prefix("```") {
let tag = lang.split_once(',').map_or(lang, |(left, _)| left); let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { 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. /// crate. For example, `pub(crate)` items.
#[lints(missing_docs_in_private_items)] #[lints(missing_docs_in_private_items)]
missing_docs_in_crate_items: bool = false, 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` /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = "current version"] #[default_text = "current version"]
#[lints( #[lints(
@ -570,6 +603,7 @@ define_Conf! {
manual_try_fold, manual_try_fold,
map_clone, map_clone,
map_unwrap_or, map_unwrap_or,
map_with_unused_argument_over_ranges,
match_like_matches_macro, match_like_matches_macro,
mem_replace_with_default, mem_replace_with_default,
missing_const_for_fn, missing_const_for_fn,
@ -608,6 +642,9 @@ define_Conf! {
/// The maximum number of single char bindings a scope may have /// The maximum number of single char bindings a scope may have
#[lints(many_single_char_names)] #[lints(many_single_char_names)]
single_char_binding_names_threshold: u64 = 4, 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 /// The maximum allowed stack size for functions in bytes
#[lints(large_stack_frames)] #[lints(large_stack_frames)]
stack_size_threshold: u64 = 512_000, stack_size_threshold: u64 = 512_000,
@ -637,6 +674,9 @@ define_Conf! {
/// The maximum number of lines a function or method can have /// The maximum number of lines a function or method can have
#[lints(too_many_lines)] #[lints(too_many_lines)]
too_many_lines_threshold: u64 = 100, 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 /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
/// reference. By default there is no limit /// reference. By default there is no limit
#[default_text = "target_pointer_width * 2"] #[default_text = "target_pointer_width * 2"]

View file

@ -20,6 +20,7 @@ extern crate rustc_driver;
extern crate rustc_errors; extern crate rustc_errors;
extern crate rustc_session; extern crate rustc_session;
extern crate rustc_span; extern crate rustc_span;
extern crate smallvec;
mod conf; mod conf;
mod metadata; mod metadata;

View file

@ -3,6 +3,7 @@ use rustc_attr::parse_version;
use rustc_session::{RustcVersion, Session}; use rustc_session::{RustcVersion, Session};
use rustc_span::{Symbol, sym}; use rustc_span::{Symbol, sym};
use serde::Deserialize; use serde::Deserialize;
use smallvec::{SmallVec, smallvec};
use std::fmt; use std::fmt;
macro_rules! msrv_aliases { macro_rules! msrv_aliases {
@ -18,7 +19,7 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items // names may refer to stabilized feature flags or library items
msrv_aliases! { msrv_aliases! {
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY } 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,81,0 { LINT_REASONS_STABILIZATION }
1,80,0 { BOX_INTO_ITER} 1,80,0 { BOX_INTO_ITER}
1,77,0 { C_STR_LITERALS } 1,77,0 { C_STR_LITERALS }
@ -54,7 +55,7 @@ msrv_aliases! {
1,33,0 { UNDERSCORE_IMPORTS } 1,33,0 { UNDERSCORE_IMPORTS }
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,29,0 { ITER_FLATTEN } 1,29,0 { ITER_FLATTEN }
1,28,0 { FROM_BOOL } 1,28,0 { FROM_BOOL, REPEAT_WITH }
1,27,0 { ITERATOR_TRY_FOLD } 1,27,0 { ITERATOR_TRY_FOLD }
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
1,24,0 { IS_ASCII_DIGIT } 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]` /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Msrv { pub struct Msrv {
stack: Vec<RustcVersion>, stack: SmallVec<[RustcVersion; 2]>,
} }
impl fmt::Display for Msrv { impl fmt::Display for Msrv {
@ -87,14 +88,14 @@ impl<'de> Deserialize<'de> for Msrv {
{ {
let v = String::deserialize(deserializer)?; let v = String::deserialize(deserializer)?;
parse_version(Symbol::intern(&v)) 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")) .ok_or_else(|| serde::de::Error::custom("not a valid Rust version"))
} }
} }
impl Msrv { impl Msrv {
pub fn empty() -> Msrv { pub fn empty() -> Msrv {
Msrv { stack: Vec::new() } Msrv { stack: SmallVec::new() }
} }
pub fn read_cargo(&mut self, sess: &Session) { pub fn read_cargo(&mut self, sess: &Session) {
@ -103,7 +104,7 @@ impl Msrv {
.and_then(|v| parse_version(Symbol::intern(&v))); .and_then(|v| parse_version(Symbol::intern(&v)));
match (self.current(), cargo_msrv) { 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)) => { (Some(clippy_msrv), Some(cargo_msrv)) => {
if clippy_msrv != cargo_msrv { if clippy_msrv != cargo_msrv {
sess.dcx().warn(format!( sess.dcx().warn(format!(

View file

@ -1,5 +1,6 @@
use serde::de::{self, Deserializer, Visitor}; use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize, ser}; use serde::{Deserialize, Serialize, ser};
use std::collections::HashMap;
use std::fmt; use std::fmt;
#[derive(Debug, Deserialize)] #[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 // these impls are never actually called but are used by the various config options that default to
// empty lists // empty lists
macro_rules! unimplemented_serialize { macro_rules! unimplemented_serialize {

View file

@ -20,8 +20,14 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
loop { loop {
let index_time = mtime("util/gh-pages/index.html"); 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())) Command::new(env::var("CARGO").unwrap_or("cargo".into()))
.arg("collect-metadata") .arg("collect-metadata")
.spawn() .spawn()

View file

@ -762,13 +762,19 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
Literal{..}(desc) Literal{..}(desc)
); );
if let Some(LintDeclSearchResult { if let Some(end) = iter.find_map(|t| {
token_kind: TokenKind::CloseBrace, if let LintDeclSearchResult {
range, token_kind: TokenKind::CloseBrace,
.. range,
}) = iter.next() ..
{ } = t
lints.push(Lint::new(name, group, desc, module, start..range.end)); {
Some(range.end)
} else {
None
}
}) {
lints.push(Lint::new(name, group, desc, module, start..end));
} }
} }
} }

View file

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

View file

@ -14,7 +14,7 @@ mod utils;
use clippy_config::Conf; use clippy_config::Conf;
use clippy_config::msrvs::{self, Msrv}; 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_hir::{ImplItem, Item, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;

View file

@ -1,10 +1,9 @@
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
use super::USELESS_ATTRIBUTE; 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::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, first_line_of_span}; 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_errors::Applicability;
use rustc_ast::{Item, ItemKind, Attribute};
use rustc_lint::{EarlyContext, LintContext}; use rustc_lint::{EarlyContext, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_span::sym; use rustc_span::sym;

View file

@ -15,7 +15,7 @@ declare_clippy_lint! {
/// `MutexGuard`. /// `MutexGuard`.
/// ///
/// ### Why is this bad? /// ### 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 /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are
/// not designed to operate in an async context across await points. /// not designed to operate in an async context across await points.
/// ///

View file

@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
&& let Some(suggestion) = simplify_not(cx, msrv, inner) && let Some(suggestion) = simplify_not(cx, msrv, inner)
&& cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow && 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( span_lint_and_sugg(
cx, cx,
NONMINIMAL_BOOL, NONMINIMAL_BOOL,

View file

@ -4,7 +4,7 @@ use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed}; use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{ExprKind, UnOp}; use rustc_hir::{BorrowKind, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::Mutability; use rustc_middle::mir::Mutability;
use rustc_middle::ty; use rustc_middle::ty;
@ -49,7 +49,7 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { 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 && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..)) && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
&& !e.span.from_expansion() && !e.span.from_expansion()

View file

@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
); );
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind { } 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 Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.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. // There probably is no obvious reason to do this, just to be consistent with `as` cases.

View file

@ -268,8 +268,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx
if !snippet if !snippet
.split("->") .split("->")
.skip(1) .skip(1)
.map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) .any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from)))
.any(|a| a)
{ {
return ControlFlow::Break(()); return ControlFlow::Break(());
} }

View file

@ -232,7 +232,7 @@ fn get_types_from_cast<'a>(
// or `to_type::MAX as from_type` // or `to_type::MAX as from_type`
let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind
// to_type::max_value(), from_type // 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) && let Some(from_sym) = int_ty_to_sym(from_type_path)
{ {
Some((limit, from_sym)) Some((limit, from_sym))
@ -245,7 +245,7 @@ fn get_types_from_cast<'a>(
if let ExprKind::Call(from_func, [limit]) = &expr.kind if let ExprKind::Call(from_func, [limit]) = &expr.kind
// `from_type::from, to_type::max_value()` // `from_type::from, to_type::max_value()`
// `from_type::from` // `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") && let Some(from_sym) = get_implementing_type(path, INTS, "from")
{ {
Some((limit, from_sym)) Some((limit, from_sym))

View file

@ -30,7 +30,7 @@ declare_clippy_lint! {
#[clippy::version = "1.35.0"] #[clippy::version = "1.35.0"]
pub COGNITIVE_COMPLEXITY, pub COGNITIVE_COMPLEXITY,
nursery, nursery,
"functions that should be split up into multiple functions" "functions that should be split up into multiple functions",
@eval_always = true @eval_always = true
} }

View file

@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
.iter() .iter()
.filter(|&&(_, name)| !name.as_str().starts_with('_')) .filter(|&&(_, name)| !name.as_str().starts_with('_'))
.any(|&(_, name)| { .any(|&(_, name)| {
let mut walker = ContainsName { let mut walker = ContainsName { name, cx };
name,
result: false,
cx,
};
// Scan block // Scan block
block let mut res = block
.stmts .stmts
.iter() .iter()
.filter(|stmt| !ignore_span.overlaps(stmt.span)) .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 { 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()
}) })
}) })
} }

View file

@ -37,7 +37,7 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
impl<'tcx> LateLintPass<'tcx> for CopyIterator { impl<'tcx> LateLintPass<'tcx> for CopyIterator {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
.. ..
}) = item.kind }) = item.kind
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()

View file

@ -5,7 +5,6 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::Span; use rustc_span::Span;
declare_lint_pass! { declare_lint_pass! {
/// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). /// 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. /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran.

View file

@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint {
(@ (@
$(#[doc = $lit:literal])* $(#[doc = $lit:literal])*
pub $lint_name:ident, pub $lint_name:ident,
$category:ident, $level:ident,
$lintcategory:expr, $lintcategory:expr,
$desc:literal, $desc:literal,
$version_expr:expr, $version_expr:expr,
@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint {
$(#[doc = $lit])* $(#[doc = $lit])*
#[clippy::version = $version_lit] #[clippy::version = $version_lit]
pub clippy::$lint_name, pub clippy::$lint_name,
$category, $level,
$desc, $desc,
report_in_external_macro:true report_in_external_macro:true
$(, @eval_always = $eval_always)? $(, @eval_always = $eval_always)?
@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident, pub $lint_name:ident,
restriction, restriction,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, 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, pub $lint_name:ident,
style, style,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Style, $desc, 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, pub $lint_name:ident,
correctness, correctness,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, 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, pub $lint_name:ident,
perf, perf,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Perf, $desc, 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, pub $lint_name:ident,
complexity, complexity,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, 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, pub $lint_name:ident,
suspicious, suspicious,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, 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, pub $lint_name:ident,
nursery, nursery,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, 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, pub $lint_name:ident,
pedantic, pedantic,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, 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, pub $lint_name:ident,
cargo, cargo,
$desc:literal $desc:literal
$(@eval_always = $eval_always: literal)? $(, @eval_always = $eval_always: literal)?
) => { ) => {
declare_clippy_lint! {@ declare_clippy_lint! {@
$(#[doc = $lit])* $(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, pub $lint_name, Allow, crate::LintCategory::Cargo, $desc,
Some($version), $version $(, $eval_always)? Some($version), $version
$(, $eval_always)?
} }
}; };

View file

@ -28,12 +28,15 @@ pub static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
#[cfg(feature = "internal")] #[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, crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO, crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::absolute_paths::ABSOLUTE_PATHS_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_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::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
crate::as_conversions::AS_CONVERSIONS_INFO, crate::as_conversions::AS_CONVERSIONS_INFO,
crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_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_SPLIT_ONCE_INFO,
crate::methods::MANUAL_STR_REPEAT_INFO, crate::methods::MANUAL_STR_REPEAT_INFO,
crate::methods::MANUAL_TRY_FOLD_INFO, crate::methods::MANUAL_TRY_FOLD_INFO,
crate::methods::MAP_ALL_ANY_IDENTITY_INFO,
crate::methods::MAP_CLONE_INFO, crate::methods::MAP_CLONE_INFO,
crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
crate::methods::MAP_ERR_IGNORE_INFO, crate::methods::MAP_ERR_IGNORE_INFO,
crate::methods::MAP_FLATTEN_INFO, crate::methods::MAP_FLATTEN_INFO,
crate::methods::MAP_IDENTITY_INFO, crate::methods::MAP_IDENTITY_INFO,
crate::methods::MAP_UNWRAP_OR_INFO, crate::methods::MAP_UNWRAP_OR_INFO,
crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO,
crate::methods::MUT_MUTEX_LOCK_INFO, crate::methods::MUT_MUTEX_LOCK_INFO,
crate::methods::NAIVE_BYTECOUNT_INFO, crate::methods::NAIVE_BYTECOUNT_INFO,
crate::methods::NEEDLESS_AS_BYTES_INFO,
crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO,
crate::methods::NEEDLESS_COLLECT_INFO, crate::methods::NEEDLESS_COLLECT_INFO,
crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,

View file

@ -187,7 +187,7 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex
impl<'tcx> LateLintPass<'tcx> for DerivableImpls { impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
items: [child], items: [child],
self_ty, self_ty,
.. ..

View file

@ -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::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::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; 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 { impl<'tcx> LateLintPass<'tcx> for Derive {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
.. ..
}) = item.kind }) = item.kind
{ {
@ -371,9 +373,8 @@ fn check_unsafe_derive_deserialize<'tcx>(
ty: Ty<'tcx>, ty: Ty<'tcx>,
) { ) {
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; let mut visitor = UnsafeVisitor { cx };
walk_item(&mut visitor, item); walk_item(&mut visitor, item).is_break()
visitor.has_unsafe
} }
if let Some(trait_def_id) = trait_ref.trait_def_id() 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> { struct UnsafeVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
has_unsafe: bool,
} }
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
type Result = ControlFlow<()>;
type NestedFilter = nested_filter::All; type NestedFilter = nested_filter::All;
fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { fn visit_fn(
if self.has_unsafe { &mut self,
return; kind: FnKind<'tcx>,
} decl: &'tcx FnDecl<'_>,
body_id: BodyId,
_: Span,
id: LocalDefId,
) -> Self::Result {
if let Some(header) = kind.header() if let Some(header) = kind.header()
&& header.safety == Safety::Unsafe && 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<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
if self.has_unsafe {
return;
}
if let ExprKind::Block(block, _) = expr.kind { if let ExprKind::Block(block, _) = expr.kind {
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { 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 { fn nested_visit_map(&mut self) -> Self::Map {

View file

@ -2,7 +2,6 @@ use clippy_config::Conf;
use clippy_utils::create_disallowed_map; use clippy_utils::create_disallowed_map;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace; use clippy_utils::macros::macro_backtrace;
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Diag; use rustc_errors::Diag;
use rustc_hir::def_id::DefIdMap; use rustc_hir::def_id::DefIdMap;
@ -14,6 +13,8 @@ use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::{ExpnId, MacroKind, Span}; use rustc_span::{ExpnId, MacroKind, Span};
use crate::utils::attr_collector::AttrStorage;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Denies the configured macros in clippy.toml /// 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. // Track the most recently seen node that can have a `derive` attribute.
// Needed to use the correct lint level. // Needed to use the correct lint level.
derive_src: Option<OwnerId>, 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 { impl DisallowedMacros {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
Self { Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros), disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
seen: FxHashSet::default(), seen: FxHashSet::default(),
derive_src: None, derive_src: None,
earlies,
} }
} }
@ -114,6 +120,15 @@ impl DisallowedMacros {
impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
impl LateLintPass<'_> for DisallowedMacros { 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<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span, None); self.check(cx, expr.span, None);
// `$t + $t` can have the context of $t, check also the span of the binary operator // `$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) { fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span, None); self.check(cx, path.span, None);
} }
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
self.check(cx, attr.span, self.derive_src);
}
} }

View file

@ -80,7 +80,7 @@ impl EarlyLintPass for DisallowedScriptIdents {
let mut symbols: Vec<_> = symbols.iter().collect(); let mut symbols: Vec<_> = symbols.iter().collect();
symbols.sort_unstable_by_key(|k| k.1); 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 // Note: `symbol.as_str()` is an expensive operation, thus should not be called
// more than once for a single symbol. // more than once for a single symbol.
let symbol_str = symbol.as_str(); let symbol_str = symbol.as_str();

View file

@ -427,11 +427,11 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### 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? /// ### Why is this bad?
/// Documentation will show the first paragraph of the doscstring in the summary page of a /// Documentation will show the first paragraph of the docstring in the summary page of a
/// module, so having a nice, short summary in the first paragraph is part of writing good docs. /// module. Having a nice, short summary in the first paragraph is part of writing good docs.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -453,7 +453,7 @@ declare_clippy_lint! {
#[clippy::version = "1.82.0"] #[clippy::version = "1.82.0"]
pub TOO_LONG_FIRST_DOC_PARAGRAPH, pub TOO_LONG_FIRST_DOC_PARAGRAPH,
nursery, nursery,
"ensure that the first line of a documentation paragraph isn't too long" "ensure the first documentation paragraph is short"
} }
declare_clippy_lint! { declare_clippy_lint! {

View file

@ -36,7 +36,7 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
impl LateLintPass<'_> for EmptyDrop { impl LateLintPass<'_> for EmptyDrop {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
items: [child], items: [child],
.. ..
}) = item.kind }) = item.kind

View file

@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ use rustc_middle::ty::{
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults,
TypeckResults,
}; };
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym; 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. // 'cuz currently nothing changes after deleting this check.
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) 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( match cx
cx.param_env, .tcx
Binder::bind_with_vars(callee_ty_adjusted, List::empty()), .infer_ctxt()
ty::PredicatePolarity::Positive, .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. // Mutable closure is used after current expr; we cannot consume it.
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {

View file

@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
// match call to write_fmt // match call to write_fmt
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind) && 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 && 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) && let Some(def_id) = path_def_id(cx, write_recv_path)
{ {
// match calls to std::io::stdout() / std::io::stderr () // match calls to std::io::stdout() / std::io::stderr ()

View file

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use clippy_config::Conf; use clippy_config::Conf;
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then; 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` /// 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> { struct SelfFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
/// Occurrences of `Self` /// Occurrences of `Self`
upper: Vec<Span>, upper: Vec<Span>,
/// Occurrences of `self` /// Occurrences of `self`
lower: Vec<Span>, 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> { impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> {
type Result = ControlFlow<()>;
type NestedFilter = OnlyBodies; type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map { fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir() 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 { for segment in path.segments {
match segment.ident.name { match segment.ident.name {
kw::SelfLower => self.lower.push(segment.ident.span), kw::SelfLower => self.lower.push(segment.ident.span),
@ -141,17 +144,19 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> {
_ => continue, _ => 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 { if name == sym::val {
self.invalid = true; ControlFlow::Break(())
} else {
ControlFlow::Continue(())
} }
} }
} }
@ -209,11 +214,9 @@ fn convert_to_from(
cx, cx,
upper: Vec::new(), upper: Vec::new(),
lower: 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; return None;
} }

View file

@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Call(box_from_raw, [arg]) = expr.kind if let ExprKind::Call(box_from_raw, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.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 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 arg_kind = cx.typeck_results().expr_ty(arg).kind()
&& let ty::RawPtr(ty, _) = arg_kind && let ty::RawPtr(ty, _) = arg_kind

View file

@ -441,7 +441,7 @@ declare_clippy_lint! {
/// fn bar(&self) -> Option<&String> { None } /// fn bar(&self) -> Option<&String> { None }
/// # } /// # }
/// ``` /// ```
#[clippy::version = "1.82.0"] #[clippy::version = "1.83.0"]
pub REF_OPTION, pub REF_OPTION,
pedantic, pedantic,
"function signature uses `&Option<T>` instead of `Option<&T>`" "function signature uses `&Option<T>` instead of `Option<&T>`"

View file

@ -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)>) { 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() if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind()
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option) && is_type_diagnostic_item(cx, *opt_ty, sym::Option)
&& let ty::Adt(_, opt_gen) = opt_ty.kind() && let ty::Adt(_, opt_gen_args) = opt_ty.kind()
&& let [gen] = opt_gen.as_slice() && let [gen_arg] = opt_gen_args.as_slice()
&& let GenericArgKind::Type(gen_ty) = gen.unpack() && let GenericArgKind::Type(gen_ty) = gen_arg.unpack()
&& !gen_ty.is_ref() && !gen_ty.is_ref()
// Need to gen the original spans, so first parsing mid, and hir parsing afterward // Need to gen the original spans, so first parsing mid, and hir parsing afterward
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind

View file

@ -340,7 +340,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> {
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) { if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new { if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashMap::default()".to_string()); 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( self.suggestions.insert(
e.span, e.span,
format!( format!(
@ -352,7 +352,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> {
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) { } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new { if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashSet::default()".to_string()); 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( self.suggestions.insert(
e.span, e.span,
format!( format!(

View file

@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
.and(cap); .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 { if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
let body = cx.tcx.hir().body(body); let body = cx.tcx.hir().body(body);
return is_infinite(cx, body.value); 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)); 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 let not_double_ended = cx
.tcx .tcx
.get_diagnostic_item(sym::DoubleEndedIterator) .get_diagnostic_item(sym::DoubleEndedIterator)
@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
if not_double_ended { if not_double_ended {
return is_infinite(cx, receiver); 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); let ty = cx.typeck_results().expr_ty(expr);
if matches!( if matches!(
get_type_diagnostic_name(cx, ty), get_type_diagnostic_name(cx, ty),

View file

@ -142,7 +142,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() 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| { && 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) Some(cx.tcx.hir().impl_item(item.id).expect_type().span)
} else { } else {
None None
@ -247,8 +247,8 @@ impl {self_ty_without_ref} {{
let sugg = format!( let sugg = format!(
" "
impl IntoIterator for {self_ty_snippet} {{ impl IntoIterator for {self_ty_snippet} {{
type IntoIter = {ret_ty};
type Item = {iter_ty}; type Item = {iter_ty};
type IntoIter = {ret_ty};
fn into_iter(self) -> Self::IntoIter {{ fn into_iter(self) -> Self::IntoIter {{
self.iter() self.iter()
}} }}

View file

@ -4,7 +4,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf; 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_session::impl_lint_pass;
use rustc_span::{BytePos, Pos, Span}; use rustc_span::{BytePos, Pos, Span};
@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
&& !item.span.from_expansion() && !item.span.from_expansion()
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Array(element_type, cst) = ty.kind() && 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 element_count = element_count.to_target_usize(cx.tcx)
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && 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) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size)

View file

@ -1,11 +1,12 @@
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node; 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_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::sym; use rustc_span::{Span, sym};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -51,6 +52,24 @@ impl LargeIncludeFile {
impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); 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 { impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Lit(lit) = &expr.kind 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_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_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")] self.emit_lint(cx, expr.span.source_callsite());
span_lint_and_then( }
cx, }
LARGE_INCLUDE_FILE,
expr.span.source_callsite(), fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
"attempted to include a large file", if !attr.span.from_expansion()
|diag| { // Currently, rustc limits the usage of macro at the top-level of attributes,
diag.note(format!( // so we don't need to recurse into each level.
"the configuration allows a maximum size of {} bytes", && let AttrKind::Normal(ref normal) = attr.kind
self.max_file_size && 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);
} }
} }
} }

View file

@ -629,7 +629,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
.filter_by_name_unhygienic(is_empty) .filter_by_name_unhygienic(is_empty)
.any(|item| is_is_empty(cx, item)) .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::Adt(id, _) => has_is_empty_impl(cx, id.did()),
ty::Array(..) | ty::Slice(..) | ty::Str => true, ty::Array(..) | ty::Slice(..) | ty::Str => true,
_ => false, _ => false,

View file

@ -73,6 +73,7 @@ pub mod deprecated_lints;
mod absolute_paths; mod absolute_paths;
mod almost_complete_range; mod almost_complete_range;
mod approx_const; mod approx_const;
mod arbitrary_source_item_ordering;
mod arc_with_non_send_sync; mod arc_with_non_send_sync;
mod as_conversions; mod as_conversions;
mod asm_syntax; mod asm_syntax;
@ -400,6 +401,7 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
use clippy_utils::macros::FormatArgsStorage; use clippy_utils::macros::FormatArgsStorage;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId}; use rustc_lint::{Lint, LintId};
use utils::attr_collector::{AttrCollector, AttrStorage};
/// Register all pre expansion lints /// Register all pre expansion lints
/// ///
@ -464,6 +466,7 @@ pub(crate) enum LintCategory {
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
Internal, Internal,
} }
#[allow(clippy::enum_glob_use)] #[allow(clippy::enum_glob_use)]
use LintCategory::*; 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 // all the internal lints
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
{ {
@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| { store.register_late_pass(|_| {
Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new()) 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)); 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(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); 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(|_| 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_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::InlineAsmX86AttSyntax));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); 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(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(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); 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` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View file

@ -29,11 +29,7 @@ pub(super) fn check(
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) { if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
return; return;
} }
} else if count } else if count.try_to_target_usize(cx.tcx).map_or(true, |x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) {
.try_to_target_usize(cx.tcx)
.map_or(true, |x| x > 32)
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
{
return; return;
} }
} }

View file

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
use hir::intravisit::{Visitor, walk_expr}; 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_ast::Label;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use super::INFINITE_LOOP; use super::INFINITE_LOOP;
@ -25,13 +26,7 @@ pub(super) fn check<'tcx>(
return; return;
}; };
// Or, its parent function is already returning `Never` // Or, its parent function is already returning `Never`
if matches!( if is_never_return(parent_fn_ret) {
parent_fn_ret,
FnRetTy::Return(hir::Ty {
kind: hir::TyKind::Never,
..
})
) {
return; 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>> { 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) { for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match parent_node { 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 { Node::Item(hir::Item {
kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _), 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,
}
}

View file

@ -19,10 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'
cx, cx,
ids: HirIdSet::default(), ids: HirIdSet::default(),
def_ids: DefIdMap::default(), def_ids: DefIdMap::default(),
skip: false,
}; };
var_visitor.visit_expr(cond); if var_visitor.visit_expr(cond).is_break() {
if var_visitor.skip {
return; return;
} }
let used_in_condition = &var_visitor.ids; let used_in_condition = &var_visitor.ids;
@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
ids: HirIdSet, ids: HirIdSet,
def_ids: DefIdMap<bool>, def_ids: DefIdMap<bool>,
skip: bool,
} }
impl<'tcx> VarCollectorVisitor<'_, 'tcx> { impl<'tcx> VarCollectorVisitor<'_, 'tcx> {
@ -104,11 +101,15 @@ impl<'tcx> VarCollectorVisitor<'_, 'tcx> {
} }
impl<'tcx> Visitor<'tcx> for 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 { 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 // 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), _ => walk_expr(self, ex),
} }

View file

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use super::WHILE_LET_ON_ITERATOR; use super::WHILE_LET_ON_ITERATOR;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; 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> { struct V<'a, 'b, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
iter_expr: &'b IterExpr, iter_expr: &'b IterExpr,
uses_iter: bool,
} }
impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> { impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) { type Result = ControlFlow<()>;
if self.uses_iter { fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result {
// return if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
} else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { ControlFlow::Break(())
self.uses_iter = true;
} else if let (e, true) = skip_fields_and_path(e) { } else if let (e, true) = skip_fields_and_path(e) {
if let Some(e) = 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 { } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
if is_res_used(self.cx, self.iter_expr.path, id) { if is_res_used(self.cx, self.iter_expr.path, id) {
self.uses_iter = true; ControlFlow::Break(())
} else {
ControlFlow::Continue(())
} }
} else { } else {
walk_expr(self, e); walk_expr(self, e)
} }
} }
} }
let mut v = V { let mut v = V { cx, iter_expr };
cx, v.visit_expr(container).is_break()
iter_expr,
uses_iter: false,
};
v.visit_expr(container);
v.uses_iter
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
@ -242,34 +241,38 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
iter_expr: &'b IterExpr, iter_expr: &'b IterExpr,
loop_id: HirId, loop_id: HirId,
after_loop: bool, after_loop: bool,
used_iter: bool,
} }
impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
type NestedFilter = OnlyBodies; type NestedFilter = OnlyBodies;
type Result = ControlFlow<()>;
fn nested_visit_map(&mut self) -> Self::Map { fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir() self.cx.tcx.hir()
} }
fn visit_expr(&mut self, e: &'tcx Expr<'_>) { fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result {
if self.used_iter {
return;
}
if self.after_loop { if self.after_loop {
if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { 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) { } else if let (e, true) = skip_fields_and_path(e) {
if let Some(e) = 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 { } 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 { } else {
walk_expr(self, e); walk_expr(self, e)
} }
} else if self.loop_id == e.hir_id { } else if self.loop_id == e.hir_id {
self.after_loop = true; self.after_loop = true;
ControlFlow::Continue(())
} else { } 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, iter_expr,
loop_id: loop_expr.hir_id, loop_id: loop_expr.hir_id,
after_loop: false, after_loop: false,
used_iter: false,
}; };
v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value); v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value)
v.used_iter .is_break()
} }
} }

View file

@ -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>> { 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 Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let GenericBound::Trait(poly) = bound { if let GenericBound::Trait(poly) = bound {
Some(&poly.trait_ref) Some(&poly.trait_ref)
} else { } else {
None None
} }
}) }) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
&& trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
{ {
return Some(trait_ref); return Some(trait_ref);
} }
@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de
.tcx .tcx
.opaque_captured_lifetimes(opaque_def_id) .opaque_captured_lifetimes(opaque_def_id)
.iter() .iter()
.filter(|&(lifetime, _)| match *lifetime { .filter(|&(lifetime, _)| {
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true, matches!(
_ => false, *lifetime,
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _)
)
}) })
.count(); .count();
num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes 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>> { 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, .. }), kind: ExprKind::Closure(&Closure { kind, body, .. }),
.. ..
}) = block.expr }) = block.expr

View file

@ -7,8 +7,7 @@ use rustc_ast::ast::LitKind;
use rustc_data_structures::packed::Pu128; use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::sym; 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<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
&& let BinOpKind::Mul = &bin_op.node && let BinOpKind::Mul = &bin_op.node
&& !in_external_macro(cx.sess(), expr.span) && !expr.span.from_expansion()
&& self.msrv.meets(msrvs::MANUAL_BITS) && self.msrv.meets(msrvs::MANUAL_BITS)
&& let ctxt = expr.span.ctxt() && let ctxt = expr.span.ctxt()
&& left_expr.span.ctxt() == ctxt && left_expr.span.ctxt() == ctxt

View file

@ -68,7 +68,7 @@ impl LateLintPass<'_> for ManualHashOne {
&& let Some(init) = local.init && let Some(init) = local.init
&& !init.span.from_expansion() && !init.span.from_expansion()
&& let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind && 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::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id)
&& let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.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) && let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id)
&& !finish_expr.span.from_expansion() && !finish_expr.span.from_expansion()
&& let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind && 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) && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
{ {

View file

@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
check_is_ascii(cx, macro_call.span, recv, &range, None); check_is_ascii(cx, macro_call.span, recv, &range, None);
} }
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind } 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 { && let Some(higher::Range {
start: Some(start), start: Some(start),
end: Some(end), end: Some(end),

View file

@ -148,7 +148,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
}) => Some(*b), }) => Some(*b),
ExprKind::Block( ExprKind::Block(
rustc_hir::Block { rustc_hir::Block {
stmts: &[], stmts: [],
expr: Some(exp), expr: Some(exp),
.. ..
}, },

View file

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_lang_item; use clippy_utils::ty::is_type_lang_item;
use rustc_ast::ast::LitKind; 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() if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind()
&& let ty::Str = ty.kind() && let ty::Str = ty.kind()
{ {
let mut visitor = MatchExprVisitor { cx, case_method: None }; let mut visitor = MatchExprVisitor { cx };
if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) {
visitor.visit_expr(scrutinee);
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) { 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()); 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> { struct MatchExprVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
case_method: Option<CaseMethod>,
} }
impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { type Result = ControlFlow<CaseMethod>;
match ex.kind { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result {
ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {}, if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind {
_ => walk_expr(self, ex), let result = self.case_altered(segment.ident.as_str(), receiver);
if result.is_break() {
return result;
}
} }
walk_expr(self, ex)
} }
} }
impl MatchExprVisitor<'_, '_> { 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) { if let Some(case_method) = get_case_method(segment_ident) {
let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); 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 { if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str {
self.case_method = Some(case_method); return ControlFlow::Break(case_method);
return true;
} }
} }
false ControlFlow::Continue(())
} }
} }

View file

@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use rustc_span::{Span, Symbol, sym}; use rustc_span::{Span, sym};
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::ControlFlow; 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 } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind
&& let Some(binding) = get_pat_binding(cx, recv, outer_arm) && 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>( fn check_method_calls<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
arm: &Arm<'tcx>, arm: &Arm<'tcx>,
method: Symbol, method: &str,
recv: &Expr<'_>, recv: &Expr<'_>,
args: &[Expr<'_>], args: &[Expr<'_>],
if_expr: &Expr<'_>, if_expr: &Expr<'_>,
@ -112,7 +112,7 @@ fn check_method_calls<'tcx>(
let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let ty = cx.typeck_results().expr_ty(recv).peel_refs();
let slice_like = ty.is_slice() || ty.is_array(); 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 "" // `s if s.is_empty()` becomes ""
// `arr if arr.is_empty()` becomes [] // `arr if arr.is_empty()` becomes []
@ -137,9 +137,9 @@ fn check_method_calls<'tcx>(
if needles.is_empty() { if needles.is_empty() {
sugg.insert_str(1, ".."); sugg.insert_str(1, "..");
} else if method == sym!(starts_with) { } else if method == "starts_with" {
sugg.insert_str(sugg.len() - 1, ", .."); sugg.insert_str(sugg.len() - 1, ", ..");
} else if method == sym!(ends_with) { } else if method == "ends_with" {
sugg.insert_str(1, ".., "); sugg.insert_str(1, ".., ");
} else { } else {
return; return;

View file

@ -319,10 +319,9 @@ fn found_good_method<'tcx>(
node: (&PatKind<'_>, &PatKind<'_>), node: (&PatKind<'_>, &PatKind<'_>),
) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> {
match node { match node {
( (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _))
PatKind::TupleStruct(ref path_left, patterns_left, _), if patterns_left.len() == 1 && patterns_right.len() == 1 =>
PatKind::TupleStruct(ref 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) { if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
find_good_method_for_match( find_good_method_for_match(
cx, cx,
@ -350,8 +349,8 @@ fn found_good_method<'tcx>(
None None
} }
}, },
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) (PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right))
| (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) | (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _))
if patterns.len() == 1 => if patterns.len() == 1 =>
{ {
if let PatKind::Wild = patterns[0].kind { if let PatKind::Wild = patterns[0].kind {
@ -381,14 +380,14 @@ fn found_good_method<'tcx>(
None 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 { if let PatKind::Wild = patterns[0].kind {
get_good_method(cx, arms, path_left) get_good_method(cx, arms, path_left)
} else { } else {
None 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, _ => None,
} }
} }

View file

@ -247,7 +247,10 @@ impl<'a> PatState<'a> {
let states = match self { let states = match self {
Self::Wild => return None, Self::Wild => return None,
Self::Other => { 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 { let Self::StdEnum(x) = self else {
unreachable!(); unreachable!();
}; };

View file

@ -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::TypeRelative(_, mname)) => mname.ident.name == method_name,
ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().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::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
ExprKind::Closure(&Closure { body, .. }) => { ExprKind::Closure(Closure { body, .. }) => {
let body = cx.tcx.hir().body(body); let body = cx.tcx.hir().body(*body);
let closure_expr = peel_blocks(body.value); let closure_expr = peel_blocks(body.value);
match closure_expr.kind { match closure_expr.kind {
ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => { ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => {
@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
// the latter only calls `effect` once // the latter only calls `effect` once
let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); 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 { Some(Self::IsSome {
receiver, receiver,
side_effect_expr_span, 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 { Some(Self::IsOk {
receiver, receiver,
side_effect_expr_span, side_effect_expr_span,

View file

@ -1,6 +1,7 @@
use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::diagnostics::span_lint; 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_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro; 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) { if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
return; 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); let init_expr = expr_or_init(cx, receiver);
if !receiver.span.eq_ctxt(init_expr.span) { if !receiver.span.eq_ctxt(init_expr.span) {
return; return;

View file

@ -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) { if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) {
// For array::IntoIter<T, const N: usize>, the length is the second generic // For array::IntoIter<T, const N: usize>, the length is the second generic
// parameter. // parameter.
substs substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from)
.const_at(1)
.try_to_target_usize(cx.tcx)
.map(u128::from)
} else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did)
&& let ExprKind::MethodCall(_, recv, ..) = iter.kind && let ExprKind::MethodCall(_, recv, ..) = iter.kind
{ {

View file

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

View file

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

View file

@ -60,13 +60,16 @@ mod manual_ok_or;
mod manual_saturating_arithmetic; mod manual_saturating_arithmetic;
mod manual_str_repeat; mod manual_str_repeat;
mod manual_try_fold; mod manual_try_fold;
mod map_all_any_identity;
mod map_clone; mod map_clone;
mod map_collect_result_unit; mod map_collect_result_unit;
mod map_err_ignore; mod map_err_ignore;
mod map_flatten; mod map_flatten;
mod map_identity; mod map_identity;
mod map_unwrap_or; mod map_unwrap_or;
mod map_with_unused_argument_over_ranges;
mod mut_mutex_lock; mod mut_mutex_lock;
mod needless_as_bytes;
mod needless_character_iteration; mod needless_character_iteration;
mod needless_collect; mod needless_collect;
mod needless_option_as_deref; mod needless_option_as_deref;
@ -4166,6 +4169,90 @@ declare_clippy_lint! {
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" "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 { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Msrv, msrv: Msrv,
@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [
NEEDLESS_CHARACTER_ITERATION, NEEDLESS_CHARACTER_ITERATION,
MANUAL_INSPECT, MANUAL_INSPECT,
UNNECESSARY_MIN_OR_MAX, 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. /// Extracts a method call name, args, and `Span` of the method name.
@ -4534,15 +4624,21 @@ impl Methods {
("all", [arg]) => { ("all", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg); unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, true); needless_character_iteration::check(cx, expr, recv, arg, true);
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) { match method_call(recv) {
iter_overeager_cloned::check( Some(("cloned", recv2, [], _, _)) => {
cx, iter_overeager_cloned::check(
expr, cx,
recv, expr,
recv2, recv,
iter_overeager_cloned::Op::NeedlessMove(arg), recv2,
false, 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]) => { ("and_then", [arg]) => {
@ -4571,6 +4667,9 @@ impl Methods {
{ {
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); 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); unit_hash::check(cx, expr, recv, arg);
}, },
("is_empty", []) => { ("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { match method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span); 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); 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", []) => { ("lock", []) => {
mut_mutex_lock::check(cx, expr, recv, span); mut_mutex_lock::check(cx, expr, recv, span);
}, },
@ -4802,6 +4912,7 @@ impl Methods {
if name == "map" { if name == "map" {
unused_enumerate_index::check(cx, expr, recv, m_arg); unused_enumerate_index::check(cx, expr, recv, m_arg);
map_clone::check(cx, expr, recv, m_arg, &self.msrv); 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) { match method_call(recv) {
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv);
@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase {
} }
#[rustfmt::skip] #[rustfmt::skip]
#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")]
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), 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), ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),

View file

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

View file

@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
// Check function calls on our collection // Check function calls on our collection
if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
if args.is_empty() 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) && 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)); self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));

View file

@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
if let Some(parent) = get_parent_expr(cx, expr) { if let Some(parent) = get_parent_expr(cx, expr) {
let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind { let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
if args.is_empty() 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) && let parse_result_ty = cx.typeck_results().expr_ty(parent)
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
&& let ty::Adt(_, substs) = parse_result_ty.kind() && 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", "calling `.parse()` on a string without trimming the trailing newline character",
"checking", "checking",
)) ))
} else if segment.ident.name == sym!(ends_with) } else if segment.ident.name.as_str() == "ends_with"
&& recv.span == expr.span && recv.span == expr.span
&& let [arg] = args && let [arg] = args
&& expr_is_string_literal_without_trailing_newline(arg) && expr_is_string_literal_without_trailing_newline(arg)

View file

@ -1,13 +1,15 @@
use super::utils::clone_or_copy_needed; 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::ty::is_copy;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; 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 core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::query::Key;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_span::sym; 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) ControlFlow::Continue(Descend::Yes)
} }
}); });
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering { 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()" } if name == "filter_map" { "map" } else { "map(..).next()" }
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
match cx.typeck_results().expr_ty(body.value).kind() { 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 { } else {
return; return;
}; };
span_lint( span_lint_and_sugg(
cx, cx,
if name == "filter_map" { if name == "filter_map" {
UNNECESSARY_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 UNNECESSARY_FIND_MAP
}, },
expr.span, 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) (true, true)
}, },
hir::ExprKind::MethodCall(segment, recv, [arg], _) => { 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() && cx.typeck_results().expr_ty(recv).is_bool()
&& path_to_local_id(arg, arg_id) && path_to_local_id(arg, arg_id)
{ {

View file

@ -79,9 +79,9 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
}, },
ExprKind::MethodCall(path, receiver, args @ [_], _) => { ExprKind::MethodCall(path, receiver, args @ [_], _) => {
if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { 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) 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) fetch_const(cx, Some(receiver), args, MinMax::Min)
} else { } else {
None None

View file

@ -12,11 +12,13 @@ use clippy_utils::is_from_proc_macro;
use clippy_utils::source::SpanRangeExt; use clippy_utils::source::SpanRangeExt;
use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::Visibility; use rustc_middle::ty::Visibility;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::symbol::kw;
use rustc_span::{Span, sym}; use rustc_span::{Span, sym};
declare_clippy_lint! { declare_clippy_lint! {
@ -110,6 +112,21 @@ impl MissingDoc {
return; 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 let has_doc = attrs
.iter() .iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta())) .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::Const(..) => {
| hir::ItemKind::Enum(..) if it.ident.name == kw::Underscore {
note_prev_span_then_ret!(self.prev_span, it.span);
}
},
hir::ItemKind::Enum(..)
| hir::ItemKind::Macro(..) | hir::ItemKind::Macro(..)
| hir::ItemKind::Mod(..) | hir::ItemKind::Mod(..)
| hir::ItemKind::Static(..) | hir::ItemKind::Static(..)

View file

@ -116,7 +116,7 @@ fn should_lint<'tcx>(
if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) { if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) {
has_debug_struct = true; 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) && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct)
{ {
has_finish_non_exhaustive = true; has_finish_non_exhaustive = true;

View file

@ -330,10 +330,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
} }
fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { 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 Some(last_stmt) = loop_block.stmts.last()
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind && 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( span_lint_and_help(
cx, cx,

View file

@ -347,7 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
if let LetStmt { if let LetStmt {
init: None, init: None,
pat: pat:
&Pat { Pat {
kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None), 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::Stmt(local_stmt))) = parents.next()
&& let Some((_, Node::Block(block))) = 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);
} }
} }
} }

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::{DefId, DefIdMap}; 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ClauseKind, PredicatePolarity}; use rustc_middle::ty::{ClauseKind, PredicatePolarity};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;

View file

@ -183,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
.iter() .iter()
.zip(fn_sig.inputs()) .zip(fn_sig.inputs())
.zip(body.params) .zip(body.params)
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) .filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
.peekable(); .peekable();
if it.peek().is_none() { if it.peek().is_none() {
return; return;

View file

@ -159,8 +159,12 @@ impl NoEffect {
// Remove `impl Future<Output = T>` to get `T` // Remove `impl Future<Output = T>` to get `T`
if cx.tcx.ty_is_opaque_future(ret_ty) if cx.tcx.ty_is_opaque_future(ret_ty)
&& let Some(true_ret_ty) = && let Some(true_ret_ty) = cx
cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty) .tcx
.infer_ctxt()
.build(cx.typing_mode())
.err_ctxt()
.get_impl_future_output_ty(ret_ty)
{ {
ret_ty = true_ret_ty; ret_ty = true_ret_ty;
} }

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then; 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_errors::Applicability;
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -18,13 +18,13 @@ declare_clippy_lint! {
/// Rust ABI can break this at any point. /// Rust ABI can break this at any point.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```rust,ignore
/// #[no_mangle] /// #[no_mangle]
/// fn example(arg_one: u32, arg_two: usize) {} /// fn example(arg_one: u32, arg_two: usize) {}
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```no_run /// ```rust,ignore
/// #[no_mangle] /// #[no_mangle]
/// extern "C" fn example(arg_one: u32, arg_two: usize) {} /// 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 { if let ItemKind::Fn(fn_sig, _, _) = &item.kind {
let attrs = cx.tcx.hir().attrs(item.hir_id()); let attrs = cx.tcx.hir().attrs(item.hir_id());
let mut app = Applicability::MaybeIncorrect; 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 { for attr in attrs {
if let Some(ident) = attr.ident() if let Some(ident) = attr.ident()
&& ident.name == rustc_span::sym::no_mangle && ident.name == rustc_span::sym::no_mangle
&& fn_sig.header.abi == Abi::Rust && 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") && !fn_attrs.contains("extern")
{ {
let sugg_span = fn_sig let sugg_span = fn_sig
.span .span
.with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len()))
.shrink_to_lo(); .shrink_to_lo();
let attr_snippet = snippet(cx, attr.span, "..");
span_lint_and_then( span_lint_and_then(
cx, cx,
NO_MANGLE_WITH_RUST_ABI, NO_MANGLE_WITH_RUST_ABI,
fn_sig.span, 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| {
diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app)
.span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app);

View file

@ -43,12 +43,12 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
match &expr.kind { match &expr.kind {
ExprKind::MethodCall(path, func, [param], _) => { ExprKind::MethodCall(path, func, [param], _) => {
if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() 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!( && matches!(
cx.tcx.get_diagnostic_name(adt.did()), cx.tcx.get_diagnostic_name(adt.did()),
Some(sym::FsOpenOptions | sym::DirBuilder) 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()))) && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
&& let ExprKind::Lit(_) = param.kind && let ExprKind::Lit(_) = param.kind
&& param.span.eq_ctxt(expr.span) && param.span.eq_ctxt(expr.span)

View file

@ -107,7 +107,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
} }
if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind 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 // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call) // the method call)
{ {

View file

@ -34,7 +34,7 @@ declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]);
impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
items: impl_items, items: impl_items,
.. ..
}) = item.kind }) = item.kind

View file

@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>(
if path_segment.ident.name == sym::offset { if path_segment.ident.name == sym::offset {
return Some((arg_0, arg_1, Method::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)); return Some((arg_0, arg_1, Method::WrappingOffset));
} }
} }

View file

@ -93,7 +93,7 @@ enum IfBlockType<'hir> {
fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> { fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> {
if let Block { if let Block {
stmts: &[], stmts: [],
expr: Some(els), expr: Some(els),
.. ..
} = block } = block
@ -163,8 +163,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
is_type_diagnostic_item(cx, caller_ty, smbl) is_type_diagnostic_item(cx, caller_ty, smbl)
&& expr_return_none_or_err(smbl, cx, if_then, caller, None) && expr_return_none_or_err(smbl, cx, if_then, caller, None)
&& match smbl { && match smbl {
sym::Option => call_sym == sym!(is_none), sym::Option => call_sym.as_str() == "is_none",
sym::Result => call_sym == sym!(is_err), sym::Result => call_sym.as_str() == "is_err",
_ => false, _ => false,
} }
}, },

View file

@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef {
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) { fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
if let TyKind::Ref(_, ref mut_ty) = ty.kind if let TyKind::Ref(_, ref mut_ty) = ty.kind
&& mut_ty.mutbl == Mutability::Not && 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 last = last_path_segment(qpath)
&& let Some(def_id) = last.res.opt_def_id() && let Some(def_id) = last.res.opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::Option, def_id) && cx.tcx.is_diagnostic_item(sym::Option, def_id)

View file

@ -347,7 +347,7 @@ fn check_final_expr<'tcx>(
let peeled_drop_expr = expr.peel_drop_temps(); let peeled_drop_expr = expr.peel_drop_temps();
match &peeled_drop_expr.kind { match &peeled_drop_expr.kind {
// simple return is always "bad" // simple return is always "bad"
ExprKind::Ret(ref inner) => { ExprKind::Ret(inner) => {
// check if expr return nothing // check if expr return nothing
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)

View file

@ -101,17 +101,21 @@ impl SemicolonBlock {
); );
} }
fn semicolon_outside_block( fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) {
&self,
cx: &LateContext<'_>,
block: &Block<'_>,
tail_stmt_expr: &Expr<'_>,
semi_span: Span,
) {
let insert_span = block.span.with_lo(block.span.hi()); 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); // For macro call semicolon statements (`mac!();`), the statement's span does not actually
let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi()); // 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) { if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) {
return; return;
@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock {
}; };
let &Stmt { let &Stmt {
kind: StmtKind::Semi(expr), kind: StmtKind::Semi(expr),
span,
.. ..
} = stmt } = stmt
else { else {
return; return;
}; };
self.semicolon_outside_block(cx, block, expr, span); self.semicolon_outside_block(cx, block, expr);
}, },
StmtKind::Semi(Expr { StmtKind::Semi(Expr {
kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _), kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),

View file

@ -26,7 +26,7 @@ declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]);
impl<'tcx> LateLintPass<'tcx> for SerdeApi { impl<'tcx> LateLintPass<'tcx> for SerdeApi {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref), of_trait: Some(trait_ref),
items, items,
.. ..
}) = item.kind }) = item.kind

View file

@ -174,7 +174,7 @@ impl SingleComponentPathImports {
} }
match &item.kind { match &item.kind {
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { ItemKind::Mod(_, ModKind::Loaded(items, ..)) => {
self.check_mod(items); self.check_mod(items);
}, },
ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {

View file

@ -235,7 +235,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
if self.initialization_found if self.initialization_found
&& let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind && let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind
&& path_to_local_id(self_arg, self.vec_alloc.local_id) && 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.is_repeat_take(extend_arg)
{ {
self.slow_expression = Some(InitializationType::Extend(expr)); self.slow_expression = Some(InitializationType::Extend(expr));
@ -247,7 +247,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
if self.initialization_found if self.initialization_found
&& let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind
&& path_to_local_id(self_arg, self.vec_alloc.local_id) && 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 // Check that is filled with 0
&& is_integer_literal(fill_arg, 0) && is_integer_literal(fill_arg, 0)
{ {
@ -269,7 +269,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
/// Returns `true` if give expression is `repeat(0).take(...)` /// Returns `true` if give expression is `repeat(0).take(...)`
fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind 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)` // Check that take is applied to `repeat(0)`
&& self.is_repeat_zero(recv) && self.is_repeat_zero(recv)
{ {

View file

@ -286,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
if !in_external_macro(cx.sess(), e.span) if !in_external_macro(cx.sess(), e.span)
&& let ExprKind::MethodCall(path, receiver, ..) = &e.kind && 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 ExprKind::Lit(lit) = &receiver.kind
&& let LitKind::Str(lit_content, _) = &lit.node && 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 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 && let ExprKind::MethodCall(path, recv, [], _) = &recv.kind
&& matches!(path.ident.name.as_str(), "to_owned" | "to_string") && matches!(path.ident.name.as_str(), "to_owned" | "to_string")
&& let ExprKind::Lit(lit) = &recv.kind && 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<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
let tyckres = cx.typeck_results(); let tyckres = cx.typeck_results();
if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind 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) && 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) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id)
&& let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind

View file

@ -334,7 +334,7 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
let mut output = expr; let mut output = expr;
loop { loop {
output = match &output.kind { output = match &output.kind {
ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, ExprKind::Paren(inner) | ExprKind::Unary(_, inner) => inner,
_ => { _ => {
return output; return output;
}, },
@ -348,13 +348,13 @@ fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
match kind { match kind {
ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), ExprKind::If(condition, _, _) => chained_binops(&condition.kind),
ExprKind::Paren(ref e) => if_statement_binops(&e.kind), ExprKind::Paren(e) => if_statement_binops(&e.kind),
ExprKind::Block(ref block, _) => { ExprKind::Block(block, _) => {
let mut output = None; let mut output = None;
for stmt in &block.stmts { for stmt in &block.stmts {
match stmt.kind { match &stmt.kind {
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { StmtKind::Expr(e) | StmtKind::Semi(e) => {
output = append_opt_vecs(output, if_statement_binops(&e.kind)); 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<'_>>> { fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
match kind { match kind {
ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), 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, _ => 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>>> { fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> {
match (&left_outer.kind, &right_outer.kind) { match (&left_outer.kind, &right_outer.kind) {
( (
ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e),
ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e),
) => chained_binops_helper(left_e, 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(left_e) | ExprKind::Unary(_, left_e), _) => chained_binops_helper(left_e, right_outer),
(_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { (_, ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e)) => chained_binops_helper(left_outer, 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: left_op, .. }, left_left, left_right),
ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), ExprKind::Binary(Spanned { node: right_op, .. }, right_left, right_right),
) => match ( ) => match (
chained_binops_helper(left_left, left_right), chained_binops_helper(left_left, left_right),
chained_binops_helper(right_left, right_right), chained_binops_helper(right_left, right_right),

View file

@ -392,7 +392,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
for stmt in self.block.stmts { for stmt in self.block.stmts {
match stmt.kind { match stmt.kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => v.visit_expr(expr), StmtKind::Expr(expr) | StmtKind::Semi(expr) => v.visit_expr(expr),
StmtKind::Let(LetStmt { ref init, .. }) => { StmtKind::Let(LetStmt { init, .. }) => {
if let Some(init) = init.as_ref() { if let Some(init) = init.as_ref() {
v.visit_expr(init); v.visit_expr(init);
} }

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