Merge commit '0621446356' into clippy-subtree-update
This commit is contained in:
parent
ed892e72dd
commit
ff428d91c2
746 changed files with 13925 additions and 7188 deletions
2
.github/workflows/clippy_changelog.yml
vendored
2
.github/workflows/clippy_changelog.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- name: Check Changelog
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR_NUMBER" | \
|
||||
body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | \
|
||||
python -c "import sys, json; print(json.load(sys.stdin)['body'])")
|
||||
output=$(awk '/^changelog:\s*\S/ && !/changelog: \[.*\]: your change/' <<< "$body" | sed "s/changelog:\s*//g")
|
||||
if [ -z "$output" ]; then
|
||||
|
|
|
|||
2
.github/workflows/clippy_mq.yml
vendored
2
.github/workflows/clippy_mq.yml
vendored
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
run: cargo test --features internal -- --skip dogfood
|
||||
|
||||
- name: Test clippy_lints
|
||||
run: cargo test --features internal
|
||||
run: cargo test
|
||||
working-directory: clippy_lints
|
||||
|
||||
- name: Test clippy_utils
|
||||
|
|
|
|||
2
.github/workflows/clippy_pr.yml
vendored
2
.github/workflows/clippy_pr.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
run: cargo test --features internal
|
||||
|
||||
- name: Test clippy_lints
|
||||
run: cargo test --features internal
|
||||
run: cargo test
|
||||
working-directory: clippy_lints
|
||||
|
||||
- name: Test clippy_utils
|
||||
|
|
|
|||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
|
|
@ -8,6 +8,10 @@ on:
|
|||
tags:
|
||||
- rust-1.**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TARGET_BRANCH: 'gh-pages'
|
||||
SHA: '${{ github.sha }}'
|
||||
|
|
|
|||
4
.github/workflows/lintcheck.yml
vendored
4
.github/workflows/lintcheck.yml
vendored
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
|
||||
- name: Run lintcheck
|
||||
if: steps.cache-json.outputs.cache-hit != 'true'
|
||||
run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
|
||||
run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
|
||||
|
||||
- name: Upload base JSON
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
@ -97,7 +97,7 @@ jobs:
|
|||
run: cargo build --manifest-path=lintcheck/Cargo.toml
|
||||
|
||||
- name: Run lintcheck
|
||||
run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
|
||||
run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
|
||||
|
||||
- name: Upload head JSON
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
|
|||
70
CHANGELOG.md
70
CHANGELOG.md
|
|
@ -6,11 +6,68 @@ document.
|
|||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[609cd310...master](https://github.com/rust-lang/rust-clippy/compare/609cd310...master)
|
||||
[3e3715c3...master](https://github.com/rust-lang/rust-clippy/compare/3e3715c3...master)
|
||||
|
||||
## Rust 1.86
|
||||
|
||||
Current stable, released 2025-04-03
|
||||
|
||||
[View all 108 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-12-27T15%3A11%3A38Z..2025-02-06T13%3A57%3A58Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
* Added [`unneeded_struct_pattern`] to `style` [#13465](https://github.com/rust-lang/rust-clippy/pull/13465)
|
||||
* Added [`doc_overindented_list_items`] to `style` [#13711](https://github.com/rust-lang/rust-clippy/pull/13711)
|
||||
* Added [`manual_ok_err`] to `complexity` [#13740](https://github.com/rust-lang/rust-clippy/pull/13740)
|
||||
* Added [`non_std_lazy_statics`] to `pedantic` [#13770](https://github.com/rust-lang/rust-clippy/pull/13770)
|
||||
* Added [`manual_repeat_n`] to `style` [#13858](https://github.com/rust-lang/rust-clippy/pull/13858)
|
||||
* Added [`manual_option_as_slice`] to `complexity` [#13901](https://github.com/rust-lang/rust-clippy/pull/13901)
|
||||
* Added [`double_ended_iterator_last`] to `perf` [#13922](https://github.com/rust-lang/rust-clippy/pull/13922)
|
||||
* Added [`useless_nonzero_new_unchecked`] to `complexity` [#13993](https://github.com/rust-lang/rust-clippy/pull/13993)
|
||||
* Added [`sliced_string_as_bytes`] to `perf` [#14002](https://github.com/rust-lang/rust-clippy/pull/14002)
|
||||
* Added [`unnecessary_semicolon`] to `pedantic` [#14032](https://github.com/rust-lang/rust-clippy/pull/14032)
|
||||
* Added [`return_and_then`] to `restriction` [#14051](https://github.com/rust-lang/rust-clippy/pull/14051)
|
||||
* Added [`manual_slice_fill`] to `style` [#14082](https://github.com/rust-lang/rust-clippy/pull/14082)
|
||||
* Added [`precedence_bits`] to `restriction` [#14115](https://github.com/rust-lang/rust-clippy/pull/14115)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`redundant_locals`] to `suspicious` (from `correctness`, now warn-by-default)
|
||||
[#13747](https://github.com/rust-lang/rust-clippy/pull/13747)
|
||||
* Moved [`format_push_string`] to `pedantic` (from `restriction`)
|
||||
[#13894](https://github.com/rust-lang/rust-clippy/pull/13894)
|
||||
* Moved [`format_collect`] to `pedantic` (from `perf`, now allow-by-default)
|
||||
[#13894](https://github.com/rust-lang/rust-clippy/pull/13894)
|
||||
* Moved [`mutex_integer`] to `restriction` (from `nursery`) [#14110](https://github.com/rust-lang/rust-clippy/pull/14110)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Add `lint-inconsistent-struct-field-initializers` configuration option to [`inconsistent_struct_constructor`]
|
||||
[#13737](https://github.com/rust-lang/rust-clippy/pull/13737)
|
||||
* [`len_zero`] now also triggers if deref target implements `is_empty()`
|
||||
[#13871](https://github.com/rust-lang/rust-clippy/pull/13871)
|
||||
* [`obfuscated_if_else`] now also triggers for the `.then(..).unwrap_or(..)` pattern
|
||||
[#14021](https://github.com/rust-lang/rust-clippy/pull/14021)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`trailing_empty_array`] no longer triggers in tests [#13844](https://github.com/rust-lang/rust-clippy/pull/13844)
|
||||
* [`missing_const_for_fn`] no longer triggers in tests [#13945](https://github.com/rust-lang/rust-clippy/pull/13945)
|
||||
* [`significant_drop_in_scrutinee`]: do not falsely warn for temporaries created by `.await` expansion
|
||||
[#13985](https://github.com/rust-lang/rust-clippy/pull/13985)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`borrow_interior_mutable_const`] Fix an ICE that can occur when taking a reference to a tuple/`struct` field of an
|
||||
interior mutable `const` [#13877](https://github.com/rust-lang/rust-clippy/pull/13877)
|
||||
|
||||
### Others
|
||||
|
||||
* Clippy now uses Rust edition 2024 [#13751](https://github.com/rust-lang/rust-clippy/pull/13751)
|
||||
|
||||
## Rust 1.85
|
||||
|
||||
Current stable, released 2025-02-20
|
||||
Released 2025-02-20
|
||||
|
||||
[View all 72 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-11-15T19%3A31%3A08Z..2024-12-26T13%3A59%3A48Z+base%3Amaster)
|
||||
|
||||
|
|
@ -5516,6 +5573,7 @@ Released 2018-09-13
|
|||
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
|
||||
[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts
|
||||
[`cfg_not_test`]: https://rust-lang.github.io/rust-clippy/master/index.html#cfg_not_test
|
||||
[`char_indices_as_byte_indices`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_indices_as_byte_indices
|
||||
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
|
||||
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
|
||||
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
|
||||
|
|
@ -5681,6 +5739,7 @@ Released 2018-09-13
|
|||
[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
|
||||
[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
|
||||
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
|
||||
[`ignore_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignore_without_reason
|
||||
[`ignored_unit_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignored_unit_patterns
|
||||
[`impl_hash_borrow_with_str_and_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_hash_borrow_with_str_and_bytes
|
||||
[`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params
|
||||
|
|
@ -5783,12 +5842,14 @@ Released 2018-09-13
|
|||
[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe
|
||||
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
|
||||
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
|
||||
[`manual_abs_diff`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff
|
||||
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
|
||||
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
|
||||
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
|
||||
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
|
||||
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
|
||||
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
|
||||
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
|
||||
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
|
||||
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
|
||||
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
|
||||
|
|
@ -6055,6 +6116,7 @@ Released 2018-09-13
|
|||
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
|
||||
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
|
||||
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
|
||||
[`redundant_test_prefix`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_test_prefix
|
||||
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
|
||||
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
|
||||
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
|
||||
|
|
@ -6156,6 +6218,7 @@ Released 2018-09-13
|
|||
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
|
||||
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
|
||||
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
|
||||
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
|
||||
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
|
||||
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
|
||||
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
|
||||
|
|
@ -6346,6 +6409,7 @@ Released 2018-09-13
|
|||
[`await-holding-invalid-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#await-holding-invalid-types
|
||||
[`cargo-ignore-publish`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cargo-ignore-publish
|
||||
[`check-incompatible-msrv-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-incompatible-msrv-in-tests
|
||||
[`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers
|
||||
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
||||
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
|
||||
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
|
||||
|
|
@ -6362,7 +6426,7 @@ Released 2018-09-13
|
|||
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
|
||||
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
|
||||
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
|
||||
[`lint-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-inconsistent-struct-field-initializers
|
||||
[`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code
|
||||
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
|
||||
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
|
||||
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
# begin autogenerated version
|
||||
version = "0.1.87"
|
||||
version = "0.1.88"
|
||||
# end autogenerated version
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
|
|
@ -27,6 +27,7 @@ clippy_config = { path = "clippy_config" }
|
|||
clippy_lints = { path = "clippy_lints" }
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
clippy_lints_internal = { path = "clippy_lints_internal", optional = true }
|
||||
tempfile = { version = "3.3", optional = true }
|
||||
termize = "0.1"
|
||||
color-print = "0.3.4"
|
||||
|
|
@ -43,7 +44,7 @@ walkdir = "2.3"
|
|||
filetime = "0.2.9"
|
||||
itertools = "0.12"
|
||||
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
|
||||
rinja = { version = "0.3", default-features = false, features = ["config"] }
|
||||
askama = { version = "0.13", default-features = false, features = ["alloc", "config", "derive"] }
|
||||
|
||||
# UI test dependencies
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
|
|
@ -58,8 +59,8 @@ tokio = { version = "1", features = ["io-util"] }
|
|||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
|
||||
[features]
|
||||
integration = ["tempfile"]
|
||||
internal = ["clippy_lints/internal", "tempfile"]
|
||||
integration = ["dep:tempfile"]
|
||||
internal = ["dep:clippy_lints_internal", "dep:tempfile"]
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
# This package uses #[feature(rustc_private)]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
- [Updating the Changelog](development/infrastructure/changelog_update.md)
|
||||
- [Release a New Version](development/infrastructure/release.md)
|
||||
- [The Clippy Book](development/infrastructure/book.md)
|
||||
- [Benchmarking Clippy](development/infrastructure/benchmarking.md)
|
||||
- [Proposals](development/proposals/README.md)
|
||||
- [Roadmap 2021](development/proposals/roadmap-2021.md)
|
||||
- [Syntax Tree Patterns](development/proposals/syntax-tree-patterns.md)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ struct A;
|
|||
impl A {
|
||||
pub fn fo(&self) {}
|
||||
pub fn foo(&self) {}
|
||||
//~^ foo_functions
|
||||
pub fn food(&self) {}
|
||||
}
|
||||
|
||||
|
|
@ -106,12 +107,14 @@ impl A {
|
|||
trait B {
|
||||
fn fo(&self) {}
|
||||
fn foo(&self) {}
|
||||
//~^ foo_functions
|
||||
fn food(&self) {}
|
||||
}
|
||||
|
||||
// Plain functions
|
||||
fn fo() {}
|
||||
fn foo() {}
|
||||
//~^ foo_functions
|
||||
fn food() {}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -122,17 +125,24 @@ fn main() {
|
|||
}
|
||||
```
|
||||
|
||||
Now we can run the test with `TESTNAME=foo_functions cargo uibless`, currently
|
||||
this test is meaningless though.
|
||||
Note that we are adding comment annotations with the name of our lint to mark
|
||||
lines where we expect an error. Except for very specific situations
|
||||
(`//@check-pass`), at least one error marker must be present in a test file for
|
||||
it to be accepted.
|
||||
|
||||
Once we have implemented our lint we can run `TESTNAME=foo_functions cargo
|
||||
uibless` to generate the `.stderr` file. If our lint makes use of structured
|
||||
suggestions then this command will also generate the corresponding `.fixed`
|
||||
file.
|
||||
|
||||
While we are working on implementing our lint, we can keep running the UI test.
|
||||
That allows us to check if the output is turning into what we want by checking the
|
||||
`.stderr` file that gets updated on every test run.
|
||||
|
||||
Running `TESTNAME=foo_functions cargo uitest` should pass on its own. When we
|
||||
commit our lint, we need to commit the generated `.stderr` files, too. In
|
||||
general, you should only commit files changed by `cargo bless` for the
|
||||
specific lint you are creating/editing.
|
||||
Once we have implemented our lint running `TESTNAME=foo_functions cargo uitest`
|
||||
should pass on its own. When we commit our lint, we need to commit the generated
|
||||
`.stderr` and if applicable `.fixed` files, too. In general, you should only
|
||||
commit files changed by `cargo bless` for the specific lint you are creating/editing.
|
||||
|
||||
> _Note:_ you can run multiple test files by specifying a comma separated list:
|
||||
> `TESTNAME=foo_functions,test2,test3`.
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ following:
|
|||
|
||||
First, take note of the toolchain
|
||||
[override](https://rust-lang.github.io/rustup/overrides.html) in
|
||||
`/rust-toolchain`. We will use this override to install Clippy into the right
|
||||
`/rust-toolchain.toml`. We will use this override to install Clippy into the right
|
||||
toolchain.
|
||||
|
||||
> Tip: You can view the active toolchain for the current directory with `rustup
|
||||
|
|
|
|||
|
|
@ -159,19 +159,21 @@ paths for Clippy can be found in [paths.rs][paths]
|
|||
To check if our type defines a method called `some_method`:
|
||||
|
||||
```rust
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::return_ty;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use clippy_utils::{sym, return_ty};
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
|
||||
// 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.as_str() == "some_method"
|
||||
//
|
||||
// Add `some_method` to `clippy_utils::sym` if it's not already there
|
||||
&& impl_item.ident.name == sym::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`
|
||||
&& is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
|
||||
&& is_type_lang_item(cx, return_ty(cx, impl_item.hir_id), LangItem::String)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ lint involves some boilerplate code.
|
|||
|
||||
A lint type is the category of items and expressions in which your lint focuses on.
|
||||
|
||||
As of the writing of this documentation update, there are 12 _types_ of lints
|
||||
As of the writing of this documentation update, there are 11 _types_ of lints
|
||||
besides the numerous standalone lints living under `clippy_lints/src/`:
|
||||
|
||||
- `cargo`
|
||||
|
|
@ -23,7 +23,6 @@ besides the numerous standalone lints living under `clippy_lints/src/`:
|
|||
- `transmute`
|
||||
- `types`
|
||||
- `unit_types`
|
||||
- `utils / internal` (Clippy internal lints)
|
||||
|
||||
These types group together lints that share some common behaviors. For instance,
|
||||
`functions` groups together lints that deal with some aspects of functions in
|
||||
|
|
|
|||
55
book/src/development/infrastructure/benchmarking.md
Normal file
55
book/src/development/infrastructure/benchmarking.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Benchmarking Clippy
|
||||
|
||||
Benchmarking Clippy is similar to using our Lintcheck tool, in fact, it even
|
||||
uses the same tool! Just by adding a `--perf` flag it will transform Lintcheck
|
||||
into a very simple but powerful benchmarking tool!
|
||||
|
||||
It requires having the [`perf` tool][perf] installed, as `perf` is what's actually
|
||||
profiling Clippy under the hood.
|
||||
|
||||
The lintcheck `--perf` tool generates a series of `perf.data` in the
|
||||
`target/lintcheck/sources/<package>-<version>` directories. Each `perf.data`
|
||||
corresponds to the package which is contained.
|
||||
|
||||
Lintcheck uses the `-g` flag, meaning that you can get stack traces for richer
|
||||
analysis, including with tools such as [flamegraph][flamegraph-perf]
|
||||
(or [`flamegraph-rs`][flamegraph-rs]).
|
||||
|
||||
Currently, we only measure instruction count, as it's the most reproducible metric
|
||||
and [rustc-perf][rustc-perf] also considers it the main number to focus on.
|
||||
|
||||
## Benchmarking a PR
|
||||
|
||||
Having a benchmarking tool directly implemented into lintcheck gives us the
|
||||
ability to benchmark any given PR just by making a before and after
|
||||
|
||||
Here's the way you can get into any PR, benchmark it, and then benchmark
|
||||
`master`.
|
||||
|
||||
The first `perf.data` will not have any numbers appended, but any subsequent
|
||||
benchmark will be written to `perf.data.number` with a number growing for 0.
|
||||
All benchmarks are compressed so that you can
|
||||
|
||||
```bash
|
||||
git fetch upstream pull/<PR_NUMBER>/head:<BRANCH_NAME>
|
||||
git switch BRANCHNAME
|
||||
|
||||
# Bench
|
||||
cargo lintcheck --perf
|
||||
|
||||
# Get last common commit, checkout that
|
||||
LAST_COMMIT=$(git log BRANCHNAME..master --oneline | tail -1 | cut -c 1-11)
|
||||
git switch -c temporary $LAST_COMMIT
|
||||
|
||||
# We're now on master
|
||||
|
||||
# Bench
|
||||
cargo lintcheck --perf
|
||||
perf diff ./target/lintcheck/sources/CRATE/perf.data ./target/lintcheck/sources/CRATE/perf.data.0
|
||||
```
|
||||
|
||||
|
||||
[perf]: https://perfwiki.github.io/main/
|
||||
[flamegraph-perf]: https://github.com/brendangregg/FlameGraph
|
||||
[flamegraph-rs]: https://github.com/flamegraph-rs/flamegraph
|
||||
[rustc-perf]: https://github.com/rust-lang/rustc-perf
|
||||
|
|
@ -88,9 +88,6 @@ git push upstream stable
|
|||
After updating the `stable` branch, tag the HEAD commit and push it to the
|
||||
Clippy repo.
|
||||
|
||||
> Note: Only push the tag once the Deploy GitHub action of the `beta` branch is
|
||||
> finished. Otherwise the deploy for the tag might fail.
|
||||
|
||||
```bash
|
||||
git tag rust-1.XX.0 # XX should be exchanged with the corresponding version
|
||||
git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ to be run inside the `rust` directory):
|
|||
4. Bump the nightly version in the Clippy repository by running these commands:
|
||||
```bash
|
||||
cargo dev sync update_nightly
|
||||
git commit -m "Bump nightly version -> YYYY-MM-DD" rust-toolchain clippy_utils/README.md
|
||||
git commit -m "Bump nightly version -> YYYY-MM-DD" rust-toolchain.toml clippy_utils/README.md
|
||||
```
|
||||
5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to
|
||||
accelerate the process ping the `@rust-lang/clippy` team in your PR and/or
|
||||
|
|
|
|||
|
|
@ -41,20 +41,23 @@ Update the file with some examples to get started:
|
|||
struct A;
|
||||
impl A {
|
||||
pub fn fo(&self) {}
|
||||
pub fn foo(&self) {} //~ ERROR: function called "foo"
|
||||
pub fn foo(&self) {}
|
||||
//~^ foo_functions
|
||||
pub fn food(&self) {}
|
||||
}
|
||||
|
||||
// Default trait methods
|
||||
trait B {
|
||||
fn fo(&self) {}
|
||||
fn foo(&self) {} //~ ERROR: function called "foo"
|
||||
fn foo(&self) {}
|
||||
//~^ foo_functions
|
||||
fn food(&self) {}
|
||||
}
|
||||
|
||||
// Plain functions
|
||||
fn fo() {}
|
||||
fn foo() {} //~ ERROR: function called "foo"
|
||||
fn foo() {}
|
||||
//~^ foo_functions
|
||||
fn food() {}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -66,19 +69,38 @@ fn main() {
|
|||
```
|
||||
|
||||
Without actual lint logic to emit the lint when we see a `foo` function name,
|
||||
this test will just pass, because no lint will be emitted. However, we can now
|
||||
run the test with the following command:
|
||||
this test will fail, because we expect errors at lines marked with
|
||||
`//~^ foo_functions`. However, we can now run the test with the following command:
|
||||
|
||||
```sh
|
||||
$ TESTNAME=foo_functions cargo uitest
|
||||
```
|
||||
|
||||
Clippy will compile and it will conclude with an `ok` for the tests:
|
||||
Clippy will compile and it will fail complaining it didn't receive any errors:
|
||||
|
||||
```
|
||||
...Clippy warnings and test outputs...
|
||||
test compile_test ... ok
|
||||
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
|
||||
error: diagnostic code `clippy::foo_functions` not found on line 8
|
||||
--> tests/ui/foo_functions.rs:9:10
|
||||
|
|
||||
9 | //~^ foo_functions
|
||||
| ^^^^^^^^^^^^^ expected because of this pattern
|
||||
|
|
||||
|
||||
error: diagnostic code `clippy::foo_functions` not found on line 16
|
||||
--> tests/ui/foo_functions.rs:17:10
|
||||
|
|
||||
17 | //~^ foo_functions
|
||||
| ^^^^^^^^^^^^^ expected because of this pattern
|
||||
|
|
||||
|
||||
error: diagnostic code `clippy::foo_functions` not found on line 23
|
||||
--> tests/ui/foo_functions.rs:24:6
|
||||
|
|
||||
24 | //~^ foo_functions
|
||||
| ^^^^^^^^^^^^^ expected because of this pattern
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
This is normal. After all, we wrote a bunch of Rust code but we haven't really
|
||||
|
|
|
|||
|
|
@ -425,6 +425,33 @@ Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
|
|||
* [`incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv)
|
||||
|
||||
|
||||
## `check-inconsistent-struct-field-initializers`
|
||||
Whether to suggest reordering constructor fields when initializers are present.
|
||||
|
||||
Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
|
||||
suggested code would compile, it can change semantics if the initializer expressions have side effects. The
|
||||
following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
|
||||
|
||||
```rust
|
||||
struct MyStruct {
|
||||
vector: Vec<u32>,
|
||||
length: usize
|
||||
}
|
||||
fn main() {
|
||||
let vector = vec![1,2,3];
|
||||
MyStruct { length: vector.len(), vector};
|
||||
}
|
||||
```
|
||||
|
||||
[from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
|
||||
|
||||
**Default Value:** `false`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
|
||||
|
||||
|
||||
## `check-private-items`
|
||||
Whether to also run the listed lints on private items.
|
||||
|
||||
|
|
@ -613,31 +640,15 @@ The maximum size of the `Err`-variant in a `Result` returned from a function
|
|||
* [`result_large_err`](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err)
|
||||
|
||||
|
||||
## `lint-inconsistent-struct-field-initializers`
|
||||
Whether to suggest reordering constructor fields when initializers are present.
|
||||
|
||||
Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
|
||||
suggested code would compile, it can change semantics if the initializer expressions have side effects. The
|
||||
following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
|
||||
|
||||
```rust
|
||||
struct MyStruct {
|
||||
vector: Vec<u32>,
|
||||
length: usize
|
||||
}
|
||||
fn main() {
|
||||
let vector = vec![1,2,3];
|
||||
MyStruct { length: vector.len(), vector};
|
||||
}
|
||||
```
|
||||
|
||||
[from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
|
||||
## `lint-commented-code`
|
||||
Whether collapsible `if` chains are linted if they contain comments inside the parts
|
||||
that would be collapsed.
|
||||
|
||||
**Default Value:** `false`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
|
||||
* [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if)
|
||||
|
||||
|
||||
## `literal-representation-threshold`
|
||||
|
|
@ -786,6 +797,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
|
||||
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
|
||||
* [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok)
|
||||
* [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff)
|
||||
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
|
||||
* [`manual_c_str_literals`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals)
|
||||
* [`manual_clamp`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp)
|
||||
|
|
@ -793,6 +805,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`manual_flatten`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten)
|
||||
* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
|
||||
* [`manual_is_ascii_check`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check)
|
||||
* [`manual_is_power_of_two`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two)
|
||||
* [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
|
||||
* [`manual_midpoint`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint)
|
||||
* [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive)
|
||||
|
|
@ -1059,7 +1072,8 @@ The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
|
|||
|
||||
|
||||
## `warn-on-all-wildcard-imports`
|
||||
Whether to allow certain wildcard imports (prelude, super in tests).
|
||||
Whether to emit warnings on all wildcard imports, including those from `prelude`, from `super` in tests,
|
||||
or for `pub use` reexports.
|
||||
|
||||
**Default Value:** `false`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
avoid-breaking-exported-api = false
|
||||
|
||||
lint-inconsistent-struct-field-initializers = true
|
||||
check-inconsistent-struct-field-initializers = true
|
||||
|
||||
lint-commented-code = true
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "rustc_lint::context::LintContext::lint"
|
||||
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
|
||||
allow-invalid = true
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "rustc_lint::context::LintContext::span_lint"
|
||||
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
|
||||
allow-invalid = true
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
|
||||
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"
|
||||
allow-invalid = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
# begin autogenerated version
|
||||
version = "0.1.87"
|
||||
version = "0.1.88"
|
||||
# end autogenerated version
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
|
|
|||
|
|
@ -120,12 +120,7 @@ impl ConfError {
|
|||
Self {
|
||||
message: message.into(),
|
||||
suggestion,
|
||||
span: Span::new(
|
||||
file.start_pos + BytePos::from_usize(span.start),
|
||||
file.start_pos + BytePos::from_usize(span.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
),
|
||||
span: span_from_toml_range(file, span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -176,11 +171,61 @@ macro_rules! default_text {
|
|||
};
|
||||
}
|
||||
|
||||
macro_rules! deserialize {
|
||||
($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
|
||||
let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
|
||||
let value_span = raw_value.span();
|
||||
let value = match <$ty>::deserialize(raw_value.into_inner()) {
|
||||
Err(e) => {
|
||||
$errors.push(ConfError::spanned(
|
||||
$file,
|
||||
e.to_string().replace('\n', " ").trim(),
|
||||
None,
|
||||
value_span,
|
||||
));
|
||||
continue;
|
||||
},
|
||||
Ok(value) => value,
|
||||
};
|
||||
(value, value_span)
|
||||
}};
|
||||
|
||||
($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
|
||||
let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
|
||||
let mut disallowed_paths_span = Range {
|
||||
start: usize::MAX,
|
||||
end: usize::MIN,
|
||||
};
|
||||
let mut disallowed_paths = Vec::new();
|
||||
for raw_value in array {
|
||||
let value_span = raw_value.span();
|
||||
let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
|
||||
{
|
||||
Err(e) => {
|
||||
$errors.push(ConfError::spanned(
|
||||
$file,
|
||||
e.to_string().replace('\n', " ").trim(),
|
||||
None,
|
||||
value_span,
|
||||
));
|
||||
continue;
|
||||
},
|
||||
Ok(disallowed_path) => disallowed_path,
|
||||
};
|
||||
disallowed_paths_span = union(&disallowed_paths_span, &value_span);
|
||||
disallowed_path.set_span(span_from_toml_range($file, value_span));
|
||||
disallowed_paths.push(disallowed_path);
|
||||
}
|
||||
(disallowed_paths, disallowed_paths_span)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! define_Conf {
|
||||
($(
|
||||
$(#[doc = $doc:literal])+
|
||||
$(#[conf_deprecated($dep:literal, $new_conf:ident)])?
|
||||
$(#[default_text = $default_text:expr])?
|
||||
$(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
|
||||
$(#[lints($($for_lints:ident),* $(,)?)])?
|
||||
$name:ident: $ty:ty = $default:expr,
|
||||
)*) => {
|
||||
|
|
@ -218,42 +263,46 @@ macro_rules! define_Conf {
|
|||
let mut value_spans = HashMap::new();
|
||||
let mut errors = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
// Declare a local variable for each field available to a configuration file.
|
||||
$(let mut $name = None;)*
|
||||
|
||||
// could get `Field` here directly, but get `String` first for diagnostics
|
||||
while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
|
||||
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
|
||||
let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
|
||||
Err(e) => {
|
||||
let e: FieldError = e;
|
||||
errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
|
||||
continue;
|
||||
}
|
||||
$(Ok(Field::$name) => {
|
||||
Ok(field) => field
|
||||
};
|
||||
|
||||
match field {
|
||||
$(Field::$name => {
|
||||
// Is this a deprecated field, i.e., is `$dep` set? If so, push a warning.
|
||||
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
|
||||
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
|
||||
let value_span = raw_value.span();
|
||||
match <$ty>::deserialize(raw_value.into_inner()) {
|
||||
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
|
||||
Ok(value) => match $name {
|
||||
Some(_) => {
|
||||
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
|
||||
}
|
||||
None => {
|
||||
$name = Some(value);
|
||||
value_spans.insert(name.get_ref().as_str().to_string(), value_span);
|
||||
// $new_conf is the same as one of the defined `$name`s, so
|
||||
// this variable is defined in line 2 of this function.
|
||||
$(match $new_conf {
|
||||
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
|
||||
"duplicate field `", stringify!($new_conf),
|
||||
"` (provided as `", stringify!($name), "`)"
|
||||
), None, name.span())),
|
||||
None => $new_conf = $name.clone(),
|
||||
})?
|
||||
},
|
||||
}
|
||||
let (value, value_span) =
|
||||
deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
|
||||
// Was this field set previously?
|
||||
if $name.is_some() {
|
||||
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
|
||||
continue;
|
||||
}
|
||||
$name = Some(value);
|
||||
value_spans.insert(name.get_ref().as_str().to_string(), value_span);
|
||||
// If this is a deprecated field, was the new field (`$new_conf`) set previously?
|
||||
// Note that `$new_conf` is one of the defined `$name`s.
|
||||
$(match $new_conf {
|
||||
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
|
||||
"duplicate field `", stringify!($new_conf),
|
||||
"` (provided as `", stringify!($name), "`)"
|
||||
), None, name.span())),
|
||||
None => $new_conf = $name.clone(),
|
||||
})?
|
||||
})*
|
||||
// ignore contents of the third_party key
|
||||
Ok(Field::third_party) => drop(map.next_value::<IgnoredAny>())
|
||||
Field::third_party => drop(map.next_value::<IgnoredAny>())
|
||||
}
|
||||
}
|
||||
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
|
||||
|
|
@ -275,6 +324,22 @@ macro_rules! define_Conf {
|
|||
};
|
||||
}
|
||||
|
||||
fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
|
||||
Range {
|
||||
start: cmp::min(x.start, y.start),
|
||||
end: cmp::max(x.end, y.end),
|
||||
}
|
||||
}
|
||||
|
||||
fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
|
||||
Span::new(
|
||||
file.start_pos + BytePos::from_usize(span.start),
|
||||
file.start_pos + BytePos::from_usize(span.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
define_Conf! {
|
||||
/// Which crates to allow absolute paths from
|
||||
#[lints(absolute_paths)]
|
||||
|
|
@ -461,6 +526,7 @@ define_Conf! {
|
|||
)]
|
||||
avoid_breaking_exported_api: bool = true,
|
||||
/// The list of types which may not be held across an await point.
|
||||
#[disallowed_paths_allow_replacements = false]
|
||||
#[lints(await_holding_invalid_type)]
|
||||
await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
|
||||
/// DEPRECATED LINT: BLACKLISTED_NAME.
|
||||
|
|
@ -474,6 +540,26 @@ define_Conf! {
|
|||
/// Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
|
||||
#[lints(incompatible_msrv)]
|
||||
check_incompatible_msrv_in_tests: bool = false,
|
||||
/// Whether to suggest reordering constructor fields when initializers are present.
|
||||
///
|
||||
/// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
|
||||
/// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
|
||||
/// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
|
||||
///
|
||||
/// ```rust
|
||||
/// struct MyStruct {
|
||||
/// vector: Vec<u32>,
|
||||
/// length: usize
|
||||
/// }
|
||||
/// fn main() {
|
||||
/// let vector = vec![1,2,3];
|
||||
/// MyStruct { length: vector.len(), vector};
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
|
||||
#[lints(inconsistent_struct_constructor)]
|
||||
check_inconsistent_struct_field_initializers: bool = false,
|
||||
/// Whether to also run the listed lints on private items.
|
||||
#[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
|
||||
check_private_items: bool = false,
|
||||
|
|
@ -486,9 +572,11 @@ define_Conf! {
|
|||
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
|
||||
cyclomatic_complexity_threshold: u64 = 25,
|
||||
/// The list of disallowed macros, written as fully qualified paths.
|
||||
#[disallowed_paths_allow_replacements = true]
|
||||
#[lints(disallowed_macros)]
|
||||
disallowed_macros: Vec<DisallowedPath> = Vec::new(),
|
||||
/// The list of disallowed methods, written as fully qualified paths.
|
||||
#[disallowed_paths_allow_replacements = true]
|
||||
#[lints(disallowed_methods)]
|
||||
disallowed_methods: Vec<DisallowedPath> = Vec::new(),
|
||||
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
|
||||
|
|
@ -497,6 +585,7 @@ define_Conf! {
|
|||
#[lints(disallowed_names)]
|
||||
disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
|
||||
/// The list of disallowed types, written as fully qualified paths.
|
||||
#[disallowed_paths_allow_replacements = true]
|
||||
#[lints(disallowed_types)]
|
||||
disallowed_types: Vec<DisallowedPath> = Vec::new(),
|
||||
/// The list of words this lint should not consider as identifiers needing ticks. The value
|
||||
|
|
@ -549,25 +638,15 @@ define_Conf! {
|
|||
/// The maximum size of the `Err`-variant in a `Result` returned from a function
|
||||
#[lints(result_large_err)]
|
||||
large_error_threshold: u64 = 128,
|
||||
/// Whether collapsible `if` chains are linted if they contain comments inside the parts
|
||||
/// that would be collapsed.
|
||||
#[lints(collapsible_if)]
|
||||
lint_commented_code: bool = false,
|
||||
/// Whether to suggest reordering constructor fields when initializers are present.
|
||||
/// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers
|
||||
///
|
||||
/// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
|
||||
/// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
|
||||
/// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
|
||||
///
|
||||
/// ```rust
|
||||
/// struct MyStruct {
|
||||
/// vector: Vec<u32>,
|
||||
/// length: usize
|
||||
/// }
|
||||
/// fn main() {
|
||||
/// let vector = vec![1,2,3];
|
||||
/// MyStruct { length: vector.len(), vector};
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
|
||||
#[lints(inconsistent_struct_constructor)]
|
||||
/// Use the `check-inconsistent-struct-field-initializers` configuration instead.
|
||||
#[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
|
||||
lint_inconsistent_struct_field_initializers: bool = false,
|
||||
/// The lower bound for linting decimal literals
|
||||
#[lints(decimal_literal_representation)]
|
||||
|
|
@ -635,6 +714,7 @@ define_Conf! {
|
|||
iter_kv_map,
|
||||
legacy_numeric_constants,
|
||||
lines_filter_map_ok,
|
||||
manual_abs_diff,
|
||||
manual_bits,
|
||||
manual_c_str_literals,
|
||||
manual_clamp,
|
||||
|
|
@ -642,6 +722,7 @@ define_Conf! {
|
|||
manual_flatten,
|
||||
manual_hash_one,
|
||||
manual_is_ascii_check,
|
||||
manual_is_power_of_two,
|
||||
manual_let_else,
|
||||
manual_midpoint,
|
||||
manual_non_exhaustive,
|
||||
|
|
@ -760,7 +841,8 @@ define_Conf! {
|
|||
/// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
|
||||
#[lints(verbose_bit_mask)]
|
||||
verbose_bit_mask_threshold: u64 = 1,
|
||||
/// Whether to allow certain wildcard imports (prelude, super in tests).
|
||||
/// Whether to emit warnings on all wildcard imports, including those from `prelude`, from `super` in tests,
|
||||
/// or for `pub use` reexports.
|
||||
#[lints(wildcard_imports)]
|
||||
warn_on_all_wildcard_imports: bool = false,
|
||||
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
|
||||
|
|
@ -981,7 +1063,23 @@ impl serde::de::Error for FieldError {
|
|||
// set and allows it.
|
||||
use fmt::Write;
|
||||
|
||||
let mut expected = expected.to_vec();
|
||||
let metadata = get_configuration_metadata();
|
||||
let deprecated = metadata
|
||||
.iter()
|
||||
.filter_map(|conf| {
|
||||
if conf.deprecation_reason.is_some() {
|
||||
Some(conf.name.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut expected = expected
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|name| !deprecated.contains(name))
|
||||
.collect::<Vec<_>>();
|
||||
expected.sort_unstable();
|
||||
|
||||
let (rows, column_widths) = calculate_dimensions(&expected);
|
||||
|
|
@ -1064,7 +1162,13 @@ mod tests {
|
|||
fn configs_are_tested() {
|
||||
let mut names: HashSet<String> = crate::get_configuration_metadata()
|
||||
.into_iter()
|
||||
.map(|meta| meta.name.replace('_', "-"))
|
||||
.filter_map(|meta| {
|
||||
if meta.deprecation_reason.is_none() {
|
||||
Some(meta.name.replace('_', "-"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let toml_files = WalkDir::new("../tests")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
rustc::untranslatable_diagnostic
|
||||
)]
|
||||
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_hir;
|
||||
extern crate rustc_middle;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use clippy_utils::def_path_def_ids;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir::PrimTy;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::Span;
|
||||
|
|
@ -21,6 +23,17 @@ pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
|
|||
path: String,
|
||||
reason: Option<String>,
|
||||
replacement: Option<String>,
|
||||
/// Setting `allow_invalid` to true suppresses a warning if `path` does not refer to an existing
|
||||
/// definition.
|
||||
///
|
||||
/// This could be useful when conditional compilation is used, or when a clippy.toml file is
|
||||
/// shared among multiple projects.
|
||||
allow_invalid: bool,
|
||||
/// The span of the `DisallowedPath`.
|
||||
///
|
||||
/// Used for diagnostics.
|
||||
#[serde(skip_serializing)]
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
|
||||
|
|
@ -36,6 +49,8 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<R
|
|||
path: enum_.path().to_owned(),
|
||||
reason: enum_.reason().map(ToOwned::to_owned),
|
||||
replacement: enum_.replacement().map(ToOwned::to_owned),
|
||||
allow_invalid: enum_.allow_invalid(),
|
||||
span: Span::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +65,8 @@ enum DisallowedPathEnum {
|
|||
path: String,
|
||||
reason: Option<String>,
|
||||
replacement: Option<String>,
|
||||
#[serde(rename = "allow-invalid")]
|
||||
allow_invalid: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +75,7 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
|
|||
&self.path
|
||||
}
|
||||
|
||||
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) + use<'_, REPLACEMENT_ALLOWED> {
|
||||
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) {
|
||||
move |diag| {
|
||||
if let Some(replacement) = &self.replacement {
|
||||
diag.span_suggestion(
|
||||
|
|
@ -72,6 +89,14 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn set_span(&mut self, span: Span) {
|
||||
self.span = span;
|
||||
}
|
||||
}
|
||||
|
||||
impl DisallowedPathEnum {
|
||||
|
|
@ -94,20 +119,87 @@ impl DisallowedPathEnum {
|
|||
Self::Simple(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn allow_invalid(&self) -> bool {
|
||||
match &self {
|
||||
Self::WithReason { allow_invalid, .. } => allow_invalid.unwrap_or_default(),
|
||||
Self::Simple(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a map of disallowed items to the reason they were disallowed.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
|
||||
tcx: TyCtxt<'_>,
|
||||
disallowed: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
|
||||
) -> DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> {
|
||||
disallowed
|
||||
.iter()
|
||||
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x))
|
||||
.flat_map(|(name, path, disallowed_path)| {
|
||||
def_path_def_ids(tcx, &path).map(move |id| (id, (name, disallowed_path)))
|
||||
})
|
||||
.collect()
|
||||
disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
|
||||
def_kind_predicate: impl Fn(DefKind) -> bool,
|
||||
predicate_description: &str,
|
||||
allow_prim_tys: bool,
|
||||
) -> (
|
||||
DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
|
||||
FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
|
||||
) {
|
||||
let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
|
||||
let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
|
||||
FxHashMap::default();
|
||||
for disallowed_path in disallowed_paths {
|
||||
let path = disallowed_path.path();
|
||||
let mut resolutions = clippy_utils::def_path_res(tcx, &path.split("::").collect::<Vec<_>>());
|
||||
|
||||
let mut found_def_id = None;
|
||||
let mut found_prim_ty = false;
|
||||
resolutions.retain(|res| match res {
|
||||
Res::Def(def_kind, def_id) => {
|
||||
found_def_id = Some(*def_id);
|
||||
def_kind_predicate(*def_kind)
|
||||
},
|
||||
Res::PrimTy(_) => {
|
||||
found_prim_ty = true;
|
||||
allow_prim_tys
|
||||
},
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if resolutions.is_empty() {
|
||||
let span = disallowed_path.span();
|
||||
|
||||
if let Some(def_id) = found_def_id {
|
||||
tcx.sess.dcx().span_warn(
|
||||
span,
|
||||
format!(
|
||||
"expected a {predicate_description}, found {} {}",
|
||||
tcx.def_descr_article(def_id),
|
||||
tcx.def_descr(def_id)
|
||||
),
|
||||
);
|
||||
} else if found_prim_ty {
|
||||
tcx.sess.dcx().span_warn(
|
||||
span,
|
||||
format!("expected a {predicate_description}, found a primitive type",),
|
||||
);
|
||||
} else if !disallowed_path.allow_invalid {
|
||||
tcx.sess.dcx().span_warn(
|
||||
span,
|
||||
format!("`{path}` does not refer to an existing {predicate_description}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for res in resolutions {
|
||||
match res {
|
||||
Res::Def(_, def_id) => {
|
||||
def_ids.insert(def_id, (path, disallowed_path));
|
||||
},
|
||||
Res::PrimTy(ty) => {
|
||||
prim_tys.insert(ty, (path, disallowed_path));
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(def_ids, prim_tys)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#[allow(unused_extern_crates)]
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_lexer;
|
||||
extern crate rustc_literal_escaper;
|
||||
|
||||
pub mod dogfood;
|
||||
pub mod fmt;
|
||||
|
|
|
|||
|
|
@ -170,7 +170,6 @@ enum DevCommand {
|
|||
"restriction",
|
||||
"cargo",
|
||||
"nursery",
|
||||
"internal",
|
||||
],
|
||||
default_value = "nursery",
|
||||
)]
|
||||
|
|
@ -334,7 +333,7 @@ struct SyncCommand {
|
|||
#[derive(Subcommand)]
|
||||
enum SyncSubcommand {
|
||||
#[command(name = "update_nightly")]
|
||||
/// Update nightly version in rust-toolchain and `clippy_utils`
|
||||
/// Update nightly version in `rust-toolchain.toml` and `clippy_utils`
|
||||
UpdateNightly,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
|
|||
|
||||
println!("Created toolchain {name}, use it in other projects with e.g. `cargo +{name} clippy`");
|
||||
if !standalone {
|
||||
println!("Note: This will need to be re-run whenever the Clippy `rust-toolchain` changes");
|
||||
println!("Note: This will need to be re-run whenever the Clippy `rust-toolchain.toml` changes");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub fn update_nightly() {
|
|||
let date = Utc::now().format("%Y-%m-%d").to_string();
|
||||
replace_region_in_file(
|
||||
UpdateMode::Change,
|
||||
Path::new("rust-toolchain"),
|
||||
Path::new("rust-toolchain.toml"),
|
||||
"# begin autogenerated nightly\n",
|
||||
"# end autogenerated nightly",
|
||||
|res| {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::utils::{UpdateMode, clippy_project_root, exit_with_failure, replace_region_in_file};
|
||||
use aho_corasick::AhoCorasickBuilder;
|
||||
use itertools::Itertools;
|
||||
use rustc_lexer::{LiteralKind, TokenKind, tokenize, unescape};
|
||||
use rustc_lexer::{LiteralKind, TokenKind, tokenize};
|
||||
use rustc_literal_escaper::{Mode, unescape_unicode};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{self, Write};
|
||||
|
|
@ -37,9 +38,8 @@ fn generate_lint_files(
|
|||
deprecated_lints: &[DeprecatedLint],
|
||||
renamed_lints: &[RenamedLint],
|
||||
) {
|
||||
let internal_lints = Lint::internal_lints(lints);
|
||||
let mut usable_lints = Lint::usable_lints(lints);
|
||||
usable_lints.sort_by_key(|lint| lint.name.clone());
|
||||
let mut lints = lints.to_owned();
|
||||
lints.sort_by_key(|lint| lint.name.clone());
|
||||
|
||||
replace_region_in_file(
|
||||
update_mode,
|
||||
|
|
@ -47,7 +47,7 @@ fn generate_lint_files(
|
|||
"[There are over ",
|
||||
" lints included in this crate!]",
|
||||
|res| {
|
||||
write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
|
||||
write!(res, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ fn generate_lint_files(
|
|||
"[There are over ",
|
||||
" lints included in this crate!]",
|
||||
|res| {
|
||||
write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
|
||||
write!(res, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ fn generate_lint_files(
|
|||
"<!-- begin autogenerated links to lint list -->\n",
|
||||
"<!-- end autogenerated links to lint list -->",
|
||||
|res| {
|
||||
for lint in usable_lints
|
||||
for lint in lints
|
||||
.iter()
|
||||
.map(|l| &*l.name)
|
||||
.chain(deprecated_lints.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
|
||||
|
|
@ -86,7 +86,7 @@ fn generate_lint_files(
|
|||
"// begin lints modules, do not remove this comment, it’s used in `update_lints`\n",
|
||||
"// end lints modules, do not remove this comment, it’s used in `update_lints`",
|
||||
|res| {
|
||||
for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
|
||||
for lint_mod in lints.iter().map(|l| &l.module).unique().sorted() {
|
||||
writeln!(res, "mod {lint_mod};").unwrap();
|
||||
}
|
||||
},
|
||||
|
|
@ -95,7 +95,7 @@ fn generate_lint_files(
|
|||
process_file(
|
||||
"clippy_lints/src/declared_lints.rs",
|
||||
update_mode,
|
||||
&gen_declared_lints(internal_lints.iter(), usable_lints.iter()),
|
||||
&gen_declared_lints(lints.iter()),
|
||||
);
|
||||
|
||||
let content = gen_deprecated_lints_test(deprecated_lints);
|
||||
|
|
@ -106,10 +106,9 @@ fn generate_lint_files(
|
|||
}
|
||||
|
||||
pub fn print_lints() {
|
||||
let (lint_list, _, _) = gather_all();
|
||||
let usable_lints = Lint::usable_lints(&lint_list);
|
||||
let usable_lint_count = usable_lints.len();
|
||||
let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
|
||||
let (lints, _, _) = gather_all();
|
||||
let lint_count = lints.len();
|
||||
let grouped_by_lint_group = Lint::by_lint_group(lints.into_iter());
|
||||
|
||||
for (lint_group, mut lints) in grouped_by_lint_group {
|
||||
println!("\n## {lint_group}");
|
||||
|
|
@ -121,7 +120,7 @@ pub fn print_lints() {
|
|||
}
|
||||
}
|
||||
|
||||
println!("there are {usable_lint_count} lints");
|
||||
println!("there are {lint_count} lints");
|
||||
}
|
||||
|
||||
/// Runs the `rename_lint` command.
|
||||
|
|
@ -402,53 +401,53 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
|
|||
}
|
||||
}
|
||||
|
||||
if path.exists() {
|
||||
if let Some(lint) = lints.iter().find(|l| l.name == name) {
|
||||
if lint.module == name {
|
||||
// The lint name is the same as the file, we can just delete the entire file
|
||||
fs::remove_file(path)?;
|
||||
} else {
|
||||
// We can't delete the entire file, just remove the declaration
|
||||
if path.exists()
|
||||
&& let Some(lint) = lints.iter().find(|l| l.name == name)
|
||||
{
|
||||
if lint.module == name {
|
||||
// The lint name is the same as the file, we can just delete the entire file
|
||||
fs::remove_file(path)?;
|
||||
} else {
|
||||
// We can't delete the entire file, just remove the declaration
|
||||
|
||||
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
|
||||
// Remove clippy_lints/src/some_mod/some_lint.rs
|
||||
let mut lint_mod_path = path.to_path_buf();
|
||||
lint_mod_path.set_file_name(name);
|
||||
lint_mod_path.set_extension("rs");
|
||||
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
|
||||
// Remove clippy_lints/src/some_mod/some_lint.rs
|
||||
let mut lint_mod_path = path.to_path_buf();
|
||||
lint_mod_path.set_file_name(name);
|
||||
lint_mod_path.set_extension("rs");
|
||||
|
||||
let _ = fs::remove_file(lint_mod_path);
|
||||
}
|
||||
|
||||
let mut content =
|
||||
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
|
||||
|
||||
eprintln!(
|
||||
"warn: you will have to manually remove any code related to `{name}` from `{}`",
|
||||
path.display()
|
||||
);
|
||||
|
||||
assert!(
|
||||
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
|
||||
"error: `{}` does not contain lint `{}`'s declaration",
|
||||
path.display(),
|
||||
lint.name
|
||||
);
|
||||
|
||||
// Remove lint declaration (declare_clippy_lint!)
|
||||
content.replace_range(lint.declaration_range.clone(), "");
|
||||
|
||||
// Remove the module declaration (mod xyz;)
|
||||
let mod_decl = format!("\nmod {name};");
|
||||
content = content.replacen(&mod_decl, "", 1);
|
||||
|
||||
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
|
||||
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
|
||||
let _ = fs::remove_file(lint_mod_path);
|
||||
}
|
||||
|
||||
remove_test_assets(name);
|
||||
remove_lint(name, lints);
|
||||
return Ok(true);
|
||||
let mut content =
|
||||
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
|
||||
|
||||
eprintln!(
|
||||
"warn: you will have to manually remove any code related to `{name}` from `{}`",
|
||||
path.display()
|
||||
);
|
||||
|
||||
assert!(
|
||||
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
|
||||
"error: `{}` does not contain lint `{}`'s declaration",
|
||||
path.display(),
|
||||
lint.name
|
||||
);
|
||||
|
||||
// Remove lint declaration (declare_clippy_lint!)
|
||||
content.replace_range(lint.declaration_range.clone(), "");
|
||||
|
||||
// Remove the module declaration (mod xyz;)
|
||||
let mod_decl = format!("\nmod {name};");
|
||||
content = content.replacen(&mod_decl, "", 1);
|
||||
|
||||
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
|
||||
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
remove_test_assets(name);
|
||||
remove_lint(name, lints);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
|
|
@ -527,22 +526,6 @@ impl Lint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns all non-deprecated lints and non-internal lints
|
||||
#[must_use]
|
||||
fn usable_lints(lints: &[Self]) -> Vec<Self> {
|
||||
lints
|
||||
.iter()
|
||||
.filter(|l| !l.group.starts_with("internal"))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all internal lints
|
||||
#[must_use]
|
||||
fn internal_lints(lints: &[Self]) -> Vec<Self> {
|
||||
lints.iter().filter(|l| l.group == "internal").cloned().collect()
|
||||
}
|
||||
|
||||
/// Returns the lints in a `HashMap`, grouped by the different lint groups
|
||||
#[must_use]
|
||||
fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
|
||||
|
|
@ -579,23 +562,14 @@ impl RenamedLint {
|
|||
|
||||
/// Generates the code for registering lints
|
||||
#[must_use]
|
||||
fn gen_declared_lints<'a>(
|
||||
internal_lints: impl Iterator<Item = &'a Lint>,
|
||||
usable_lints: impl Iterator<Item = &'a Lint>,
|
||||
) -> String {
|
||||
let mut details: Vec<_> = internal_lints
|
||||
.map(|l| (false, &l.module, l.name.to_uppercase()))
|
||||
.chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
|
||||
.collect();
|
||||
fn gen_declared_lints<'a>(lints: impl Iterator<Item = &'a Lint>) -> String {
|
||||
let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
|
||||
details.sort_unstable();
|
||||
|
||||
let mut output = GENERATED_FILE_COMMENT.to_string();
|
||||
output.push_str("pub static LINTS: &[&crate::LintInfo] = &[\n");
|
||||
|
||||
for (is_public, module_name, lint_name) in details {
|
||||
if !is_public {
|
||||
output.push_str(" #[cfg(feature = \"internal\")]\n");
|
||||
}
|
||||
for (module_name, lint_name) in details {
|
||||
let _: fmt::Result = writeln!(output, " crate::{module_name}::{lint_name}_INFO,");
|
||||
}
|
||||
output.push_str("];\n");
|
||||
|
|
@ -830,7 +804,7 @@ fn remove_line_splices(s: &str) -> String {
|
|||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
||||
let mut res = String::with_capacity(s.len());
|
||||
unescape::unescape_unicode(s, unescape::Mode::Str, &mut |range, ch| {
|
||||
unescape_unicode(s, Mode::Str, &mut |range, ch| {
|
||||
if ch.is_ok() {
|
||||
res.push_str(&s[range]);
|
||||
}
|
||||
|
|
@ -936,41 +910,6 @@ mod tests {
|
|||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_usable_lints() {
|
||||
let lints = vec![
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"Not Deprecated",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"internal",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"internal_style",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
];
|
||||
let expected = vec![Lint::new(
|
||||
"should_assert_eq2",
|
||||
"Not Deprecated",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
)];
|
||||
assert_eq!(expected, Lint::usable_lints(&lints));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_by_lint_group() {
|
||||
let lints = vec![
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ pub fn clippy_project_root() -> PathBuf {
|
|||
let current_dir = std::env::current_dir().unwrap();
|
||||
for path in current_dir.ancestors() {
|
||||
let result = fs::read_to_string(path.join("Cargo.toml"));
|
||||
if let Err(err) = &result {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = &result
|
||||
&& err.kind() == io::ErrorKind::NotFound
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = result.unwrap();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
# begin autogenerated version
|
||||
version = "0.1.87"
|
||||
version = "0.1.88"
|
||||
# end autogenerated version
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
|
|
@ -19,10 +19,7 @@ itertools = "0.12"
|
|||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
tempfile = { version = "3.3.0", optional = true }
|
||||
toml = "0.7.3"
|
||||
regex = { version = "1.5", optional = true }
|
||||
unicode-normalization = "0.1"
|
||||
unicode-script = { version = "0.5", default-features = false }
|
||||
semver = "1.0"
|
||||
|
|
@ -31,10 +28,6 @@ url = "2.2"
|
|||
[dev-dependencies]
|
||||
walkdir = "2.3"
|
||||
|
||||
[features]
|
||||
# build clippy with internal lints enabled, off by default
|
||||
internal = ["serde_json", "tempfile", "regex"]
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
# This crate uses #[feature(rustc_private)]
|
||||
rustc_private = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use clippy_config::types::{
|
|||
SourceItemOrderingWithinModuleItemGroupings,
|
||||
};
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::is_cfg_test;
|
||||
use rustc_hir::{
|
||||
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant,
|
||||
VariantData,
|
||||
|
|
@ -263,10 +264,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
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);
|
||||
}
|
||||
if let Some(cur_v) = cur_v
|
||||
&& 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);
|
||||
}
|
||||
|
|
@ -278,10 +280,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
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);
|
||||
}
|
||||
if let Some(cur_f) = cur_f
|
||||
&& 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);
|
||||
}
|
||||
|
|
@ -342,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
struct CurItem<'a> {
|
||||
item: &'a Item<'a>,
|
||||
order: usize,
|
||||
name: String,
|
||||
name: Option<String>,
|
||||
}
|
||||
let mut cur_t: Option<CurItem<'_>> = None;
|
||||
|
||||
|
|
@ -359,32 +362,36 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
// as no sorting by source map/line of code has to be applied.
|
||||
//
|
||||
for item in items {
|
||||
if is_cfg_test(cx.tcx, item.hir_id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if item.span.in_external_macro(cx.sess().source_map()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ident = if let Some(ident) = item.kind.ident() {
|
||||
ident
|
||||
} else if let ItemKind::Impl(_) = item.kind
|
||||
&& !get_item_name(item).is_empty()
|
||||
{
|
||||
rustc_span::Ident::empty() // FIXME: a bit strange, is there a better way to do it?
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if 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 ident.name == rustc_span::sym::std && item.span.is_dummy() {
|
||||
if let ItemKind::ExternCrate(None, _) = item.kind {
|
||||
// Filters the auto-included Rust standard library.
|
||||
if let Some(ident) = item.kind.ident() {
|
||||
if ident.name.as_str().starts_with('_') {
|
||||
// Filters out unnamed macro-like impls for various derives,
|
||||
// e.g. serde::Serialize or num_derive::FromPrimitive.
|
||||
continue;
|
||||
}
|
||||
println!("Unknown item: {item:?}");
|
||||
|
||||
if 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;
|
||||
}
|
||||
if cfg!(debug_assertions) {
|
||||
rustc_middle::bug!("unknown item: {item:?}");
|
||||
}
|
||||
}
|
||||
} else if let ItemKind::Impl(_) = item.kind
|
||||
&& get_item_name(item).is_some()
|
||||
{
|
||||
// keep going below
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item_kind = convert_module_item_kind(&item.kind);
|
||||
|
|
@ -493,7 +500,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte
|
|||
/// 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 {
|
||||
fn get_item_name(item: &Item<'_>) -> Option<String> {
|
||||
match item.kind {
|
||||
ItemKind::Impl(im) => {
|
||||
if let TyKind::Path(path) = im.self_ty.kind {
|
||||
|
|
@ -513,27 +520,19 @@ fn get_item_name(item: &Item<'_>) -> String {
|
|||
}
|
||||
|
||||
segs.push(String::new());
|
||||
segs.join("!!")
|
||||
Some(segs.join("!!"))
|
||||
},
|
||||
QPath::TypeRelative(_, _path_seg) => {
|
||||
// This case doesn't exist in the clippy tests codebase.
|
||||
String::new()
|
||||
None
|
||||
},
|
||||
QPath::LangItem(_, _) => String::new(),
|
||||
QPath::LangItem(_, _) => None,
|
||||
}
|
||||
} else {
|
||||
// Impls for anything that isn't a named type can be skipped.
|
||||
String::new()
|
||||
None
|
||||
}
|
||||
},
|
||||
// FIXME: `Ident::empty` for anonymous items is a bit strange, is there
|
||||
// a better way to do it?
|
||||
_ => item
|
||||
.kind
|
||||
.ident()
|
||||
.unwrap_or(rustc_span::Ident::empty())
|
||||
.name
|
||||
.as_str()
|
||||
.to_owned(),
|
||||
_ => item.kind.ident().map(|name| name.as_str().to_owned()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,17 @@ declare_clippy_lint! {
|
|||
/// regardless of whether good alternatives exist or not. If you want more
|
||||
/// precise lints for `as`, please consider using these separate lints:
|
||||
///
|
||||
/// - clippy::cast_lossless
|
||||
/// - clippy::cast_possible_truncation
|
||||
/// - clippy::cast_possible_wrap
|
||||
/// - clippy::cast_precision_loss
|
||||
/// - clippy::cast_sign_loss
|
||||
/// - clippy::char_lit_as_u8
|
||||
/// - clippy::fn_to_numeric_cast
|
||||
/// - clippy::fn_to_numeric_cast_with_truncation
|
||||
/// - clippy::ptr_as_ptr
|
||||
/// - clippy::unnecessary_cast
|
||||
/// - invalid_reference_casting
|
||||
/// - `clippy::cast_lossless`
|
||||
/// - `clippy::cast_possible_truncation`
|
||||
/// - `clippy::cast_possible_wrap`
|
||||
/// - `clippy::cast_precision_loss`
|
||||
/// - `clippy::cast_sign_loss`
|
||||
/// - `clippy::char_lit_as_u8`
|
||||
/// - `clippy::fn_to_numeric_cast`
|
||||
/// - `clippy::fn_to_numeric_cast_with_truncation`
|
||||
/// - `clippy::ptr_as_ptr`
|
||||
/// - `clippy::unnecessary_cast`
|
||||
/// - `invalid_reference_casting`
|
||||
///
|
||||
/// There is a good explanation the reason why this lint should work in this
|
||||
/// way and how it is useful [in this
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ fn build_sugg<'tcx>(
|
|||
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
|
||||
Sugg::hir_with_applicability(cx, lhs, "_", app)
|
||||
}
|
||||
.maybe_par();
|
||||
.maybe_paren();
|
||||
|
||||
// Determine whether we need to reference the argument to clone_from().
|
||||
let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);
|
||||
|
|
@ -284,7 +284,7 @@ fn build_sugg<'tcx>(
|
|||
let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
|
||||
// `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
|
||||
// `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
|
||||
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par();
|
||||
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_paren();
|
||||
let inner_type = cx.typeck_results().expr_ty(ref_expr);
|
||||
// If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
|
||||
// deref to a mutable reference.
|
||||
|
|
@ -296,7 +296,7 @@ fn build_sugg<'tcx>(
|
|||
} else {
|
||||
// `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
|
||||
// `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
|
||||
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr()
|
||||
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_paren().mut_addr()
|
||||
};
|
||||
|
||||
match call_kind {
|
||||
|
|
|
|||
|
|
@ -8,17 +8,18 @@ use rustc_span::{DUMMY_SP, sym};
|
|||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner]) {
|
||||
for lint in items {
|
||||
if let Some(lint_name) = extract_clippy_lint(lint) {
|
||||
if lint_name.as_str() == "restriction" && name != sym::allow {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
lint.span(),
|
||||
"`clippy::restriction` is not meant to be enabled as a group",
|
||||
None,
|
||||
"enable the restriction lints you need individually",
|
||||
);
|
||||
}
|
||||
if let Some(lint_name) = extract_clippy_lint(lint)
|
||||
&& lint_name.as_str() == "restriction"
|
||||
&& name != sym::allow
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
lint.span(),
|
||||
"`clippy::restriction` is not meant to be enabled as a group",
|
||||
None,
|
||||
"enable the restriction lints you need individually",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ use rustc_span::Span;
|
|||
use semver::Version;
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, span: Span, lit: &MetaItemLit) {
|
||||
if let LitKind::Str(is, _) = lit.kind {
|
||||
if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() {
|
||||
return;
|
||||
}
|
||||
if let LitKind::Str(is, _) = lit.kind
|
||||
&& (is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok())
|
||||
{
|
||||
return;
|
||||
}
|
||||
span_lint(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -36,10 +36,7 @@ fn check_duplicated_attr(
|
|||
}
|
||||
let Some(ident) = attr.ident() else { return };
|
||||
let name = ident.name;
|
||||
if name == sym::doc
|
||||
|| name == sym::cfg_attr_trace
|
||||
|| name == sym::rustc_on_unimplemented
|
||||
|| name == sym::reason {
|
||||
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason {
|
||||
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
|
||||
// conditions are the same.
|
||||
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ mod useless_attribute;
|
|||
mod utils;
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::msrvs::{self, Msrv, MsrvStack};
|
||||
use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
|
||||
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind};
|
||||
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -448,6 +449,31 @@ declare_clippy_lint! {
|
|||
"duplicated attribute"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for ignored tests without messages.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The reason for ignoring the test may not be obvious.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[test]
|
||||
/// #[ignore]
|
||||
/// fn test() {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[test]
|
||||
/// #[ignore = "Some good reason"]
|
||||
/// fn test() {}
|
||||
/// ```
|
||||
#[clippy::version = "1.85.0"]
|
||||
pub IGNORE_WITHOUT_REASON,
|
||||
pedantic,
|
||||
"ignored tests without messages"
|
||||
}
|
||||
|
||||
pub struct Attributes {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
|
@ -532,6 +558,7 @@ impl_lint_pass!(PostExpansionEarlyAttributes => [
|
|||
ALLOW_ATTRIBUTES,
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
DEPRECATED_SEMVER,
|
||||
IGNORE_WITHOUT_REASON,
|
||||
USELESS_ATTRIBUTE,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
SHOULD_PANIC_WITHOUT_EXPECT,
|
||||
|
|
@ -546,28 +573,27 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
|
|||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if let Some(items) = &attr.meta_item_list() {
|
||||
if let Some(ident) = attr.ident() {
|
||||
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
|
||||
allow_attributes::check(cx, attr);
|
||||
}
|
||||
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
|
||||
if let Some(items) = &attr.meta_item_list()
|
||||
&& let Some(ident) = attr.ident()
|
||||
{
|
||||
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
|
||||
allow_attributes::check(cx, attr);
|
||||
}
|
||||
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
|
||||
allow_attributes_without_reason::check(cx, ident.name, items, attr);
|
||||
}
|
||||
if is_lint_level(ident.name, attr.id) {
|
||||
blanket_clippy_restriction_lints::check(cx, ident.name, items);
|
||||
}
|
||||
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
||||
return;
|
||||
}
|
||||
for item in items {
|
||||
if let MetaItemInner::MetaItem(mi) = &item
|
||||
&& let MetaItemKind::NameValue(lit) = &mi.kind
|
||||
&& mi.has_name(sym::since)
|
||||
{
|
||||
allow_attributes_without_reason::check(cx, ident.name, items, attr);
|
||||
}
|
||||
if is_lint_level(ident.name, attr.id) {
|
||||
blanket_clippy_restriction_lints::check(cx, ident.name, items);
|
||||
}
|
||||
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
||||
return;
|
||||
}
|
||||
for item in items {
|
||||
if let MetaItemInner::MetaItem(mi) = &item
|
||||
&& let MetaItemKind::NameValue(lit) = &mi.kind
|
||||
&& mi.has_name(sym::since)
|
||||
{
|
||||
deprecated_semver::check(cx, item.span(), lit);
|
||||
}
|
||||
deprecated_semver::check(cx, item.span(), lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -575,6 +601,22 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
|
|||
if attr.has_name(sym::should_panic) {
|
||||
should_panic_without_expect::check(cx, attr);
|
||||
}
|
||||
|
||||
if attr.has_name(sym::ignore)
|
||||
&& match &attr.kind {
|
||||
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }),
|
||||
AttrKind::DocComment(..) => true,
|
||||
}
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
IGNORE_WITHOUT_REASON,
|
||||
attr.span,
|
||||
"`#[ignore]` without reason",
|
||||
None,
|
||||
"add a reason with `= \"..\"`",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute],
|
|||
diag.warn(
|
||||
"unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI",
|
||||
)
|
||||
.help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
|
||||
.help("qualify the desired ABI explicitly via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
|
||||
.span_label(packed_span, "`packed` representation set here");
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,75 +14,75 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
if attr.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
if let Some(lint_list) = &attr.meta_item_list() {
|
||||
if attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id)) {
|
||||
for lint in lint_list {
|
||||
match item.kind {
|
||||
ItemKind::Use(..) => {
|
||||
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
|
||||
return;
|
||||
};
|
||||
if let Some(lint_list) = &attr.meta_item_list()
|
||||
&& attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id))
|
||||
{
|
||||
for lint in lint_list {
|
||||
match item.kind {
|
||||
ItemKind::Use(..) => {
|
||||
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if namespace.is_none()
|
||||
&& matches!(
|
||||
name.as_str(),
|
||||
"ambiguous_glob_reexports"
|
||||
| "dead_code"
|
||||
| "deprecated"
|
||||
| "hidden_glob_reexports"
|
||||
| "unreachable_pub"
|
||||
| "unused"
|
||||
| "unused_braces"
|
||||
| "unused_import_braces"
|
||||
| "unused_imports"
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if namespace.is_none()
|
||||
&& matches!(
|
||||
name.as_str(),
|
||||
"ambiguous_glob_reexports"
|
||||
| "dead_code"
|
||||
| "deprecated"
|
||||
| "hidden_glob_reexports"
|
||||
| "unreachable_pub"
|
||||
| "unused"
|
||||
| "unused_braces"
|
||||
| "unused_import_braces"
|
||||
| "unused_imports"
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if namespace == Some(sym::clippy)
|
||||
&& matches!(
|
||||
name.as_str(),
|
||||
"wildcard_imports"
|
||||
| "enum_glob_use"
|
||||
| "redundant_pub_crate"
|
||||
| "macro_use_imports"
|
||||
| "unsafe_removed_from_name"
|
||||
| "module_name_repetitions"
|
||||
| "single_component_path_imports"
|
||||
| "disallowed_types"
|
||||
| "unused_trait_names"
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
},
|
||||
ItemKind::ExternCrate(..) => {
|
||||
if is_word(lint, sym::unused_imports) && skip_unused_imports {
|
||||
return;
|
||||
}
|
||||
if is_word(lint, sym::unused_extern_crates) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
if namespace == Some(sym::clippy)
|
||||
&& matches!(
|
||||
name.as_str(),
|
||||
"wildcard_imports"
|
||||
| "enum_glob_use"
|
||||
| "redundant_pub_crate"
|
||||
| "macro_use_imports"
|
||||
| "unsafe_removed_from_name"
|
||||
| "module_name_repetitions"
|
||||
| "single_component_path_imports"
|
||||
| "disallowed_types"
|
||||
| "unused_trait_names"
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
},
|
||||
ItemKind::ExternCrate(..) => {
|
||||
if is_word(lint, sym::unused_imports) && skip_unused_imports {
|
||||
return;
|
||||
}
|
||||
if is_word(lint, sym::unused_extern_crates) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
let line_span = first_line_of_span(cx, attr.span);
|
||||
}
|
||||
let line_span = first_line_of_span(cx, attr.span);
|
||||
|
||||
if let Some(src) = line_span.get_source_text(cx) {
|
||||
if src.contains("#[") {
|
||||
#[expect(clippy::collapsible_span_lint_calls)]
|
||||
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
|
||||
diag.span_suggestion(
|
||||
line_span,
|
||||
"if you just forgot a `!`, use",
|
||||
src.replacen("#[", "#![", 1),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(src) = line_span.get_source_text(cx)
|
||||
&& src.contains("#[")
|
||||
{
|
||||
#[expect(clippy::collapsible_span_lint_calls)]
|
||||
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
|
||||
diag.span_suggestion(
|
||||
line_span,
|
||||
"if you just forgot a `!`, use",
|
||||
src.replacen("#[", "#![", 1),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,9 +179,14 @@ pub struct AwaitHolding {
|
|||
|
||||
impl AwaitHolding {
|
||||
pub(crate) fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
def_ids: create_disallowed_map(tcx, &conf.await_holding_invalid_types),
|
||||
}
|
||||
let (def_ids, _) = create_disallowed_map(
|
||||
tcx,
|
||||
&conf.await_holding_invalid_types,
|
||||
crate::disallowed_types::def_kind_predicate,
|
||||
"type",
|
||||
false,
|
||||
);
|
||||
Self { def_ids }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,10 +197,9 @@ impl<'tcx> LateLintPass<'tcx> for AwaitHolding {
|
|||
def_id,
|
||||
..
|
||||
}) = expr.kind
|
||||
&& let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id)
|
||||
{
|
||||
if let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) {
|
||||
self.check_interior_types(cx, coroutine_layout);
|
||||
}
|
||||
self.check_interior_types(cx, coroutine_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::HasSession;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{is_else_clause, is_in_const_context};
|
||||
use clippy_utils::{higher, is_else_clause, is_in_const_context, span_contains_comment};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
|
|
@ -46,18 +47,25 @@ declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let ExprKind::If(cond, then, Some(else_)) = expr.kind
|
||||
&& matches!(cond.kind, ExprKind::DropTemps(_))
|
||||
if !expr.span.from_expansion()
|
||||
&& let Some(higher::If {
|
||||
cond,
|
||||
then,
|
||||
r#else: Some(r#else),
|
||||
}) = higher::If::hir(expr)
|
||||
&& let Some(then_lit) = as_int_bool_lit(then)
|
||||
&& let Some(else_lit) = as_int_bool_lit(else_)
|
||||
&& let Some(else_lit) = as_int_bool_lit(r#else)
|
||||
&& then_lit != else_lit
|
||||
&& !expr.span.from_expansion()
|
||||
&& !is_in_const_context(cx)
|
||||
{
|
||||
let ty = cx.typeck_results().expr_ty(then);
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
|
||||
Applicability::MaybeIncorrect
|
||||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
let snippet = {
|
||||
let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
|
||||
let mut sugg = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability);
|
||||
if !then_lit {
|
||||
sugg = !sugg;
|
||||
}
|
||||
|
|
@ -72,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
|
|||
s
|
||||
};
|
||||
|
||||
let into_snippet = snippet.clone().maybe_par();
|
||||
let into_snippet = snippet.clone().maybe_paren();
|
||||
let as_snippet = snippet.as_ty(ty);
|
||||
|
||||
span_lint_and_then(
|
||||
|
|
@ -91,10 +99,11 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
|
|||
}
|
||||
}
|
||||
|
||||
fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> {
|
||||
if let ExprKind::Block(b, _) = e.kind
|
||||
fn as_int_bool_lit(expr: &Expr<'_>) -> Option<bool> {
|
||||
if let ExprKind::Block(b, _) = expr.kind
|
||||
&& b.stmts.is_empty()
|
||||
&& let Some(e) = b.expr
|
||||
&& !e.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = e.kind
|
||||
&& let LitKind::Int(x, _) = lit.node
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
|
|||
use rustc_lint::{LateContext, LateLintPass, Level};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_span::{Span, SyntaxContext, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -242,11 +242,11 @@ struct Hir2Qmm<'a, 'tcx, 'v> {
|
|||
impl<'v> Hir2Qmm<'_, '_, 'v> {
|
||||
fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
|
||||
for a in a {
|
||||
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
|
||||
if binop.node == op {
|
||||
v = self.extract(op, &[lhs, rhs], v)?;
|
||||
continue;
|
||||
}
|
||||
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind
|
||||
&& binop.node == op
|
||||
{
|
||||
v = self.extract(op, &[lhs, rhs], v)?;
|
||||
continue;
|
||||
}
|
||||
v.push(self.run(a)?);
|
||||
}
|
||||
|
|
@ -349,9 +349,13 @@ impl SuggestContext<'_, '_, '_> {
|
|||
if let Some(str) = simplify_not(self.cx, self.msrv, terminal) {
|
||||
self.output.push_str(&str);
|
||||
} else {
|
||||
self.output.push('!');
|
||||
self.output
|
||||
.push_str(&Sugg::hir_opt(self.cx, terminal)?.maybe_par().to_string());
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = Sugg::hir_with_context(self.cx, terminal, SyntaxContext::root(), "", &mut app);
|
||||
// Ignore the case If the expression is inside a macro expansion, or the default snippet is used
|
||||
if app != Applicability::MachineApplicable {
|
||||
return None;
|
||||
}
|
||||
self.output.push_str(&(!snip).to_string());
|
||||
}
|
||||
},
|
||||
True | False | Not(_) => {
|
||||
|
|
@ -414,12 +418,12 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio
|
|||
let lhs_snippet = lhs.span.get_source_text(cx)?;
|
||||
let rhs_snippet = rhs.span.get_source_text(cx)?;
|
||||
|
||||
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) {
|
||||
if let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) {
|
||||
// e.g. `(a as u64) < b`. Without the parens the `<` is
|
||||
// interpreted as a start of generic arguments for `u64`
|
||||
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
|
||||
}
|
||||
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')'))
|
||||
&& let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node)
|
||||
{
|
||||
// e.g. `(a as u64) < b`. Without the parens the `<` is
|
||||
// interpreted as a start of generic arguments for `u64`
|
||||
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
|
||||
}
|
||||
|
||||
Some(format!("{lhs_snippet}{op}{rhs_snippet}"))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::reference::DEREF_ADDROF;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
|
||||
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed, is_mutable};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -73,6 +73,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
|
|||
}
|
||||
})
|
||||
&& !is_from_proc_macro(cx, e)
|
||||
&& let e_ty = cx.typeck_results().expr_ty_adjusted(e)
|
||||
// check if the reference is coercing to a mutable reference
|
||||
&& (!matches!(e_ty.kind(), ty::Ref(_, _, Mutability::Mut)) || is_mutable(cx, deref_target))
|
||||
&& let Some(deref_text) = deref_target.span.get_source_text(cx)
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
|
@ -90,10 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
|
|||
|
||||
// has deref trait -> give 2 help
|
||||
// doesn't have deref trait -> give 1 help
|
||||
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() {
|
||||
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
|
||||
return;
|
||||
}
|
||||
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait()
|
||||
&& !implements_trait(cx, *inner_ty, deref_trait_id, &[])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::{is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
|
||||
use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow};
|
||||
use rustc_span::BytePos;
|
||||
|
||||
use super::BORROW_AS_PTR;
|
||||
|
|
@ -29,10 +30,6 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
|
||||
let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
|
||||
let operator_kind = match mutability {
|
||||
Mutability::Not => "const",
|
||||
Mutability::Mut => "mut",
|
||||
};
|
||||
// Make sure that the span to be replaced doesn't include parentheses, that could break the
|
||||
// suggestion.
|
||||
let span = if has_enclosing_paren(snippet_with_applicability(cx, expr.span, "", &mut app)) {
|
||||
|
|
@ -42,7 +39,7 @@ pub(super) fn check<'tcx>(
|
|||
} else {
|
||||
expr.span
|
||||
};
|
||||
(format!("&raw {operator_kind} {snip}"), span)
|
||||
(format!("&raw {} {snip}", mutability.ptr_str()), span)
|
||||
} else {
|
||||
let Some(std_or_core) = std_or_core(cx) else {
|
||||
return false;
|
||||
|
|
@ -59,3 +56,25 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for an implicit cast from reference to raw pointer outside an explicit `as`.
|
||||
pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if !expr.span.from_expansion()
|
||||
&& let ExprKind::AddrOf(BorrowKind::Ref, _, pointee) = expr.kind
|
||||
&& !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..)))
|
||||
&& let [deref, borrow] = cx.typeck_results().expr_adjustments(expr)
|
||||
&& matches!(deref.kind, Adjust::Deref(..))
|
||||
&& let Adjust::Borrow(AutoBorrow::RawPtr(mutability)) = borrow.kind
|
||||
// Do not suggest taking a raw pointer to a temporary value
|
||||
&& !is_expr_temporary_value(cx, pointee)
|
||||
{
|
||||
span_lint_and_then(cx, BORROW_AS_PTR, expr.span, "implicit borrow as raw pointer", |diag| {
|
||||
diag.span_suggestion_verbose(
|
||||
expr.span.until(pointee.span),
|
||||
"use a raw pointer instead",
|
||||
format!("&raw {} ", mutability.ptr_str()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub(super) fn check(
|
|||
span,
|
||||
format!("casting the result of `{cast_from}::abs()` to {cast_to}"),
|
||||
"replace with",
|
||||
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_par()),
|
||||
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ pub(super) fn check(
|
|||
diag.span_suggestion_verbose(
|
||||
expr.span,
|
||||
"use `Into::into` instead",
|
||||
format!("{}.into()", from_sugg.maybe_par()),
|
||||
format!("{}.into()", from_sugg.maybe_paren()),
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
|||
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX))
|
||||
},
|
||||
ExprKind::MethodCall(method, _, [lo, hi], _) => {
|
||||
if method.ident.as_str() == "clamp" {
|
||||
if method.ident.as_str() == "clamp"
|
||||
//FIXME: make this a diagnostic item
|
||||
if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
|
||||
return lo_bits.max(hi_bits);
|
||||
}
|
||||
&& let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi))
|
||||
{
|
||||
return lo_bits.max(hi_bits);
|
||||
}
|
||||
nbits
|
||||
},
|
||||
|
|
@ -185,7 +185,7 @@ fn offer_suggestion(
|
|||
) {
|
||||
let cast_to_snip = snippet(cx, cast_to_span, "..");
|
||||
let suggestion = if cast_to_snip == "_" {
|
||||
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
|
||||
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_paren())
|
||||
} else {
|
||||
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,16 +19,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
cx.typeck_results().expr_ty(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.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.
|
||||
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
|
||||
{
|
||||
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
}
|
||||
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind
|
||||
&& 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.
|
||||
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
|
||||
{
|
||||
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,42 +21,41 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
|
|||
start_ty,
|
||||
end_ty,
|
||||
}) = expr_cast_chain_tys(cx, expr)
|
||||
&& let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty))
|
||||
{
|
||||
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
|
||||
let from_size = from_layout.size.bytes();
|
||||
let to_size = to_layout.size.bytes();
|
||||
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CAST_SLICE_DIFFERENT_SIZES,
|
||||
expr.span,
|
||||
format!(
|
||||
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
|
||||
start_ty.ty, end_ty.ty,
|
||||
),
|
||||
|diag| {
|
||||
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
|
||||
let from_size = from_layout.size.bytes();
|
||||
let to_size = to_layout.size.bytes();
|
||||
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CAST_SLICE_DIFFERENT_SIZES,
|
||||
expr.span,
|
||||
format!(
|
||||
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
|
||||
start_ty.ty, end_ty.ty,
|
||||
),
|
||||
|diag| {
|
||||
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
|
||||
|
||||
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
|
||||
Mutability::Mut => ("_mut", "mut"),
|
||||
Mutability::Not => ("", "const"),
|
||||
};
|
||||
let sugg = format!(
|
||||
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
|
||||
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
|
||||
// T`, extract just the `T`
|
||||
end_ty.ty
|
||||
);
|
||||
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
|
||||
Mutability::Mut => ("_mut", "mut"),
|
||||
Mutability::Not => ("", "const"),
|
||||
};
|
||||
let sugg = format!(
|
||||
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
|
||||
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
|
||||
// T`, extract just the `T`
|
||||
end_ty.ty
|
||||
);
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
|
||||
sugg,
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
|
||||
sugg,
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal file
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::is_normalizable;
|
||||
use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::Spanned;
|
||||
|
||||
use super::MANUAL_DANGLING_PTR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
|
||||
if let TyKind::Ptr(ref ptr_ty) = to.kind {
|
||||
let init_expr = expr_or_init(cx, from);
|
||||
if is_expr_const_aligned(cx, init_expr, ptr_ty.ty)
|
||||
&& let Some(std_or_core) = std_or_core(cx)
|
||||
{
|
||||
let sugg_fn = match ptr_ty.mutbl {
|
||||
Mutability::Not => "ptr::dangling",
|
||||
Mutability::Mut => "ptr::dangling_mut",
|
||||
};
|
||||
|
||||
let sugg = if let TyKind::Infer(()) = ptr_ty.ty.kind {
|
||||
format!("{std_or_core}::{sugg_fn}()")
|
||||
} else if let Some(mut_ty_snip) = ptr_ty.ty.span.get_source_text(cx) {
|
||||
format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_DANGLING_PTR,
|
||||
expr.span,
|
||||
"manual creation of a dangling pointer",
|
||||
"use",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the given expression is a call to `align_of` whose generic argument matches the target
|
||||
// type, or a positive constant literal that matches the target type's alignment.
|
||||
fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to),
|
||||
ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool {
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind
|
||||
&& let Some(fun_id) = path_def_id(cx, fun)
|
||||
&& match_def_path(cx, fun_id, &paths::ALIGN_OF)
|
||||
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
|
||||
&& let [GenericArg::Type(generic_ty)] = args.args
|
||||
{
|
||||
let typeck = cx.typeck_results();
|
||||
return typeck.node_type(generic_ty.hir_id) == typeck.node_type(to.hir_id);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>) -> bool {
|
||||
let LitKind::Int(val, _) = lit.node else { return false };
|
||||
if val == 0 {
|
||||
return false;
|
||||
}
|
||||
let to_mid_ty = cx.typeck_results().node_type(to.hir_id);
|
||||
is_normalizable(cx, cx.param_env, to_mid_ty)
|
||||
&& cx
|
||||
.tcx
|
||||
.layout_of(cx.typing_env().as_query_input(to_mid_ty))
|
||||
.is_ok_and(|layout| {
|
||||
let align = u128::from(layout.align.abi.bytes());
|
||||
u128::from(val) <= align
|
||||
})
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ mod char_lit_as_u8;
|
|||
mod fn_to_numeric_cast;
|
||||
mod fn_to_numeric_cast_any;
|
||||
mod fn_to_numeric_cast_with_truncation;
|
||||
mod manual_dangling_ptr;
|
||||
mod ptr_as_ptr;
|
||||
mod ptr_cast_constness;
|
||||
mod ref_as_ptr;
|
||||
|
|
@ -71,7 +72,7 @@ declare_clippy_lint! {
|
|||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let y: i8 = -1;
|
||||
/// y as u128; // will return 18446744073709551615
|
||||
/// y as u64; // will return 18446744073709551615
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub CAST_SIGN_LOSS,
|
||||
|
|
@ -759,6 +760,32 @@ declare_clippy_lint! {
|
|||
"detects `as *mut _` and `as *const _` conversion"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of small constant literals or `mem::align_of` results to raw pointers.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This creates a dangling pointer and is better expressed as
|
||||
/// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let ptr = 4 as *const u32;
|
||||
/// let aligned = std::mem::align_of::<u32>() as *const u32;
|
||||
/// let mut_ptr: *mut i64 = 8 as *mut _;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let ptr = std::ptr::dangling::<u32>();
|
||||
/// let aligned = std::ptr::dangling::<u32>();
|
||||
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
|
||||
/// ```
|
||||
#[clippy::version = "1.87.0"]
|
||||
pub MANUAL_DANGLING_PTR,
|
||||
style,
|
||||
"casting small constant literals to pointers to create dangling pointers"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
|
@ -795,6 +822,7 @@ impl_lint_pass!(Casts => [
|
|||
ZERO_PTR,
|
||||
REF_AS_PTR,
|
||||
AS_POINTER_UNDERSCORE,
|
||||
MANUAL_DANGLING_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
|
@ -823,6 +851,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
|
||||
if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
|
||||
manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
}
|
||||
|
||||
if cast_to.is_numeric() {
|
||||
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
|
||||
if cast_from.is_numeric() {
|
||||
|
|
@ -846,6 +878,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
}
|
||||
}
|
||||
|
||||
if self.msrv.meets(cx, msrvs::RAW_REF_OP) {
|
||||
borrow_as_ptr::check_implicit_cast(cx, expr);
|
||||
}
|
||||
cast_ptr_alignment::check(cx, expr);
|
||||
char_lit_as_u8::check(cx, expr);
|
||||
ptr_as_ptr::check(cx, expr, self.msrv);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
|
|||
|
||||
(
|
||||
"try `pointer::cast`, a safer alternative",
|
||||
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
|
||||
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_paren()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
|
||||
if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) {
|
||||
let sugg = Sugg::hir(cx, cast_expr, "_");
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app);
|
||||
let constness = match *to_mutbl {
|
||||
Mutability::Not => "const",
|
||||
Mutability::Mut => "mut",
|
||||
|
|
@ -65,8 +66,8 @@ pub(super) fn check<'tcx>(
|
|||
expr.span,
|
||||
"`as` casting between raw pointers while changing only its constness",
|
||||
format!("try `pointer::cast_{constness}`, a safer alternative"),
|
||||
format!("{}.cast_{constness}()", sugg.maybe_par()),
|
||||
Applicability::MachineApplicable,
|
||||
format!("{}.cast_{constness}()", sugg.maybe_paren()),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,11 +130,11 @@ pub(super) fn check<'tcx>(
|
|||
| LitKind::Float(_, LitFloatType::Suffixed(_))
|
||||
if cast_from.kind() == cast_to.kind() =>
|
||||
{
|
||||
if let Some(src) = cast_expr.span.get_source_text(cx) {
|
||||
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) {
|
||||
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
|
||||
return true;
|
||||
}
|
||||
if let Some(src) = cast_expr.span.get_source_text(cx)
|
||||
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
|
||||
{
|
||||
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -253,11 +253,11 @@ fn get_types_from_cast<'a>(
|
|||
match limit.kind {
|
||||
// `from_type::from(_)`
|
||||
ExprKind::Call(path, _) => {
|
||||
if let ExprKind::Path(ref path) = path.kind {
|
||||
if let ExprKind::Path(ref path) = path.kind
|
||||
// `to_type`
|
||||
if let Some(to_type) = get_implementing_type(path, types, func) {
|
||||
return Some((from_type, to_type));
|
||||
}
|
||||
&& let Some(to_type) = get_implementing_type(path, types, func)
|
||||
{
|
||||
return Some((from_type, to_type));
|
||||
}
|
||||
},
|
||||
// `to_type::MAX`
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ impl CognitiveComplexity {
|
|||
|
||||
let mut cc = 1u64;
|
||||
let mut returns = 0u64;
|
||||
let mut prev_expr: Option<&ExprKind<'tcx>> = None;
|
||||
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
|
||||
match e.kind {
|
||||
ExprKind::If(_, _, _) => {
|
||||
|
|
@ -73,9 +74,14 @@ impl CognitiveComplexity {
|
|||
}
|
||||
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
|
||||
},
|
||||
ExprKind::Ret(_) => returns += 1,
|
||||
ExprKind::Ret(_) => {
|
||||
if !matches!(prev_expr, Some(ExprKind::Ret(_))) {
|
||||
returns += 1;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
prev_expr = Some(&e.kind);
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_ast::ast;
|
||||
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -75,105 +77,152 @@ declare_clippy_lint! {
|
|||
"nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
|
||||
}
|
||||
|
||||
declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
|
||||
pub struct CollapsibleIf {
|
||||
let_chains_enabled: bool,
|
||||
lint_commented_code: bool,
|
||||
}
|
||||
|
||||
impl EarlyLintPass for CollapsibleIf {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
||||
if let ast::ExprKind::If(cond, then, else_) = &expr.kind
|
||||
impl CollapsibleIf {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
let_chains_enabled: tcx.features().let_chains(),
|
||||
lint_commented_code: conf.lint_commented_code,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
|
||||
if !block_starts_with_comment(cx, else_block)
|
||||
&& let Some(else_) = expr_block(else_block)
|
||||
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
|
||||
&& !else_.span.from_expansion()
|
||||
&& let ExprKind::If(..) = else_.kind
|
||||
{
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let up_to_else = then_span.between(else_block.span);
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
|
||||
!c.is_whitespace()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
else_block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) {
|
||||
if let Some(inner) = expr_block(then)
|
||||
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
|
||||
&& let ExprKind::If(check_inner, _, None) = &inner.kind
|
||||
&& self.eligible_condition(check_inner)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& inner.span.ctxt() == ctxt
|
||||
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_IF,
|
||||
expr.span,
|
||||
"this `if` statement can be collapsed",
|
||||
|diag| {
|
||||
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
|
||||
let then_closing_bracket = {
|
||||
let end = then.span.shrink_to_hi();
|
||||
end.with_lo(end.lo() - rustc_span::BytePos(1))
|
||||
.with_leading_whitespace(cx)
|
||||
.into_span()
|
||||
};
|
||||
let inner_if = inner.span.split_at(2).0;
|
||||
let mut sugg = vec![
|
||||
// Remove the outer then block `{`
|
||||
(then_open_bracket, String::new()),
|
||||
// Remove the outer then block '}'
|
||||
(then_closing_bracket, String::new()),
|
||||
// Replace inner `if` by `&&`
|
||||
(inner_if, String::from("&&")),
|
||||
];
|
||||
sugg.extend(parens_around(check));
|
||||
sugg.extend(parens_around(check_inner));
|
||||
|
||||
diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
|
||||
self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
|
||||
|
||||
impl LateLintPass<'_> for CollapsibleIf {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::If(cond, then, else_) = &expr.kind
|
||||
&& !expr.span.from_expansion()
|
||||
{
|
||||
if let Some(else_) = else_ {
|
||||
check_collapsible_maybe_if_let(cx, then.span, else_);
|
||||
} else if !matches!(cond.kind, ast::ExprKind::Let(..)) {
|
||||
check_collapsible_no_if_let(cx, expr, cond, then);
|
||||
if let Some(else_) = else_
|
||||
&& let ExprKind::Block(else_, None) = else_.kind
|
||||
{
|
||||
Self::check_collapsible_else_if(cx, then.span, else_);
|
||||
} else if else_.is_none()
|
||||
&& self.eligible_condition(cond)
|
||||
&& let ExprKind::Block(then, None) = then.kind
|
||||
{
|
||||
self.check_collapsible_if_if(cx, expr, cond, then);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
|
||||
fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
|
||||
// We trim all opening braces and whitespaces and then check if the next string is a comment.
|
||||
let trimmed_block_text = snippet_block(cx, expr.span, "..", None)
|
||||
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
|
||||
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
|
||||
.to_owned();
|
||||
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
|
||||
}
|
||||
|
||||
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
|
||||
if let ast::ExprKind::Block(ref block, _) = else_.kind
|
||||
&& !block_starts_with_comment(cx, block)
|
||||
&& let Some(else_) = expr_block(block)
|
||||
&& else_.attrs.is_empty()
|
||||
&& !else_.span.from_expansion()
|
||||
&& let ast::ExprKind::If(..) = else_.kind
|
||||
{
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let up_to_else = then_span.between(block.span);
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
|
||||
!c.is_whitespace()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
/// If `block` is a block with either one expression or a statement containing an expression,
|
||||
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
|
||||
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match block.stmts {
|
||||
[] => block.expr,
|
||||
[stmt] => {
|
||||
if let StmtKind::Semi(expr) = stmt.kind {
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
|
||||
if !block_starts_with_comment(cx, then)
|
||||
&& let Some(inner) = expr_block(then)
|
||||
&& inner.attrs.is_empty()
|
||||
&& let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind
|
||||
// Prevent triggering on `if c { if let a = b { .. } }`.
|
||||
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& inner.span.ctxt() == ctxt
|
||||
/// If the expression is a `||`, suggest parentheses around it.
|
||||
fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
|
||||
if let ExprKind::Binary(op, _, _) = expr.peel_drop_temps().kind
|
||||
&& op.node == BinOpKind::Or
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_IF,
|
||||
expr.span,
|
||||
"this `if` statement can be collapsed",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
|
||||
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"if {} {}",
|
||||
lhs.and(&rhs),
|
||||
snippet_block(cx, content.span, "..", Some(expr.span)),
|
||||
),
|
||||
app, // snippet
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// If the block contains only one expression, return it.
|
||||
fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
|
||||
if let [stmt] = &*block.stmts
|
||||
&& let ast::StmtKind::Expr(expr) | ast::StmtKind::Semi(expr) = &stmt.kind
|
||||
{
|
||||
Some(expr)
|
||||
vec![
|
||||
(expr.span.shrink_to_lo(), String::from("(")),
|
||||
(expr.span.shrink_to_hi(), String::from(")")),
|
||||
]
|
||||
} else {
|
||||
None
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
|
|||
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
|
||||
unreachable!();
|
||||
};
|
||||
let lhs = Sugg::hir(cx, lhs, "..").maybe_par();
|
||||
let lhs = Sugg::hir(cx, lhs, "..").maybe_paren();
|
||||
let rhs = Sugg::hir(cx, rhs, "..").addr();
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ fn lint_branches_sharing_code<'tcx>(
|
|||
let suggestion = reindent_multiline(&suggestion, true, indent);
|
||||
|
||||
let span = span.with_hi(last_block.span.hi());
|
||||
// Improve formatting if the inner block has indention (i.e. normal Rust formatting)
|
||||
// Improve formatting if the inner block has indentation (i.e. normal Rust formatting)
|
||||
let span = span
|
||||
.map_range(cx, |src, range| {
|
||||
(range.start > 4 && src.get(range.start - 4..range.start)? == " ")
|
||||
|
|
@ -539,10 +539,10 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
|
|||
.filter(|stmt| !ignore_span.overlaps(stmt.span))
|
||||
.try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
|
||||
|
||||
if let Some(expr) = block.expr {
|
||||
if res.is_continue() {
|
||||
res = intravisit::walk_expr(&mut walker, expr);
|
||||
}
|
||||
if let Some(expr) = block.expr
|
||||
&& res.is_continue()
|
||||
{
|
||||
res = intravisit::walk_expr(&mut walker, expr);
|
||||
}
|
||||
|
||||
res.is_break()
|
||||
|
|
|
|||
|
|
@ -165,17 +165,4 @@ macro_rules! declare_clippy_lint {
|
|||
$(, $eval_always)?
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
$(#[doc = $lit:literal])*
|
||||
pub $lint_name:ident,
|
||||
internal,
|
||||
$desc:literal
|
||||
) => {
|
||||
declare_clippy_lint! {@
|
||||
$(#[doc = $lit])*
|
||||
pub $lint_name, Allow, crate::LintCategory::Internal, $desc,
|
||||
None, "0.0.0"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,36 +3,6 @@
|
|||
// Manual edits will be overwritten.
|
||||
|
||||
pub static LINTS: &[&crate::LintInfo] = &[
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA_INFO,
|
||||
#[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,
|
||||
|
|
@ -52,6 +22,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_SEMVER_INFO,
|
||||
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
|
||||
crate::attrs::IGNORE_WITHOUT_REASON_INFO,
|
||||
crate::attrs::INLINE_ALWAYS_INFO,
|
||||
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
|
||||
crate::attrs::NON_MINIMAL_CFG_INFO,
|
||||
|
|
@ -96,6 +67,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::casts::FN_TO_NUMERIC_CAST_INFO,
|
||||
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
|
||||
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
|
||||
crate::casts::MANUAL_DANGLING_PTR_INFO,
|
||||
crate::casts::PTR_AS_PTR_INFO,
|
||||
crate::casts::PTR_CAST_CONSTNESS_INFO,
|
||||
crate::casts::REF_AS_PTR_INFO,
|
||||
|
|
@ -286,6 +258,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::literal_representation::UNREADABLE_LITERAL_INFO,
|
||||
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
|
||||
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
|
||||
crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO,
|
||||
crate::loops::EMPTY_LOOP_INFO,
|
||||
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
|
||||
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
|
||||
|
|
@ -312,6 +285,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
|
||||
crate::macro_use::MACRO_USE_IMPORTS_INFO,
|
||||
crate::main_recursion::MAIN_RECURSION_INFO,
|
||||
crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO,
|
||||
crate::manual_assert::MANUAL_ASSERT_INFO,
|
||||
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
|
||||
crate::manual_bits::MANUAL_BITS_INFO,
|
||||
|
|
@ -334,7 +308,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
|
||||
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
|
||||
crate::manual_strip::MANUAL_STRIP_INFO,
|
||||
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
|
||||
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
|
||||
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
|
||||
crate::match_result_ok::MATCH_RESULT_OK_INFO,
|
||||
|
|
@ -344,10 +317,10 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::matches::MANUAL_MAP_INFO,
|
||||
crate::matches::MANUAL_OK_ERR_INFO,
|
||||
crate::matches::MANUAL_UNWRAP_OR_INFO,
|
||||
crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO,
|
||||
crate::matches::MATCH_AS_REF_INFO,
|
||||
crate::matches::MATCH_BOOL_INFO,
|
||||
crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO,
|
||||
crate::matches::MATCH_ON_VEC_ITEMS_INFO,
|
||||
crate::matches::MATCH_OVERLAPPING_ARM_INFO,
|
||||
crate::matches::MATCH_REF_PATS_INFO,
|
||||
crate::matches::MATCH_SAME_ARMS_INFO,
|
||||
|
|
@ -488,6 +461,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
|
||||
crate::methods::SUSPICIOUS_SPLITN_INFO,
|
||||
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
|
||||
crate::methods::SWAP_WITH_TEMPORARY_INFO,
|
||||
crate::methods::TYPE_ID_ON_BOX_INFO,
|
||||
crate::methods::UNBUFFERED_BYTES_INFO,
|
||||
crate::methods::UNINIT_ASSUMED_INIT_INFO,
|
||||
|
|
@ -664,6 +638,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
|
||||
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
|
||||
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
|
||||
crate::redundant_test_prefix::REDUNDANT_TEST_PREFIX_INFO,
|
||||
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
|
||||
crate::ref_option_ref::REF_OPTION_REF_INFO,
|
||||
crate::ref_patterns::REF_PATTERNS_INFO,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_ty_alias;
|
||||
use clippy_utils::source::SpanRangeExt as _;
|
||||
use hir::ExprKind;
|
||||
use hir::def::Res;
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -70,15 +71,26 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs {
|
|||
&& let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant()
|
||||
&& !var.is_field_list_non_exhaustive()
|
||||
&& !expr.span.from_expansion() && !qpath.span().from_expansion()
|
||||
// do not suggest replacing an expression by a type name with placeholders
|
||||
&& !base.is_suggestable_infer_ty()
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())];
|
||||
if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) {
|
||||
// Remove `<`, '>` has already been removed by the existing removal expression.
|
||||
removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new()));
|
||||
}
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
|
||||
expr.span.with_lo(qpath.qself_span().hi()),
|
||||
expr.span,
|
||||
"use of `default` to create a unit struct",
|
||||
"remove this call to `default`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
diag.multipart_suggestion(
|
||||
"remove this call to `default`",
|
||||
removals,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
|
|||
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
|
||||
// end deprecated lints. used by `cargo dev deprecate_lint`
|
||||
]}
|
||||
|
||||
|
|
|
|||
|
|
@ -1133,61 +1133,60 @@ fn report<'tcx>(
|
|||
|
||||
impl<'tcx> Dereferencing<'tcx> {
|
||||
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
|
||||
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
|
||||
if let Some(pat) = outer_pat {
|
||||
// Check for auto-deref
|
||||
if !matches!(
|
||||
cx.typeck_results().expr_adjustments(e),
|
||||
[
|
||||
Adjustment {
|
||||
kind: Adjust::Deref(_),
|
||||
..
|
||||
},
|
||||
Adjustment {
|
||||
kind: Adjust::Deref(_),
|
||||
..
|
||||
},
|
||||
if let Some(outer_pat) = self.ref_locals.get_mut(&local)
|
||||
&& let Some(pat) = outer_pat
|
||||
// Check for auto-deref
|
||||
&& !matches!(
|
||||
cx.typeck_results().expr_adjustments(e),
|
||||
[
|
||||
Adjustment {
|
||||
kind: Adjust::Deref(_),
|
||||
..
|
||||
]
|
||||
) {
|
||||
match get_parent_expr(cx, e) {
|
||||
// Field accesses are the same no matter the number of references.
|
||||
Some(Expr {
|
||||
kind: ExprKind::Field(..),
|
||||
..
|
||||
}) => (),
|
||||
Some(&Expr {
|
||||
span,
|
||||
kind: ExprKind::Unary(UnOp::Deref, _),
|
||||
..
|
||||
}) if !span.from_expansion() => {
|
||||
// Remove explicit deref.
|
||||
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
|
||||
pat.replacements.push((span, snip.into()));
|
||||
},
|
||||
Some(parent) if !parent.span.from_expansion() => {
|
||||
// Double reference might be needed at this point.
|
||||
if parent.precedence() == ExprPrecedence::Unambiguous {
|
||||
// Parentheses would be needed here, don't lint.
|
||||
*outer_pat = None;
|
||||
} else {
|
||||
pat.always_deref = false;
|
||||
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
|
||||
pat.replacements.push((e.span, format!("&{snip}")));
|
||||
}
|
||||
},
|
||||
_ if !e.span.from_expansion() => {
|
||||
// Double reference might be needed at this point.
|
||||
pat.always_deref = false;
|
||||
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
|
||||
pat.replacements.push((e.span, format!("&{snip}")));
|
||||
},
|
||||
// Edge case for macros. The span of the identifier will usually match the context of the
|
||||
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
|
||||
// macros
|
||||
_ => *outer_pat = None,
|
||||
},
|
||||
Adjustment {
|
||||
kind: Adjust::Deref(_),
|
||||
..
|
||||
},
|
||||
..
|
||||
]
|
||||
)
|
||||
{
|
||||
match get_parent_expr(cx, e) {
|
||||
// Field accesses are the same no matter the number of references.
|
||||
Some(Expr {
|
||||
kind: ExprKind::Field(..),
|
||||
..
|
||||
}) => (),
|
||||
Some(&Expr {
|
||||
span,
|
||||
kind: ExprKind::Unary(UnOp::Deref, _),
|
||||
..
|
||||
}) if !span.from_expansion() => {
|
||||
// Remove explicit deref.
|
||||
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
|
||||
pat.replacements.push((span, snip.into()));
|
||||
},
|
||||
Some(parent) if !parent.span.from_expansion() => {
|
||||
// Double reference might be needed at this point.
|
||||
if parent.precedence() == ExprPrecedence::Unambiguous {
|
||||
// Parentheses would be needed here, don't lint.
|
||||
*outer_pat = None;
|
||||
} else {
|
||||
pat.always_deref = false;
|
||||
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
|
||||
pat.replacements.push((e.span, format!("&{snip}")));
|
||||
}
|
||||
}
|
||||
},
|
||||
_ if !e.span.from_expansion() => {
|
||||
// Double reference might be needed at this point.
|
||||
pat.always_deref = false;
|
||||
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
|
||||
pat.replacements.push((e.span, format!("&{snip}")));
|
||||
},
|
||||
// Edge case for macros. The span of the identifier will usually match the context of the
|
||||
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
|
||||
// macros
|
||||
_ => *outer_pat = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,18 +94,18 @@ fn check_struct<'tcx>(
|
|||
ty_args: GenericArgsRef<'_>,
|
||||
typeck_results: &'tcx TypeckResults<'tcx>,
|
||||
) {
|
||||
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
|
||||
if let Some(PathSegment { args, .. }) = p.segments.last() {
|
||||
let args = args.map(|a| a.args).unwrap_or(&[]);
|
||||
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
|
||||
&& let Some(PathSegment { args, .. }) = p.segments.last()
|
||||
{
|
||||
let args = args.map(|a| a.args).unwrap_or(&[]);
|
||||
|
||||
// ty_args contains the generic parameters of the type declaration, while args contains the
|
||||
// arguments used at instantiation time. If both len are not equal, it means that some
|
||||
// parameters were not provided (which means that the default values were used); in this
|
||||
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
|
||||
// is a type or a const.
|
||||
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
|
||||
return;
|
||||
}
|
||||
// ty_args contains the generic parameters of the type declaration, while args contains the
|
||||
// arguments used at instantiation time. If both len are not equal, it means that some
|
||||
// parameters were not provided (which means that the default values were used); in this
|
||||
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
|
||||
// is a type or a const.
|
||||
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
|||
self_ty,
|
||||
..
|
||||
}) = item.kind
|
||||
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
|
||||
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
|
||||
&& !item.span.from_expansion()
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
|
|||
}) = item.kind
|
||||
{
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
|
||||
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
|
||||
|
||||
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
|
|
@ -235,7 +235,7 @@ fn check_hash_peq<'tcx>(
|
|||
{
|
||||
// Look for the PartialEq implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
|
||||
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
|
||||
|
||||
if !hash_is_automatically_derived || peq_is_automatically_derived {
|
||||
return;
|
||||
|
|
@ -278,7 +278,7 @@ fn check_ord_partial_ord<'tcx>(
|
|||
{
|
||||
// Look for the PartialOrd implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
|
||||
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
|
||||
|
||||
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
|
||||
return;
|
||||
|
|
@ -349,6 +349,10 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
|
|||
{
|
||||
return;
|
||||
}
|
||||
// The presence of `unsafe` fields prevents deriving `Clone` automatically
|
||||
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
|
|
@ -426,10 +430,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
|||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::Block(block, _) = expr.kind {
|
||||
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
if let ExprKind::Block(block, _) = expr.kind
|
||||
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
walk_expr(self, expr)
|
||||
|
|
@ -479,7 +483,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
|
|||
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
|
||||
}
|
||||
|
||||
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
|
||||
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
|
||||
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
|
||||
// Initial map from generic index to param def.
|
||||
// Vec<(param_def, needs_eq)>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use clippy_config::types::{DisallowedPath, create_disallowed_map};
|
|||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_hir::{
|
||||
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
|
||||
|
|
@ -72,8 +73,15 @@ pub struct DisallowedMacros {
|
|||
|
||||
impl DisallowedMacros {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
|
||||
let (disallowed, _) = create_disallowed_map(
|
||||
tcx,
|
||||
&conf.disallowed_macros,
|
||||
|def_kind| matches!(def_kind, DefKind::Macro(_)),
|
||||
"macro",
|
||||
false,
|
||||
);
|
||||
Self {
|
||||
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
|
||||
disallowed,
|
||||
seen: FxHashSet::default(),
|
||||
derive_src: None,
|
||||
earlies,
|
||||
|
|
|
|||
|
|
@ -63,9 +63,19 @@ pub struct DisallowedMethods {
|
|||
|
||||
impl DisallowedMethods {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
disallowed: create_disallowed_map(tcx, &conf.disallowed_methods),
|
||||
}
|
||||
let (disallowed, _) = create_disallowed_map(
|
||||
tcx,
|
||||
&conf.disallowed_methods,
|
||||
|def_kind| {
|
||||
matches!(
|
||||
def_kind,
|
||||
DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn
|
||||
)
|
||||
},
|
||||
"function",
|
||||
false,
|
||||
);
|
||||
Self { disallowed }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,12 +84,7 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
|
|||
impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let (id, span) = match &expr.kind {
|
||||
ExprKind::Path(path)
|
||||
if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) =
|
||||
cx.qpath_res(path, expr.hir_id) =>
|
||||
{
|
||||
(id, expr.span)
|
||||
},
|
||||
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
|
||||
ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {
|
||||
(id, name.ident.span)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_config::types::DisallowedPath;
|
||||
use clippy_config::types::{DisallowedPath, create_disallowed_map};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -60,22 +60,7 @@ pub struct DisallowedTypes {
|
|||
|
||||
impl DisallowedTypes {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
|
||||
let mut def_ids = DefIdMap::default();
|
||||
let mut prim_tys = FxHashMap::default();
|
||||
for disallowed_path in &conf.disallowed_types {
|
||||
let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>();
|
||||
for res in clippy_utils::def_path_res(tcx, &path) {
|
||||
match res {
|
||||
Res::Def(_, id) => {
|
||||
def_ids.insert(id, (disallowed_path.path(), disallowed_path));
|
||||
},
|
||||
Res::PrimTy(ty) => {
|
||||
prim_tys.insert(ty, (disallowed_path.path(), disallowed_path));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
|
||||
Self { def_ids, prim_tys }
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +80,19 @@ impl DisallowedTypes {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn def_kind_predicate(def_kind: DefKind) -> bool {
|
||||
matches!(
|
||||
def_kind,
|
||||
DefKind::Struct
|
||||
| DefKind::Union
|
||||
| DefKind::Enum
|
||||
| DefKind::Trait
|
||||
| DefKind::TyAlias
|
||||
| DefKind::ForeignTy
|
||||
| DefKind::AssocTy
|
||||
)
|
||||
}
|
||||
|
||||
impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
|
||||
|
|
|
|||
|
|
@ -113,20 +113,20 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b
|
|||
s != "-" && s.contains('-')
|
||||
}
|
||||
|
||||
if let Ok(url) = Url::parse(word) {
|
||||
if let Ok(url) = Url::parse(word)
|
||||
// try to get around the fact that `foo::bar` parses as a valid URL
|
||||
if !url.cannot_be_a_base() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOC_MARKDOWN,
|
||||
span,
|
||||
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
|
||||
"try",
|
||||
format!("<{word}>"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return;
|
||||
}
|
||||
&& !url.cannot_be_a_base()
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOC_MARKDOWN,
|
||||
span,
|
||||
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
|
||||
"try",
|
||||
format!("<{word}>"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
|
||||
use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_doc_hidden, return_ty};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty};
|
||||
use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
pub fn check(
|
||||
cx: &LateContext<'_>,
|
||||
|
|
@ -13,7 +16,6 @@ pub fn check(
|
|||
sig: FnSig<'_>,
|
||||
headers: DocHeaders,
|
||||
body_id: Option<BodyId>,
|
||||
panic_info: Option<(Span, bool)>,
|
||||
check_private_items: bool,
|
||||
) {
|
||||
if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) {
|
||||
|
|
@ -46,13 +48,16 @@ pub fn check(
|
|||
),
|
||||
_ => (),
|
||||
}
|
||||
if !headers.panics && panic_info.is_some_and(|el| !el.1) {
|
||||
if !headers.panics
|
||||
&& let Some(body_id) = body_id
|
||||
&& let Some(panic_span) = find_panic(cx, body_id)
|
||||
{
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
MISSING_PANICS_DOC,
|
||||
span,
|
||||
"docs for function which may panic missing `# Panics` section",
|
||||
panic_info.map(|el| el.0),
|
||||
Some(panic_span),
|
||||
"first possible panic found here",
|
||||
);
|
||||
}
|
||||
|
|
@ -89,3 +94,39 @@ pub fn check(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
|
||||
let mut panic_span = None;
|
||||
let typeck = cx.tcx.typeck_body(body_id);
|
||||
for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& (is_panic(cx, macro_call.def_id)
|
||||
|| matches!(
|
||||
cx.tcx.get_diagnostic_name(macro_call.def_id),
|
||||
Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
|
||||
))
|
||||
&& !cx.tcx.hir_is_inside_const_context(expr.hir_id)
|
||||
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
|
||||
&& panic_span.is_none()
|
||||
{
|
||||
panic_span = Some(macro_call.span);
|
||||
}
|
||||
|
||||
// check for `unwrap` and `expect` for both `Option` and `Result`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or_else(|| method_chain_args(expr, &["expect"]))
|
||||
&& let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs()
|
||||
&& matches!(
|
||||
get_type_diagnostic_name(cx, receiver_ty),
|
||||
Some(sym::Option | sym::Result)
|
||||
)
|
||||
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
|
||||
&& panic_span.is_none()
|
||||
{
|
||||
panic_span = Some(expr.span);
|
||||
}
|
||||
|
||||
// Visit all nodes to fulfill any `#[expect]`s after the first linted panic
|
||||
ControlFlow::<!>::Continue(())
|
||||
});
|
||||
panic_span
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::Visitable;
|
||||
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
|
||||
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
|
||||
use pulldown_cmark::Event::{
|
||||
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
|
||||
TaskListMarker, Text,
|
||||
|
|
@ -16,18 +13,15 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
|
|||
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
|
||||
use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty;
|
||||
use rustc_resolve::rustdoc::{
|
||||
DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range,
|
||||
span_of_fragments,
|
||||
};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::ops::Range;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -194,6 +188,19 @@ declare_clippy_lint! {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Individual panics within a function can be ignored with `#[expect]` or
|
||||
/// `#[allow]`:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::num::NonZeroUsize;
|
||||
/// pub fn will_not_panic(x: usize) {
|
||||
/// #[expect(clippy::missing_panics_doc, reason = "infallible")]
|
||||
/// let y = NonZeroUsize::new(1).unwrap();
|
||||
///
|
||||
/// // If any panics are added in the future the lint will still catch them
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.51.0"]
|
||||
pub MISSING_PANICS_DOC,
|
||||
pedantic,
|
||||
|
|
@ -657,20 +664,16 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
|
|||
self.check_private_items,
|
||||
);
|
||||
match item.kind {
|
||||
ItemKind::Fn { sig, body: body_id, .. } => {
|
||||
ItemKind::Fn { sig, body, .. } => {
|
||||
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id())
|
||||
|| item.span.in_external_macro(cx.tcx.sess.source_map()))
|
||||
{
|
||||
let body = cx.tcx.hir_body(body_id);
|
||||
|
||||
let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
|
||||
missing_headers::check(
|
||||
cx,
|
||||
item.owner_id,
|
||||
sig,
|
||||
headers,
|
||||
Some(body_id),
|
||||
panic_info,
|
||||
Some(body),
|
||||
self.check_private_items,
|
||||
);
|
||||
}
|
||||
|
|
@ -697,15 +700,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
|
|||
if let TraitItemKind::Fn(sig, ..) = trait_item.kind
|
||||
&& !trait_item.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
missing_headers::check(
|
||||
cx,
|
||||
trait_item.owner_id,
|
||||
sig,
|
||||
headers,
|
||||
None,
|
||||
None,
|
||||
self.check_private_items,
|
||||
);
|
||||
missing_headers::check(cx, trait_item.owner_id, sig, headers, None, self.check_private_items);
|
||||
}
|
||||
},
|
||||
Node::ImplItem(impl_item) => {
|
||||
|
|
@ -713,16 +708,12 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
|
|||
&& !impl_item.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
&& !is_trait_impl_item(cx, impl_item.hir_id())
|
||||
{
|
||||
let body = cx.tcx.hir_body(body_id);
|
||||
|
||||
let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(impl_item.owner_id), body.value);
|
||||
missing_headers::check(
|
||||
cx,
|
||||
impl_item.owner_id,
|
||||
sig,
|
||||
headers,
|
||||
Some(body_id),
|
||||
panic_span,
|
||||
self.check_private_items,
|
||||
);
|
||||
}
|
||||
|
|
@ -880,19 +871,18 @@ fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a
|
|||
if let Some(start) = code_starts_at
|
||||
&& let Some(end) = code_ends_at
|
||||
&& code_includes_link
|
||||
&& let Some(span) = fragments.span(cx, start..end)
|
||||
{
|
||||
if let Some(span) = fragments.span(cx, start..end) {
|
||||
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
|
||||
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
"wrap the entire group in `<code>` tags",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("separate code snippets will be shown with a gap");
|
||||
});
|
||||
}
|
||||
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
|
||||
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
"wrap the entire group in `<code>` tags",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("separate code snippets will be shown with a gap");
|
||||
});
|
||||
}
|
||||
code_includes_link = false;
|
||||
code_starts_at = None;
|
||||
|
|
@ -1169,72 +1159,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
headers
|
||||
}
|
||||
|
||||
struct FindPanicUnwrap<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
is_const: bool,
|
||||
panic_span: Option<Span>,
|
||||
typeck_results: &'tcx ty::TypeckResults<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
|
||||
pub fn find_span(
|
||||
cx: &'a LateContext<'tcx>,
|
||||
typeck_results: &'tcx ty::TypeckResults<'tcx>,
|
||||
body: impl Visitable<'tcx>,
|
||||
) -> Option<(Span, bool)> {
|
||||
let mut vis = Self {
|
||||
cx,
|
||||
is_const: false,
|
||||
panic_span: None,
|
||||
typeck_results,
|
||||
};
|
||||
body.visit(&mut vis);
|
||||
vis.panic_span.map(|el| (el, vis.is_const))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::OnlyBodies;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if self.panic_span.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
|
||||
if is_panic(self.cx, macro_call.def_id)
|
||||
|| matches!(
|
||||
self.cx.tcx.item_name(macro_call.def_id).as_str(),
|
||||
"assert" | "assert_eq" | "assert_ne"
|
||||
)
|
||||
{
|
||||
self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id);
|
||||
self.panic_span = Some(macro_call.span);
|
||||
}
|
||||
}
|
||||
|
||||
// check for `unwrap` and `expect` for both `Option` and `Result`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
|
||||
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
|
||||
if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
|
||||
|| is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
|
||||
{
|
||||
self.panic_span = Some(expr.span);
|
||||
}
|
||||
}
|
||||
|
||||
// and check sub-expressions
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
// Panics in const blocks will cause compilation to fail.
|
||||
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
|
||||
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
|
||||
if range.end < range.start {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,10 @@ pub fn check(
|
|||
match parser.parse_item(ForceCollect::No) {
|
||||
Ok(Some(item)) => match &item.kind {
|
||||
ItemKind::Fn(box Fn {
|
||||
ident, sig, body: Some(block), ..
|
||||
ident,
|
||||
sig,
|
||||
body: Some(block),
|
||||
..
|
||||
}) if ident.name == sym::main => {
|
||||
if !ignore {
|
||||
get_test_spans(&item, *ident, &mut test_attr_spans);
|
||||
|
|
|
|||
|
|
@ -144,10 +144,10 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|
|||
// ..
|
||||
// }
|
||||
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
|
||||
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
|
||||
if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
|
||||
return body.hir_id == drop_expr.hir_id;
|
||||
}
|
||||
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
|
||||
&& let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id)
|
||||
{
|
||||
return body.hir_id == drop_expr.hir_id;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{SpanRangeExt, snippet_indent};
|
||||
use clippy_utils::tokenize_with_text;
|
||||
|
|
@ -89,7 +91,7 @@ declare_clippy_lint! {
|
|||
#[derive(Debug)]
|
||||
struct ItemInfo {
|
||||
kind: &'static str,
|
||||
name: Symbol,
|
||||
name: Option<Symbol>,
|
||||
span: Span,
|
||||
mod_items: Option<NodeId>,
|
||||
}
|
||||
|
|
@ -315,8 +317,12 @@ impl EmptyLineAfter {
|
|||
for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) {
|
||||
stop.comment_out(cx, &mut suggestions);
|
||||
}
|
||||
let name = match info.name {
|
||||
Some(name) => format!("{} `{name}`", info.kind).into(),
|
||||
None => Cow::from("the following item"),
|
||||
};
|
||||
diag.multipart_suggestion_verbose(
|
||||
format!("if the doc comment should not document `{}` comment it out", info.name),
|
||||
format!("if the doc comment should not document {name} then comment it out"),
|
||||
suggestions,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
|
@ -381,13 +387,10 @@ impl EmptyLineAfter {
|
|||
) {
|
||||
self.items.push(ItemInfo {
|
||||
kind: kind.descr(),
|
||||
// FIXME: this `sym::empty` can be leaked, see
|
||||
// https://github.com/rust-lang/rust/pull/138740#discussion_r2021979899
|
||||
name: if let Some(ident) = ident { ident.name } else { kw::Empty },
|
||||
span: if let Some(ident) = ident {
|
||||
span.with_hi(ident.span.hi())
|
||||
} else {
|
||||
span.with_hi(span.lo())
|
||||
name: ident.map(|ident| ident.name),
|
||||
span: match ident {
|
||||
Some(ident) => span.with_hi(ident.span.hi()),
|
||||
None => span.shrink_to_lo(),
|
||||
},
|
||||
mod_items: match kind {
|
||||
ItemKind::Mod(_, _, ModKind::Loaded(items, _, _, _)) => items
|
||||
|
|
@ -447,7 +450,7 @@ impl EarlyLintPass for EmptyLineAfter {
|
|||
fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) {
|
||||
self.items.push(ItemInfo {
|
||||
kind: "crate",
|
||||
name: kw::Crate,
|
||||
name: Some(kw::Crate),
|
||||
span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()),
|
||||
mod_items: krate
|
||||
.items
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
|
||||
use clippy_utils::attrs::span_contains_cfg;
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_hir::def::CtorOf;
|
||||
use rustc_hir::def::DefKind::Ctor;
|
||||
use rustc_hir::def::Res::Def;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -70,10 +75,23 @@ declare_clippy_lint! {
|
|||
"finds enum variants with empty brackets"
|
||||
}
|
||||
|
||||
declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
|
||||
#[derive(Debug)]
|
||||
enum Usage {
|
||||
Unused { redundant_use_sites: Vec<Span> },
|
||||
Used,
|
||||
NoDefinition { redundant_use_sites: Vec<Span> },
|
||||
}
|
||||
|
||||
impl EarlyLintPass for EmptyWithBrackets {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
#[derive(Default)]
|
||||
pub struct EmptyWithBrackets {
|
||||
// Value holds `Usage::Used` if the empty tuple variant was used as a function
|
||||
empty_tuple_enum_variants: FxIndexMap<LocalDefId, Usage>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
|
||||
|
||||
impl LateLintPass<'_> for EmptyWithBrackets {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if let ItemKind::Struct(ident, var_data, _) = &item.kind
|
||||
&& has_brackets(var_data)
|
||||
&& let span_after_ident = item.span.with_lo(ident.span.hi())
|
||||
|
|
@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
|
||||
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
|
||||
// the span of the parentheses/braces
|
||||
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
|
||||
|
||||
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
|
||||
span_lint_and_then(
|
||||
if has_no_fields(cx, &variant.data, span_after_ident) {
|
||||
match variant.data {
|
||||
VariantData::Struct { .. } => {
|
||||
// Empty struct variants can be linted immediately
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
|
||||
span_after_ident,
|
||||
"enum variant has empty brackets",
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span_after_ident,
|
||||
"remove the brackets",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
VariantData::Tuple(.., local_def_id) => {
|
||||
// Don't lint reachable tuple enums
|
||||
if cx.effective_visibilities.is_reachable(variant.def_id) {
|
||||
return;
|
||||
}
|
||||
if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) {
|
||||
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
|
||||
// definition was encountered. Now that there's a definition, convert it
|
||||
// to Usage::Unused.
|
||||
if let Usage::NoDefinition { redundant_use_sites } = entry {
|
||||
*entry = Usage::Unused {
|
||||
redundant_use_sites: redundant_use_sites.clone(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
self.empty_tuple_enum_variants.insert(
|
||||
local_def_id,
|
||||
Usage::Unused {
|
||||
redundant_use_sites: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
VariantData::Unit(..) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
|
||||
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) {
|
||||
// Do not count expressions from macro expansion as a redundant use site.
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
match self.empty_tuple_enum_variants.get_mut(&def_id) {
|
||||
Some(
|
||||
&mut (Usage::Unused {
|
||||
ref mut redundant_use_sites,
|
||||
}
|
||||
| Usage::NoDefinition {
|
||||
ref mut redundant_use_sites,
|
||||
}),
|
||||
) => {
|
||||
redundant_use_sites.push(parentheses_span);
|
||||
},
|
||||
None => {
|
||||
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
|
||||
self.empty_tuple_enum_variants.insert(
|
||||
def_id,
|
||||
Usage::NoDefinition {
|
||||
redundant_use_sites: vec![parentheses_span],
|
||||
},
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
} else {
|
||||
// The parentheses are not redundant.
|
||||
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
|
||||
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
|
||||
// Ignore all variants with Usage::Used or Usage::NoDefinition
|
||||
let Usage::Unused { redundant_use_sites } = usage else {
|
||||
continue;
|
||||
};
|
||||
// Attempt to fetch the Variant from LocalDefId.
|
||||
let Node::Variant(variant) = cx.tcx.hir_node(
|
||||
cx.tcx
|
||||
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
// Span of the parentheses in variant definition
|
||||
let span = variant.span.with_lo(variant.ident.span.hi());
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
|
||||
span_after_ident,
|
||||
variant.hir_id,
|
||||
span,
|
||||
"enum variant has empty brackets",
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span_after_ident,
|
||||
"remove the brackets",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
if redundant_use_sites.is_empty() {
|
||||
// If there's no redundant use sites, the definition is the only place to modify.
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span,
|
||||
"remove the brackets",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
let mut parentheses_spans: Vec<_> =
|
||||
redundant_use_sites.iter().map(|span| (*span, String::new())).collect();
|
||||
parentheses_spans.push((span, String::new()));
|
||||
diagnostic.multipart_suggestion(
|
||||
"remove the brackets",
|
||||
parentheses_spans,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_no_ident_token(braces_span_str: &str) -> bool {
|
||||
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
|
||||
fn has_brackets(var_data: &VariantData<'_>) -> bool {
|
||||
!matches!(var_data, VariantData::Unit(..))
|
||||
}
|
||||
|
||||
fn has_brackets(var_data: &VariantData) -> bool {
|
||||
!matches!(var_data, VariantData::Unit(_))
|
||||
}
|
||||
|
||||
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
|
||||
if !var_data.fields().is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn has_no_fields(cx: &LateContext<'_>, var_data: &VariantData<'_>, braces_span: Span) -> bool {
|
||||
var_data.fields().is_empty() &&
|
||||
// there might still be field declarations hidden from the AST
|
||||
// (conditionally compiled code using #[cfg(..)])
|
||||
|
||||
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
has_no_ident_token(braces_span_str.as_ref())
|
||||
!span_contains_cfg(cx, braces_span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_has_no_ident_token() {
|
||||
let input = "{ field: u8 }";
|
||||
assert!(!has_no_ident_token(input));
|
||||
|
||||
let input = "(u8, String);";
|
||||
assert!(!has_no_ident_token(input));
|
||||
|
||||
let input = " {
|
||||
// test = 5
|
||||
}
|
||||
";
|
||||
assert!(has_no_ident_token(input));
|
||||
|
||||
let input = " ();";
|
||||
assert!(has_no_ident_token(input));
|
||||
// If expression HIR ID and callee HIR ID are same, returns the span of the parentheses, else,
|
||||
// returns None.
|
||||
fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
|
||||
if let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
|
||||
&& let ExprKind::Call(callee, ..) = parent.kind
|
||||
&& callee.hir_id == expr.hir_id
|
||||
{
|
||||
Some(parent.span.with_lo(expr.span.hi()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the LocalDefId of the variant being called as a function if it exists.
|
||||
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
|
||||
if let ExprKind::Path(QPath::Resolved(
|
||||
_,
|
||||
Path {
|
||||
res: Def(Ctor(CtorOf::Variant, _), def_id),
|
||||
..
|
||||
},
|
||||
)) = expr.kind
|
||||
{
|
||||
def_id.as_local()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,14 +95,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
return;
|
||||
};
|
||||
|
||||
if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
|
||||
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if then_search.edits.is_empty() && else_search.edits.is_empty() {
|
||||
// No insertions
|
||||
return;
|
||||
} else if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
|
||||
// If there are other uses of the key, and the key is not copy,
|
||||
// we cannot perform a fix automatically, but continue to emit a lint.
|
||||
None
|
||||
} else if then_search.edits.is_empty() || else_search.edits.is_empty() {
|
||||
// if .. { insert } else { .. } or if .. { .. } else { insert }
|
||||
let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
|
||||
|
|
@ -123,10 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
|
||||
),
|
||||
};
|
||||
format!(
|
||||
Some(format!(
|
||||
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {then_str} else {else_str}",
|
||||
map_ty.entry_path(),
|
||||
)
|
||||
))
|
||||
} else {
|
||||
// if .. { insert } else { insert }
|
||||
let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
|
||||
|
|
@ -142,13 +141,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
};
|
||||
let indent_str = snippet_indent(cx, expr.span);
|
||||
let indent_str = indent_str.as_deref().unwrap_or("");
|
||||
format!(
|
||||
Some(format!(
|
||||
"match {map_str}.entry({key_str}) {{\n{indent_str} {entry}::{then_entry} => {}\n\
|
||||
{indent_str} {entry}::{else_entry} => {}\n{indent_str}}}",
|
||||
reindent_multiline(&then_str, true, Some(4 + indent_str.len())),
|
||||
reindent_multiline(&else_str, true, Some(4 + indent_str.len())),
|
||||
entry = map_ty.entry_path(),
|
||||
)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if then_search.edits.is_empty() {
|
||||
|
|
@ -163,17 +162,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
} else {
|
||||
then_search.snippet_occupied(cx, then_expr.span, &mut app)
|
||||
};
|
||||
format!(
|
||||
Some(format!(
|
||||
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}",
|
||||
map_ty.entry_path(),
|
||||
)
|
||||
))
|
||||
} else if let Some(insertion) = then_search.as_single_insertion() {
|
||||
let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
|
||||
if contains_expr.negated {
|
||||
if insertion.value.can_have_side_effects() {
|
||||
format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});")
|
||||
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});"))
|
||||
} else {
|
||||
format!("{map_str}.entry({key_str}).or_insert({value_str});")
|
||||
Some(format!("{map_str}.entry({key_str}).or_insert({value_str});"))
|
||||
}
|
||||
} else {
|
||||
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
|
||||
|
|
@ -183,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
} else {
|
||||
let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
|
||||
if contains_expr.negated {
|
||||
format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});")
|
||||
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});"))
|
||||
} else {
|
||||
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
|
||||
// This would need to be a different lint.
|
||||
|
|
@ -192,7 +191,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
}
|
||||
};
|
||||
|
||||
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
|
||||
if let Some(sugg) = sugg {
|
||||
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
|
||||
} else {
|
||||
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
|
|||
.ok()
|
||||
.map(|val| rustc_middle::mir::Const::from_value(val, ty));
|
||||
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) {
|
||||
if let ty::Adt(adt, _) = ty.kind() {
|
||||
if adt.is_enum() {
|
||||
ty = adt.repr().discr_type().to_ty(cx.tcx);
|
||||
}
|
||||
if let ty::Adt(adt, _) = ty.kind()
|
||||
&& adt.is_enum()
|
||||
{
|
||||
ty = adt.repr().discr_type().to_ty(cx.tcx);
|
||||
}
|
||||
match ty.kind() {
|
||||
ty::Int(IntTy::Isize) => {
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
|
|||
_: Span,
|
||||
fn_def_id: LocalDefId,
|
||||
) {
|
||||
if let Some(header) = fn_kind.header() {
|
||||
if header.abi != ExternAbi::Rust {
|
||||
return;
|
||||
}
|
||||
if let Some(header) = fn_kind.header()
|
||||
&& header.abi != ExternAbi::Rust
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let parent_id = cx
|
||||
|
|
@ -93,12 +93,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
|
|||
// find `self` ty for this trait if relevant
|
||||
if let ItemKind::Trait(_, _, _, _, _, items) = item.kind {
|
||||
for trait_item in items {
|
||||
if trait_item.id.owner_id.def_id == fn_def_id {
|
||||
if trait_item.id.owner_id.def_id == fn_def_id
|
||||
// be sure we have `self` parameter in this function
|
||||
if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
|
||||
trait_self_ty =
|
||||
Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
|
||||
}
|
||||
&& trait_item.kind == (AssocItemKind::Fn { has_self: true })
|
||||
{
|
||||
trait_self_ty = Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,22 +141,22 @@ fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool {
|
|||
|
||||
impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
|
||||
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
|
||||
if cmt.place.projections.is_empty() {
|
||||
if let PlaceBase::Local(lid) = cmt.place.base {
|
||||
// FIXME(rust/#120456) - is `swap_remove` correct?
|
||||
self.set.swap_remove(&lid);
|
||||
}
|
||||
if cmt.place.projections.is_empty()
|
||||
&& let PlaceBase::Local(lid) = cmt.place.base
|
||||
{
|
||||
// FIXME(rust/#120456) - is `swap_remove` correct?
|
||||
self.set.swap_remove(&lid);
|
||||
}
|
||||
}
|
||||
|
||||
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
|
||||
|
||||
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
|
||||
if cmt.place.projections.is_empty() {
|
||||
if let PlaceBase::Local(lid) = cmt.place.base {
|
||||
// FIXME(rust/#120456) - is `swap_remove` correct?
|
||||
self.set.swap_remove(&lid);
|
||||
}
|
||||
if cmt.place.projections.is_empty()
|
||||
&& let PlaceBase::Local(lid) = cmt.place.base
|
||||
{
|
||||
// FIXME(rust/#120456) - is `swap_remove` correct?
|
||||
self.set.swap_remove(&lid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,10 +170,11 @@ impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
|
|||
|
||||
// skip if there is a `self` parameter binding to a type
|
||||
// that contains `Self` (i.e.: `self: Box<Self>`), see #4804
|
||||
if let Some(trait_self_ty) = self.trait_self_ty {
|
||||
if self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) {
|
||||
return;
|
||||
}
|
||||
if let Some(trait_self_ty) = self.trait_self_ty
|
||||
&& self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower
|
||||
&& cmt.place.ty().contains(trait_self_ty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
|
|||
|
||||
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
|
||||
if is_panic(self.lcx, macro_call.def_id) {
|
||||
self.result.push(expr.span);
|
||||
}
|
||||
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr)
|
||||
&& is_panic(self.lcx, macro_call.def_id)
|
||||
{
|
||||
self.result.push(expr.span);
|
||||
}
|
||||
|
||||
// check for `unwrap`
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
|
|||
};
|
||||
}
|
||||
|
||||
suggestion.maybe_par()
|
||||
suggestion.maybe_paren()
|
||||
}
|
||||
|
||||
fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
|
|
@ -165,7 +165,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, ar
|
|||
expr.span,
|
||||
"logarithm for bases 2, 10 and e can be computed more accurately",
|
||||
"consider using",
|
||||
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_par()),
|
||||
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
@ -228,24 +228,24 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
|
|||
|
||||
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
// Check receiver
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) {
|
||||
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver)
|
||||
&& let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
|
||||
Some("exp")
|
||||
} else if F32(2.0) == value || F64(2.0) == value {
|
||||
Some("exp2")
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"exponent for bases 2 and e can be computed more accurately",
|
||||
"consider using",
|
||||
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"exponent for bases 2 and e can be computed more accurately",
|
||||
"consider using",
|
||||
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
// Check argument
|
||||
|
|
@ -254,13 +254,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
|||
(
|
||||
SUBOPTIMAL_FLOPS,
|
||||
"square-root of a number can be computed more efficiently and accurately",
|
||||
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
|
||||
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
|
||||
)
|
||||
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
|
||||
(
|
||||
IMPRECISE_FLOPS,
|
||||
"cube-root of a number can be computed more accurately",
|
||||
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
|
||||
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
|
||||
)
|
||||
} else if let Some(exponent) = get_integer_from_float_constant(&value) {
|
||||
(
|
||||
|
|
@ -268,7 +268,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
|||
"exponentiation with integer powers can be computed more efficiently",
|
||||
format!(
|
||||
"{}.powi({})",
|
||||
Sugg::hir(cx, receiver, "..").maybe_par(),
|
||||
Sugg::hir(cx, receiver, "..").maybe_paren(),
|
||||
numeric_literal::format(&exponent.to_string(), None, false)
|
||||
),
|
||||
)
|
||||
|
|
@ -289,55 +289,53 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
|||
}
|
||||
|
||||
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
|
||||
if value == Int(2) {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
if let Some(grandparent) = get_parent_expr(cx, parent) {
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
|
||||
{
|
||||
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0])
|
||||
&& value == Int(2)
|
||||
&& let Some(parent) = get_parent_expr(cx, expr)
|
||||
{
|
||||
if let Some(grandparent) = get_parent_expr(cx, parent)
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
|
||||
&& method_name.as_str() == "sqrt"
|
||||
&& detect_hypot(cx, receiver).is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: op @ (BinOpKind::Add | BinOpKind::Sub),
|
||||
..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = parent.kind
|
||||
{
|
||||
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
|
||||
|
||||
// Negate expr if original code has subtraction and expr is on the right side
|
||||
let maybe_neg_sugg = |expr, hir_id| {
|
||||
let sugg = Sugg::hir(cx, expr, "..");
|
||||
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
|
||||
-sugg
|
||||
} else {
|
||||
sugg
|
||||
}
|
||||
};
|
||||
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: op @ (BinOpKind::Add | BinOpKind::Sub),
|
||||
..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = parent.kind
|
||||
{
|
||||
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
|
||||
|
||||
// Negate expr if original code has subtraction and expr is on the right side
|
||||
let maybe_neg_sugg = |expr, hir_id| {
|
||||
let sugg = Sugg::hir(cx, expr, "..");
|
||||
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
|
||||
-sugg
|
||||
} else {
|
||||
sugg
|
||||
}
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
parent.span,
|
||||
"multiply and add expressions can be calculated more efficiently and accurately",
|
||||
"consider using",
|
||||
format!(
|
||||
"{}.mul_add({}, {})",
|
||||
Sugg::hir(cx, receiver, "..").maybe_par(),
|
||||
maybe_neg_sugg(receiver, expr.hir_id),
|
||||
maybe_neg_sugg(other_addend, other_addend.hir_id),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
parent.span,
|
||||
"multiply and add expressions can be calculated more efficiently and accurately",
|
||||
"consider using",
|
||||
format!(
|
||||
"{}.mul_add({}, {})",
|
||||
Sugg::hir(cx, receiver, "..").maybe_paren(),
|
||||
maybe_neg_sugg(receiver, expr.hir_id),
|
||||
maybe_neg_sugg(other_addend, other_addend.hir_id),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -371,7 +369,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
|
|||
{
|
||||
return Some(format!(
|
||||
"{}.hypot({})",
|
||||
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
|
||||
Sugg::hir(cx, lmul_lhs, "..").maybe_paren(),
|
||||
Sugg::hir(cx, rmul_lhs, "..")
|
||||
));
|
||||
}
|
||||
|
|
@ -403,7 +401,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
|
|||
{
|
||||
return Some(format!(
|
||||
"{}.hypot({})",
|
||||
Sugg::hir(cx, largs_0, "..").maybe_par(),
|
||||
Sugg::hir(cx, largs_0, "..").maybe_paren(),
|
||||
Sugg::hir(cx, rargs_0, "..")
|
||||
));
|
||||
}
|
||||
|
|
@ -449,7 +447,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
expr.span,
|
||||
"(e.pow(x) - 1) can be computed more accurately",
|
||||
"consider using",
|
||||
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
|
||||
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
@ -483,12 +481,12 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
rhs,
|
||||
) = &expr.kind
|
||||
{
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind {
|
||||
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind
|
||||
&& method_name.as_str() == "sqrt"
|
||||
&& detect_hypot(cx, receiver).is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let maybe_neg_sugg = |expr| {
|
||||
|
|
@ -566,15 +564,15 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
/// If the two expressions are not negations of each other, then it
|
||||
/// returns None.
|
||||
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
|
||||
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
|
||||
if eq_expr_value(cx, expr1_negated, expr2) {
|
||||
return Some((false, expr2));
|
||||
}
|
||||
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind
|
||||
&& eq_expr_value(cx, expr1_negated, expr2)
|
||||
{
|
||||
return Some((false, expr2));
|
||||
}
|
||||
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
|
||||
if eq_expr_value(cx, expr1, expr2_negated) {
|
||||
return Some((true, expr1));
|
||||
}
|
||||
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind
|
||||
&& eq_expr_value(cx, expr1, expr2_negated)
|
||||
{
|
||||
return Some((true, expr1));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -591,11 +589,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
{
|
||||
let positive_abs_sugg = (
|
||||
"manual implementation of `abs` method",
|
||||
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
|
||||
);
|
||||
let negative_abs_sugg = (
|
||||
"manual implementation of negation of `abs` method",
|
||||
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
|
||||
);
|
||||
let sugg = if is_testing_positive(cx, cond, body) {
|
||||
if if_expr_positive {
|
||||
|
|
@ -672,7 +670,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
"consider using",
|
||||
format!(
|
||||
"{}.log({})",
|
||||
Sugg::hir(cx, largs_self, "..").maybe_par(),
|
||||
Sugg::hir(cx, largs_self, "..").maybe_paren(),
|
||||
Sugg::hir(cx, rargs_self, ".."),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
|
|
@ -703,7 +701,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
|
||||
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
|
||||
{
|
||||
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind
|
||||
&& let ast::LitKind::Float(ref value, float_type) = literal.node
|
||||
&& float_type == ast::LitFloatType::Unsuffixed
|
||||
|
|
@ -726,7 +724,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
|
||||
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
|
||||
{
|
||||
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind
|
||||
&& let ast::LitKind::Float(ref value, float_type) = literal.node
|
||||
&& float_type == ast::LitFloatType::Unsuffixed
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
|||
.into_owned()
|
||||
} else {
|
||||
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
|
||||
format!("{}.to_string()", sugg.maybe_par())
|
||||
format!("{}.to_string()", sugg.maybe_paren())
|
||||
};
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ declare_clippy_lint! {
|
|||
/// format!("{var:.prec$}");
|
||||
/// ```
|
||||
///
|
||||
/// If allow-mixed-uninlined-format-args is set to false in clippy.toml,
|
||||
/// If `allow-mixed-uninlined-format-args` is set to `false` in clippy.toml,
|
||||
/// the following code will also trigger the lint:
|
||||
/// ```no_run
|
||||
/// # let var = 42;
|
||||
|
|
@ -159,7 +159,7 @@ declare_clippy_lint! {
|
|||
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
|
||||
#[clippy::version = "1.66.0"]
|
||||
pub UNINLINED_FORMAT_ARGS,
|
||||
pedantic,
|
||||
style,
|
||||
"using non-inlined variables in `format!` calls"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators, sym};
|
||||
use rustc_ast::{FormatArgsPiece, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{Symbol, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -185,13 +185,13 @@ impl FormatImplExpr<'_, '_> {
|
|||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::LowerExp => sym::LowerExp,
|
||||
FormatTrait::UpperExp => sym::UpperExp,
|
||||
FormatTrait::Octal => sym::Octal,
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
FormatTrait::Binary => sym::Binary,
|
||||
FormatTrait::LowerHex => sym::LowerHex,
|
||||
FormatTrait::UpperHex => sym::UpperHex,
|
||||
}
|
||||
&& trait_name == self.format_trait_impl.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
|
|
|
|||
|
|
@ -138,27 +138,28 @@ impl EarlyLintPass for Formatting {
|
|||
|
||||
/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
|
||||
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
|
||||
if !lhs.span.from_expansion() && !rhs.span.from_expansion() {
|
||||
let eq_span = lhs.span.between(rhs.span);
|
||||
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
|
||||
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
|
||||
let op = op.as_str();
|
||||
let eqop_span = lhs.span.between(sub_rhs.span);
|
||||
if eq_snippet.ends_with('=') {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ASSIGNMENT_FORMATTING,
|
||||
eqop_span,
|
||||
format!(
|
||||
"this looks like you are trying to use `.. {op}= ..`, but you \
|
||||
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind
|
||||
&& !lhs.span.from_expansion()
|
||||
&& !rhs.span.from_expansion()
|
||||
{
|
||||
let eq_span = lhs.span.between(rhs.span);
|
||||
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind
|
||||
&& let Some(eq_snippet) = snippet_opt(cx, eq_span)
|
||||
{
|
||||
let op = op.as_str();
|
||||
let eqop_span = lhs.span.between(sub_rhs.span);
|
||||
if eq_snippet.ends_with('=') {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ASSIGNMENT_FORMATTING,
|
||||
eqop_span,
|
||||
format!(
|
||||
"this looks like you are trying to use `.. {op}= ..`, but you \
|
||||
really are doing `.. = ({op} ..)`"
|
||||
),
|
||||
None,
|
||||
format!("to remove this lint, use either `{op}=` or `= {op}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
None,
|
||||
format!("to remove this lint, use either `{op}=` or `= {op}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
|
|||
};
|
||||
|
||||
let sugg =
|
||||
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
|
||||
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_paren();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::source::snippet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind};
|
||||
use rustc_hir::{BlockCheckMode, Body, ExprKind, FnDecl, ImplicitSelfKind, UnsafeSource};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Span;
|
||||
|
|
@ -40,14 +40,25 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
|
|||
name
|
||||
};
|
||||
|
||||
// Body must be &(mut) <self_data>.name
|
||||
// Body must be `&(mut) <self_data>.name`, potentially in an `unsafe` block
|
||||
// self_data is not necessarily self, to also lint sub-getters, etc…
|
||||
|
||||
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& let Some(block_expr) = block.expr
|
||||
{
|
||||
block_expr
|
||||
if let ExprKind::Block(unsafe_block, _) = block_expr.kind
|
||||
&& unsafe_block.stmts.is_empty()
|
||||
&& matches!(
|
||||
unsafe_block.rules,
|
||||
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
)
|
||||
&& let Some(unsafe_block_expr) = unsafe_block.expr
|
||||
{
|
||||
unsafe_block_expr
|
||||
} else {
|
||||
block_expr
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,9 +59,7 @@ impl RenamedFnArgs {
|
|||
let mut renamed: Vec<(Span, String)> = vec![];
|
||||
|
||||
debug_assert!(default_idents.size_hint() == current_idents.size_hint());
|
||||
while let (Some(default_ident), Some(current_ident)) =
|
||||
(default_idents.next(), current_idents.next())
|
||||
{
|
||||
while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) {
|
||||
let has_name_to_check = |ident: Option<Ident>| {
|
||||
if let Some(ident) = ident
|
||||
&& ident.name != kw::Underscore
|
||||
|
|
|
|||
|
|
@ -47,16 +47,16 @@ pub(super) fn check_fn(
|
|||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind
|
||||
// don't lint extern functions decls, it's not their fault
|
||||
if sig.header.abi == ExternAbi::Rust {
|
||||
check_arg_number(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
too_many_arguments_threshold,
|
||||
);
|
||||
}
|
||||
&& sig.header.abi == ExternAbi::Rust
|
||||
{
|
||||
check_arg_number(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
too_many_arguments_threshold,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
|
|||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
|
||||
.maybe_par()
|
||||
.maybe_paren()
|
||||
.to_string();
|
||||
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
|
||||
let method_body = if let Some(first_stmt) = then_block.stmts.first() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
|
||||
use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
|
|
@ -134,6 +134,10 @@ fn lint_implicit_returns(
|
|||
},
|
||||
|
||||
ExprKind::Match(_, arms, _) => {
|
||||
if let Some(await_expr) = desugar_await(expr) {
|
||||
lint_return(cx, await_expr.hir_id, await_expr.span);
|
||||
return LintLocation::Inner;
|
||||
}
|
||||
for arm in arms {
|
||||
let res = lint_implicit_returns(
|
||||
cx,
|
||||
|
|
@ -153,18 +157,18 @@ fn lint_implicit_returns(
|
|||
ExprKind::Loop(block, ..) => {
|
||||
let mut add_return = false;
|
||||
let _: Option<!> = for_each_expr_without_closures(block, |e| {
|
||||
if let ExprKind::Break(dest, sub_expr) = e.kind {
|
||||
if dest.target_id.ok() == Some(expr.hir_id) {
|
||||
if call_site_span.is_none() && e.span.ctxt() == ctxt {
|
||||
// At this point sub_expr can be `None` in async functions which either diverge, or return
|
||||
// the unit type.
|
||||
if let Some(sub_expr) = sub_expr {
|
||||
lint_break(cx, e.hir_id, e.span, sub_expr.span);
|
||||
}
|
||||
} else {
|
||||
// the break expression is from a macro call, add a return to the loop
|
||||
add_return = true;
|
||||
if let ExprKind::Break(dest, sub_expr) = e.kind
|
||||
&& dest.target_id.ok() == Some(expr.hir_id)
|
||||
{
|
||||
if call_site_span.is_none() && e.span.ctxt() == ctxt {
|
||||
// At this point sub_expr can be `None` in async functions which either diverge, or return
|
||||
// the unit type.
|
||||
if let Some(sub_expr) = sub_expr {
|
||||
lint_break(cx, e.hir_id, e.span, sub_expr.span);
|
||||
}
|
||||
} else {
|
||||
// the break expression is from a macro call, add a return to the loop
|
||||
add_return = true;
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
|
|
@ -241,6 +245,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
|
|||
Some(e) => e,
|
||||
None => return,
|
||||
}
|
||||
} else if let Some(expr) = get_async_closure_expr(cx.tcx, body.value) {
|
||||
expr
|
||||
} else {
|
||||
body.value
|
||||
};
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ fn check_subtraction(
|
|||
// This part of the condition is voluntarily split from the one before to ensure that
|
||||
// if `snippet_opt` fails, it won't try the next conditions.
|
||||
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
|
||||
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
|
||||
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren)
|
||||
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
|
||||
{
|
||||
let sugg = format!(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use rustc_hir::{
|
|||
};
|
||||
use rustc_hir_analysis::lower_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{self, AssocItem, ClauseKind, Generics, Ty, TyCtxt};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
|
|||
assocs
|
||||
.filter_by_name_unhygienic(constraint.ident.name)
|
||||
.next()
|
||||
.is_some_and(|assoc| assoc.is_type())
|
||||
.is_some_and(AssocItem::is_type)
|
||||
})
|
||||
{
|
||||
emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound);
|
||||
|
|
|
|||
|
|
@ -65,13 +65,13 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
pub struct InconsistentStructConstructor {
|
||||
lint_inconsistent_struct_field_initializers: bool,
|
||||
check_inconsistent_struct_field_initializers: bool,
|
||||
}
|
||||
|
||||
impl InconsistentStructConstructor {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers,
|
||||
check_inconsistent_struct_field_initializers: conf.check_inconsistent_struct_field_initializers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
|
|||
let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand);
|
||||
let applicability = if all_fields_are_shorthand {
|
||||
Applicability::MachineApplicable
|
||||
} else if self.lint_inconsistent_struct_field_initializers {
|
||||
} else if self.check_inconsistent_struct_field_initializers {
|
||||
Applicability::MaybeIncorrect
|
||||
} else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ use clippy_config::Conf;
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
|
||||
use clippy_utils::{higher, is_from_proc_macro, is_in_test};
|
||||
use clippy_utils::{higher, is_from_proc_macro, is_in_test, sym};
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -136,28 +135,28 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
|
||||
let const_range = to_const_range(cx, range, size);
|
||||
|
||||
if let (Some(start), _) = const_range {
|
||||
if start > size {
|
||||
span_lint(
|
||||
cx,
|
||||
OUT_OF_BOUNDS_INDEXING,
|
||||
range.start.map_or(expr.span, |start| start.span),
|
||||
"range is out of bounds",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let (Some(start), _) = const_range
|
||||
&& start > size
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
OUT_OF_BOUNDS_INDEXING,
|
||||
range.start.map_or(expr.span, |start| start.span),
|
||||
"range is out of bounds",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let (_, Some(end)) = const_range {
|
||||
if end > size {
|
||||
span_lint(
|
||||
cx,
|
||||
OUT_OF_BOUNDS_INDEXING,
|
||||
range.end.map_or(expr.span, |end| end.span),
|
||||
"range is out of bounds",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let (_, Some(end)) = const_range
|
||||
&& end > size
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
OUT_OF_BOUNDS_INDEXING,
|
||||
range.end.map_or(expr.span, |end| end.span),
|
||||
"range is out of bounds",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let (Some(_), Some(_)) = const_range {
|
||||
|
|
@ -268,7 +267,7 @@ fn ty_has_applicable_get_function<'tcx>(
|
|||
index_expr: &Expr<'_>,
|
||||
) -> bool {
|
||||
if let ty::Adt(_, _) = array_ty.kind()
|
||||
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| {
|
||||
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym::get).map(|m| {
|
||||
cx.tcx
|
||||
.fn_sig(m.def_id)
|
||||
.skip_binder()
|
||||
|
|
|
|||
|
|
@ -156,11 +156,12 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
|||
.and(cap);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
if method.ident.name.as_str() == "flat_map"
|
||||
&& args.len() == 1
|
||||
&& let ExprKind::Closure(&Closure { body, .. }) = args[0].kind
|
||||
{
|
||||
let body = cx.tcx.hir_body(body);
|
||||
return is_infinite(cx, body.value);
|
||||
}
|
||||
Finite
|
||||
},
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg
|
|||
expr.span,
|
||||
"manual implementation of `Instant::elapsed`",
|
||||
"try",
|
||||
format!("{}.elapsed()", sugg.maybe_par()),
|
||||
format!("{}.elapsed()", sugg.maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,14 +130,14 @@ impl IntPlusOne {
|
|||
BinOpKind::Le => "<",
|
||||
_ => return None,
|
||||
};
|
||||
if let Some(snippet) = node.span.get_source_text(cx) {
|
||||
if let Some(other_side_snippet) = other_side.span.get_source_text(cx) {
|
||||
let rec = match side {
|
||||
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
|
||||
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
|
||||
};
|
||||
return rec;
|
||||
}
|
||||
if let Some(snippet) = node.span.get_source_text(cx)
|
||||
&& let Some(other_side_snippet) = other_side.span.get_source_text(cx)
|
||||
{
|
||||
let rec = match side {
|
||||
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
|
||||
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
|
||||
};
|
||||
return rec;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -157,10 +157,10 @@ impl IntPlusOne {
|
|||
|
||||
impl EarlyLintPass for IntPlusOne {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
|
||||
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
|
||||
if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
|
||||
Self::emit_warning(cx, item, rec);
|
||||
}
|
||||
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind
|
||||
&& let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs)
|
||||
{
|
||||
Self::emit_warning(cx, item, rec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,49 +91,49 @@ fn upcast_comparison_bounds_err<'tcx>(
|
|||
rhs: &'tcx Expr<'_>,
|
||||
invert: bool,
|
||||
) {
|
||||
if let Some((lb, ub)) = lhs_bounds {
|
||||
if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) {
|
||||
if rel == Rel::Eq || rel == Rel::Ne {
|
||||
if norm_rhs_val < lb || norm_rhs_val > ub {
|
||||
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
|
||||
}
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val < lb
|
||||
} else {
|
||||
ub < norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val <= lb
|
||||
} else {
|
||||
ub <= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val >= ub
|
||||
} else {
|
||||
lb >= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val > ub
|
||||
} else {
|
||||
lb > norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
if let Some((lb, ub)) = lhs_bounds
|
||||
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs)
|
||||
{
|
||||
if rel == Rel::Eq || rel == Rel::Ne {
|
||||
if norm_rhs_val < lb || norm_rhs_val > ub {
|
||||
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
|
||||
}
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val < lb
|
||||
} else {
|
||||
ub < norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val <= lb
|
||||
} else {
|
||||
ub <= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val >= ub
|
||||
} else {
|
||||
lb >= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val > ub
|
||||
} else {
|
||||
lb > norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,22 +377,21 @@ impl ItemNameRepetitions {
|
|||
"field name starts with the struct's name",
|
||||
);
|
||||
}
|
||||
if field_words.len() > item_name_words.len() {
|
||||
if field_words.len() > item_name_words.len()
|
||||
// lint only if the end is not covered by the start
|
||||
if field_words
|
||||
&& field_words
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(item_name_words.iter().rev())
|
||||
.all(|(a, b)| a == b)
|
||||
{
|
||||
span_lint_hir(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
field.hir_id,
|
||||
field.span,
|
||||
"field name ends with the struct's name",
|
||||
);
|
||||
}
|
||||
{
|
||||
span_lint_hir(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
field.hir_id,
|
||||
field.span,
|
||||
"field name ends with the struct's name",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -445,57 +444,56 @@ impl LateLintPass<'_> for ItemNameRepetitions {
|
|||
|
||||
let item_name = ident.name.as_str();
|
||||
let item_camel = to_camel_case(item_name);
|
||||
if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
|
||||
if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules {
|
||||
// constants don't have surrounding modules
|
||||
if !mod_camel.is_empty() {
|
||||
if mod_name == &ident.name
|
||||
&& let ItemKind::Mod(..) = item.kind
|
||||
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
|
||||
{
|
||||
span_lint(
|
||||
if !item.span.from_expansion() && is_present_in_source(cx, item.span)
|
||||
&& let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules
|
||||
// constants don't have surrounding modules
|
||||
&& !mod_camel.is_empty()
|
||||
{
|
||||
if mod_name == &ident.name
|
||||
&& let ItemKind::Mod(..) = item.kind
|
||||
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_INCEPTION,
|
||||
item.span,
|
||||
"module has the same name as its containing module",
|
||||
);
|
||||
}
|
||||
|
||||
// The `module_name_repetitions` lint should only trigger if the item has the module in its
|
||||
// name. Having the same name is accepted.
|
||||
if cx.tcx.visibility(item.owner_id).is_public()
|
||||
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
|
||||
&& item_camel.len() > mod_camel.len()
|
||||
{
|
||||
let matching = count_match_start(mod_camel, &item_camel);
|
||||
let rmatching = count_match_end(mod_camel, &item_camel);
|
||||
let nchars = mod_camel.chars().count();
|
||||
|
||||
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
|
||||
|
||||
if matching.char_count == nchars {
|
||||
match item_camel.chars().nth(nchars) {
|
||||
Some(c) if is_word_beginning(c) => span_lint(
|
||||
cx,
|
||||
MODULE_INCEPTION,
|
||||
item.span,
|
||||
"module has the same name as its containing module",
|
||||
);
|
||||
}
|
||||
|
||||
// The `module_name_repetitions` lint should only trigger if the item has the module in its
|
||||
// name. Having the same name is accepted.
|
||||
if cx.tcx.visibility(item.owner_id).is_public()
|
||||
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
|
||||
&& item_camel.len() > mod_camel.len()
|
||||
{
|
||||
let matching = count_match_start(mod_camel, &item_camel);
|
||||
let rmatching = count_match_end(mod_camel, &item_camel);
|
||||
let nchars = mod_camel.chars().count();
|
||||
|
||||
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
|
||||
|
||||
if matching.char_count == nchars {
|
||||
match item_camel.chars().nth(nchars) {
|
||||
Some(c) if is_word_beginning(c) => span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name starts with its containing module's name",
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if rmatching.char_count == nchars
|
||||
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name ends with its containing module's name",
|
||||
);
|
||||
}
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name starts with its containing module's name",
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if rmatching.char_count == nchars
|
||||
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name ends with its containing module's name",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue