Auto merge of #132746 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`
This commit is contained in:
bors 2024-11-08 16:49:13 +00:00
commit 59cec72a57
249 changed files with 5023 additions and 900 deletions

View File

@ -2861,7 +2861,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags 2.6.0",
"getopts",
"memchr",
"pulldown-cmark-escape 0.11.0",
"unicase",

View File

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

View File

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

View File

@ -1,24 +1,7 @@
name: Clippy Test
on:
push:
# Ignore bors branches, since they are covered by `clippy_bors.yml`
branches-ignore:
- auto
- try
# Don't run Clippy tests, when only text files were modified
paths-ignore:
- 'COPYRIGHT'
- 'LICENSE-*'
- '**.md'
- '**.txt'
pull_request:
# Don't run Clippy tests, when only text files were modified
paths-ignore:
- 'COPYRIGHT'
- 'LICENSE-*'
- '**.md'
- '**.txt'
env:
RUST_BACKTRACE: 1
@ -35,7 +18,7 @@ concurrency:
jobs:
base:
# NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml
# NOTE: If you modify this job, make sure you copy the changes to clippy_mq.yml
runs-on: ubuntu-latest
steps:
@ -73,3 +56,24 @@ jobs:
run: .github/driver.sh
env:
OS: ${{ runner.os }}
# We need to have the "conclusion" job also on PR CI, to make it possible
# to add PRs to a merge queue.
conclusion:
needs: [ base ]
# We need to ensure this job does *not* get skipped if its dependencies fail,
# because a skipped job is considered a success by GitHub. So we have to
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
# when the workflow is canceled manually.
#
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
# Manually check the status of all dependencies. `if: failure()` does not work.
- name: Conclusion
run: |
# Print the dependent jobs to see them in the CI log
jq -C <<< '${{ toJson(needs) }}'
# Check if all jobs that we depend on (in the needs array) were successful.
jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

View File

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

View File

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

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_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
[`arbitrary_source_item_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering
[`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
@ -5689,6 +5690,7 @@ Released 2018-09-13
[`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
[`map_all_any_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
@ -5696,6 +5698,7 @@ Released 2018-09-13
[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
[`map_with_unused_argument_over_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges
[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool
[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
@ -5761,6 +5764,7 @@ Released 2018-09-13
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
[`needless_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes
[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
[`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
@ -6205,12 +6209,14 @@ Released 2018-09-13
[`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
[`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline
[`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline
[`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold
[`source-item-ordering`]: https://doc.rust-lang.org/clippy/lint_configuration.html#source-item-ordering
[`stack-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#stack-size-threshold
[`standard-macro-braces`]: https://doc.rust-lang.org/clippy/lint_configuration.html#standard-macro-braces
[`struct-field-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#struct-field-name-threshold
@ -6218,6 +6224,7 @@ Released 2018-09-13
[`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack
[`too-many-arguments-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-arguments-threshold
[`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold
[`trait-assoc-item-kinds-order`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trait-assoc-item-kinds-order
[`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit
[`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold
[`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size

View File

@ -21,7 +21,6 @@ All contributors are expected to follow the [Rust Code of Conduct].
- [Rust Analyzer](#rust-analyzer)
- [How Clippy works](#how-clippy-works)
- [Issue and PR triage](#issue-and-pr-triage)
- [Bors and Homu](#bors-and-homu)
- [Contributions](#contributions)
- [License](#license)
@ -213,16 +212,6 @@ We have prioritization labels and a sync-blocker label, which are described belo
Or rather: before the sync this should be addressed,
e.g. by removing a lint again, so it doesn't hit beta/stable.
## Bors and Homu
We use a bot powered by [Homu][homu] to help automate testing and landing of pull
requests in Clippy. The bot's username is @bors.
You can find the Clippy bors queue [here][homu_queue].
If you have @bors permissions, you can find an overview of the available
commands [here][homu_instructions].
[triage]: https://forge.rust-lang.org/release/triage-procedure.html
[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
@ -230,9 +219,6 @@ commands [here][homu_instructions].
[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
[homu]: https://github.com/rust-lang/homu
[homu_instructions]: https://bors.rust-lang.org/
[homu_queue]: https://bors.rust-lang.org/queue/clippy
## Contributions
@ -244,7 +230,7 @@ All PRs should include a `changelog` entry with a short comment explaining the c
"what do you believe is important from an outsider's perspective?" Often, PRs are only related to a single property of a
lint, and then it's good to mention that one. Otherwise, it's better to include too much detail than too little.
Clippy's [changelog] is created from these comments. Every release, someone gets all commits from bors with a
Clippy's [changelog] is created from these comments. Every release, someone gets all merge commits with a
`changelog: XYZ` entry and combines them into the changelog. This is a manual process.
Examples:

View File

@ -39,7 +39,7 @@ toml = "0.7.3"
walkdir = "2.3"
filetime = "0.2.9"
itertools = "0.12"
pulldown-cmark = "0.11"
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
rinja = { version = "0.3", default-features = false, features = ["config"] }
# UI test dependencies

View File

@ -1,11 +1,10 @@
# Clippy
[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto)
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license)
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View File

@ -1,12 +1,11 @@
# Clippy
[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto)
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license)
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View File

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

View File

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

View File

@ -23,7 +23,7 @@ impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint {
// Check our expr is calling a method with pattern matching
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind
// Check if the name of this method is `our_fancy_method`
&& path.ident.name == sym!(our_fancy_method)
&& path.ident.name.as_str() == "our_fancy_method"
// We can check the type of the self argument whenever necessary.
// (It's necessary if we want to check that method is specifically belonging to a specific trait,
// for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
@ -41,10 +41,6 @@ information on the pattern matching. As mentioned in [Define
Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern
matching with `MethodCall` in case the reader wishes to explore more.
Additionally, we use the [`clippy_utils::sym!`][sym] macro to conveniently
convert an input `our_fancy_method` into a `Symbol` and compare that symbol to
the [`Ident`]'s name in the [`PathSegment`] in the [`MethodCall`].
## Checking if a `impl` block implements a method
While sometimes we want to check whether a method is being called or not, other
@ -71,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
// Check if item is a method/function
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// Check the method is named `our_fancy_method`
&& impl_item.ident.name == sym!(our_fancy_method)
&& impl_item.ident.name.as_str() == "our_fancy_method"
// We can also check it has a parameter `self`
&& signature.decl.implicit_self.has_implicit_self()
// We can go even further and even check if its return type is `String`
@ -85,9 +81,6 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
[`check_impl_item`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_impl_item
[`ExprKind`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html
[`Ident`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/struct.Ident.html
[`ImplItem`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_hir/hir/struct.ImplItem.html
[`LateLintPass`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html
[`MethodCall`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.MethodCall
[`PathSegment`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/struct.PathSegment.html
[sym]: https://doc.rust-lang.org/stable/nightly-rustc/clippy_utils/macro.sym.html

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 append `ClipPy` to the default list.
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "AccessKit", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
---
**Affected lints:**
@ -666,6 +666,16 @@ crate. For example, `pub(crate)` items.
* [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
## `module-item-order-groupings`
The named groupings of different source item kinds within modules.
**Default Value:** `[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]]`
---
**Affected lints:**
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
## `msrv`
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
@ -710,6 +720,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
* [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone)
* [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or)
* [`map_with_unused_argument_over_ranges`](https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges)
* [`match_like_matches_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro)
* [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default)
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
@ -783,6 +794,16 @@ The maximum number of single char bindings a scope may have
* [`many_single_char_names`](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names)
## `source-item-ordering`
Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
**Default Value:** `["enum", "impl", "module", "struct", "trait"]`
---
**Affected lints:**
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
## `stack-size-threshold`
The maximum allowed stack size for functions in bytes
@ -862,6 +883,16 @@ The maximum number of lines a function or method can have
* [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
## `trait-assoc-item-kinds-order`
The order of associated items in traits.
**Default Value:** `["const", "type", "fn"]`
---
**Affected lints:**
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
## `trivial-copy-size-limit`
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
reference. By default there is no limit

View File

@ -1,6 +1,10 @@
use crate::ClippyConfiguration;
use crate::msrvs::Msrv;
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
use crate::types::{
DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
};
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::edit_distance::edit_distance;
@ -17,8 +21,9 @@
#[rustfmt::skip]
const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
"MHz", "GHz", "THz",
"AccessKit",
"CoreFoundation", "CoreGraphics", "CoreText",
"CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
"DevOps",
"Direct2D", "Direct3D", "DirectWrite", "DirectX",
"ECMAScript",
@ -46,6 +51,29 @@
const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
&["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
use SourceItemOrderingModuleItemKind::*;
&[
("modules", &[ExternCrate, Mod, ForeignMod]),
("use", &[Use]),
("macros", &[Macro]),
("global_asm", &[GlobalAsm]),
("UPPER_SNAKE_CASE", &[Static, Const]),
("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
("lower_snake_case", &[Fn]),
]
};
const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
use SourceItemOrderingTraitAssocItemKind::*;
&[Const, Type, Fn]
};
const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
use SourceItemOrderingCategory::*;
&[Enum, Impl, Module, Struct, Trait]
};
/// Conf with parse errors
#[derive(Default)]
@ -102,7 +130,9 @@ pub fn sanitize_explanation(raw_docs: &str) -> String {
// Remove tags and hidden code:
let mut explanation = String::with_capacity(128);
let mut in_code = false;
for line in raw_docs.lines().map(str::trim) {
for line in raw_docs.lines() {
let line = line.strip_prefix(' ').unwrap_or(line);
if let Some(lang) = line.strip_prefix("```") {
let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
@ -530,6 +560,9 @@ pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// crate. For example, `pub(crate)` items.
#[lints(missing_docs_in_private_items)]
missing_docs_in_crate_items: bool = false,
/// The named groupings of different source item kinds within modules.
#[lints(arbitrary_source_item_ordering)]
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = "current version"]
#[lints(
@ -570,6 +603,7 @@ pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
manual_try_fold,
map_clone,
map_unwrap_or,
map_with_unused_argument_over_ranges,
match_like_matches_macro,
mem_replace_with_default,
missing_const_for_fn,
@ -608,6 +642,9 @@ pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// The maximum number of single char bindings a scope may have
#[lints(many_single_char_names)]
single_char_binding_names_threshold: u64 = 4,
/// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
#[lints(arbitrary_source_item_ordering)]
source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
/// The maximum allowed stack size for functions in bytes
#[lints(large_stack_frames)]
stack_size_threshold: u64 = 512_000,
@ -637,6 +674,9 @@ pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// The maximum number of lines a function or method can have
#[lints(too_many_lines)]
too_many_lines_threshold: u64 = 100,
/// The order of associated items in traits.
#[lints(arbitrary_source_item_ordering)]
trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
/// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
/// reference. By default there is no limit
#[default_text = "target_pointer_width * 2"]

View File

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

View File

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

View File

@ -1,5 +1,6 @@
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize, ser};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Deserialize)]
@ -102,6 +103,306 @@ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
}
}
/// Represents the item categories that can be ordered by the source ordering lint.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingCategory {
Enum,
Impl,
Module,
Struct,
Trait,
}
/// Represents which item categories are enabled for ordering.
///
/// The [`Deserialize`] implementation checks that there are no duplicates in
/// the user configuration.
pub struct SourceItemOrdering(Vec<SourceItemOrderingCategory>);
impl SourceItemOrdering {
pub fn contains(&self, category: &SourceItemOrderingCategory) -> bool {
self.0.contains(category)
}
}
impl<T> From<T> for SourceItemOrdering
where
T: Into<Vec<SourceItemOrderingCategory>>,
{
fn from(value: T) -> Self {
Self(value.into())
}
}
impl core::fmt::Debug for SourceItemOrdering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'de> Deserialize<'de> for SourceItemOrdering {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let items = Vec::<SourceItemOrderingCategory>::deserialize(deserializer)?;
let mut items_set = std::collections::HashSet::new();
for item in &items {
if items_set.contains(item) {
return Err(de::Error::custom(format!(
"The category \"{item:?}\" was enabled more than once in the source ordering configuration."
)));
}
items_set.insert(item);
}
Ok(Self(items))
}
}
impl Serialize for SourceItemOrdering {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.0.serialize(serializer)
}
}
/// Represents the items that can occur within a module.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingModuleItemKind {
ExternCrate,
Mod,
ForeignMod,
Use,
Macro,
GlobalAsm,
Static,
Const,
TyAlias,
Enum,
Struct,
Union,
Trait,
TraitAlias,
Impl,
Fn,
}
impl SourceItemOrderingModuleItemKind {
pub fn all_variants() -> Vec<Self> {
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
use SourceItemOrderingModuleItemKind::*;
vec![
ExternCrate,
Mod,
ForeignMod,
Use,
Macro,
GlobalAsm,
Static,
Const,
TyAlias,
Enum,
Struct,
Union,
Trait,
TraitAlias,
Impl,
Fn,
]
}
}
/// Represents the configured ordering of items within a module.
///
/// The [`Deserialize`] implementation checks that no item kinds have been
/// omitted and that there are no duplicates in the user configuration.
#[derive(Clone)]
pub struct SourceItemOrderingModuleItemGroupings {
groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
}
impl SourceItemOrderingModuleItemGroupings {
fn build_lut(
groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
) -> HashMap<SourceItemOrderingModuleItemKind, usize> {
let mut lut = HashMap::new();
for (group_index, (_, items)) in groups.iter().enumerate() {
for item in items {
lut.insert(item.clone(), group_index);
}
}
lut
}
pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<usize> {
self.lut.get(item).copied()
}
}
impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrderingModuleItemGroupings {
fn from(value: &[(&str, &[SourceItemOrderingModuleItemKind])]) -> Self {
let groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)> =
value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect();
let lut = Self::build_lut(&groups);
Self { groups, lut }
}
}
impl core::fmt::Debug for SourceItemOrderingModuleItemGroupings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.groups.fmt(f)
}
}
impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let groups = Vec::<(String, Vec<SourceItemOrderingModuleItemKind>)>::deserialize(deserializer)?;
let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
let lut = Self::build_lut(&groups);
let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
for item in lut.keys() {
expected_items.retain(|i| i != item);
}
let all_items = SourceItemOrderingModuleItemKind::all_variants();
if expected_items.is_empty() && items_total == all_items.len() {
let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else {
return Err(de::Error::custom("Error in internal LUT."));
};
let Some((_, use_group_items)) = groups.get(*use_group_index) else {
return Err(de::Error::custom("Error in internal LUT."));
};
if use_group_items.len() > 1 {
return Err(de::Error::custom(
"The group containing the \"use\" item kind may not contain any other item kinds. \
The \"use\" items will (generally) be sorted by rustfmt already. \
Therefore it makes no sense to implement linting rules that may conflict with rustfmt.",
));
}
Ok(Self { groups, lut })
} else if items_total != all_items.len() {
Err(de::Error::custom(format!(
"Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
The module item kinds are: {all_items:?}"
)))
} else {
Err(de::Error::custom(format!(
"Not all module item kinds were part of the configured source ordering rule. \
All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
The module item kinds are: {all_items:?}"
)))
}
}
}
impl Serialize for SourceItemOrderingModuleItemGroupings {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.groups.serialize(serializer)
}
}
/// Represents all kinds of trait associated items.
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingTraitAssocItemKind {
Const,
Fn,
Type,
}
impl SourceItemOrderingTraitAssocItemKind {
pub fn all_variants() -> Vec<Self> {
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
use SourceItemOrderingTraitAssocItemKind::*;
vec![Const, Fn, Type]
}
}
/// Represents the order in which associated trait items should be ordered.
///
/// The reason to wrap a `Vec` in a newtype is to be able to implement
/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks
/// on configuration completeness at the time of loading the clippy config,
/// letting the user know if there's any issues with the config (e.g. not
/// listing all item kinds that should be sorted).
#[derive(Clone)]
pub struct SourceItemOrderingTraitAssocItemKinds(Vec<SourceItemOrderingTraitAssocItemKind>);
impl SourceItemOrderingTraitAssocItemKinds {
pub fn index_of(&self, item: &SourceItemOrderingTraitAssocItemKind) -> Option<usize> {
self.0.iter().position(|i| i == item)
}
}
impl<T> From<T> for SourceItemOrderingTraitAssocItemKinds
where
T: Into<Vec<SourceItemOrderingTraitAssocItemKind>>,
{
fn from(value: T) -> Self {
Self(value.into())
}
}
impl core::fmt::Debug for SourceItemOrderingTraitAssocItemKinds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'de> Deserialize<'de> for SourceItemOrderingTraitAssocItemKinds {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let items = Vec::<SourceItemOrderingTraitAssocItemKind>::deserialize(deserializer)?;
let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants();
for item in &items {
expected_items.retain(|i| i != item);
}
let all_items = SourceItemOrderingTraitAssocItemKind::all_variants();
if expected_items.is_empty() && items.len() == all_items.len() {
Ok(Self(items))
} else if items.len() != all_items.len() {
Err(de::Error::custom(format!(
"Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \
The trait associated item kinds are: {all_items:?}",
)))
} else {
Err(de::Error::custom(format!(
"Not all trait associated item kinds were part of the configured source ordering rule. \
All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
The trait associated item kinds are: {all_items:?}"
)))
}
}
}
impl Serialize for SourceItemOrderingTraitAssocItemKinds {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.0.serialize(serializer)
}
}
// these impls are never actually called but are used by the various config options that default to
// empty lists
macro_rules! unimplemented_serialize {

View File

@ -20,8 +20,14 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
loop {
let index_time = mtime("util/gh-pages/index.html");
let times = [
"clippy_lints/src",
"util/gh-pages/index_template.html",
"tests/compile-test.rs",
]
.map(mtime);
if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") {
if times.iter().any(|&time| index_time < time) {
Command::new(env::var("CARGO").unwrap_or("cargo".into()))
.arg("collect-metadata")
.spawn()

View File

@ -762,13 +762,19 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
Literal{..}(desc)
);
if let Some(LintDeclSearchResult {
if let Some(end) = iter.find_map(|t| {
if let LintDeclSearchResult {
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 @@
use clippy_config::Conf;
use clippy_config::msrvs::{self, Msrv};
use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast};
use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
use rustc_hir::{ImplItem, Item, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass;

View File

@ -1,10 +1,9 @@
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
use super::USELESS_ATTRIBUTE;
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, first_line_of_span};
use rustc_ast::MetaItemInner;
use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner};
use rustc_errors::Applicability;
use rustc_ast::{Item, ItemKind, Attribute};
use rustc_lint::{EarlyContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;

View File

@ -15,7 +15,7 @@
/// `MutexGuard`.
///
/// ### Why is this bad?
/// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and
/// The Mutex types found in [`std::sync`](https://doc.rust-lang.org/stable/std/sync/) and
/// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are
/// not designed to operate in an async context across await points.
///

View File

@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
&& let Some(suggestion) = simplify_not(cx, msrv, inner)
&& cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
{
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
let maybe_par = if let Some(sug) = Sugg::hir_opt(cx, inner) {
match sug {
Sugg::BinOp(..) => true,
Sugg::MaybeParen(sug) if !has_enclosing_paren(&sug) => true,
_ => false,
}
} else {
false
};
let suggestion = if maybe_par {
format!("({suggestion})")
} else {
suggestion
};
span_lint_and_sugg(
cx,
NONMINIMAL_BOOL,

View File

@ -4,7 +4,7 @@
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, UnOp};
use rustc_hir::{BorrowKind, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::Mutability;
use rustc_middle::ty;
@ -49,7 +49,7 @@
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind
&& let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
&& !e.span.from_expansion()

View File

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

View File

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

View File

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

View File

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

View File

@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
.iter()
.filter(|&&(_, name)| !name.as_str().starts_with('_'))
.any(|&(_, name)| {
let mut walker = ContainsName {
name,
result: false,
cx,
};
let mut walker = ContainsName { name, cx };
// Scan block
block
let mut res = block
.stmts
.iter()
.filter(|stmt| !ignore_span.overlaps(stmt.span))
.for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
.try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
if let Some(expr) = block.expr {
intravisit::walk_expr(&mut walker, expr);
if res.is_continue() {
res = intravisit::walk_expr(&mut walker, expr);
}
}
walker.result
res.is_break()
})
})
}

View File

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

View File

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

View File

@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint {
(@
$(#[doc = $lit:literal])*
pub $lint_name:ident,
$category:ident,
$level:ident,
$lintcategory:expr,
$desc:literal,
$version_expr:expr,
@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint {
$(#[doc = $lit])*
#[clippy::version = $version_lit]
pub clippy::$lint_name,
$category,
$level,
$desc,
report_in_external_macro:true
$(, @eval_always = $eval_always)?
@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
restriction,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Restriction, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -49,12 +50,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
style,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Style, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -63,12 +65,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
correctness,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Deny, crate::LintCategory::Correctness, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
@ -78,12 +81,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
perf,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Perf, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -92,12 +96,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
complexity,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Complexity, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -106,12 +111,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
suspicious,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -120,12 +126,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
nursery,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Nursery, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -134,12 +141,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
pedantic,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};
(
@ -148,12 +156,13 @@ macro_rules! declare_clippy_lint {
pub $lint_name:ident,
cargo,
$desc:literal
$(@eval_always = $eval_always: literal)?
$(, @eval_always = $eval_always: literal)?
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Cargo, $desc,
Some($version), $version $(, $eval_always)?
Some($version), $version
$(, $eval_always)?
}
};

View File

@ -28,12 +28,15 @@
#[cfg(feature = "internal")]
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_INFO,
crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO,
crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
crate::as_conversions::AS_CONVERSIONS_INFO,
crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO,
@ -414,14 +417,17 @@
crate::methods::MANUAL_SPLIT_ONCE_INFO,
crate::methods::MANUAL_STR_REPEAT_INFO,
crate::methods::MANUAL_TRY_FOLD_INFO,
crate::methods::MAP_ALL_ANY_IDENTITY_INFO,
crate::methods::MAP_CLONE_INFO,
crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
crate::methods::MAP_ERR_IGNORE_INFO,
crate::methods::MAP_FLATTEN_INFO,
crate::methods::MAP_IDENTITY_INFO,
crate::methods::MAP_UNWRAP_OR_INFO,
crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO,
crate::methods::MUT_MUTEX_LOCK_INFO,
crate::methods::NAIVE_BYTECOUNT_INFO,
crate::methods::NEEDLESS_AS_BYTES_INFO,
crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO,
crate::methods::NEEDLESS_COLLECT_INFO,
crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,

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 {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
of_trait: Some(trait_ref),
items: [child],
self_ty,
..

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

View File

@ -2,7 +2,6 @@
use clippy_utils::create_disallowed_map;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace;
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Diag;
use rustc_hir::def_id::DefIdMap;
@ -14,6 +13,8 @@
use rustc_session::impl_lint_pass;
use rustc_span::{ExpnId, MacroKind, Span};
use crate::utils::attr_collector::AttrStorage;
declare_clippy_lint! {
/// ### What it does
/// Denies the configured macros in clippy.toml
@ -64,14 +65,19 @@ pub struct DisallowedMacros {
// Track the most recently seen node that can have a `derive` attribute.
// Needed to use the correct lint level.
derive_src: Option<OwnerId>,
// When a macro is disallowed in an early pass, it's stored
// and emitted during the late pass. This happens for attributes.
earlies: AttrStorage,
}
impl DisallowedMacros {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
seen: FxHashSet::default(),
derive_src: None,
earlies,
}
}
@ -114,6 +120,15 @@ fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option<OwnerId
impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
impl LateLintPass<'_> for DisallowedMacros {
fn check_crate(&mut self, cx: &LateContext<'_>) {
// once we check a crate in the late pass we can emit the early pass lints
if let Some(attr_spans) = self.earlies.clone().0.get() {
for span in attr_spans {
self.check(cx, *span, None);
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span, None);
// `$t + $t` can have the context of $t, check also the span of the binary operator
@ -164,8 +179,4 @@ fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span, None);
}
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
self.check(cx, attr.span, self.derive_src);
}
}

View File

@ -80,7 +80,7 @@ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
let mut symbols: Vec<_> = symbols.iter().collect();
symbols.sort_unstable_by_key(|k| k.1);
for (symbol, &span) in &symbols {
for &(symbol, &span) in &symbols {
// Note: `symbol.as_str()` is an expensive operation, thus should not be called
// more than once for a single symbol.
let symbol_str = symbol.as_str();

View File

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

View File

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

View File

@ -9,8 +9,7 @@
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt,
TypeckResults,
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults,
};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym;
@ -204,7 +203,12 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc
// 'cuz currently nothing changes after deleting this check.
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
}) {
match cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().type_implements_fn_trait(
match cx
.tcx
.infer_ctxt()
.build(cx.typing_mode())
.err_ctxt()
.type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,

View File

@ -58,7 +58,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// match call to write_fmt
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind)
&& let ExprKind::Call(write_recv_path, []) = write_recv.kind
&& write_fun.ident.name == sym!(write_fmt)
&& write_fun.ident.name.as_str() == "write_fmt"
&& let Some(def_id) = path_def_id(cx, write_recv_path)
{
// match calls to std::io::stdout() / std::io::stderr ()

View File

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use clippy_config::Conf;
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
@ -115,25 +117,26 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
}
/// Finds the occurrences of `Self` and `self`
///
/// Returns `ControlFlow::break` if any of the `self`/`Self` usages were from an expansion, or the
/// body contained a binding already named `val`.
struct SelfFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
/// Occurrences of `Self`
upper: Vec<Span>,
/// Occurrences of `self`
lower: Vec<Span>,
/// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding
/// already named `val`
invalid: bool,
}
impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> {
type Result = ControlFlow<()>;
type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result {
for segment in path.segments {
match segment.ident.name {
kw::SelfLower => self.lower.push(segment.ident.span),
@ -141,17 +144,19 @@ fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
_ => continue,
}
self.invalid |= segment.ident.span.from_expansion();
}
if !self.invalid {
walk_path(self, path);
if segment.ident.span.from_expansion() {
return ControlFlow::Break(());
}
}
fn visit_name(&mut self, name: Symbol) {
walk_path(self, path)
}
fn visit_name(&mut self, name: Symbol) -> Self::Result {
if name == sym::val {
self.invalid = true;
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
}
@ -209,11 +214,9 @@ fn convert_to_from(
cx,
upper: Vec::new(),
lower: Vec::new(),
invalid: false,
};
finder.visit_expr(body.value);
if finder.invalid {
if finder.visit_expr(body.value).is_break() {
return None;
}

View File

@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Call(box_from_raw, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind
&& seg.ident.name == sym!(from_raw)
&& seg.ident.name.as_str() == "from_raw"
&& let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id))
&& let arg_kind = cx.typeck_results().expr_ty(arg).kind()
&& let ty::RawPtr(ty, _) = arg_kind

View File

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

View File

@ -15,9 +15,9 @@
fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) {
if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind()
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option)
&& let ty::Adt(_, opt_gen) = opt_ty.kind()
&& let [gen] = opt_gen.as_slice()
&& let GenericArgKind::Type(gen_ty) = gen.unpack()
&& let ty::Adt(_, opt_gen_args) = opt_ty.kind()
&& let [gen_arg] = opt_gen_args.as_slice()
&& let GenericArgKind::Type(gen_ty) = gen_arg.unpack()
&& !gen_ty.is_ref()
// Need to gen the original spans, so first parsing mid, and hir parsing afterward
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind

View File

@ -340,7 +340,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
} else if method.ident.name.as_str() == "with_capacity" {
self.suggestions.insert(
e.span,
format!(
@ -352,7 +352,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
} else if method.ident.name.as_str() == "with_capacity" {
self.suggestions.insert(
e.span,
format!(

View File

@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
.and(cap);
}
}
if method.ident.name == sym!(flat_map) && args.len() == 1 {
if method.ident.name.as_str() == "flat_map" && args.len() == 1 {
if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
let body = cx.tcx.hir().body(body);
return is_infinite(cx, body.value);
@ -224,7 +224,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
return MaybeInfinite.and(is_infinite(cx, receiver));
}
}
if method.ident.name == sym!(last) && args.is_empty() {
if method.ident.name.as_str() == "last" && args.is_empty() {
let not_double_ended = cx
.tcx
.get_diagnostic_item(sym::DoubleEndedIterator)
@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
if not_double_ended {
return is_infinite(cx, receiver);
}
} else if method.ident.name == sym!(collect) {
} else if method.ident.name.as_str() == "collect" {
let ty = cx.typeck_results().expr_ty(expr);
if matches!(
get_type_diagnostic_name(cx, ty),

View File

@ -142,7 +142,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some()
})
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
if item.ident.name == sym!(IntoIter) {
if item.ident.name.as_str() == "IntoIter" {
Some(cx.tcx.hir().impl_item(item.id).expect_type().span)
} else {
None
@ -247,8 +247,8 @@ fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'
let sugg = format!(
"
impl IntoIterator for {self_ty_snippet} {{
type IntoIter = {ret_ty};
type Item = {iter_ty};
type IntoIter = {ret_ty};
fn into_iter(self) -> Self::IntoIter {{
self.iter()
}}

View File

@ -4,7 +4,7 @@
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, ConstKind};
use rustc_middle::ty::{self, ParamEnv};
use rustc_session::impl_lint_pass;
use rustc_span::{BytePos, Pos, Span};
@ -56,7 +56,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
&& !item.span.from_expansion()
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Array(element_type, cst) = ty.kind()
&& let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok((_, ty::ValTree::Leaf(element_count))) = cst.eval_valtree(cx.tcx, ParamEnv::empty(), item.span)
&& let element_count = element_count.to_target_usize(cx.tcx)
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes())
&& u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size)

View File

@ -1,11 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
use rustc_ast::LitKind;
use clippy_utils::source::snippet_opt;
use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind, Attribute, LitKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
use rustc_span::{Span, sym};
declare_clippy_lint! {
/// ### What it does
@ -51,6 +52,24 @@ pub fn new(conf: &'static Conf) -> Self {
impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
impl LargeIncludeFile {
fn emit_lint(&self, cx: &LateContext<'_>, span: Span) {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
LARGE_INCLUDE_FILE,
span,
"attempted to include a large file",
|diag| {
diag.note(format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
));
},
);
}
}
impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Lit(lit) = &expr.kind
@ -66,19 +85,33 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
LARGE_INCLUDE_FILE,
expr.span.source_callsite(),
"attempted to include a large file",
|diag| {
diag.note(format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
));
},
);
self.emit_lint(cx, expr.span.source_callsite());
}
}
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
if !attr.span.from_expansion()
// Currently, rustc limits the usage of macro at the top-level of attributes,
// so we don't need to recurse into each level.
&& let AttrKind::Normal(ref normal) = attr.kind
&& let AttrArgs::Eq(_, AttrArgsEq::Hir(ref meta)) = normal.item.args
&& !attr.span.contains(meta.span)
// Since the `include_str` is already expanded at this point, we can only take the
// whole attribute snippet and then modify for our suggestion.
&& let Some(snippet) = snippet_opt(cx, attr.span)
// We cannot remove this because a `#[doc = include_str!("...")]` attribute can
// occupy several lines.
&& let Some(start) = snippet.find('[')
&& let Some(end) = snippet.rfind(']')
&& let snippet = &snippet[start + 1..end]
// We check that the expansion actually comes from `include_str!` and not just from
// another macro.
&& let Some(sub_snippet) = snippet.trim().strip_prefix("doc")
&& let Some(sub_snippet) = sub_snippet.trim().strip_prefix("=")
&& let sub_snippet = sub_snippet.trim()
&& (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!"))
{
self.emit_lint(cx, attr.span);
}
}
}

View File

@ -629,7 +629,7 @@ fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
.filter_by_name_unhygienic(is_empty)
.any(|item| is_is_empty(cx, item))
}),
ty::Alias(ty::Projection, ref proj) => has_is_empty_impl(cx, proj.def_id),
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),
ty::Adt(id, _) => has_is_empty_impl(cx, id.did()),
ty::Array(..) | ty::Slice(..) | ty::Str => true,
_ => false,

View File

@ -73,6 +73,7 @@
mod absolute_paths;
mod almost_complete_range;
mod approx_const;
mod arbitrary_source_item_ordering;
mod arc_with_non_send_sync;
mod as_conversions;
mod asm_syntax;
@ -400,6 +401,7 @@
use clippy_utils::macros::FormatArgsStorage;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use utils::attr_collector::{AttrCollector, AttrStorage};
/// Register all pre expansion lints
///
@ -464,6 +466,7 @@ pub(crate) enum LintCategory {
#[cfg(feature = "internal")]
Internal,
}
#[allow(clippy::enum_glob_use)]
use LintCategory::*;
@ -585,6 +588,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
))
});
let attr_storage = AttrStorage::default();
let attrs = attr_storage.clone();
store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone())));
// all the internal lints
#[cfg(feature = "internal")]
{
@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| {
Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new())
});
store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons));
}
store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe));
@ -795,7 +803,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf)));
let attrs = attr_storage.clone();
store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone())));
store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf)));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
@ -953,5 +962,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

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

View File

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
use hir::intravisit::{Visitor, walk_expr};
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node};
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
use rustc_ast::Label;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use super::INFINITE_LOOP;
@ -25,13 +26,7 @@ pub(super) fn check<'tcx>(
return;
};
// Or, its parent function is already returning `Never`
if matches!(
parent_fn_ret,
FnRetTy::Return(hir::Ty {
kind: hir::TyKind::Never,
..
})
) {
if is_never_return(parent_fn_ret) {
return;
}
@ -69,6 +64,16 @@ pub(super) fn check<'tcx>(
fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match parent_node {
// Skip `Coroutine` closures, these are the body of `async fn`, not async closures.
// This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
Node::Expr(Expr {
kind:
ExprKind::Closure(hir::Closure {
kind: hir::ClosureKind::Coroutine(_),
..
}),
..
}) => (),
Node::Item(hir::Item {
kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _),
..
@ -143,3 +148,41 @@ fn visit_expr(&mut self, ex: &'hir Expr<'_>) {
}
}
}
/// 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,
ids: HirIdSet::default(),
def_ids: DefIdMap::default(),
skip: false,
};
var_visitor.visit_expr(cond);
if var_visitor.skip {
if var_visitor.visit_expr(cond).is_break() {
return;
}
let used_in_condition = &var_visitor.ids;
@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
ids: HirIdSet,
def_ids: DefIdMap<bool>,
skip: bool,
}
impl<'tcx> VarCollectorVisitor<'_, 'tcx> {
@ -104,11 +101,15 @@ fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
}
impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
type Result = ControlFlow<()>;
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result {
match ex.kind {
ExprKind::Path(_) => self.insert_def_id(ex),
ExprKind::Path(_) => {
self.insert_def_id(ex);
ControlFlow::Continue(())
},
// If there is any function/method call… we just stop analysis
ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
ExprKind::Call(..) | ExprKind::MethodCall(..) => ControlFlow::Break(()),
_ => walk_expr(self, ex),
}

View File

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

View File

@ -111,8 +111,7 @@ fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>)
} else {
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);
}
@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de
.tcx
.opaque_captured_lifetimes(opaque_def_id)
.iter()
.filter(|&(lifetime, _)| match *lifetime {
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true,
_ => false,
.filter(|&(lifetime, _)| {
matches!(
*lifetime,
ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _)
)
})
.count();
num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes
}
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
if let Some(Expr {
if let Some(&Expr {
kind: ExprKind::Closure(&Closure { kind, body, .. }),
..
}) = block.expr

View File

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

View File

@ -68,7 +68,7 @@ fn check_local(&mut self, cx: &LateContext<'_>, local: &LetStmt<'_>) {
&& let Some(init) = local.init
&& !init.span.from_expansion()
&& let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind
&& seg.ident.name == sym!(build_hasher)
&& seg.ident.name.as_str() == "build_hasher"
&& let Node::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id)
&& let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.hir_id)
@ -96,7 +96,7 @@ fn check_local(&mut self, cx: &LateContext<'_>, local: &LetStmt<'_>) {
&& let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id)
&& !finish_expr.span.from_expansion()
&& let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind
&& seg.ident.name == sym!(finish)
&& seg.ident.name.as_str() == "finish"
&& self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
{

View File

@ -105,7 +105,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
check_is_ascii(cx, macro_call.span, recv, &range, None);
}
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
&& path.ident.name == sym!(contains)
&& path.ident.name.as_str() == "contains"
&& let Some(higher::Range {
start: Some(start),
end: Some(end),

View File

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

View File

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

View File

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

View File

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

View File

@ -247,7 +247,10 @@ fn get_std_enum_variant<'tcx>(
let states = match self {
Self::Wild => return None,
Self::Other => {
*self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other)));
*self = Self::StdEnum(
cx.arena
.alloc_from_iter(std::iter::repeat_with(|| Self::Other).take(adt.variants().len())),
);
let Self::StdEnum(x) = self else {
unreachable!();
};

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::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name,
ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
ExprKind::Closure(&Closure { body, .. }) => {
let body = cx.tcx.hir().body(body);
ExprKind::Closure(Closure { body, .. }) => {
let body = cx.tcx.hir().body(*body);
let closure_expr = peel_blocks(body.value);
match closure_expr.kind {
ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => {
@ -234,12 +234,12 @@ fn hir(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, filter_param_id: HirId) -
// the latter only calls `effect` once
let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span);
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) {
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" {
Some(Self::IsSome {
receiver,
side_effect_expr_span,
})
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) {
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" {
Some(Self::IsOk {
receiver,
side_effect_expr_span,

View File

@ -1,6 +1,7 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{find_binding_init, path_to_local};
use clippy_utils::macros::{is_assert_macro, root_macro_call};
use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local};
use rustc_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
return;
}
if let Some(parent) = get_parent_expr(cx, expr) {
if let Some(parent) = get_parent_expr(cx, parent) {
if is_inside_always_const_context(cx.tcx, expr.hir_id)
&& let Some(macro_call) = root_macro_call(parent.span)
&& is_assert_macro(cx, macro_call.def_id)
{
return;
}
}
}
let init_expr = expr_or_init(cx, receiver);
if !receiver.span.eq_ctxt(init_expr.span) {
return;

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

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_saturating_arithmetic;
mod manual_str_repeat;
mod manual_try_fold;
mod map_all_any_identity;
mod map_clone;
mod map_collect_result_unit;
mod map_err_ignore;
mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod map_with_unused_argument_over_ranges;
mod mut_mutex_lock;
mod needless_as_bytes;
mod needless_character_iteration;
mod needless_collect;
mod needless_option_as_deref;
@ -4166,6 +4169,90 @@
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
}
declare_clippy_lint! {
/// ### What it does
/// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`.
///
/// ### Why is this bad?
/// The `len()` and `is_empty()` methods are also directly available on strings, and they
/// return identical results. In particular, `len()` on a string returns the number of
/// bytes.
///
/// ### Example
/// ```
/// let len = "some string".as_bytes().len();
/// let b = "some string".as_bytes().is_empty();
/// ```
/// Use instead:
/// ```
/// let len = "some string".len();
/// let b = "some string".is_empty();
/// ```
#[clippy::version = "1.84.0"]
pub NEEDLESS_AS_BYTES,
complexity,
"detect useless calls to `as_bytes()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`.
///
/// ### Why is this bad?
/// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`.
///
/// ### Example
/// ```
/// # let mut v = [""];
/// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a);
/// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity);
/// ```
/// Use instead:
/// ```
/// # let mut v = [""];
/// let e1 = v.iter().all(|s| s.is_empty());
/// let e2 = v.iter().any(|s| s.is_empty());
/// ```
#[clippy::version = "1.84.0"]
pub MAP_ALL_ANY_IDENTITY,
complexity,
"combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call"
}
declare_clippy_lint! {
/// ### What it does
///
/// Checks for `Iterator::map` over ranges without using the parameter which
/// could be more clearly expressed using `std::iter::repeat(...).take(...)`
/// or `std::iter::repeat_n`.
///
/// ### Why is this bad?
///
/// It expresses the intent more clearly to `take` the correct number of times
/// from a generating function than to apply a closure to each number in a
/// range only to discard them.
///
/// ### Example
///
/// ```no_run
/// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect();
/// ```
/// Use instead:
/// ```no_run
/// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect();
/// ```
///
/// ### Known Issues
///
/// This lint may suggest replacing a `Map<Range>` with a `Take<RepeatWith>`.
/// The former implements some traits that the latter does not, such as
/// `DoubleEndedIterator`.
#[clippy::version = "1.84.0"]
pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
restriction,
"map of a trivial closure (not dependent on parameter) over a range"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4327,6 +4414,9 @@ pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
NEEDLESS_CHARACTER_ITERATION,
MANUAL_INSPECT,
UNNECESSARY_MIN_OR_MAX,
NEEDLESS_AS_BYTES,
MAP_ALL_ANY_IDENTITY,
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -4534,7 +4624,8 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
("all", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, true);
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => {
iter_overeager_cloned::check(
cx,
expr,
@ -4543,6 +4634,11 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
);
},
Some(("map", _, [map_arg], _, map_call_span)) => {
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all");
},
_ => {},
}
},
("and_then", [arg]) => {
@ -4571,6 +4667,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
{
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 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
unit_hash::check(cx, expr, recv, arg);
},
("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
match method_call(recv) {
Some(("as_bytes", prev_recv, [], _, _)) => {
needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span);
},
Some(("as_str", recv, [], as_str_span, _)) => {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
},
_ => {},
}
is_empty::check(cx, expr, recv);
},
@ -4795,6 +4900,11 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
);
}
},
("len", []) => {
if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {
needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span);
}
},
("lock", []) => {
mut_mutex_lock::check(cx, expr, recv, span);
},
@ -4802,6 +4912,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if name == "map" {
unused_enumerate_index::check(cx, expr, recv, m_arg);
map_clone::check(cx, expr, recv, m_arg, &self.msrv);
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span);
match method_call(recv) {
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv);
@ -5182,7 +5293,6 @@ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
}
#[rustfmt::skip]
#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")]
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),

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 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// Check function calls on our collection
if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
if args.is_empty()
&& method_name.ident.name == sym!(collect)
&& method_name.ident.name.as_str() == "collect"
&& is_trait_method(self.cx, expr, sym::Iterator)
{
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));

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

View File

@ -1,13 +1,15 @@
use super::utils::clone_or_copy_needed;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_copy;
use clippy_utils::usage::mutated_variables;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_middle::query::Key;
use rustc_middle::ty;
use rustc_span::sym;
@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
ControlFlow::Continue(Descend::Yes)
}
});
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering {
// Check if the closure is .filter_map(|x| Some(x))
if name == "filter_map"
&& let hir::ExprKind::Call(expr, args) = body.value.kind
&& is_res_lang_ctor(cx, path_res(cx, expr), OptionSome)
&& arg_id.ty_def_id() == args[0].hir_id().ty_def_id()
&& let hir::ExprKind::Path(_) = args[0].kind
{
span_lint_and_sugg(
cx,
UNNECESSARY_FILTER_MAP,
expr.span,
format!("{name} is unnecessary"),
"try removing the filter_map",
String::new(),
Applicability::MaybeIncorrect,
);
}
if name == "filter_map" { "map" } else { "map(..).next()" }
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
match cx.typeck_results().expr_ty(body.value).kind() {
@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
} else {
return;
};
span_lint(
span_lint_and_sugg(
cx,
if name == "filter_map" {
UNNECESSARY_FILTER_MAP
@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
UNNECESSARY_FIND_MAP
},
expr.span,
format!("this `.{name}` can be written more simply using `.{sugg}`"),
format!("this `.{name}` can be written more simply"),
"try instead",
sugg.to_string(),
Applicability::MaybeIncorrect,
);
}
}
@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
(true, true)
},
hir::ExprKind::MethodCall(segment, recv, [arg], _) => {
if segment.ident.name == sym!(then_some)
if segment.ident.name.as_str() == "then_some"
&& cx.typeck_results().expr_ty(recv).is_bool()
&& path_to_local_id(arg, arg_id)
{

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 @ [_], _) => {
if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) {
if path.ident.name == sym!(max) {
if path.ident.name.as_str() == "max" {
fetch_const(cx, Some(receiver), args, MinMax::Max)
} else if path.ident.name == sym!(min) {
} else if path.ident.name.as_str() == "min" {
fetch_const(cx, Some(receiver), args, MinMax::Min)
} else {
None

View File

@ -12,11 +12,13 @@
use clippy_utils::source::SpanRangeExt;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::Visibility;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::symbol::kw;
use rustc_span::{Span, sym};
declare_clippy_lint! {
@ -110,6 +112,21 @@ fn check_missing_docs_attrs(
return;
}
if let Some(parent_def_id) = cx.tcx.opt_parent(def_id.to_def_id())
&& let DefKind::AnonConst
| DefKind::AssocConst
| DefKind::AssocFn
| DefKind::Closure
| DefKind::Const
| DefKind::Fn
| DefKind::InlineConst
| DefKind::Static { .. }
| DefKind::SyntheticCoroutineBody = cx.tcx.def_kind(parent_def_id)
{
// Nested item has no generated documentation, so it doesn't need to be documented.
return;
}
let has_doc = attrs
.iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()))
@ -184,8 +201,12 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
}
}
},
hir::ItemKind::Const(..)
| hir::ItemKind::Enum(..)
hir::ItemKind::Const(..) => {
if it.ident.name == kw::Underscore {
note_prev_span_then_ret!(self.prev_span, it.span);
}
},
hir::ItemKind::Enum(..)
| hir::ItemKind::Macro(..)
| hir::ItemKind::Mod(..)
| hir::ItemKind::Static(..)

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) {
has_debug_struct = true;
} else if path.ident.name == sym!(finish_non_exhaustive)
} else if path.ident.name.as_str() == "finish_non_exhaustive"
&& is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct)
{
has_finish_non_exhaustive = true;

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) {
if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind
if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind
&& let Some(last_stmt) = loop_block.stmts.last()
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind
&& let ast::ExprKind::Continue(_) = inner_expr.kind
&& let ast::ExprKind::Continue(continue_label) = inner_expr.kind
&& compare_labels(loop_label.as_ref(), continue_label.as_ref())
{
span_lint_and_help(
cx,

View File

@ -347,7 +347,7 @@ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>) {
if let LetStmt {
init: None,
pat:
&Pat {
Pat {
kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None),
..
},
@ -357,7 +357,7 @@ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>) {
&& let Some((_, Node::Stmt(local_stmt))) = parents.next()
&& let Some((_, Node::Block(block))) = parents.next()
{
check(cx, local, local_stmt, block, binding_id);
check(cx, local, local_stmt, block, *binding_id);
}
}
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::def_id::{DefId, DefIdMap};
use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, BoundPolarity, WherePredicate};
use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicate};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ClauseKind, PredicatePolarity};
use rustc_session::declare_lint_pass;

View File

@ -183,7 +183,7 @@ fn check_fn(
.iter()
.zip(fn_sig.inputs())
.zip(body.params)
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
.filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
.peekable();
if it.peek().is_none() {
return;

View File

@ -159,8 +159,12 @@ fn check_no_effect(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
// Remove `impl Future<Output = T>` to get `T`
if cx.tcx.ty_is_opaque_future(ret_ty)
&& let Some(true_ret_ty) =
cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty)
&& let Some(true_ret_ty) = cx
.tcx
.infer_ctxt()
.build(cx.typing_mode())
.err_ctxt()
.get_impl_future_output_ty(ret_ty)
{
ret_ty = true_ret_ty;
}

View File

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

View File

@ -43,12 +43,12 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
match &expr.kind {
ExprKind::MethodCall(path, func, [param], _) => {
if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def()
&& ((path.ident.name == sym!(mode)
&& ((path.ident.name.as_str() == "mode"
&& matches!(
cx.tcx.get_diagnostic_name(adt.did()),
Some(sym::FsOpenOptions | sym::DirBuilder)
))
|| (path.ident.name == sym!(set_mode)
|| (path.ident.name.as_str() == "set_mode"
&& cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
&& let ExprKind::Lit(_) = param.kind
&& param.span.eq_ctxt(expr.span)

View File

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

View File

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

View File

@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>(
if path_segment.ident.name == sym::offset {
return Some((arg_0, arg_1, Method::Offset));
}
if path_segment.ident.name == sym!(wrapping_offset) {
if path_segment.ident.name.as_str() == "wrapping_offset" {
return Some((arg_0, arg_1, Method::WrappingOffset));
}
}

View File

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

View File

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

View File

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

View File

@ -101,17 +101,21 @@ fn semicolon_inside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail:
);
}
fn semicolon_outside_block(
&self,
cx: &LateContext<'_>,
block: &Block<'_>,
tail_stmt_expr: &Expr<'_>,
semi_span: Span,
) {
fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) {
let insert_span = block.span.with_lo(block.span.hi());
// account for macro calls
let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span);
let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi());
// For macro call semicolon statements (`mac!();`), the statement's span does not actually
// include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon
// based on a source snippet.
// (Does not use `stmt_span` as that requires `.from_expansion()` to return true,
// which is not the case for e.g. `line!();` and `asm!();`)
let Some(remove_span) = cx
.sess()
.source_map()
.mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite())
else {
return;
};
if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) {
return;
@ -150,13 +154,12 @@ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
};
let &Stmt {
kind: StmtKind::Semi(expr),
span,
..
} = stmt
else {
return;
};
self.semicolon_outside_block(cx, block, expr, span);
self.semicolon_outside_block(cx, block, expr);
},
StmtKind::Semi(Expr {
kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),

View File

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

View File

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

View File

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

View File

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

View File

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

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