Merge branch 'master' into doc_link_with_quotes
This commit is contained in:
commit
c9be57dbf3
2
.github/ISSUE_TEMPLATE/blank_issue.yml
vendored
2
.github/ISSUE_TEMPLATE/blank_issue.yml
vendored
@ -9,7 +9,7 @@ body:
|
||||
attributes:
|
||||
label: Description
|
||||
description: >
|
||||
Please provide a discription of the issue, along with any information
|
||||
Please provide a description of the issue, along with any information
|
||||
you feel relevant to replicate it.
|
||||
validations:
|
||||
required: true
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -18,7 +18,7 @@ body:
|
||||
id: reproducer
|
||||
attributes:
|
||||
label: Reproducer
|
||||
description: Please provide the code and steps to repoduce the bug
|
||||
description: Please provide the code and steps to reproduce the bug
|
||||
value: |
|
||||
I tried this code:
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/false_negative.yml
vendored
2
.github/ISSUE_TEMPLATE/false_negative.yml
vendored
@ -23,7 +23,7 @@ body:
|
||||
id: reproducer
|
||||
attributes:
|
||||
label: Reproducer
|
||||
description: Please provide the code and steps to repoduce the bug
|
||||
description: Please provide the code and steps to reproduce the bug
|
||||
value: |
|
||||
I tried this code:
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/false_positive.yml
vendored
2
.github/ISSUE_TEMPLATE/false_positive.yml
vendored
@ -24,7 +24,7 @@ body:
|
||||
attributes:
|
||||
label: Reproducer
|
||||
description: >
|
||||
Please provide the code and steps to repoduce the bug together with the
|
||||
Please provide the code and steps to reproduce the bug together with the
|
||||
output from Clippy.
|
||||
value: |
|
||||
I tried this code:
|
||||
|
2
.github/ISSUE_TEMPLATE/ice.yml
vendored
2
.github/ISSUE_TEMPLATE/ice.yml
vendored
@ -10,7 +10,7 @@ body:
|
||||
attributes:
|
||||
label: Summary
|
||||
description: |
|
||||
If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occured.
|
||||
If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occurred.
|
||||
|
||||
[mve]: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
|
||||
validations:
|
||||
|
1
.github/deploy.sh
vendored
1
.github/deploy.sh
vendored
@ -8,6 +8,7 @@ rm -rf out/master/ || exit 0
|
||||
echo "Making the docs for master"
|
||||
mkdir out/master/
|
||||
cp util/gh-pages/index.html out/master
|
||||
cp util/gh-pages/script.js out/master
|
||||
cp util/gh-pages/lints.json out/master
|
||||
|
||||
if [[ -n $TAG_NAME ]]; then
|
||||
|
13
.github/workflows/clippy.yml
vendored
13
.github/workflows/clippy.yml
vendored
@ -6,14 +6,14 @@ on:
|
||||
branches-ignore:
|
||||
- auto
|
||||
- try
|
||||
# Don't run Clippy tests, when only textfiles were modified
|
||||
# Don't run Clippy tests, when only text files were modified
|
||||
paths-ignore:
|
||||
- 'COPYRIGHT'
|
||||
- 'LICENSE-*'
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
pull_request:
|
||||
# Don't run Clippy tests, when only textfiles were modified
|
||||
# Don't run Clippy tests, when only text files were modified
|
||||
paths-ignore:
|
||||
- 'COPYRIGHT'
|
||||
- 'LICENSE-*'
|
||||
@ -37,7 +37,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -74,10 +74,3 @@ jobs:
|
||||
run: bash .github/driver.sh
|
||||
env:
|
||||
OS: ${{ runner.os }}
|
||||
|
||||
- name: Test cargo dev new lint
|
||||
run: |
|
||||
cargo dev new_lint --name new_early_pass --pass early
|
||||
cargo dev new_lint --name new_late_pass --pass late
|
||||
cargo check
|
||||
git reset --hard HEAD
|
||||
|
15
.github/workflows/clippy_bors.yml
vendored
15
.github/workflows/clippy_bors.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
@ -88,7 +88,7 @@ jobs:
|
||||
if: matrix.host == 'i686-unknown-linux-gnu'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -143,13 +143,6 @@ jobs:
|
||||
env:
|
||||
OS: ${{ runner.os }}
|
||||
|
||||
- name: Test cargo dev new lint
|
||||
run: |
|
||||
cargo dev new_lint --name new_early_pass --pass early
|
||||
cargo dev new_lint --name new_late_pass --pass late
|
||||
cargo check
|
||||
git reset --hard HEAD
|
||||
|
||||
integration_build:
|
||||
needs: changelog
|
||||
runs-on: ubuntu-latest
|
||||
@ -161,7 +154,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -219,7 +212,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
|
9
.github/workflows/clippy_dev.yml
vendored
9
.github/workflows/clippy_dev.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
# Run
|
||||
- name: Build
|
||||
@ -36,6 +36,13 @@ jobs:
|
||||
- name: Test fmt
|
||||
run: cargo dev fmt --check
|
||||
|
||||
- name: Test cargo dev new lint
|
||||
run: |
|
||||
cargo dev new_lint --name new_early_pass --pass early
|
||||
cargo dev new_lint --name new_late_pass --pass late
|
||||
cargo check
|
||||
git reset --hard HEAD
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
# workflow is successful listening to webhooks only.
|
||||
|
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -21,10 +21,10 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
path: 'out'
|
||||
|
2
.github/workflows/remark.yml
vendored
2
.github/workflows/remark.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.3
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1.4.4
|
||||
|
488
CHANGELOG.md
488
CHANGELOG.md
@ -6,14 +6,418 @@ document.
|
||||
|
||||
## Unreleased / In Rust Nightly
|
||||
|
||||
[e181011...master](https://github.com/rust-lang/rust-clippy/compare/e181011...master)
|
||||
[d0cf3481...master](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...master)
|
||||
|
||||
## Rust 1.58 (beta)
|
||||
## Rust 1.61
|
||||
|
||||
Current beta, release 2022-01-13
|
||||
Current stable, released 2022-05-19
|
||||
|
||||
[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`only_used_in_recursion`]
|
||||
[#8422](https://github.com/rust-lang/rust-clippy/pull/8422)
|
||||
* [`cast_enum_truncation`]
|
||||
[#8381](https://github.com/rust-lang/rust-clippy/pull/8381)
|
||||
* [`missing_spin_loop`]
|
||||
[#8174](https://github.com/rust-lang/rust-clippy/pull/8174)
|
||||
* [`deref_by_slicing`]
|
||||
[#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
|
||||
* [`needless_match`]
|
||||
[#8471](https://github.com/rust-lang/rust-clippy/pull/8471)
|
||||
* [`allow_attributes_without_reason`] (Requires `#![feature(lint_reasons)]`)
|
||||
[#8504](https://github.com/rust-lang/rust-clippy/pull/8504)
|
||||
* [`print_in_format_impl`]
|
||||
[#8253](https://github.com/rust-lang/rust-clippy/pull/8253)
|
||||
* [`unnecessary_find_map`]
|
||||
[#8489](https://github.com/rust-lang/rust-clippy/pull/8489)
|
||||
* [`or_then_unwrap`]
|
||||
[#8561](https://github.com/rust-lang/rust-clippy/pull/8561)
|
||||
* [`unnecessary_join`]
|
||||
[#8579](https://github.com/rust-lang/rust-clippy/pull/8579)
|
||||
* [`iter_with_drain`]
|
||||
[#8483](https://github.com/rust-lang/rust-clippy/pull/8483)
|
||||
* [`cast_enum_constructor`]
|
||||
[#8562](https://github.com/rust-lang/rust-clippy/pull/8562)
|
||||
* [`cast_slice_different_sizes`]
|
||||
[#8445](https://github.com/rust-lang/rust-clippy/pull/8445)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`transmute_undefined_repr`] to `nursery` (now allow-by-default)
|
||||
[#8432](https://github.com/rust-lang/rust-clippy/pull/8432)
|
||||
* Moved [`try_err`] to `restriction`
|
||||
[#8544](https://github.com/rust-lang/rust-clippy/pull/8544)
|
||||
* Move [`iter_with_drain`] to `nursery`
|
||||
[#8541](https://github.com/rust-lang/rust-clippy/pull/8541)
|
||||
* Renamed `to_string_in_display` to [`recursive_format_impl`]
|
||||
[#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`dbg_macro`]: The lint level can now be set with crate attributes and works inside macros
|
||||
[#8411](https://github.com/rust-lang/rust-clippy/pull/8411)
|
||||
* [`ptr_as_ptr`]: Now works inside macros
|
||||
[#8442](https://github.com/rust-lang/rust-clippy/pull/8442)
|
||||
* [`use_self`]: Now works for variants in match expressions
|
||||
[#8456](https://github.com/rust-lang/rust-clippy/pull/8456)
|
||||
* [`await_holding_lock`]: Now lints for `parking_lot::{Mutex, RwLock}`
|
||||
[#8419](https://github.com/rust-lang/rust-clippy/pull/8419)
|
||||
* [`recursive_format_impl`]: Now checks for format calls on `self`
|
||||
[#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`new_without_default`]: No longer lints for `new()` methods with `#[doc(hidden)]`
|
||||
[#8472](https://github.com/rust-lang/rust-clippy/pull/8472)
|
||||
* [`transmute_undefined_repr`]: No longer lints for single field structs with `#[repr(C)]`,
|
||||
generic parameters, wide pointers, unions, tuples and allow several forms of type erasure
|
||||
[#8425](https://github.com/rust-lang/rust-clippy/pull/8425)
|
||||
[#8553](https://github.com/rust-lang/rust-clippy/pull/8553)
|
||||
[#8440](https://github.com/rust-lang/rust-clippy/pull/8440)
|
||||
[#8547](https://github.com/rust-lang/rust-clippy/pull/8547)
|
||||
* [`match_single_binding`], [`match_same_arms`], [`match_as_ref`], [`match_bool`]: No longer
|
||||
lint `match` expressions with `cfg`ed arms
|
||||
[#8443](https://github.com/rust-lang/rust-clippy/pull/8443)
|
||||
* [`single_component_path_imports`]: No longer lint on macros
|
||||
[#8537](https://github.com/rust-lang/rust-clippy/pull/8537)
|
||||
* [`ptr_arg`]: Allow `&mut` arguments for `Cow<_>`
|
||||
[#8552](https://github.com/rust-lang/rust-clippy/pull/8552)
|
||||
* [`needless_borrow`]: No longer lints for method calls
|
||||
[#8441](https://github.com/rust-lang/rust-clippy/pull/8441)
|
||||
* [`match_same_arms`]: Now ensures that interposing arm patterns don't overlap
|
||||
[#8232](https://github.com/rust-lang/rust-clippy/pull/8232)
|
||||
* [`default_trait_access`]: Now allows `Default::default` in update expressions
|
||||
[#8433](https://github.com/rust-lang/rust-clippy/pull/8433)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`redundant_slicing`]: Fixed suggestion for a method calls
|
||||
[#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
|
||||
* [`map_flatten`]: Long suggestions will now be split up into two help messages
|
||||
[#8520](https://github.com/rust-lang/rust-clippy/pull/8520)
|
||||
* [`unnecessary_lazy_evaluations`]: Now shows suggestions for longer code snippets
|
||||
[#8543](https://github.com/rust-lang/rust-clippy/pull/8543)
|
||||
* [`unnecessary_sort_by`]: Now suggests `Reverse` including the path
|
||||
[#8462](https://github.com/rust-lang/rust-clippy/pull/8462)
|
||||
* [`search_is_some`]: More suggestions are now `MachineApplicable`
|
||||
[#8536](https://github.com/rust-lang/rust-clippy/pull/8536)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`new_without_default`]: Document `pub` requirement for the struct and fields
|
||||
[#8429](https://github.com/rust-lang/rust-clippy/pull/8429)
|
||||
|
||||
## Rust 1.60
|
||||
|
||||
Released 2022-04-07
|
||||
|
||||
[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`single_char_lifetime_names`]
|
||||
[#8236](https://github.com/rust-lang/rust-clippy/pull/8236)
|
||||
* [`iter_overeager_cloned`]
|
||||
[#8203](https://github.com/rust-lang/rust-clippy/pull/8203)
|
||||
* [`transmute_undefined_repr`]
|
||||
[#8398](https://github.com/rust-lang/rust-clippy/pull/8398)
|
||||
* [`default_union_representation`]
|
||||
[#8289](https://github.com/rust-lang/rust-clippy/pull/8289)
|
||||
* [`manual_bits`]
|
||||
[#8213](https://github.com/rust-lang/rust-clippy/pull/8213)
|
||||
* [`borrow_as_ptr`]
|
||||
[#8210](https://github.com/rust-lang/rust-clippy/pull/8210)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`disallowed_methods`] and [`disallowed_types`] to `style` (now warn-by-default)
|
||||
[#8261](https://github.com/rust-lang/rust-clippy/pull/8261)
|
||||
* Rename `ref_in_deref` to [`needless_borrow`]
|
||||
[#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
|
||||
* Moved [`mutex_atomic`] to `nursery` (now allow-by-default)
|
||||
[#8260](https://github.com/rust-lang/rust-clippy/pull/8260)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`ptr_arg`]: Now takes the argument usage into account and lints for mutable references
|
||||
[#8271](https://github.com/rust-lang/rust-clippy/pull/8271)
|
||||
* [`unused_io_amount`]: Now supports async read and write traits
|
||||
[#8179](https://github.com/rust-lang/rust-clippy/pull/8179)
|
||||
* [`while_let_on_iterator`]: Improved detection to catch more cases
|
||||
[#8221](https://github.com/rust-lang/rust-clippy/pull/8221)
|
||||
* [`trait_duplication_in_bounds`]: Now covers trait functions with `Self` bounds
|
||||
[#8252](https://github.com/rust-lang/rust-clippy/pull/8252)
|
||||
* [`unwrap_used`]: Now works for `.get(i).unwrap()` and `.get_mut(i).unwrap()`
|
||||
[#8372](https://github.com/rust-lang/rust-clippy/pull/8372)
|
||||
* [`map_clone`]: The suggestion takes `msrv` into account
|
||||
[#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
|
||||
* [`manual_bits`] and [`borrow_as_ptr`]: Now track the `clippy::msrv` attribute
|
||||
[#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
|
||||
* [`disallowed_methods`]: Now works for methods on primitive types
|
||||
[#8112](https://github.com/rust-lang/rust-clippy/pull/8112)
|
||||
* [`not_unsafe_ptr_arg_deref`]: Now works for type aliases
|
||||
[#8273](https://github.com/rust-lang/rust-clippy/pull/8273)
|
||||
* [`needless_question_mark`]: Now works for async functions
|
||||
[#8311](https://github.com/rust-lang/rust-clippy/pull/8311)
|
||||
* [`iter_not_returning_iterator`]: Now handles type projections
|
||||
[#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
|
||||
* [`wrong_self_convention`]: Now detects wrong `self` references in more cases
|
||||
[#8208](https://github.com/rust-lang/rust-clippy/pull/8208)
|
||||
* [`single_match`]: Now works for `match` statements with tuples
|
||||
[#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`erasing_op`]: No longer triggers if the output type changes
|
||||
[#8204](https://github.com/rust-lang/rust-clippy/pull/8204)
|
||||
* [`if_same_then_else`]: No longer triggers for `if let` statements
|
||||
[#8297](https://github.com/rust-lang/rust-clippy/pull/8297)
|
||||
* [`manual_memcpy`]: No longer lints on `VecDeque`
|
||||
[#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
|
||||
* [`trait_duplication_in_bounds`]: Now takes path segments into account
|
||||
[#8315](https://github.com/rust-lang/rust-clippy/pull/8315)
|
||||
* [`deref_addrof`]: No longer lints when the dereference or borrow occurs in different a context
|
||||
[#8268](https://github.com/rust-lang/rust-clippy/pull/8268)
|
||||
* [`type_repetition_in_bounds`]: Now checks for full equality to prevent false positives
|
||||
[#8224](https://github.com/rust-lang/rust-clippy/pull/8224)
|
||||
* [`ptr_arg`]: No longer lint for mutable references in traits
|
||||
[#8369](https://github.com/rust-lang/rust-clippy/pull/8369)
|
||||
* [`implicit_clone`]: No longer lints for double references
|
||||
[#8231](https://github.com/rust-lang/rust-clippy/pull/8231)
|
||||
* [`needless_lifetimes`]: No longer lints lifetimes for explicit `self` types
|
||||
[#8278](https://github.com/rust-lang/rust-clippy/pull/8278)
|
||||
* [`op_ref`]: No longer lints in `BinOp` impl if that can cause recursion
|
||||
[#8298](https://github.com/rust-lang/rust-clippy/pull/8298)
|
||||
* [`enum_variant_names`]: No longer triggers for empty variant names
|
||||
[#8329](https://github.com/rust-lang/rust-clippy/pull/8329)
|
||||
* [`redundant_closure`]: No longer lints for `Arc<T>` or `Rc<T>`
|
||||
[#8193](https://github.com/rust-lang/rust-clippy/pull/8193)
|
||||
* [`iter_not_returning_iterator`]: No longer lints on trait implementations but therefore on trait definitions
|
||||
[#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
|
||||
* [`single_match`]: No longer lints on exhaustive enum patterns without a wildcard
|
||||
[#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
|
||||
* [`manual_swap`]: No longer lints on cases that involve automatic dereferences
|
||||
[#8220](https://github.com/rust-lang/rust-clippy/pull/8220)
|
||||
* [`useless_format`]: Now works for implicit named arguments
|
||||
[#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
|
||||
[#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
|
||||
* [`chars_next_cmp`]: Correctly excapes the suggestion
|
||||
[#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
|
||||
* [`explicit_write`]: Add suggestions for `write!`s with format arguments
|
||||
[#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
|
||||
* [`manual_memcpy`]: Suggests `copy_from_slice` when applicable
|
||||
[#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
|
||||
* [`or_fun_call`]: Improved suggestion display for long arguments
|
||||
[#8292](https://github.com/rust-lang/rust-clippy/pull/8292)
|
||||
* [`unnecessary_cast`]: Now correctly includes the sign
|
||||
[#8350](https://github.com/rust-lang/rust-clippy/pull/8350)
|
||||
* [`cmp_owned`]: No longer flips the comparison order
|
||||
[#8299](https://github.com/rust-lang/rust-clippy/pull/8299)
|
||||
* [`explicit_counter_loop`]: Now correctly suggests `iter()` on references
|
||||
[#8382](https://github.com/rust-lang/rust-clippy/pull/8382)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`manual_split_once`]
|
||||
[#8250](https://github.com/rust-lang/rust-clippy/pull/8250)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`map_flatten`]: Add documentation for the `Option` type
|
||||
[#8354](https://github.com/rust-lang/rust-clippy/pull/8354)
|
||||
* Document that Clippy's driver might use a different code generation than rustc
|
||||
[#8037](https://github.com/rust-lang/rust-clippy/pull/8037)
|
||||
* Clippy's lint list will now automatically focus the search box
|
||||
[#8343](https://github.com/rust-lang/rust-clippy/pull/8343)
|
||||
|
||||
### Others
|
||||
|
||||
* Clippy now warns if we find multiple Clippy config files exist
|
||||
[#8326](https://github.com/rust-lang/rust-clippy/pull/8326)
|
||||
|
||||
## Rust 1.59
|
||||
|
||||
Released 2022-02-24
|
||||
|
||||
[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`index_refutable_slice`]
|
||||
[#7643](https://github.com/rust-lang/rust-clippy/pull/7643)
|
||||
* [`needless_splitn`]
|
||||
[#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
|
||||
* [`unnecessary_to_owned`]
|
||||
[#7978](https://github.com/rust-lang/rust-clippy/pull/7978)
|
||||
* [`needless_late_init`]
|
||||
[#7995](https://github.com/rust-lang/rust-clippy/pull/7995)
|
||||
* [`octal_escapes`] [#8007](https://github.com/rust-lang/rust-clippy/pull/8007)
|
||||
* [`return_self_not_must_use`]
|
||||
[#8071](https://github.com/rust-lang/rust-clippy/pull/8071)
|
||||
* [`init_numbered_fields`]
|
||||
[#8170](https://github.com/rust-lang/rust-clippy/pull/8170)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Move `if_then_panic` to `pedantic` and rename to [`manual_assert`] (now
|
||||
allow-by-default) [#7810](https://github.com/rust-lang/rust-clippy/pull/7810)
|
||||
* Rename `disallow_type` to [`disallowed_types`] and `disallowed_method` to
|
||||
[`disallowed_methods`]
|
||||
[#7984](https://github.com/rust-lang/rust-clippy/pull/7984)
|
||||
* Move [`map_flatten`] to `complexity` (now warn-by-default)
|
||||
[#8054](https://github.com/rust-lang/rust-clippy/pull/8054)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`match_overlapping_arm`]: Fix false negative where after included ranges,
|
||||
overlapping ranges weren't linted anymore
|
||||
[#7909](https://github.com/rust-lang/rust-clippy/pull/7909)
|
||||
* [`deprecated_cfg_attr`]: Now takes the specified MSRV into account
|
||||
[#7944](https://github.com/rust-lang/rust-clippy/pull/7944)
|
||||
* [`cast_lossless`]: Now also lints for `bool` to integer casts
|
||||
[#7948](https://github.com/rust-lang/rust-clippy/pull/7948)
|
||||
* [`let_underscore_lock`]: Also emit lints for the `parking_lot` crate
|
||||
[#7957](https://github.com/rust-lang/rust-clippy/pull/7957)
|
||||
* [`needless_borrow`]
|
||||
[#7977](https://github.com/rust-lang/rust-clippy/pull/7977)
|
||||
* Lint when a borrow is auto-dereffed more than once
|
||||
* Lint in the trailing expression of a block for a match arm
|
||||
* [`strlen_on_c_strings`]
|
||||
[8001](https://github.com/rust-lang/rust-clippy/pull/8001)
|
||||
* Lint when used without a fully-qualified path
|
||||
* Suggest removing the surrounding unsafe block when possible
|
||||
* [`non_ascii_literal`]: Now also lints on `char`s, not just `string`s
|
||||
[#8034](https://github.com/rust-lang/rust-clippy/pull/8034)
|
||||
* [`single_char_pattern`]: Now also lints on `split_inclusive`, `split_once`,
|
||||
`rsplit_once`, `replace`, and `replacen`
|
||||
[#8077](https://github.com/rust-lang/rust-clippy/pull/8077)
|
||||
* [`unwrap_or_else_default`]: Now also lints on `std` constructors like
|
||||
`Vec::new`, `HashSet::new`, and `HashMap::new`
|
||||
[#8163](https://github.com/rust-lang/rust-clippy/pull/8163)
|
||||
* [`shadow_reuse`]: Now also lints on shadowed `if let` bindings, instead of
|
||||
[`shadow_unrelated`]
|
||||
[#8165](https://github.com/rust-lang/rust-clippy/pull/8165)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`or_fun_call`], [`unnecessary_lazy_evaluations`]: Improve heuristics, so that
|
||||
cheap functions (e.g. calling `.len()` on a `Vec`) won't get linted anymore
|
||||
[#7639](https://github.com/rust-lang/rust-clippy/pull/7639)
|
||||
* [`manual_split_once`]: No longer suggests code changing the original behavior
|
||||
[#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
|
||||
* Don't show [`no_effect`] or [`unnecessary_operation`] warning for unit struct
|
||||
implementing `FnOnce`
|
||||
[#7898](https://github.com/rust-lang/rust-clippy/pull/7898)
|
||||
* [`semicolon_if_nothing_returned`]: Fixed a bug, where the lint wrongly
|
||||
triggered on `let-else` statements
|
||||
[#7955](https://github.com/rust-lang/rust-clippy/pull/7955)
|
||||
* [`if_then_some_else_none`]: No longer lints if there is an early return
|
||||
[#7980](https://github.com/rust-lang/rust-clippy/pull/7980)
|
||||
* [`needless_collect`]: No longer suggests removal of `collect` when removal
|
||||
would create code requiring mutably borrowing a value multiple times
|
||||
[#7982](https://github.com/rust-lang/rust-clippy/pull/7982)
|
||||
* [`shadow_same`]: Fix false positive for `async` function's params
|
||||
[#7997](https://github.com/rust-lang/rust-clippy/pull/7997)
|
||||
* [`suboptimal_flops`]: No longer triggers in constant functions
|
||||
[#8009](https://github.com/rust-lang/rust-clippy/pull/8009)
|
||||
* [`type_complexity`]: No longer lints on associated types in traits
|
||||
[#8030](https://github.com/rust-lang/rust-clippy/pull/8030)
|
||||
* [`question_mark`]: No longer lints if returned object is not local
|
||||
[#8080](https://github.com/rust-lang/rust-clippy/pull/8080)
|
||||
* [`option_if_let_else`]: No longer lint on complex sub-patterns
|
||||
[#8086](https://github.com/rust-lang/rust-clippy/pull/8086)
|
||||
* [`blocks_in_if_conditions`]: No longer lints on empty closures
|
||||
[#8100](https://github.com/rust-lang/rust-clippy/pull/8100)
|
||||
* [`enum_variant_names`]: No longer lint when first prefix is only a substring
|
||||
of a camel-case word
|
||||
[#8127](https://github.com/rust-lang/rust-clippy/pull/8127)
|
||||
* [`identity_op`]: Only lint on integral operands
|
||||
[#8183](https://github.com/rust-lang/rust-clippy/pull/8183)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`search_is_some`]: Fix suggestion for `any()` not taking item by reference
|
||||
[#7463](https://github.com/rust-lang/rust-clippy/pull/7463)
|
||||
* [`almost_swapped`]: Now detects if there is a `no_std` or `no_core` attribute
|
||||
and adapts the suggestion accordingly
|
||||
[#7877](https://github.com/rust-lang/rust-clippy/pull/7877)
|
||||
* [`redundant_pattern_matching`]: Fix suggestion for deref expressions
|
||||
[#7949](https://github.com/rust-lang/rust-clippy/pull/7949)
|
||||
* [`explicit_counter_loop`]: Now also produces a suggestion for non-`usize`
|
||||
types [#7950](https://github.com/rust-lang/rust-clippy/pull/7950)
|
||||
* [`manual_map`]: Fix suggestion when used with unsafe functions and blocks
|
||||
[#7968](https://github.com/rust-lang/rust-clippy/pull/7968)
|
||||
* [`option_map_or_none`]: Suggest `map` over `and_then` when possible
|
||||
[#7971](https://github.com/rust-lang/rust-clippy/pull/7971)
|
||||
* [`option_if_let_else`]: No longer expands macros in the suggestion
|
||||
[#7974](https://github.com/rust-lang/rust-clippy/pull/7974)
|
||||
* [`iter_cloned_collect`]: Suggest `copied` over `cloned` when possible
|
||||
[#8006](https://github.com/rust-lang/rust-clippy/pull/8006)
|
||||
* [`doc_markdown`]: No longer uses inline hints to improve readability of
|
||||
suggestion [#8011](https://github.com/rust-lang/rust-clippy/pull/8011)
|
||||
* [`needless_question_mark`]: Now better explains the suggestion
|
||||
[#8028](https://github.com/rust-lang/rust-clippy/pull/8028)
|
||||
* [`single_char_pattern`]: Escape backslash `\` in suggestion
|
||||
[#8067](https://github.com/rust-lang/rust-clippy/pull/8067)
|
||||
* [`needless_bool`]: Suggest `a != b` over `!(a == b)`
|
||||
[#8117](https://github.com/rust-lang/rust-clippy/pull/8117)
|
||||
* [`iter_skip_next`]: Suggest to add a `mut` if it is necessary in order to
|
||||
apply this lints suggestion
|
||||
[#8133](https://github.com/rust-lang/rust-clippy/pull/8133)
|
||||
* [`neg_multiply`]: Now produces a suggestion
|
||||
[#8144](https://github.com/rust-lang/rust-clippy/pull/8144)
|
||||
* [`needless_return`]: Now suggests the unit type `()` over an empty block `{}`
|
||||
in match arms [#8185](https://github.com/rust-lang/rust-clippy/pull/8185)
|
||||
* [`suboptimal_flops`]: Now gives a syntactically correct suggestion for
|
||||
`to_radians` and `to_degrees`
|
||||
[#8187](https://github.com/rust-lang/rust-clippy/pull/8187)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`undocumented_unsafe_blocks`]
|
||||
[#7945](https://github.com/rust-lang/rust-clippy/pull/7945)
|
||||
[#7988](https://github.com/rust-lang/rust-clippy/pull/7988)
|
||||
* [`unnecessary_cast`]
|
||||
[#8167](https://github.com/rust-lang/rust-clippy/pull/8167)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`print_stdout`], [`print_stderr`], [`dbg_macro`]: Document how the lint level
|
||||
can be changed crate-wide
|
||||
[#8040](https://github.com/rust-lang/rust-clippy/pull/8040)
|
||||
* Added a note to the `README` that config changes don't apply to already
|
||||
compiled code [#8175](https://github.com/rust-lang/rust-clippy/pull/8175)
|
||||
|
||||
### Others
|
||||
|
||||
* [Clippy's lint
|
||||
list](https://rust-lang.github.io/rust-clippy/master/index.html) now displays
|
||||
the version a lint was added. :tada:
|
||||
[#7813](https://github.com/rust-lang/rust-clippy/pull/7813)
|
||||
* New and improved issue templates
|
||||
[#8032](https://github.com/rust-lang/rust-clippy/pull/8032)
|
||||
* _Dev:_ Add `cargo dev lint` command, to run your modified Clippy version on a
|
||||
file [#7917](https://github.com/rust-lang/rust-clippy/pull/7917)
|
||||
|
||||
## Rust 1.58
|
||||
|
||||
Released 2022-01-13
|
||||
|
||||
[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
|
||||
|
||||
### Rust 1.58.1
|
||||
|
||||
* Move [`non_send_fields_in_send_ty`] to `nursery` (now allow-by-default)
|
||||
[#8075](https://github.com/rust-lang/rust-clippy/pull/8075)
|
||||
* [`useless_format`]: Handle implicit named arguments
|
||||
[#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
|
||||
|
||||
### New lints
|
||||
|
||||
* [`transmute_num_to_bytes`]
|
||||
@ -124,7 +528,7 @@ Current beta, release 2022-01-13
|
||||
|
||||
## Rust 1.57
|
||||
|
||||
Current stable, released 2021-12-02
|
||||
Released 2021-12-02
|
||||
|
||||
[7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa)
|
||||
|
||||
@ -1267,7 +1671,7 @@ Released 2020-11-19
|
||||
* [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038)
|
||||
* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998)
|
||||
* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044)
|
||||
* [`to_string_in_display`] [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
|
||||
* `to_string_in_display` [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
|
||||
* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
|
||||
|
||||
### Moves and Deprecations
|
||||
@ -1310,7 +1714,7 @@ Released 2020-11-19
|
||||
[#5949](https://github.com/rust-lang/rust-clippy/pull/5949)
|
||||
* [`doc_markdown`]: allow using "GraphQL" without backticks
|
||||
[#5996](https://github.com/rust-lang/rust-clippy/pull/5996)
|
||||
* [`to_string_in_display`]: avoid linting when calling `to_string()` on anything that is not `self`
|
||||
* `to_string_in_display`: avoid linting when calling `to_string()` on anything that is not `self`
|
||||
[#5971](https://github.com/rust-lang/rust-clippy/pull/5971)
|
||||
* [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays
|
||||
[#6034](https://github.com/rust-lang/rust-clippy/pull/6034)
|
||||
@ -2871,6 +3275,7 @@ Released 2018-09-13
|
||||
<!-- lint disable no-unused-definitions -->
|
||||
<!-- begin autogenerated links to lint list -->
|
||||
[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
|
||||
[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
|
||||
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
|
||||
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
|
||||
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
|
||||
@ -2878,12 +3283,15 @@ Released 2018-09-13
|
||||
[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
|
||||
[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
|
||||
[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
|
||||
[`await_holding_invalid_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type
|
||||
[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
|
||||
[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
|
||||
[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
|
||||
[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
|
||||
[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
|
||||
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
|
||||
[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr
|
||||
[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt
|
||||
[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
|
||||
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
|
||||
[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
|
||||
@ -2891,12 +3299,17 @@ Released 2018-09-13
|
||||
[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
|
||||
[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
|
||||
[`box_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection
|
||||
[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec
|
||||
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
|
||||
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
|
||||
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
|
||||
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
|
||||
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
|
||||
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
|
||||
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
|
||||
[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
|
||||
[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
|
||||
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
|
||||
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
|
||||
[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
|
||||
[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
|
||||
@ -2904,6 +3317,7 @@ Released 2018-09-13
|
||||
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
|
||||
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
|
||||
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
|
||||
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
|
||||
[`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
|
||||
@ -2921,9 +3335,12 @@ Released 2018-09-13
|
||||
[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
|
||||
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
|
||||
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
|
||||
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
|
||||
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
|
||||
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
|
||||
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
|
||||
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
|
||||
[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
|
||||
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
|
||||
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
|
||||
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
|
||||
@ -2934,11 +3351,15 @@ Released 2018-09-13
|
||||
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
|
||||
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
|
||||
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
|
||||
[`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing
|
||||
[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
|
||||
[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
|
||||
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
|
||||
[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
|
||||
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
|
||||
[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
|
||||
[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
|
||||
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
|
||||
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
|
||||
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
|
||||
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
|
||||
@ -2947,20 +3368,26 @@ Released 2018-09-13
|
||||
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
|
||||
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
|
||||
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
|
||||
[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
|
||||
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
|
||||
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
|
||||
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
|
||||
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
|
||||
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
|
||||
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
|
||||
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
|
||||
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
|
||||
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
|
||||
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
|
||||
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
|
||||
[`empty_structs_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_structs_with_brackets
|
||||
[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
|
||||
[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
|
||||
[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
|
||||
[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
|
||||
[`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let
|
||||
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
|
||||
[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
|
||||
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
|
||||
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
|
||||
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
|
||||
@ -2997,19 +3424,26 @@ Released 2018-09-13
|
||||
[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
|
||||
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
|
||||
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
|
||||
[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option
|
||||
[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
|
||||
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
|
||||
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
|
||||
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
|
||||
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
|
||||
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
|
||||
[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
|
||||
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
|
||||
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
|
||||
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
|
||||
[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
|
||||
[`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first
|
||||
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
|
||||
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
|
||||
[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
|
||||
[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
|
||||
[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
|
||||
[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching
|
||||
[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result
|
||||
[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else
|
||||
[`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
|
||||
@ -3038,11 +3472,15 @@ Released 2018-09-13
|
||||
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
|
||||
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
|
||||
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
|
||||
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
|
||||
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
|
||||
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
|
||||
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
|
||||
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
|
||||
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
|
||||
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
|
||||
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
|
||||
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
|
||||
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
|
||||
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
|
||||
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
|
||||
@ -3053,11 +3491,13 @@ Released 2018-09-13
|
||||
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
|
||||
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
|
||||
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
|
||||
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
|
||||
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
|
||||
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
|
||||
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
|
||||
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
|
||||
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
|
||||
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
|
||||
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
|
||||
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
|
||||
[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
|
||||
@ -3110,6 +3550,7 @@ Released 2018-09-13
|
||||
[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
|
||||
[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
|
||||
[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
|
||||
[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
|
||||
[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
|
||||
[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
|
||||
[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default
|
||||
@ -3125,8 +3566,10 @@ Released 2018-09-13
|
||||
[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
|
||||
[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
|
||||
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
|
||||
[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
|
||||
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
|
||||
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
|
||||
[`mixed_read_write_in_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_read_write_in_expression
|
||||
[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
|
||||
[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
|
||||
[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
|
||||
@ -3155,7 +3598,9 @@ Released 2018-09-13
|
||||
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
|
||||
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
|
||||
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
|
||||
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
|
||||
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
|
||||
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
|
||||
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
|
||||
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
|
||||
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
|
||||
@ -3168,7 +3613,9 @@ Released 2018-09-13
|
||||
[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
|
||||
[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
|
||||
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
|
||||
[`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive
|
||||
[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
|
||||
[`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace
|
||||
[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
|
||||
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
|
||||
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
|
||||
@ -3179,25 +3626,34 @@ Released 2018-09-13
|
||||
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
|
||||
[`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
|
||||
[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
|
||||
[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
|
||||
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
|
||||
[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
|
||||
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
|
||||
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
|
||||
[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
|
||||
[`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
|
||||
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
|
||||
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
|
||||
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
|
||||
[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or
|
||||
[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else
|
||||
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
|
||||
[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used
|
||||
[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
|
||||
[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
|
||||
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
|
||||
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
|
||||
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
|
||||
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
|
||||
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
|
||||
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
|
||||
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
|
||||
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
|
||||
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
|
||||
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
|
||||
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
|
||||
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
|
||||
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
|
||||
[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
|
||||
[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
|
||||
@ -3208,13 +3664,16 @@ Released 2018-09-13
|
||||
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
|
||||
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
|
||||
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
|
||||
[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
|
||||
[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
|
||||
[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
|
||||
[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
|
||||
[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
|
||||
[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
|
||||
[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
|
||||
[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
|
||||
[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
|
||||
[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
|
||||
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
|
||||
[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
|
||||
[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
|
||||
@ -3229,14 +3688,18 @@ Released 2018-09-13
|
||||
[`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
|
||||
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
|
||||
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
|
||||
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
|
||||
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
|
||||
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
|
||||
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
|
||||
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
|
||||
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
|
||||
[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
|
||||
[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
|
||||
[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else
|
||||
[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err
|
||||
[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
|
||||
[`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use
|
||||
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
|
||||
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
|
||||
@ -3255,10 +3718,12 @@ Released 2018-09-13
|
||||
[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
|
||||
[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
|
||||
[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
|
||||
[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
|
||||
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
|
||||
[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
|
||||
[`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names
|
||||
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
|
||||
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
|
||||
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
|
||||
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
|
||||
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
|
||||
@ -3277,6 +3742,7 @@ Released 2018-09-13
|
||||
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
|
||||
[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
|
||||
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
|
||||
[`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter
|
||||
[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
|
||||
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
|
||||
[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
|
||||
@ -3288,6 +3754,7 @@ Released 2018-09-13
|
||||
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
|
||||
[`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
|
||||
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
|
||||
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
|
||||
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
|
||||
@ -3305,8 +3772,10 @@ Released 2018-09-13
|
||||
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
|
||||
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
|
||||
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
|
||||
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
|
||||
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
|
||||
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
|
||||
[`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
|
||||
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
|
||||
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
|
||||
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
|
||||
@ -3322,12 +3791,16 @@ Released 2018-09-13
|
||||
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
|
||||
[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
|
||||
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
|
||||
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
|
||||
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
|
||||
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
|
||||
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
|
||||
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
|
||||
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
|
||||
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
|
||||
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
|
||||
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
|
||||
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
|
||||
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
|
||||
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
|
||||
[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
|
||||
@ -3348,6 +3821,8 @@ Released 2018-09-13
|
||||
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
|
||||
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
|
||||
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
|
||||
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
|
||||
[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
|
||||
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
|
||||
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
|
||||
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
|
||||
@ -3388,5 +3863,6 @@ Released 2018-09-13
|
||||
[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
|
||||
[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
|
||||
[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
|
||||
[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
|
||||
[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
|
||||
<!-- end autogenerated links to lint list -->
|
||||
|
@ -67,9 +67,9 @@ and resolved paths.
|
||||
|
||||
[`T-AST`] issues will generally need you to match against a predefined syntax structure.
|
||||
To figure out how this syntax structure is encoded in the AST, it is recommended to run
|
||||
`rustc -Z ast-json` on an example of the structure and compare with the [nodes in the AST docs].
|
||||
`rustc -Z unpretty=ast-tree` on an example of the structure and compare with the [nodes in the AST docs].
|
||||
Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
|
||||
But we can make it nest-less by using [if_chain] macro, [like this][nest-less].
|
||||
But we can make it nest-less by using [let chains], [like this][nest-less].
|
||||
|
||||
[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good-first-issue`]
|
||||
first. Sometimes they are only somewhat involved code wise, but not difficult per-se.
|
||||
@ -87,9 +87,9 @@ an AST expression). `match_def_path()` in Clippy's `utils` module can also be us
|
||||
[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium
|
||||
[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty
|
||||
[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/
|
||||
[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/mem_forget.rs#L29-L43
|
||||
[if_chain]: https://docs.rs/if_chain/*/if_chain
|
||||
[nest-less]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/bit_mask.rs#L124-L150
|
||||
[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/mem_forget.rs#L31-L45
|
||||
[let chains]: https://github.com/rust-lang/rust/pull/94927
|
||||
[nest-less]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/bit_mask.rs#L133-L159
|
||||
|
||||
## Writing code
|
||||
|
||||
|
18
Cargo.toml
18
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.60"
|
||||
version = "0.1.63"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
@ -21,14 +21,14 @@ name = "clippy-driver"
|
||||
path = "src/driver.rs"
|
||||
|
||||
[dependencies]
|
||||
clippy_lints = { version = "0.1", path = "clippy_lints" }
|
||||
clippy_lints = { path = "clippy_lints" }
|
||||
semver = "1.0"
|
||||
rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
|
||||
rustc_tools_util = { path = "rustc_tools_util" }
|
||||
tempfile = { version = "3.2", optional = true }
|
||||
termize = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
cargo_metadata = "0.14"
|
||||
compiletest_rs = { version = "0.7.1", features = ["tmp"] }
|
||||
compiletest_rs = { version = "0.8", features = ["tmp"] }
|
||||
tester = "0.9"
|
||||
regex = "1.5"
|
||||
# This is used by the `collect-metadata` alias.
|
||||
@ -40,16 +40,18 @@ filetime = "0.2"
|
||||
rustc-workspace-hack = "1.0"
|
||||
|
||||
# UI test dependencies
|
||||
clap = { version = "3.1", features = ["derive"] }
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
derive-new = "0.5"
|
||||
if_chain = "1.0"
|
||||
itertools = "0.10"
|
||||
itertools = "0.10.1"
|
||||
quote = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = { version = "1.0.125", features = ["derive"] }
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
futures = "0.3"
|
||||
parking_lot = "0.11.2"
|
||||
parking_lot = "0.12"
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
rustc-semver = "1.1"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
|
||||
|
@ -4,15 +4,18 @@ version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytecount = "0.6"
|
||||
clap = "2.33"
|
||||
aho-corasick = "0.7"
|
||||
clap = "3.1"
|
||||
indoc = "1.0"
|
||||
itertools = "0.10"
|
||||
itertools = "0.10.1"
|
||||
opener = "0.5"
|
||||
regex = "1.5"
|
||||
shell-escape = "0.1"
|
||||
tempfile = "3.2"
|
||||
walkdir = "2.3"
|
||||
cargo_metadata = "0.14"
|
||||
|
||||
[features]
|
||||
deny-warnings = []
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
# This package uses #[feature(rustc_private)]
|
||||
rustc_private = true
|
||||
|
@ -1,66 +1,39 @@
|
||||
//! `bless` updates the reference files in the repo with changed output files
|
||||
//! from the last test run.
|
||||
|
||||
use crate::cargo_clippy_path;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::lazy::SyncLazy;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::clippy_project_root;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
||||
#[cfg(windows)]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
|
||||
|
||||
static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::new(|| {
|
||||
let mut path = std::env::current_exe().unwrap();
|
||||
path.set_file_name(CARGO_CLIPPY_EXE);
|
||||
fs::metadata(path).ok()?.modified().ok()
|
||||
});
|
||||
static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> =
|
||||
SyncLazy::new(|| cargo_clippy_path().metadata().ok()?.modified().ok());
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the path to a test file is broken
|
||||
pub fn bless(ignore_timestamp: bool) {
|
||||
let test_suite_dirs = [
|
||||
clippy_project_root().join("tests").join("ui"),
|
||||
clippy_project_root().join("tests").join("ui-internal"),
|
||||
clippy_project_root().join("tests").join("ui-toml"),
|
||||
clippy_project_root().join("tests").join("ui-cargo"),
|
||||
];
|
||||
for test_suite_dir in &test_suite_dirs {
|
||||
WalkDir::new(test_suite_dir)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|f| f.path().extension() == Some(OsStr::new("rs")))
|
||||
.for_each(|f| {
|
||||
let test_name = f.path().strip_prefix(test_suite_dir).unwrap();
|
||||
for &ext in &["stdout", "stderr", "fixed"] {
|
||||
let test_name_ext = format!("stage-id.{}", ext);
|
||||
update_reference_file(
|
||||
f.path().with_extension(ext),
|
||||
test_name.with_extension(test_name_ext),
|
||||
ignore_timestamp,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
let extensions = ["stdout", "stderr", "fixed"].map(OsStr::new);
|
||||
|
||||
WalkDir::new(build_dir())
|
||||
.into_iter()
|
||||
.map(Result::unwrap)
|
||||
.filter(|entry| entry.path().extension().map_or(false, |ext| extensions.contains(&ext)))
|
||||
.for_each(|entry| update_reference_file(&entry, ignore_timestamp));
|
||||
}
|
||||
|
||||
fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) {
|
||||
let test_output_path = build_dir().join(test_name);
|
||||
let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap();
|
||||
fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) {
|
||||
let test_output_path = test_output_entry.path();
|
||||
|
||||
// If compiletest did not write any changes during the test run,
|
||||
// we don't have to update anything
|
||||
if !test_output_path.exists() {
|
||||
return;
|
||||
}
|
||||
let reference_file_name = test_output_entry.file_name().to_str().unwrap().replace(".stage-id", "");
|
||||
let reference_file_path = Path::new("tests")
|
||||
.join(test_output_path.strip_prefix(build_dir()).unwrap())
|
||||
.with_file_name(reference_file_name);
|
||||
|
||||
// If the test output was not updated since the last clippy build, it may be outdated
|
||||
if !ignore_timestamp && !updated_since_clippy_build(&test_output_path).unwrap_or(true) {
|
||||
if !ignore_timestamp && !updated_since_clippy_build(test_output_entry).unwrap_or(true) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,23 +42,14 @@ fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignor
|
||||
|
||||
if test_output_file != reference_file {
|
||||
// If a test run caused an output file to change, update the reference file
|
||||
println!("updating {}", &relative_reference_file_path.display());
|
||||
println!("updating {}", reference_file_path.display());
|
||||
fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file");
|
||||
|
||||
// We need to re-read the file now because it was potentially updated from copying
|
||||
let reference_file = fs::read(&reference_file_path).unwrap_or_default();
|
||||
|
||||
if reference_file.is_empty() {
|
||||
// If we copied over an empty output file, we remove the now empty reference file
|
||||
println!("removing {}", &relative_reference_file_path.display());
|
||||
fs::remove_file(reference_file_path).expect("Could not remove reference file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn updated_since_clippy_build(path: &Path) -> Option<bool> {
|
||||
fn updated_since_clippy_build(entry: &DirEntry) -> Option<bool> {
|
||||
let clippy_build_time = (*CLIPPY_BUILD_TIME)?;
|
||||
let modified = fs::metadata(path).ok()?.modified().ok()?;
|
||||
let modified = entry.metadata().ok()?.modified().ok()?;
|
||||
Some(modified >= clippy_build_time)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
#![feature(let_chains)]
|
||||
#![feature(let_else)]
|
||||
#![feature(once_cell)]
|
||||
#![feature(rustc_private)]
|
||||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
// warn on lints, that are included in `rust-lang/rust`s bootstrap
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
extern crate rustc_lexer;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod bless;
|
||||
@ -13,6 +18,19 @@ pub mod serve;
|
||||
pub mod setup;
|
||||
pub mod update_lints;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
||||
#[cfg(windows)]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
|
||||
|
||||
/// Returns the path to the `cargo-clippy` binary
|
||||
#[must_use]
|
||||
pub fn cargo_clippy_path() -> PathBuf {
|
||||
let mut path = std::env::current_exe().expect("failed to get current executable name");
|
||||
path.set_file_name(CARGO_CLIPPY_EXE);
|
||||
path
|
||||
}
|
||||
|
||||
/// Returns the path to the Clippy project directory
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -1,19 +1,55 @@
|
||||
use std::process::{self, Command};
|
||||
use crate::cargo_clippy_path;
|
||||
use std::process::{self, Command, ExitStatus};
|
||||
use std::{fs, io};
|
||||
|
||||
pub fn run(filename: &str) {
|
||||
let code = Command::new("cargo")
|
||||
.args(["run", "--bin", "clippy-driver", "--"])
|
||||
.args(["-L", "./target/debug"])
|
||||
.args(["-Z", "no-codegen"])
|
||||
.args(["--edition", "2021"])
|
||||
.arg(filename)
|
||||
.status()
|
||||
.expect("failed to run cargo")
|
||||
.code();
|
||||
|
||||
if code.is_none() {
|
||||
eprintln!("Killed by signal");
|
||||
fn exit_if_err(status: io::Result<ExitStatus>) {
|
||||
match status.expect("failed to run command").code() {
|
||||
Some(0) => {},
|
||||
Some(n) => process::exit(n),
|
||||
None => {
|
||||
eprintln!("Killed by signal");
|
||||
process::exit(1);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<'a>(path: &str, args: impl Iterator<Item = &'a str>) {
|
||||
let is_file = match fs::metadata(path) {
|
||||
Ok(metadata) => metadata.is_file(),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read {path}: {e:?}");
|
||||
process::exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
if is_file {
|
||||
exit_if_err(
|
||||
Command::new("cargo")
|
||||
.args(["run", "--bin", "clippy-driver", "--"])
|
||||
.args(["-L", "./target/debug"])
|
||||
.args(["-Z", "no-codegen"])
|
||||
.args(["--edition", "2021"])
|
||||
.arg(path)
|
||||
.args(args)
|
||||
.status(),
|
||||
);
|
||||
} else {
|
||||
exit_if_err(Command::new("cargo").arg("build").status());
|
||||
|
||||
// Run in a tempdir as changes to clippy do not retrigger linting
|
||||
let target = tempfile::Builder::new()
|
||||
.prefix("clippy")
|
||||
.tempdir()
|
||||
.expect("failed to create tempdir");
|
||||
|
||||
let status = Command::new(cargo_clippy_path())
|
||||
.arg("clippy")
|
||||
.args(args)
|
||||
.current_dir(path)
|
||||
.env("CARGO_TARGET_DIR", target.as_ref())
|
||||
.status();
|
||||
|
||||
target.close().expect("failed to remove tempdir");
|
||||
exit_if_err(status);
|
||||
}
|
||||
|
||||
process::exit(code.unwrap_or(1));
|
||||
}
|
||||
|
@ -2,122 +2,137 @@
|
||||
// warn on lints, that are included in `rust-lang/rust`s bootstrap
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
|
||||
use indoc::indoc;
|
||||
fn main() {
|
||||
let matches = get_clap_config();
|
||||
|
||||
match matches.subcommand() {
|
||||
("bless", Some(matches)) => {
|
||||
Some(("bless", matches)) => {
|
||||
bless::bless(matches.is_present("ignore-timestamp"));
|
||||
},
|
||||
("fmt", Some(matches)) => {
|
||||
Some(("fmt", matches)) => {
|
||||
fmt::run(matches.is_present("check"), matches.is_present("verbose"));
|
||||
},
|
||||
("update_lints", Some(matches)) => {
|
||||
Some(("update_lints", matches)) => {
|
||||
if matches.is_present("print-only") {
|
||||
update_lints::print_lints();
|
||||
} else if matches.is_present("check") {
|
||||
update_lints::run(update_lints::UpdateMode::Check);
|
||||
update_lints::update(update_lints::UpdateMode::Check);
|
||||
} else {
|
||||
update_lints::run(update_lints::UpdateMode::Change);
|
||||
update_lints::update(update_lints::UpdateMode::Change);
|
||||
}
|
||||
},
|
||||
("new_lint", Some(matches)) => {
|
||||
Some(("new_lint", matches)) => {
|
||||
match new_lint::create(
|
||||
matches.value_of("pass"),
|
||||
matches.value_of("name"),
|
||||
matches.value_of("category"),
|
||||
matches.is_present("msrv"),
|
||||
) {
|
||||
Ok(_) => update_lints::run(update_lints::UpdateMode::Change),
|
||||
Ok(_) => update_lints::update(update_lints::UpdateMode::Change),
|
||||
Err(e) => eprintln!("Unable to create lint: {}", e),
|
||||
}
|
||||
},
|
||||
("setup", Some(sub_command)) => match sub_command.subcommand() {
|
||||
("intellij", Some(matches)) => setup::intellij::setup_rustc_src(
|
||||
matches
|
||||
.value_of("rustc-repo-path")
|
||||
.expect("this field is mandatory and therefore always valid"),
|
||||
),
|
||||
("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")),
|
||||
("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")),
|
||||
Some(("setup", sub_command)) => match sub_command.subcommand() {
|
||||
Some(("intellij", matches)) => {
|
||||
if matches.is_present("remove") {
|
||||
setup::intellij::remove_rustc_src();
|
||||
} else {
|
||||
setup::intellij::setup_rustc_src(
|
||||
matches
|
||||
.value_of("rustc-repo-path")
|
||||
.expect("this field is mandatory and therefore always valid"),
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(("git-hook", matches)) => {
|
||||
if matches.is_present("remove") {
|
||||
setup::git_hook::remove_hook();
|
||||
} else {
|
||||
setup::git_hook::install_hook(matches.is_present("force-override"));
|
||||
}
|
||||
},
|
||||
Some(("vscode-tasks", matches)) => {
|
||||
if matches.is_present("remove") {
|
||||
setup::vscode::remove_tasks();
|
||||
} else {
|
||||
setup::vscode::install_tasks(matches.is_present("force-override"));
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
("remove", Some(sub_command)) => match sub_command.subcommand() {
|
||||
("git-hook", Some(_)) => setup::git_hook::remove_hook(),
|
||||
("intellij", Some(_)) => setup::intellij::remove_rustc_src(),
|
||||
("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(),
|
||||
Some(("remove", sub_command)) => match sub_command.subcommand() {
|
||||
Some(("git-hook", _)) => setup::git_hook::remove_hook(),
|
||||
Some(("intellij", _)) => setup::intellij::remove_rustc_src(),
|
||||
Some(("vscode-tasks", _)) => setup::vscode::remove_tasks(),
|
||||
_ => {},
|
||||
},
|
||||
("serve", Some(matches)) => {
|
||||
Some(("serve", matches)) => {
|
||||
let port = matches.value_of("port").unwrap().parse().unwrap();
|
||||
let lint = matches.value_of("lint");
|
||||
serve::run(port, lint);
|
||||
},
|
||||
("lint", Some(matches)) => {
|
||||
let filename = matches.value_of("filename").unwrap();
|
||||
lint::run(filename);
|
||||
Some(("lint", matches)) => {
|
||||
let path = matches.value_of("path").unwrap();
|
||||
let args = matches.values_of("args").into_iter().flatten();
|
||||
lint::run(path, args);
|
||||
},
|
||||
Some(("rename_lint", matches)) => {
|
||||
let old_name = matches.value_of("old_name").unwrap();
|
||||
let new_name = matches.value_of("new_name").unwrap_or(old_name);
|
||||
let uplift = matches.is_present("uplift");
|
||||
update_lints::rename(old_name, new_name, uplift);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_clap_config<'a>() -> ArgMatches<'a> {
|
||||
App::new("Clippy developer tooling")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
fn get_clap_config() -> ArgMatches {
|
||||
Command::new("Clippy developer tooling")
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(
|
||||
SubCommand::with_name("bless")
|
||||
.about("bless the test output changes")
|
||||
.arg(
|
||||
Arg::with_name("ignore-timestamp")
|
||||
.long("ignore-timestamp")
|
||||
.help("Include files updated before clippy was built"),
|
||||
),
|
||||
Command::new("bless").about("bless the test output changes").arg(
|
||||
Arg::new("ignore-timestamp")
|
||||
.long("ignore-timestamp")
|
||||
.help("Include files updated before clippy was built"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("fmt")
|
||||
Command::new("fmt")
|
||||
.about("Run rustfmt on all projects and tests")
|
||||
.arg(
|
||||
Arg::with_name("check")
|
||||
.long("check")
|
||||
.help("Use the rustfmt --check option"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.help("Echo commands run"),
|
||||
),
|
||||
.arg(Arg::new("check").long("check").help("Use the rustfmt --check option"))
|
||||
.arg(Arg::new("verbose").short('v').long("verbose").help("Echo commands run")),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("update_lints")
|
||||
Command::new("update_lints")
|
||||
.about("Updates lint registration and information from the source code")
|
||||
.long_about(
|
||||
"Makes sure that:\n \
|
||||
* the lint count in README.md is correct\n \
|
||||
* the changelog contains markdown link references at the bottom\n \
|
||||
* all lint groups include the correct lints\n \
|
||||
* lint modules in `clippy_lints/*` are visible in `src/lifb.rs` via `pub mod`\n \
|
||||
* lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \
|
||||
* all lints are registered in the lint store",
|
||||
)
|
||||
.arg(Arg::with_name("print-only").long("print-only").help(
|
||||
.arg(Arg::new("print-only").long("print-only").help(
|
||||
"Print a table of lints to STDOUT. \
|
||||
This does not include deprecated and internal lints. \
|
||||
(Does not modify any files)",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name("check")
|
||||
Arg::new("check")
|
||||
.long("check")
|
||||
.help("Checks that `cargo dev update_lints` has been run. Used on CI."),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("new_lint")
|
||||
Command::new("new_lint")
|
||||
.about("Create new lint and run `cargo dev update_lints`")
|
||||
.arg(
|
||||
Arg::with_name("pass")
|
||||
.short("p")
|
||||
Arg::new("pass")
|
||||
.short('p')
|
||||
.long("pass")
|
||||
.help("Specify whether the lint runs during the early or late pass")
|
||||
.takes_value(true)
|
||||
@ -125,16 +140,16 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("name")
|
||||
.short("n")
|
||||
Arg::new("name")
|
||||
.short('n')
|
||||
.long("name")
|
||||
.help("Name of the new lint in snake case, ex: fn_too_long")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("category")
|
||||
.short("c")
|
||||
Arg::new("category")
|
||||
.short('c')
|
||||
.long("category")
|
||||
.help("What category the lint belongs to")
|
||||
.default_value("nursery")
|
||||
@ -153,83 +168,139 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
|
||||
])
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("msrv")
|
||||
.long("msrv")
|
||||
.help("Add MSRV config code to the lint"),
|
||||
),
|
||||
.arg(Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint")),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("setup")
|
||||
Command::new("setup")
|
||||
.about("Support for setting up your personal development environment")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(
|
||||
SubCommand::with_name("intellij")
|
||||
Command::new("intellij")
|
||||
.about("Alter dependencies so Intellij Rust can find rustc internals")
|
||||
.arg(
|
||||
Arg::with_name("rustc-repo-path")
|
||||
Arg::new("remove")
|
||||
.long("remove")
|
||||
.help("Remove the dependencies added with 'cargo dev setup intellij'")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("rustc-repo-path")
|
||||
.long("repo-path")
|
||||
.short("r")
|
||||
.short('r')
|
||||
.help("The path to a rustc repo that will be used for setting the dependencies")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.conflicts_with("remove")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("git-hook")
|
||||
Command::new("git-hook")
|
||||
.about("Add a pre-commit git hook that formats your code to make it look pretty")
|
||||
.arg(
|
||||
Arg::with_name("force-override")
|
||||
Arg::new("remove")
|
||||
.long("remove")
|
||||
.help("Remove the pre-commit hook added with 'cargo dev setup git-hook'")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("force-override")
|
||||
.long("force-override")
|
||||
.short("f")
|
||||
.short('f')
|
||||
.help("Forces the override of an existing git pre-commit hook")
|
||||
.required(false),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vscode-tasks")
|
||||
Command::new("vscode-tasks")
|
||||
.about("Add several tasks to vscode for formatting, validation and testing")
|
||||
.arg(
|
||||
Arg::with_name("force-override")
|
||||
Arg::new("remove")
|
||||
.long("remove")
|
||||
.help("Remove the tasks added with 'cargo dev setup vscode-tasks'")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("force-override")
|
||||
.long("force-override")
|
||||
.short("f")
|
||||
.short('f')
|
||||
.help("Forces the override of existing vscode tasks")
|
||||
.required(false),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove")
|
||||
Command::new("remove")
|
||||
.about("Support for undoing changes done by the setup command")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook"))
|
||||
.subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks"))
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(Command::new("git-hook").about("Remove any existing pre-commit git hook"))
|
||||
.subcommand(Command::new("vscode-tasks").about("Remove any existing vscode tasks"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("intellij")
|
||||
.about("Removes rustc source paths added via `cargo dev setup intellij`"),
|
||||
Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("serve")
|
||||
Command::new("serve")
|
||||
.about("Launch a local 'ALL the Clippy Lints' website in a browser")
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
Arg::new("port")
|
||||
.long("port")
|
||||
.short("p")
|
||||
.short('p')
|
||||
.help("Local port for the http server")
|
||||
.default_value("8000")
|
||||
.validator_os(serve::validate_port),
|
||||
)
|
||||
.arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")),
|
||||
.arg(Arg::new("lint").help("Which lint's page to load initially (optional)")),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("lint")
|
||||
.about("Manually run clippy on a file")
|
||||
Command::new("lint")
|
||||
.about("Manually run clippy on a file or package")
|
||||
.after_help(indoc! {"
|
||||
EXAMPLES
|
||||
Lint a single file:
|
||||
cargo dev lint tests/ui/attrs.rs
|
||||
|
||||
Lint a package directory:
|
||||
cargo dev lint tests/ui-cargo/wildcard_dependencies/fail
|
||||
cargo dev lint ~/my-project
|
||||
|
||||
Run rustfix:
|
||||
cargo dev lint ~/my-project -- --fix
|
||||
|
||||
Set lint levels:
|
||||
cargo dev lint file.rs -- -W clippy::pedantic
|
||||
cargo dev lint ~/my-project -- -- -W clippy::pedantic
|
||||
"})
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
Arg::new("path")
|
||||
.required(true)
|
||||
.help("The path to a file to lint"),
|
||||
.help("The path to a file or package directory to lint"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("args")
|
||||
.multiple_occurrences(true)
|
||||
.help("Pass extra arguments to cargo/clippy-driver"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("rename_lint")
|
||||
.about("Renames the given lint")
|
||||
.arg(
|
||||
Arg::new("old_name")
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The name of the lint to rename"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("new_name")
|
||||
.index(2)
|
||||
.required_unless_present("uplift")
|
||||
.help("The new name of the lint"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("uplift")
|
||||
.long("uplift")
|
||||
.help("This lint will be uplifted into rustc"),
|
||||
),
|
||||
)
|
||||
.get_matches()
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::clippy_project_root;
|
||||
use indoc::indoc;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, ErrorKind};
|
||||
@ -132,16 +133,24 @@ fn to_camel_case(name: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_stabilisation_version() -> String {
|
||||
let mut command = cargo_metadata::MetadataCommand::new();
|
||||
command.no_deps();
|
||||
if let Ok(metadata) = command.exec() {
|
||||
if let Some(pkg) = metadata.packages.iter().find(|pkg| pkg.name == "clippy") {
|
||||
return format!("{}.{}.0", pkg.version.minor, pkg.version.patch);
|
||||
}
|
||||
fn get_stabilization_version() -> String {
|
||||
fn parse_manifest(contents: &str) -> Option<String> {
|
||||
let version = contents
|
||||
.lines()
|
||||
.filter_map(|l| l.split_once('='))
|
||||
.find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
|
||||
let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
|
||||
return None;
|
||||
};
|
||||
let (minor, patch) = version.split_once('.')?;
|
||||
Some(format!(
|
||||
"{}.{}.0",
|
||||
minor.parse::<u32>().ok()?,
|
||||
patch.parse::<u32>().ok()?
|
||||
))
|
||||
}
|
||||
|
||||
String::from("<TODO set version(see doc/adding_lints.md)>")
|
||||
let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
|
||||
parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
|
||||
}
|
||||
|
||||
fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
|
||||
@ -190,7 +199,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
|
||||
},
|
||||
};
|
||||
|
||||
let version = get_stabilisation_version();
|
||||
let version = get_stabilization_version();
|
||||
let lint_name = lint.name;
|
||||
let category = lint.category;
|
||||
let name_camel = to_camel_case(lint.name);
|
||||
@ -224,7 +233,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
|
||||
)
|
||||
});
|
||||
|
||||
result.push_str(&format!(
|
||||
let _ = write!(
|
||||
result,
|
||||
indoc! {r#"
|
||||
declare_clippy_lint! {{
|
||||
/// ### What it does
|
||||
@ -248,7 +258,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
|
||||
version = version,
|
||||
name_upper = name_upper,
|
||||
category = category,
|
||||
));
|
||||
);
|
||||
|
||||
result.push_str(&if enable_msrv {
|
||||
format!(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::ffi::OsStr;
|
||||
use std::num::ParseIntError;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::thread;
|
||||
@ -59,9 +60,6 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn validate_port(arg: &OsStr) -> Result<(), OsString> {
|
||||
match arg.to_string_lossy().parse::<u16>() {
|
||||
Ok(_port) => Ok(()),
|
||||
Err(err) => Err(OsString::from(err.to_string())),
|
||||
}
|
||||
pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> {
|
||||
arg.to_string_lossy().parse::<u16>().map(|_| ())
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use std::path::Path;
|
||||
const CLIPPY_DEV_DIR: &str = "clippy_dev";
|
||||
|
||||
/// This function verifies that the tool is being executed in the clippy directory.
|
||||
/// This is useful to ensure that setups only modify Clippys resources. The verification
|
||||
/// This is useful to ensure that setups only modify Clippy's resources. The verification
|
||||
/// is done by checking that `clippy_dev` is a sub directory of the current directory.
|
||||
///
|
||||
/// It will print an error message and return `false` if the directory could not be
|
||||
@ -17,7 +17,7 @@ fn verify_inside_clippy_dir() -> bool {
|
||||
if path.exists() && path.is_dir() {
|
||||
true
|
||||
} else {
|
||||
eprintln!("error: unable to verify that the working directory is clippys directory");
|
||||
eprintln!("error: unable to verify that the working directory is clippy's directory");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.60"
|
||||
version = "0.1.63"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
@ -12,7 +12,7 @@ edition = "2021"
|
||||
cargo_metadata = "0.14"
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
if_chain = "1.0"
|
||||
itertools = "0.10"
|
||||
itertools = "0.10.1"
|
||||
pulldown-cmark = { version = "0.9", default-features = false }
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.6"
|
||||
|
@ -87,9 +87,7 @@ impl ApproxConstant {
|
||||
let s = s.as_str();
|
||||
if s.parse::<f64>().is_ok() {
|
||||
for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
|
||||
if is_approx_const(constant, s, min_digits)
|
||||
&& msrv.as_ref().map_or(true, |msrv| meets_msrv(self.msrv.as_ref(), msrv))
|
||||
{
|
||||
if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| meets_msrv(self.msrv, msrv)) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
APPROX_CONSTANT,
|
||||
|
@ -139,11 +139,11 @@ impl<'tcx> LateLintPass<'tcx> for Arithmetic {
|
||||
}
|
||||
|
||||
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
|
||||
let body_owner = cx.tcx.hir().body_owner(body.id());
|
||||
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
|
||||
|
||||
match cx.tcx.hir().body_owner_kind(body_owner) {
|
||||
hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
|
||||
let body_span = cx.tcx.hir().span(body_owner);
|
||||
let body_span = cx.tcx.def_span(body_owner);
|
||||
|
||||
if let Some(span) = self.const_span {
|
||||
if span.contains(body_span) {
|
||||
|
@ -50,7 +50,7 @@ declare_clippy_lint! {
|
||||
/// ### Known problems
|
||||
/// Clippy cannot know for sure if `a op= a op b` should have
|
||||
/// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
|
||||
/// If `a op= a op b` is really the correct behaviour it should be
|
||||
/// If `a op= a op b` is really the correct behavior it should be
|
||||
/// written as `a = a op a op b` as it's less confusing.
|
||||
///
|
||||
/// ### Example
|
||||
|
@ -255,7 +255,38 @@ declare_clippy_lint! {
|
||||
"usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for attributes that allow lints without a reason.
|
||||
///
|
||||
/// (This requires the `lint_reasons` feature)
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Allowing a lint should always have a reason. This reason should be documented to
|
||||
/// ensure that others understand the reasoning
|
||||
///
|
||||
/// ### Example
|
||||
/// Bad:
|
||||
/// ```rust
|
||||
/// #![feature(lint_reasons)]
|
||||
///
|
||||
/// #![allow(clippy::some_lint)]
|
||||
/// ```
|
||||
///
|
||||
/// Good:
|
||||
/// ```rust
|
||||
/// #![feature(lint_reasons)]
|
||||
///
|
||||
/// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
restriction,
|
||||
"ensures that all `allow` and `expect` attributes have a reason"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Attributes => [
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
INLINE_ALWAYS,
|
||||
DEPRECATED_SEMVER,
|
||||
USELESS_ATTRIBUTE,
|
||||
@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
if is_lint_level(ident.name) {
|
||||
check_clippy_lint_names(cx, ident.name, items);
|
||||
}
|
||||
if matches!(ident.name, sym::allow | sym::expect) {
|
||||
check_lint_reason(cx, ident.name, items, attr);
|
||||
}
|
||||
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
||||
return;
|
||||
}
|
||||
@ -301,9 +335,6 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
}
|
||||
if let Some(lint_list) = &attr.meta_item_list() {
|
||||
if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
|
||||
// permit `unused_imports`, `deprecated`, `unreachable_pub`,
|
||||
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
|
||||
// and `unused_imports` for `extern crate` items with `macro_use`
|
||||
for lint in lint_list {
|
||||
match item.kind {
|
||||
ItemKind::Use(..) => {
|
||||
@ -311,10 +342,12 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
|| is_word(lint, sym::deprecated)
|
||||
|| is_word(lint, sym!(unreachable_pub))
|
||||
|| is_word(lint, sym!(unused))
|
||||
|| extract_clippy_lint(lint)
|
||||
.map_or(false, |s| s.as_str() == "wildcard_imports")
|
||||
|| extract_clippy_lint(lint)
|
||||
.map_or(false, |s| s.as_str() == "enum_glob_use")
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| {
|
||||
matches!(
|
||||
s.as_str(),
|
||||
"wildcard_imports" | "enum_glob_use" | "redundant_pub_crate",
|
||||
)
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -404,6 +437,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
|
||||
}
|
||||
}
|
||||
|
||||
fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
|
||||
// Check for the feature
|
||||
if !cx.tcx.sess.features_untracked().lint_reasons {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the reason is present
|
||||
if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
|
||||
&& let MetaItemKind::NameValue(_) = &item.kind
|
||||
&& item.path == sym::reason
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
attr.span,
|
||||
&format!("`{}` attribute without specifying a reason", name.as_str()),
|
||||
None,
|
||||
"try adding a reason at the end with `, reason = \"..\"`",
|
||||
);
|
||||
}
|
||||
|
||||
fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if let ItemKind::Fn(_, _, eid) = item.kind {
|
||||
is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
|
||||
@ -528,22 +585,21 @@ impl EarlyLintPass for EarlyAttributes {
|
||||
}
|
||||
|
||||
fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
||||
for attr in &item.attrs {
|
||||
let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind {
|
||||
attr
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if attr.style == AttrStyle::Outer {
|
||||
if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut iter = item.attrs.iter().peekable();
|
||||
while let Some(attr) = iter.next() {
|
||||
if matches!(attr.kind, AttrKind::Normal(..))
|
||||
&& attr.style == AttrStyle::Outer
|
||||
&& is_present_in_source(cx, attr.span)
|
||||
{
|
||||
let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
|
||||
let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt(), item.span.parent());
|
||||
let end_of_attr_to_next_attr_or_item = Span::new(
|
||||
attr.span.hi(),
|
||||
iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
|
||||
item.span.ctxt(),
|
||||
item.span.parent(),
|
||||
);
|
||||
|
||||
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
|
||||
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
|
||||
let lines = snippet.split('\n').collect::<Vec<_>>();
|
||||
let lines = without_block_comments(lines);
|
||||
|
||||
@ -563,7 +619,7 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
|
||||
|
||||
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option<RustcVersion>) {
|
||||
if_chain! {
|
||||
if meets_msrv(msrv.as_ref(), &msrvs::TOOL_ATTRIBUTES);
|
||||
if meets_msrv(msrv, msrvs::TOOL_ATTRIBUTES);
|
||||
// check cfg_attr
|
||||
if attr.has_name(sym::cfg_attr);
|
||||
if let Some(items) = attr.meta_item_list();
|
||||
@ -573,8 +629,15 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Opti
|
||||
if feature_item.has_name(sym::rustfmt);
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
if let Some(skip_item) = &items[1].meta_item();
|
||||
if skip_item.has_name(sym!(rustfmt_skip)) ||
|
||||
skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip;
|
||||
if skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip;
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
if attr.style == AttrStyle::Outer;
|
||||
@ -659,5 +722,5 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
}
|
||||
|
||||
fn is_lint_level(symbol: Symbol) -> bool {
|
||||
matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
|
||||
matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{match_def_path, paths};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
|
||||
use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::GeneratorInteriorTypeCause;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::utils::conf::DisallowedType;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to await while holding a
|
||||
/// non-async-aware MutexGuard.
|
||||
/// Checks for calls to await while holding a non-async-aware MutexGuard.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The Mutex types found in std::sync and parking_lot
|
||||
@ -22,41 +24,57 @@ declare_clippy_lint! {
|
||||
/// either by introducing a scope or an explicit call to Drop::drop.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)).
|
||||
/// Will report false positive for explicitly dropped guards
|
||||
/// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
|
||||
/// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// use std::sync::Mutex;
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::sync::Mutex;
|
||||
/// # async fn baz() {}
|
||||
/// async fn foo(x: &Mutex<u32>) {
|
||||
/// let guard = x.lock().unwrap();
|
||||
/// let mut guard = x.lock().unwrap();
|
||||
/// *guard += 1;
|
||||
/// bar.await;
|
||||
/// baz().await;
|
||||
/// }
|
||||
///
|
||||
/// async fn bar(x: &Mutex<u32>) {
|
||||
/// let mut guard = x.lock().unwrap();
|
||||
/// *guard += 1;
|
||||
/// drop(guard); // explicit drop
|
||||
/// baz().await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust,ignore
|
||||
/// use std::sync::Mutex;
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::sync::Mutex;
|
||||
/// # async fn baz() {}
|
||||
/// async fn foo(x: &Mutex<u32>) {
|
||||
/// {
|
||||
/// let guard = x.lock().unwrap();
|
||||
/// let mut guard = x.lock().unwrap();
|
||||
/// *guard += 1;
|
||||
/// }
|
||||
/// bar.await;
|
||||
/// baz().await;
|
||||
/// }
|
||||
///
|
||||
/// async fn bar(x: &Mutex<u32>) {
|
||||
/// {
|
||||
/// let mut guard = x.lock().unwrap();
|
||||
/// *guard += 1;
|
||||
/// } // guard dropped here at end of scope
|
||||
/// baz().await;
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.45.0"]
|
||||
pub AWAIT_HOLDING_LOCK,
|
||||
pedantic,
|
||||
"Inside an async function, holding a MutexGuard while calling await"
|
||||
suspicious,
|
||||
"inside an async function, holding a `MutexGuard` while calling `await`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to await while holding a
|
||||
/// `RefCell` `Ref` or `RefMut`.
|
||||
/// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `RefCell` refs only check for exclusive mutable access
|
||||
@ -64,40 +82,123 @@ declare_clippy_lint! {
|
||||
/// risks panics from a mutable ref shared while other refs are outstanding.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)).
|
||||
/// Will report false positive for explicitly dropped refs
|
||||
/// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
|
||||
/// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// use std::cell::RefCell;
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::cell::RefCell;
|
||||
/// # async fn baz() {}
|
||||
/// async fn foo(x: &RefCell<u32>) {
|
||||
/// let mut y = x.borrow_mut();
|
||||
/// *y += 1;
|
||||
/// bar.await;
|
||||
/// baz().await;
|
||||
/// }
|
||||
///
|
||||
/// async fn bar(x: &RefCell<u32>) {
|
||||
/// let mut y = x.borrow_mut();
|
||||
/// *y += 1;
|
||||
/// drop(y); // explicit drop
|
||||
/// baz().await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust,ignore
|
||||
/// use std::cell::RefCell;
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::cell::RefCell;
|
||||
/// # async fn baz() {}
|
||||
/// async fn foo(x: &RefCell<u32>) {
|
||||
/// {
|
||||
/// let mut y = x.borrow_mut();
|
||||
/// *y += 1;
|
||||
/// }
|
||||
/// bar.await;
|
||||
/// baz().await;
|
||||
/// }
|
||||
///
|
||||
/// async fn bar(x: &RefCell<u32>) {
|
||||
/// {
|
||||
/// let mut y = x.borrow_mut();
|
||||
/// *y += 1;
|
||||
/// } // y dropped here at end of scope
|
||||
/// baz().await;
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
pub AWAIT_HOLDING_REFCELL_REF,
|
||||
pedantic,
|
||||
"Inside an async function, holding a RefCell ref while calling await"
|
||||
suspicious,
|
||||
"inside an async function, holding a `RefCell` ref while calling `await`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Allows users to configure types which should not be held across `await`
|
||||
/// suspension points.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// There are some types which are perfectly "safe" to be used concurrently
|
||||
/// from a memory access perspective but will cause bugs at runtime if they
|
||||
/// are held in such a way.
|
||||
///
|
||||
/// ### Known problems
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```toml
|
||||
/// await-holding-invalid-types = [
|
||||
/// # You can specify a type name
|
||||
/// "CustomLockType",
|
||||
/// # You can (optionally) specify a reason
|
||||
/// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # async fn baz() {}
|
||||
/// struct CustomLockType;
|
||||
/// struct OtherCustomLockType;
|
||||
/// async fn foo() {
|
||||
/// let _x = CustomLockType;
|
||||
/// let _y = OtherCustomLockType;
|
||||
/// baz().await; // Lint violation
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
pub AWAIT_HOLDING_INVALID_TYPE,
|
||||
suspicious,
|
||||
"holding a type across an await point which is not allowed to be held as per the configuration"
|
||||
}
|
||||
|
||||
impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AwaitHolding {
|
||||
conf_invalid_types: Vec<DisallowedType>,
|
||||
def_ids: FxHashMap<DefId, DisallowedType>,
|
||||
}
|
||||
|
||||
impl AwaitHolding {
|
||||
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
|
||||
Self {
|
||||
conf_invalid_types,
|
||||
def_ids: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for AwaitHolding {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
for conf in &self.conf_invalid_types {
|
||||
let path = match conf {
|
||||
DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
|
||||
};
|
||||
let segs: Vec<_> = path.split("::").collect();
|
||||
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
|
||||
self.def_ids.insert(id, conf.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
|
||||
use AsyncGeneratorKind::{Block, Closure, Fn};
|
||||
if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
|
||||
@ -105,7 +206,7 @@ impl LateLintPass<'_> for AwaitHolding {
|
||||
hir_id: body.value.hir_id,
|
||||
};
|
||||
let typeck_results = cx.tcx.typeck_body(body_id);
|
||||
check_interior_types(
|
||||
self.check_interior_types(
|
||||
cx,
|
||||
typeck_results.generator_interior_types.as_ref().skip_binder(),
|
||||
body.value.span,
|
||||
@ -114,33 +215,68 @@ impl LateLintPass<'_> for AwaitHolding {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
|
||||
for ty_cause in ty_causes {
|
||||
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
|
||||
if is_mutex_guard(cx, adt.did) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
AWAIT_HOLDING_LOCK,
|
||||
ty_cause.span,
|
||||
"this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await",
|
||||
ty_cause.scope_span.or(Some(span)),
|
||||
"these are all the await points this lock is held through",
|
||||
);
|
||||
}
|
||||
if is_refcell_ref(cx, adt.did) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
AWAIT_HOLDING_REFCELL_REF,
|
||||
ty_cause.span,
|
||||
"this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await",
|
||||
ty_cause.scope_span.or(Some(span)),
|
||||
"these are all the await points this ref is held through",
|
||||
);
|
||||
impl AwaitHolding {
|
||||
fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
|
||||
for ty_cause in ty_causes {
|
||||
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
|
||||
if is_mutex_guard(cx, adt.did()) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
AWAIT_HOLDING_LOCK,
|
||||
ty_cause.span,
|
||||
"this `MutexGuard` is held across an `await` point",
|
||||
|diag| {
|
||||
diag.help(
|
||||
"consider using an async-aware `Mutex` type or ensuring the \
|
||||
`MutexGuard` is dropped before calling await",
|
||||
);
|
||||
diag.span_note(
|
||||
ty_cause.scope_span.unwrap_or(span),
|
||||
"these are all the `await` points this lock is held through",
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if is_refcell_ref(cx, adt.did()) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
AWAIT_HOLDING_REFCELL_REF,
|
||||
ty_cause.span,
|
||||
"this `RefCell` reference is held across an `await` point",
|
||||
|diag| {
|
||||
diag.help("ensure the reference is dropped before calling `await`");
|
||||
diag.span_note(
|
||||
ty_cause.scope_span.unwrap_or(span),
|
||||
"these are all the `await` points this reference is held through",
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
|
||||
emit_invalid_type(cx, ty_cause.span, disallowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
|
||||
let (type_name, reason) = match disallowed {
|
||||
DisallowedType::Simple(path) => (path, &None),
|
||||
DisallowedType::WithReason { path, reason } => (path, reason),
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
AWAIT_HOLDING_INVALID_TYPE,
|
||||
span,
|
||||
&format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
|
||||
|diag| {
|
||||
if let Some(reason) = reason {
|
||||
diag.note(reason.clone());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
||||
match_def_path(cx, def_id, &paths::MUTEX_GUARD)
|
||||
|| match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
@ -130,23 +129,24 @@ impl<'tcx> LateLintPass<'tcx> for BitMask {
|
||||
}
|
||||
}
|
||||
}
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(op, left, right) = &e.kind;
|
||||
if BinOpKind::Eq == op.node;
|
||||
if let ExprKind::Binary(op1, left1, right1) = &left.kind;
|
||||
if BinOpKind::BitAnd == op1.node;
|
||||
if let ExprKind::Lit(lit) = &right1.kind;
|
||||
if let LitKind::Int(n, _) = lit.node;
|
||||
if let ExprKind::Lit(lit1) = &right.kind;
|
||||
if let LitKind::Int(0, _) = lit1.node;
|
||||
if n.leading_zeros() == n.count_zeros();
|
||||
if n > u128::from(self.verbose_bit_mask_threshold);
|
||||
then {
|
||||
span_lint_and_then(cx,
|
||||
VERBOSE_BIT_MASK,
|
||||
e.span,
|
||||
"bit mask could be simplified with a call to `trailing_zeros`",
|
||||
|diag| {
|
||||
|
||||
if let ExprKind::Binary(op, left, right) = &e.kind
|
||||
&& BinOpKind::Eq == op.node
|
||||
&& let ExprKind::Binary(op1, left1, right1) = &left.kind
|
||||
&& BinOpKind::BitAnd == op1.node
|
||||
&& let ExprKind::Lit(lit) = &right1.kind
|
||||
&& let LitKind::Int(n, _) = lit.node
|
||||
&& let ExprKind::Lit(lit1) = &right.kind
|
||||
&& let LitKind::Int(0, _) = lit1.node
|
||||
&& n.leading_zeros() == n.count_zeros()
|
||||
&& n > u128::from(self.verbose_bit_mask_threshold)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
VERBOSE_BIT_MASK,
|
||||
e.span,
|
||||
"bit mask could be simplified with a call to `trailing_zeros`",
|
||||
|diag| {
|
||||
let sugg = Sugg::hir(cx, left1, "...").maybe_par();
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
@ -154,8 +154,8 @@ impl<'tcx> LateLintPass<'tcx> for BitMask {
|
||||
format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_block_with_applicability;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{differing_macro_contexts, get_parent_expr};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||
if let Some(ex) = &block.expr {
|
||||
// don't dig into the expression here, just suggest that they remove
|
||||
// the block
|
||||
if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
|
||||
if expr.span.from_expansion() || ex.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||
}
|
||||
} else {
|
||||
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||
if span.from_expansion() || differing_macro_contexts(expr.span, span) {
|
||||
if span.from_expansion() || expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
// move block higher
|
||||
|
@ -137,7 +137,7 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
||||
}
|
||||
for (n, expr) in self.terminals.iter().enumerate() {
|
||||
if eq_expr_value(self.cx, e, expr) {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Term(n as u8));
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
||||
if eq_expr_value(self.cx, e_lhs, expr_lhs);
|
||||
if eq_expr_value(self.cx, e_rhs, expr_rhs);
|
||||
then {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
|
||||
}
|
||||
}
|
||||
@ -157,7 +157,7 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
||||
let n = self.terminals.len();
|
||||
self.terminals.push(e);
|
||||
if n < 32 {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
Ok(Bool::Term(n as u8))
|
||||
} else {
|
||||
Err("too many literals".to_owned())
|
||||
|
@ -57,7 +57,7 @@ impl BorrowAsPtr {
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for BorrowAsPtr {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if !meets_msrv(self.msrv.as_ref(), &msrvs::BORROW_AS_PTR) {
|
||||
if !meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
70
clippy_lints/src/bytes_count_to_len.rs
Normal file
70
clippy_lints/src/bytes_count_to_len.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// It checks for `str::bytes().count()` and suggests replacing it with
|
||||
/// `str::len()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `str::bytes().count()` is longer and may not be as performant as using
|
||||
/// `str::len()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// "hello".bytes().count();
|
||||
/// String::from("hello").bytes().count();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// "hello".len();
|
||||
/// String::from("hello").len();
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub BYTES_COUNT_TO_LEN,
|
||||
complexity,
|
||||
"Using `bytes().count()` when `len()` performs the same functionality"
|
||||
}
|
||||
|
||||
declare_lint_pass!(BytesCountToLen => [BYTES_COUNT_TO_LEN]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for BytesCountToLen {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::MethodCall(_, expr_args, _) = &expr.kind;
|
||||
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if match_def_path(cx, expr_def_id, &paths::ITER_COUNT);
|
||||
|
||||
if let [bytes_expr] = &**expr_args;
|
||||
if let hir::ExprKind::MethodCall(_, bytes_args, _) = &bytes_expr.kind;
|
||||
if let Some(bytes_def_id) = cx.typeck_results().type_dependent_def_id(bytes_expr.hir_id);
|
||||
if match_def_path(cx, bytes_def_id, &paths::STR_BYTES);
|
||||
|
||||
if let [str_expr] = &**bytes_args;
|
||||
let ty = cx.typeck_results().expr_ty(str_expr).peel_refs();
|
||||
|
||||
if is_type_diagnostic_item(cx, ty, sym::String) || ty.kind() == &ty::Str;
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
BYTES_COUNT_TO_LEN,
|
||||
expr.span,
|
||||
"using long and hard to read `.bytes().count()`",
|
||||
"consider calling `.len()` instead",
|
||||
format!("{}.len()", snippet_with_applicability(cx, str_expr.span, "..", &mut applicability)),
|
||||
applicability
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
54
clippy_lints/src/cargo/common_metadata.rs
Normal file
54
clippy_lints/src/cargo/common_metadata.rs
Normal file
@ -0,0 +1,54 @@
|
||||
//! lint on missing cargo common metadata
|
||||
|
||||
use cargo_metadata::Metadata;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
use super::CARGO_COMMON_METADATA;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: bool) {
|
||||
for package in &metadata.packages {
|
||||
// only run the lint if publish is `None` (`publish = true` or skipped entirely)
|
||||
// or if the vector isn't empty (`publish = ["something"]`)
|
||||
if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish {
|
||||
if is_empty_str(&package.description) {
|
||||
missing_warning(cx, package, "package.description");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.license) && is_empty_str(&package.license_file) {
|
||||
missing_warning(cx, package, "either package.license or package.license_file");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.repository) {
|
||||
missing_warning(cx, package, "package.repository");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.readme) {
|
||||
missing_warning(cx, package, "package.readme");
|
||||
}
|
||||
|
||||
if is_empty_vec(&package.keywords) {
|
||||
missing_warning(cx, package, "package.keywords");
|
||||
}
|
||||
|
||||
if is_empty_vec(&package.categories) {
|
||||
missing_warning(cx, package, "package.categories");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) {
|
||||
let message = format!("package `{}` is missing `{}` metadata", package.name, field);
|
||||
span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message);
|
||||
}
|
||||
|
||||
fn is_empty_str<T: AsRef<std::ffi::OsStr>>(value: &Option<T>) -> bool {
|
||||
value.as_ref().map_or(true, |s| s.as_ref().is_empty())
|
||||
}
|
||||
|
||||
fn is_empty_vec(value: &[String]) -> bool {
|
||||
// This works because empty iterators return true
|
||||
value.iter().all(String::is_empty)
|
||||
}
|
92
clippy_lints/src/cargo/feature_name.rs
Normal file
92
clippy_lints/src/cargo/feature_name.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use cargo_metadata::Metadata;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
use super::{NEGATIVE_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES};
|
||||
|
||||
static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
|
||||
static SUFFIXES: [&str; 2] = ["-support", "_support"];
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
||||
for package in &metadata.packages {
|
||||
let mut features: Vec<&String> = package.features.keys().collect();
|
||||
features.sort();
|
||||
for feature in features {
|
||||
let prefix_opt = {
|
||||
let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
|
||||
if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
|
||||
Some(PREFIXES[i - 1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(prefix) = prefix_opt {
|
||||
lint(cx, feature, prefix, true);
|
||||
}
|
||||
|
||||
let suffix_opt: Option<&str> = {
|
||||
let i = SUFFIXES.partition_point(|suffix| {
|
||||
suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
|
||||
});
|
||||
if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
|
||||
Some(SUFFIXES[i - 1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(suffix) = suffix_opt {
|
||||
lint(cx, feature, suffix, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_negative_prefix(s: &str) -> bool {
|
||||
s.starts_with("no")
|
||||
}
|
||||
|
||||
fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
|
||||
let is_negative = is_prefix && is_negative_prefix(substring);
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
if is_negative {
|
||||
NEGATIVE_FEATURE_NAMES
|
||||
} else {
|
||||
REDUNDANT_FEATURE_NAMES
|
||||
},
|
||||
DUMMY_SP,
|
||||
&format!(
|
||||
"the \"{}\" {} in the feature name \"{}\" is {}",
|
||||
substring,
|
||||
if is_prefix { "prefix" } else { "suffix" },
|
||||
feature,
|
||||
if is_negative { "negative" } else { "redundant" }
|
||||
),
|
||||
None,
|
||||
&format!(
|
||||
"consider renaming the feature to \"{}\"{}",
|
||||
if is_prefix {
|
||||
feature.strip_prefix(substring)
|
||||
} else {
|
||||
feature.strip_suffix(substring)
|
||||
}
|
||||
.unwrap(),
|
||||
if is_negative {
|
||||
", but make sure the feature adds functionality"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefixes_sorted() {
|
||||
let mut sorted_prefixes = PREFIXES;
|
||||
sorted_prefixes.sort_unstable();
|
||||
assert_eq!(PREFIXES, sorted_prefixes);
|
||||
let mut sorted_suffixes = SUFFIXES;
|
||||
sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
|
||||
assert_eq!(SUFFIXES, sorted_suffixes);
|
||||
}
|
221
clippy_lints/src/cargo/mod.rs
Normal file
221
clippy_lints/src/cargo/mod.rs
Normal file
@ -0,0 +1,221 @@
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use rustc_hir::hir_id::CRATE_HIR_ID;
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
mod common_metadata;
|
||||
mod feature_name;
|
||||
mod multiple_crate_versions;
|
||||
mod wildcard_dependencies;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks to see if all common metadata is defined in
|
||||
/// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It will be more difficult for users to discover the
|
||||
/// purpose of the crate, and key information related to it.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` is missing a description field:
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
/// license = "MIT OR Apache-2.0"
|
||||
/// keywords = ["clippy", "lint", "plugin"]
|
||||
/// categories = ["development-tools", "development-tools::cargo-plugins"]
|
||||
/// ```
|
||||
///
|
||||
/// Should include a description field like:
|
||||
///
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` includes all common metadata
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
/// license = "MIT OR Apache-2.0"
|
||||
/// keywords = ["clippy", "lint", "plugin"]
|
||||
/// categories = ["development-tools", "development-tools::cargo-plugins"]
|
||||
/// ```
|
||||
#[clippy::version = "1.32.0"]
|
||||
pub CARGO_COMMON_METADATA,
|
||||
cargo,
|
||||
"common metadata is defined in `Cargo.toml`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// These prefixes and suffixes have no significant meaning.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # The `Cargo.toml` with feature name redundancy
|
||||
/// [features]
|
||||
/// default = ["use-abc", "with-def", "ghi-support"]
|
||||
/// use-abc = [] // redundant
|
||||
/// with-def = [] // redundant
|
||||
/// ghi-support = [] // redundant
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// [features]
|
||||
/// default = ["abc", "def", "ghi"]
|
||||
/// abc = []
|
||||
/// def = []
|
||||
/// ghi = []
|
||||
/// ```
|
||||
///
|
||||
#[clippy::version = "1.57.0"]
|
||||
pub REDUNDANT_FEATURE_NAMES,
|
||||
cargo,
|
||||
"usage of a redundant feature name"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for negative feature names with prefix `no-` or `not-`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Features are supposed to be additive, and negatively-named features violate it.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # The `Cargo.toml` with negative feature names
|
||||
/// [features]
|
||||
/// default = []
|
||||
/// no-abc = []
|
||||
/// not-def = []
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// [features]
|
||||
/// default = ["abc", "def"]
|
||||
/// abc = []
|
||||
/// def = []
|
||||
///
|
||||
/// ```
|
||||
#[clippy::version = "1.57.0"]
|
||||
pub NEGATIVE_FEATURE_NAMES,
|
||||
cargo,
|
||||
"usage of a negative feature name"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks to see if multiple versions of a crate are being
|
||||
/// used.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This bloats the size of targets, and can lead to
|
||||
/// confusing error messages when structs or traits are used interchangeably
|
||||
/// between different versions of a crate.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Because this can be caused purely by the dependencies
|
||||
/// themselves, it's not always possible to fix this issue.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning.
|
||||
/// [dependencies]
|
||||
/// ctrlc = "=3.1.0"
|
||||
/// ansi_term = "=0.11.0"
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub MULTIPLE_CRATE_VERSIONS,
|
||||
cargo,
|
||||
"multiple versions of the same crate being used"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for wildcard dependencies in the `Cargo.toml`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html),
|
||||
/// it is highly unlikely that you work with any possible version of your dependency,
|
||||
/// and wildcard dependencies would cause unnecessary breakage in the ecosystem.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// [dependencies]
|
||||
/// regex = "*"
|
||||
/// ```
|
||||
#[clippy::version = "1.32.0"]
|
||||
pub WILDCARD_DEPENDENCIES,
|
||||
cargo,
|
||||
"wildcard dependencies being used"
|
||||
}
|
||||
|
||||
pub struct Cargo {
|
||||
pub ignore_publish: bool,
|
||||
}
|
||||
|
||||
impl_lint_pass!(Cargo => [
|
||||
CARGO_COMMON_METADATA,
|
||||
REDUNDANT_FEATURE_NAMES,
|
||||
NEGATIVE_FEATURE_NAMES,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
WILDCARD_DEPENDENCIES
|
||||
]);
|
||||
|
||||
impl LateLintPass<'_> for Cargo {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
static NO_DEPS_LINTS: &[&Lint] = &[
|
||||
CARGO_COMMON_METADATA,
|
||||
REDUNDANT_FEATURE_NAMES,
|
||||
NEGATIVE_FEATURE_NAMES,
|
||||
WILDCARD_DEPENDENCIES,
|
||||
];
|
||||
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
|
||||
|
||||
if !NO_DEPS_LINTS
|
||||
.iter()
|
||||
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
|
||||
{
|
||||
match MetadataCommand::new().no_deps().exec() {
|
||||
Ok(metadata) => {
|
||||
common_metadata::check(cx, &metadata, self.ignore_publish);
|
||||
feature_name::check(cx, &metadata);
|
||||
wildcard_dependencies::check(cx, &metadata);
|
||||
},
|
||||
Err(e) => {
|
||||
for lint in NO_DEPS_LINTS {
|
||||
span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !WITH_DEPS_LINTS
|
||||
.iter()
|
||||
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
|
||||
{
|
||||
match MetadataCommand::new().exec() {
|
||||
Ok(metadata) => {
|
||||
multiple_crate_versions::check(cx, &metadata);
|
||||
},
|
||||
Err(e) => {
|
||||
for lint in WITH_DEPS_LINTS {
|
||||
span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
clippy_lints/src/cargo/multiple_crate_versions.rs
Normal file
63
clippy_lints/src/cargo/multiple_crate_versions.rs
Normal file
@ -0,0 +1,63 @@
|
||||
//! lint on multiple versions of a crate being used
|
||||
|
||||
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_hir::def_id::LOCAL_CRATE;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
use super::MULTIPLE_CRATE_VERSIONS;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
||||
let local_name = cx.tcx.crate_name(LOCAL_CRATE);
|
||||
let mut packages = metadata.packages.clone();
|
||||
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
if_chain! {
|
||||
if let Some(resolve) = &metadata.resolve;
|
||||
if let Some(local_id) = packages
|
||||
.iter()
|
||||
.find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None });
|
||||
then {
|
||||
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
|
||||
let group: Vec<&Package> = group.collect();
|
||||
|
||||
if group.len() <= 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
|
||||
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
|
||||
versions.sort();
|
||||
let versions = versions.iter().join(", ");
|
||||
|
||||
span_lint(
|
||||
cx,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
DUMMY_SP,
|
||||
&format!("multiple versions for dependency `{}`: {}", name, versions),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool {
|
||||
fn depends_on(node: &Node, dep_id: &PackageId) -> bool {
|
||||
node.deps.iter().any(|dep| {
|
||||
dep.pkg == *dep_id
|
||||
&& dep
|
||||
.dep_kinds
|
||||
.iter()
|
||||
.any(|info| matches!(info.kind, DependencyKind::Normal))
|
||||
})
|
||||
}
|
||||
|
||||
nodes
|
||||
.iter()
|
||||
.filter(|node| depends_on(node, dep_id))
|
||||
.any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id))
|
||||
}
|
27
clippy_lints/src/cargo/wildcard_dependencies.rs
Normal file
27
clippy_lints/src/cargo/wildcard_dependencies.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use cargo_metadata::Metadata;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use if_chain::if_chain;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
use super::WILDCARD_DEPENDENCIES;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
||||
for dep in &metadata.packages[0].dependencies {
|
||||
// VersionReq::any() does not work
|
||||
if_chain! {
|
||||
if let Ok(wildcard_ver) = semver::VersionReq::parse("*");
|
||||
if let Some(ref source) = dep.source;
|
||||
if !source.starts_with("git");
|
||||
if dep.req == wildcard_ver;
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
WILDCARD_DEPENDENCIES,
|
||||
DUMMY_SP,
|
||||
&format!("wildcard dependency for `{}`", dep.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
//! lint on missing cargo common metadata
|
||||
|
||||
use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
|
||||
use rustc_hir::hir_id::CRATE_HIR_ID;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks to see if all common metadata is defined in
|
||||
/// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It will be more difficult for users to discover the
|
||||
/// purpose of the crate, and key information related to it.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` is missing a description field:
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
/// license = "MIT OR Apache-2.0"
|
||||
/// keywords = ["clippy", "lint", "plugin"]
|
||||
/// categories = ["development-tools", "development-tools::cargo-plugins"]
|
||||
/// ```
|
||||
///
|
||||
/// Should include a description field like:
|
||||
///
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` includes all common metadata
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
/// license = "MIT OR Apache-2.0"
|
||||
/// keywords = ["clippy", "lint", "plugin"]
|
||||
/// categories = ["development-tools", "development-tools::cargo-plugins"]
|
||||
/// ```
|
||||
#[clippy::version = "1.32.0"]
|
||||
pub CARGO_COMMON_METADATA,
|
||||
cargo,
|
||||
"common metadata is defined in `Cargo.toml`"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct CargoCommonMetadata {
|
||||
ignore_publish: bool,
|
||||
}
|
||||
|
||||
impl CargoCommonMetadata {
|
||||
pub fn new(ignore_publish: bool) -> Self {
|
||||
Self { ignore_publish }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(CargoCommonMetadata => [
|
||||
CARGO_COMMON_METADATA
|
||||
]);
|
||||
|
||||
fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) {
|
||||
let message = format!("package `{}` is missing `{}` metadata", package.name, field);
|
||||
span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message);
|
||||
}
|
||||
|
||||
fn is_empty_str<T: AsRef<std::ffi::OsStr>>(value: &Option<T>) -> bool {
|
||||
value.as_ref().map_or(true, |s| s.as_ref().is_empty())
|
||||
}
|
||||
|
||||
fn is_empty_vec(value: &[String]) -> bool {
|
||||
// This works because empty iterators return true
|
||||
value.iter().all(String::is_empty)
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for CargoCommonMetadata {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
if is_lint_allowed(cx, CARGO_COMMON_METADATA, CRATE_HIR_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false);
|
||||
|
||||
for package in metadata.packages {
|
||||
// only run the lint if publish is `None` (`publish = true` or skipped entirely)
|
||||
// or if the vector isn't empty (`publish = ["something"]`)
|
||||
if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish {
|
||||
if is_empty_str(&package.description) {
|
||||
missing_warning(cx, &package, "package.description");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.license) && is_empty_str(&package.license_file) {
|
||||
missing_warning(cx, &package, "either package.license or package.license_file");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.repository) {
|
||||
missing_warning(cx, &package, "package.repository");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.readme) {
|
||||
missing_warning(cx, &package, "package.readme");
|
||||
}
|
||||
|
||||
if is_empty_vec(&package.keywords) {
|
||||
missing_warning(cx, &package, "package.keywords");
|
||||
}
|
||||
|
||||
if is_empty_vec(&package.categories) {
|
||||
missing_warning(cx, &package, "package.categories");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,12 +42,12 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &
|
||||
if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind;
|
||||
if (2..=6).contains(&ext_literal.as_str().len());
|
||||
if ext_literal.as_str().starts_with('.');
|
||||
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10))
|
||||
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10));
|
||||
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|
||||
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit());
|
||||
then {
|
||||
let mut ty = ctx.typeck_results().expr_ty(obj);
|
||||
ty = match ty.kind() {
|
||||
ty::Ref(_, ty, ..) => ty,
|
||||
ty::Ref(_, ty, ..) => *ty,
|
||||
_ => ty
|
||||
};
|
||||
|
||||
@ -55,8 +55,8 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &
|
||||
ty::Str => {
|
||||
return Some(span);
|
||||
},
|
||||
ty::Adt(&ty::AdtDef { did, .. }, _) => {
|
||||
if ctx.tcx.is_diagnostic_item(sym::String, did) {
|
||||
ty::Adt(def, _) => {
|
||||
if ctx.tcx.is_diagnostic_item(sym::String, def.did()) {
|
||||
return Some(span);
|
||||
}
|
||||
},
|
||||
|
42
clippy_lints/src/casts/cast_abs_to_unsigned.rs
Normal file
42
clippy_lints/src/casts/cast_abs_to_unsigned.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{meets_msrv, msrvs};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_semver::RustcVersion;
|
||||
|
||||
use super::CAST_ABS_TO_UNSIGNED;
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
cast_expr: &Expr<'_>,
|
||||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) {
|
||||
if_chain! {
|
||||
if meets_msrv(msrv, msrvs::UNSIGNED_ABS);
|
||||
if cast_from.is_integral();
|
||||
if cast_to.is_integral();
|
||||
if cast_from.is_signed();
|
||||
if !cast_to.is_signed();
|
||||
if let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind;
|
||||
if let method_name = method_path.ident.name.as_str();
|
||||
if method_name == "abs";
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CAST_ABS_TO_UNSIGNED,
|
||||
expr.span,
|
||||
&format!("casting the result of `{}::{}()` to {}", cast_from, method_name, cast_to),
|
||||
"replace with",
|
||||
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
21
clippy_lints/src/casts/cast_enum_constructor.rs
Normal file
21
clippy_lints/src/casts/cast_enum_constructor.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::CAST_ENUM_CONSTRUCTOR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>) {
|
||||
if matches!(cast_from.kind(), ty::FnDef(..))
|
||||
&& let ExprKind::Path(path) = &cast_expr.kind
|
||||
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = cx.qpath_res(path, cast_expr.hir_id)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_ENUM_CONSTRUCTOR,
|
||||
expr.span,
|
||||
"cast of an enum tuple constructor to an integer",
|
||||
);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ pub(super) fn check(
|
||||
cast_op: &Expr<'_>,
|
||||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
msrv: &Option<RustcVersion>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) {
|
||||
if !should_lint(cx, expr, cast_from, cast_to, msrv) {
|
||||
return;
|
||||
@ -68,7 +68,7 @@ fn should_lint(
|
||||
expr: &Expr<'_>,
|
||||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
msrv: &Option<RustcVersion>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) -> bool {
|
||||
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
|
||||
if in_constant(cx, expr.hir_id) {
|
||||
@ -93,9 +93,9 @@ fn should_lint(
|
||||
} else {
|
||||
64
|
||||
};
|
||||
from_nbits < to_nbits
|
||||
!is_isize_or_usize(cast_from) && from_nbits < to_nbits
|
||||
},
|
||||
(false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true,
|
||||
(false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv, msrvs::FROM_BOOL) => true,
|
||||
(_, _) => {
|
||||
matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
|
||||
},
|
||||
|
@ -1,12 +1,15 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::expr_or_init;
|
||||
use clippy_utils::ty::is_isize_or_usize;
|
||||
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
|
||||
use rustc_ast::ast;
|
||||
use rustc_attr::IntType;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, FloatTy, Ty};
|
||||
|
||||
use super::{utils, CAST_POSSIBLE_TRUNCATION};
|
||||
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
|
||||
|
||||
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
|
||||
@ -26,21 +29,19 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
||||
ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
|
||||
ExprKind::Binary(op, left, right) => match op.node {
|
||||
BinOpKind::Div => {
|
||||
apply_reductions(cx, nbits, left, signed)
|
||||
- (if signed {
|
||||
0 // let's be conservative here
|
||||
} else {
|
||||
// by dividing by 1, we remove 0 bits, etc.
|
||||
get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
|
||||
})
|
||||
apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
|
||||
// let's be conservative here
|
||||
0
|
||||
} else {
|
||||
// by dividing by 1, we remove 0 bits, etc.
|
||||
get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
|
||||
})
|
||||
},
|
||||
BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
|
||||
.unwrap_or(u64::max_value())
|
||||
.min(apply_reductions(cx, nbits, left, signed)),
|
||||
BinOpKind::Shr => {
|
||||
apply_reductions(cx, nbits, left, signed)
|
||||
- constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))
|
||||
},
|
||||
BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
|
||||
.saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
|
||||
_ => nbits,
|
||||
},
|
||||
ExprKind::MethodCall(method, [left, right], _) => {
|
||||
@ -75,8 +76,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
|
||||
(true, true) => {
|
||||
let msg = match (cast_from.kind(), cast_to.is_integral()) {
|
||||
(ty::Int(_) | ty::Uint(_), true) => {
|
||||
let from_nbits = apply_reductions(
|
||||
cx,
|
||||
utils::int_ty_to_nbits(cast_from, cx.tcx),
|
||||
@ -108,19 +109,60 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
|
||||
)
|
||||
},
|
||||
|
||||
(false, true) => {
|
||||
(ty::Adt(def, _), true) if def.is_enum() => {
|
||||
let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
|
||||
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
|
||||
{
|
||||
let i = def.variant_index_with_ctor_id(id);
|
||||
let variant = def.variant(i);
|
||||
let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
|
||||
(nbits, Some(variant))
|
||||
} else {
|
||||
(utils::enum_ty_to_nbits(*def, cx.tcx), None)
|
||||
};
|
||||
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
|
||||
|
||||
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
|
||||
matches!(
|
||||
ty,
|
||||
IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
|
||||
)
|
||||
});
|
||||
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
|
||||
(false, false) if from_nbits > to_nbits => "",
|
||||
(true, false) if from_nbits > to_nbits => "",
|
||||
(false, true) if from_nbits > 64 => "",
|
||||
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Some(variant) = variant {
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_ENUM_TRUNCATION,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting `{}::{}` to `{}` will truncate the value{}",
|
||||
cast_from, variant.name, cast_to, suffix,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
format!(
|
||||
"casting `{}` to `{}` may truncate the value{}",
|
||||
cast_from, cast_to, suffix,
|
||||
)
|
||||
},
|
||||
|
||||
(ty::Float(_), true) => {
|
||||
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
|
||||
},
|
||||
|
||||
(_, _) => {
|
||||
if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
|
||||
&& matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
|
||||
{
|
||||
"casting `f64` to `f32` may truncate the value".to_string()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
|
||||
"casting `f64` to `f32` may truncate the value".to_string()
|
||||
},
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
|
||||
|
@ -1,11 +1,10 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_hir_ty_cfg_dependant;
|
||||
use if_chain::if_chain;
|
||||
use clippy_utils::ty::is_c_void;
|
||||
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, match_any_def_paths, paths};
|
||||
use rustc_hir::{Expr, ExprKind, GenericArg};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::CAST_PTR_ALIGNMENT;
|
||||
|
||||
@ -20,61 +19,78 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
);
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
} else if let ExprKind::MethodCall(method_path, [self_arg, ..], _) = &expr.kind {
|
||||
if_chain! {
|
||||
if method_path.ident.name == sym!(cast);
|
||||
if let Some(generic_args) = method_path.args;
|
||||
if let [GenericArg::Type(cast_to)] = generic_args.args;
|
||||
if method_path.ident.name == sym!(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.
|
||||
if !is_hir_ty_cfg_dependant(cx, cast_to);
|
||||
then {
|
||||
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);
|
||||
}
|
||||
&& !is_hir_ty_cfg_dependant(cx, cast_to)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
|
||||
if_chain! {
|
||||
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind();
|
||||
if let ty::RawPtr(to_ptr_ty) = &cast_to.kind();
|
||||
if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty);
|
||||
if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty);
|
||||
if from_layout.align.abi < to_layout.align.abi;
|
||||
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind()
|
||||
&& let ty::RawPtr(to_ptr_ty) = &cast_to.kind()
|
||||
&& let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty)
|
||||
&& let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty)
|
||||
&& from_layout.align.abi < to_layout.align.abi
|
||||
// with c_void, we inherently need to trust the user
|
||||
if !is_c_void(cx, from_ptr_ty.ty);
|
||||
&& !is_c_void(cx, from_ptr_ty.ty)
|
||||
// when casting from a ZST, we don't know enough to properly lint
|
||||
if !from_layout.is_zst();
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
|
||||
cast_from,
|
||||
cast_to,
|
||||
from_layout.align.abi.bytes(),
|
||||
to_layout.align.abi.bytes(),
|
||||
),
|
||||
);
|
||||
}
|
||||
&& !from_layout.is_zst()
|
||||
&& !is_used_as_unaligned(cx, expr)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
|
||||
cast_from,
|
||||
cast_to,
|
||||
from_layout.align.abi.bytes(),
|
||||
to_layout.align.abi.bytes(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given type is either `core::ffi::c_void` or
|
||||
/// one of the platform specific `libc::<platform>::c_void` of libc.
|
||||
fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
if let ty::Adt(adt, _) = ty.kind() {
|
||||
let names = cx.get_def_path(adt.did);
|
||||
|
||||
if names.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if names[0] == sym::libc || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) {
|
||||
return true;
|
||||
}
|
||||
fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
let Some(parent) = get_parent_expr(cx, e) else {
|
||||
return false;
|
||||
};
|
||||
match parent.kind {
|
||||
ExprKind::MethodCall(name, [self_arg, ..], _) if self_arg.hir_id == e.hir_id => {
|
||||
if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned")
|
||||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
||||
&& let Some(def_id) = cx.tcx.impl_of_method(def_id)
|
||||
&& cx.tcx.type_of(def_id).is_unsafe_ptr()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
|
||||
static PATHS: &[&[&str]] = &[
|
||||
paths::PTR_READ_UNALIGNED.as_slice(),
|
||||
paths::PTR_WRITE_UNALIGNED.as_slice(),
|
||||
paths::PTR_UNALIGNED_VOLATILE_LOAD.as_slice(),
|
||||
paths::PTR_UNALIGNED_VOLATILE_STORE.as_slice(),
|
||||
];
|
||||
if let ExprKind::Path(path) = &func.kind
|
||||
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
|
||||
&& match_any_def_paths(cx, def_id, PATHS).is_some()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
false
|
||||
}
|
||||
|
143
clippy_lints/src/casts/cast_slice_different_sizes.rs
Normal file
143
clippy_lints/src/casts/cast_slice_different_sizes.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
|
||||
use rustc_semver::RustcVersion;
|
||||
|
||||
use super::CAST_SLICE_DIFFERENT_SIZES;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Option<RustcVersion>) {
|
||||
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
|
||||
if !meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if this cast is the child of another cast expression then don't emit something for it, the full
|
||||
// chain will be analyzed
|
||||
if is_child_of_cast(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(CastChainInfo {
|
||||
left_cast,
|
||||
start_ty,
|
||||
end_ty,
|
||||
}) = expr_cast_chain_tys(cx, expr)
|
||||
{
|
||||
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 {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CAST_SLICE_DIFFERENT_SIZES,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
|
||||
start_ty.ty, from_size, end_ty.ty, to_size,
|
||||
),
|
||||
|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
|
||||
);
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
|
||||
sugg,
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let map = cx.tcx.hir();
|
||||
if_chain! {
|
||||
if let Some(parent_id) = map.find_parent_node(expr.hir_id);
|
||||
if let Some(parent) = map.find(parent_id);
|
||||
then {
|
||||
let expr = match parent {
|
||||
Node::Block(block) => {
|
||||
if let Some(parent_expr) = block.expr {
|
||||
parent_expr
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
Node::Expr(expr) => expr,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
matches!(expr.kind, ExprKind::Cast(..))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
|
||||
/// the type is one of those slices
|
||||
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
|
||||
match ty.kind() {
|
||||
ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
|
||||
ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
struct CastChainInfo<'tcx> {
|
||||
/// The left most part of the cast chain, or in other words, the first cast in the chain
|
||||
/// Used for diagnostics
|
||||
left_cast: &'tcx Expr<'tcx>,
|
||||
/// The starting type of the cast chain
|
||||
start_ty: TypeAndMut<'tcx>,
|
||||
/// The final type of the cast chain
|
||||
end_ty: TypeAndMut<'tcx>,
|
||||
}
|
||||
|
||||
/// Returns a `CastChainInfo` with the left-most cast in the chain and the original ptr T and final
|
||||
/// ptr U if the expression is composed of casts.
|
||||
/// Returns None if the expr is not a Cast
|
||||
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<CastChainInfo<'tcx>> {
|
||||
if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
|
||||
let cast_to = cx.typeck_results().expr_ty(expr);
|
||||
let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
|
||||
|
||||
// If the expression that makes up the source of this cast is itself a cast, recursively
|
||||
// call `expr_cast_chain_tys` and update the end type with the final target type.
|
||||
// Otherwise, this cast is not immediately nested, just construct the info for this cast
|
||||
if let Some(prev_info) = expr_cast_chain_tys(cx, cast_expr) {
|
||||
Some(CastChainInfo {
|
||||
end_ty: to_slice_ty,
|
||||
..prev_info
|
||||
})
|
||||
} else {
|
||||
let cast_from = cx.typeck_results().expr_ty(cast_expr);
|
||||
let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
|
||||
Some(CastChainInfo {
|
||||
left_cast: cast_expr,
|
||||
start_ty: from_slice_ty,
|
||||
end_ty: to_slice_ty,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
mod cast_abs_to_unsigned;
|
||||
mod cast_enum_constructor;
|
||||
mod cast_lossless;
|
||||
mod cast_possible_truncation;
|
||||
mod cast_possible_wrap;
|
||||
@ -5,6 +7,7 @@ mod cast_precision_loss;
|
||||
mod cast_ptr_alignment;
|
||||
mod cast_ref_to_mut;
|
||||
mod cast_sign_loss;
|
||||
mod cast_slice_different_sizes;
|
||||
mod char_lit_as_u8;
|
||||
mod fn_to_numeric_cast;
|
||||
mod fn_to_numeric_cast_any;
|
||||
@ -267,7 +270,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Casting a function pointer to an integer can have surprising results and can occur
|
||||
/// accidentally if parantheses are omitted from a function call. If you aren't doing anything
|
||||
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything
|
||||
/// low-level with function pointers then you can opt-out of casting functions to integers in
|
||||
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
|
||||
/// pointer casts in your code.
|
||||
@ -303,7 +306,7 @@ declare_clippy_lint! {
|
||||
/// Checks for casts of `&T` to `&mut T` anywhere in the code.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It’s basically guaranteed to be undefined behaviour.
|
||||
/// It’s basically guaranteed to be undefined behavior.
|
||||
/// `UnsafeCell` is the only way to obtain aliasable data that is considered
|
||||
/// mutable.
|
||||
///
|
||||
@ -390,6 +393,111 @@ declare_clippy_lint! {
|
||||
"casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts from an enum type to an integral type which will definitely truncate the
|
||||
/// value.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The resulting integral value will not match the value of the variant it came from.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// enum E { X = 256 };
|
||||
/// let _ = E::X as u8;
|
||||
/// ```
|
||||
#[clippy::version = "1.60.0"]
|
||||
pub CAST_ENUM_TRUNCATION,
|
||||
suspicious,
|
||||
"casts from an enum type to an integral type which will truncate the value"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `as` casts between raw pointers to slices with differently sized elements.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The produced raw pointer to a slice does not update its length metadata. The produced
|
||||
/// pointer will point to a different number of bytes than the original pointer because the
|
||||
/// length metadata of a raw slice pointer is in elements rather than bytes.
|
||||
/// Producing a slice reference from the raw pointer will either create a slice with
|
||||
/// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
|
||||
///
|
||||
/// ### Example
|
||||
/// // Missing data
|
||||
/// ```rust
|
||||
/// let a = [1_i32, 2, 3, 4];
|
||||
/// let p = &a as *const [i32] as *const [u8];
|
||||
/// unsafe {
|
||||
/// println!("{:?}", &*p);
|
||||
/// }
|
||||
/// ```
|
||||
/// // Undefined Behavior (note: also potential alignment issues)
|
||||
/// ```rust
|
||||
/// let a = [1_u8, 2, 3, 4];
|
||||
/// let p = &a as *const [u8] as *const [u32];
|
||||
/// unsafe {
|
||||
/// println!("{:?}", &*p);
|
||||
/// }
|
||||
/// ```
|
||||
/// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
|
||||
/// ```rust
|
||||
/// let a = [1_i32, 2, 3, 4];
|
||||
/// let old_ptr = &a as *const [i32];
|
||||
/// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
|
||||
/// // The length comes from the known length of 4 i32s times the 4 bytes per i32
|
||||
/// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
|
||||
/// unsafe {
|
||||
/// println!("{:?}", &*new_ptr);
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.60.0"]
|
||||
pub CAST_SLICE_DIFFERENT_SIZES,
|
||||
correctness,
|
||||
"casting using `as` between raw pointers to slices of types with different sizes"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts from an enum tuple constructor to an integer.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The cast is easily confused with casting a c-like enum value to an integer.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// enum E { X(i32) };
|
||||
/// let _ = E::X as usize;
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub CAST_ENUM_CONSTRUCTOR,
|
||||
suspicious,
|
||||
"casts from an enum tuple constructor to an integer"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for uses of the `abs()` method that cast the result to unsigned.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `unsigned_abs()` method avoids panic when called on the MIN value.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let x: i32 = -42;
|
||||
/// let y: u32 = x.abs() as u32;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let x: i32 = -42;
|
||||
/// let y: u32 = x.unsigned_abs();
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub CAST_ABS_TO_UNSIGNED,
|
||||
suspicious,
|
||||
"casting the result of `abs()` to an unsigned integer can panic"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Option<RustcVersion>,
|
||||
}
|
||||
@ -409,16 +517,24 @@ impl_lint_pass!(Casts => [
|
||||
CAST_LOSSLESS,
|
||||
CAST_REF_TO_MUT,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
CAST_SLICE_DIFFERENT_SIZES,
|
||||
UNNECESSARY_CAST,
|
||||
FN_TO_NUMERIC_CAST_ANY,
|
||||
FN_TO_NUMERIC_CAST,
|
||||
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
|
||||
CHAR_LIT_AS_U8,
|
||||
PTR_AS_PTR,
|
||||
CAST_ENUM_TRUNCATION,
|
||||
CAST_ENUM_CONSTRUCTOR,
|
||||
CAST_ABS_TO_UNSIGNED
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if !in_external_macro(cx.sess(), expr.span) {
|
||||
ptr_as_ptr::check(cx, expr, self.msrv);
|
||||
}
|
||||
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
@ -441,21 +557,23 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
|
||||
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
|
||||
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
if cast_from.is_numeric() {
|
||||
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
|
||||
cast_precision_loss::check(cx, expr, cast_from, cast_to);
|
||||
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
|
||||
}
|
||||
|
||||
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
|
||||
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
|
||||
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
|
||||
}
|
||||
}
|
||||
|
||||
cast_ref_to_mut::check(cx, expr);
|
||||
cast_ptr_alignment::check(cx, expr);
|
||||
char_lit_as_u8::check(cx, expr);
|
||||
ptr_as_ptr::check(cx, expr, &self.msrv);
|
||||
ptr_as_ptr::check(cx, expr, self.msrv);
|
||||
cast_slice_different_sizes::check(cx, expr, self.msrv);
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
|
@ -12,8 +12,8 @@ use rustc_semver::RustcVersion;
|
||||
|
||||
use super::PTR_AS_PTR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
|
||||
if !meets_msrv(msrv.as_ref(), &msrvs::POINTER_CAST) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Option<RustcVersion>) {
|
||||
if !meets_msrv(msrv, msrvs::POINTER_CAST) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,8 @@ use clippy_utils::source::snippet_opt;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Expr, ExprKind, Lit, QPath, TyKind, UnOp};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
|
||||
@ -18,6 +19,17 @@ pub(super) fn check(
|
||||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
) -> bool {
|
||||
// skip non-primitive type cast
|
||||
if_chain! {
|
||||
if let ExprKind::Cast(_, cast_to) = expr.kind;
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
|
||||
if let Res::PrimTy(_) = path.res;
|
||||
then {}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lit) = get_numeric_literal(cast_expr) {
|
||||
let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
|
||||
use clippy_utils::ty::{read_explicit_enum_value, EnumValue};
|
||||
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
|
||||
|
||||
/// Returns the size in bits of an integral type.
|
||||
/// Will return 0 if the type is not an int or uint variant
|
||||
@ -23,3 +24,52 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn enum_value_nbits(value: EnumValue) -> u64 {
|
||||
match value {
|
||||
EnumValue::Unsigned(x) => 128 - x.leading_zeros(),
|
||||
EnumValue::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1,
|
||||
EnumValue::Signed(x) => 128 - x.leading_zeros(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 {
|
||||
let mut explicit = 0i128;
|
||||
let (start, end) = adt
|
||||
.variants()
|
||||
.iter()
|
||||
.fold((0, i128::MIN), |(start, end), variant| match variant.discr {
|
||||
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
|
||||
Some(x) => (start, end.max(x)),
|
||||
None => (i128::MIN, end),
|
||||
},
|
||||
VariantDiscr::Explicit(id) => match read_explicit_enum_value(tcx, id) {
|
||||
Some(EnumValue::Signed(x)) => {
|
||||
explicit = x;
|
||||
(start.min(x), end.max(x))
|
||||
},
|
||||
Some(EnumValue::Unsigned(x)) => match i128::try_from(x) {
|
||||
Ok(x) => {
|
||||
explicit = x;
|
||||
(start, end.max(x))
|
||||
},
|
||||
Err(_) => (i128::MIN, end),
|
||||
},
|
||||
None => (start, end),
|
||||
},
|
||||
});
|
||||
|
||||
if start > end {
|
||||
// No variants.
|
||||
0
|
||||
} else {
|
||||
let neg_bits = if start < 0 {
|
||||
128 - (-(start + 1)).leading_zeros() + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
|
||||
neg_bits.max(pos_bits).into()
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ declare_clippy_lint! {
|
||||
/// Could be written:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # let foo = 1;
|
||||
/// # let _ =
|
||||
/// i32::try_from(foo).is_ok()
|
||||
@ -57,7 +56,7 @@ impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
|
||||
if !meets_msrv(self.msrv.as_ref(), &msrvs::TRY_FROM) {
|
||||
if !meets_msrv(self.msrv, msrvs::TRY_FROM) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -123,7 +122,7 @@ struct Conversion<'a> {
|
||||
}
|
||||
|
||||
/// The kind of conversion that is checked
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum ConversionType {
|
||||
SignedToUnsigned,
|
||||
SignedToSigned,
|
||||
|
@ -48,7 +48,7 @@ impl CognitiveComplexity {
|
||||
impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
|
||||
|
||||
impl CognitiveComplexity {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn check<'tcx>(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
@ -70,7 +70,7 @@ impl CognitiveComplexity {
|
||||
let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
|
||||
returns
|
||||
} else {
|
||||
#[allow(clippy::integer_division)]
|
||||
#[expect(clippy::integer_division)]
|
||||
(returns / 2)
|
||||
};
|
||||
|
||||
@ -82,7 +82,7 @@ impl CognitiveComplexity {
|
||||
|
||||
if rust_cc > self.limit.limit() {
|
||||
let fn_span = match kind {
|
||||
FnKind::ItemFn(ident, _, _, _) | FnKind::Method(ident, _, _) => ident.span,
|
||||
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
|
||||
FnKind::Closure => {
|
||||
let header_span = body_span.with_hi(decl.output.span().lo());
|
||||
let pos = snippet_opt(cx, header_span).and_then(|snip| {
|
||||
|
@ -13,13 +13,14 @@
|
||||
//! This lint is **warn** by default
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet_block, snippet_block_with_applicability};
|
||||
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -42,7 +43,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// Should be written:
|
||||
///
|
||||
/// ```rust.ignore
|
||||
/// ```rust,ignore
|
||||
/// if x && y {
|
||||
/// …
|
||||
/// }
|
||||
@ -76,7 +77,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// Should be written:
|
||||
///
|
||||
/// ```rust.ignore
|
||||
/// ```rust,ignore
|
||||
/// if x {
|
||||
/// …
|
||||
/// } else if y {
|
||||
@ -102,7 +103,7 @@ impl EarlyLintPass for CollapsibleIf {
|
||||
fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
||||
if let ast::ExprKind::If(check, then, else_) = &expr.kind {
|
||||
if let Some(else_) = else_ {
|
||||
check_collapsible_maybe_if_let(cx, else_);
|
||||
check_collapsible_maybe_if_let(cx, then.span, else_);
|
||||
} else if let ast::ExprKind::Let(..) = check.kind {
|
||||
// Prevent triggering on `if let a = b { if c { .. } }`.
|
||||
} else {
|
||||
@ -119,7 +120,7 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
|
||||
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
|
||||
}
|
||||
|
||||
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
|
||||
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
|
||||
if_chain! {
|
||||
if let ast::ExprKind::Block(ref block, _) = else_.kind;
|
||||
if !block_starts_with_comment(cx, block);
|
||||
@ -128,6 +129,11 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
|
||||
if !else_.span.from_expansion();
|
||||
if let ast::ExprKind::If(..) = else_.kind;
|
||||
then {
|
||||
// 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,
|
||||
@ -135,7 +141,11 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
|
||||
block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability).into_owned(),
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
@ -3,11 +3,12 @@ use clippy_utils::higher::IfLetOrMatch;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Arm, Expr, Guard, HirId, Pat, PatKind};
|
||||
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{MultiSpan, Span};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -108,7 +109,10 @@ fn check_arm<'tcx>(
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
|
||||
};
|
||||
// the binding must not be used in the if guard
|
||||
if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !is_local_used(cx, *e, binding_id));
|
||||
if outer_guard.map_or(
|
||||
true,
|
||||
|(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
|
||||
);
|
||||
// ...or anywhere in the inner expression
|
||||
if match inner {
|
||||
IfLetOrMatch::IfLet(_, _, body, els) => {
|
||||
@ -129,8 +133,8 @@ fn check_arm<'tcx>(
|
||||
&msg,
|
||||
|diag| {
|
||||
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
|
||||
help_span.push_span_label(binding_span, "replace this binding".into());
|
||||
help_span.push_span_label(inner_then_pat.span, "with this pattern".into());
|
||||
help_span.push_span_label(binding_span, "replace this binding");
|
||||
help_span.push_span_label(inner_then_pat.span, "with this pattern");
|
||||
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
|
||||
},
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ use clippy_utils::{
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::{Applicability, DiagnosticBuilder};
|
||||
use rustc_errors::{Applicability, Diagnostic};
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Block, Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
@ -126,7 +126,7 @@ declare_clippy_lint! {
|
||||
/// Duplicate code is less maintainable.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// * The lint doesn't check if the moved expressions modify values that are beeing used in
|
||||
/// * The lint doesn't check if the moved expressions modify values that are being used in
|
||||
/// the if condition. The suggestion can in that case modify the behavior of the program.
|
||||
/// See [rust-clippy#7452](https://github.com/rust-lang/rust-clippy/issues/7452)
|
||||
///
|
||||
@ -489,7 +489,7 @@ fn emit_branches_sharing_code_lint(
|
||||
add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit();
|
||||
}
|
||||
|
||||
let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
|
||||
let add_optional_msgs = |diag: &mut Diagnostic| {
|
||||
if add_expr_note {
|
||||
diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
|
||||
}
|
||||
|
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
|
||||
use rustc_ast::token::{Token, TokenKind};
|
||||
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for use of `crate` as opposed to `$crate` in a macro definition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
|
||||
/// crate. Rarely is the former intended. See:
|
||||
/// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// #[macro_export]
|
||||
/// macro_rules! print_message {
|
||||
/// () => {
|
||||
/// println!("{}", crate::MESSAGE);
|
||||
/// };
|
||||
/// }
|
||||
/// pub const MESSAGE: &str = "Hello!";
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[macro_export]
|
||||
/// macro_rules! print_message {
|
||||
/// () => {
|
||||
/// println!("{}", $crate::MESSAGE);
|
||||
/// };
|
||||
/// }
|
||||
/// pub const MESSAGE: &str = "Hello!";
|
||||
/// ```
|
||||
///
|
||||
/// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
|
||||
/// macro definition, e.g.:
|
||||
/// ```rust,ignore
|
||||
/// #[allow(clippy::crate_in_macro_def)]
|
||||
/// macro_rules! ok { ... crate::foo ... }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub CRATE_IN_MACRO_DEF,
|
||||
suspicious,
|
||||
"using `crate` in a macro definition"
|
||||
}
|
||||
declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
|
||||
|
||||
impl EarlyLintPass for CrateInMacroDef {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if_chain! {
|
||||
if item.attrs.iter().any(is_macro_export);
|
||||
if let ItemKind::MacroDef(macro_def) = &item.kind;
|
||||
let tts = macro_def.body.inner_tokens();
|
||||
if let Some(span) = contains_unhygienic_crate_reference(&tts);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CRATE_IN_MACRO_DEF,
|
||||
span,
|
||||
"`crate` references the macro call's crate",
|
||||
"to reference the macro definition's crate, use",
|
||||
String::from("$crate"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_macro_export(attr: &Attribute) -> bool {
|
||||
if_chain! {
|
||||
if let AttrKind::Normal(attr_item, _) = &attr.kind;
|
||||
if let [segment] = attr_item.path.segments.as_slice();
|
||||
then {
|
||||
segment.ident.name == sym::macro_export
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
|
||||
let mut prev_is_dollar = false;
|
||||
let mut cursor = tts.trees();
|
||||
while let Some(curr) = cursor.next() {
|
||||
if_chain! {
|
||||
if !prev_is_dollar;
|
||||
if let Some(span) = is_crate_keyword(&curr);
|
||||
if let Some(next) = cursor.look_ahead(0);
|
||||
if is_token(next, &TokenKind::ModSep);
|
||||
then {
|
||||
return Some(span);
|
||||
}
|
||||
}
|
||||
if let TokenTree::Delimited(_, _, tts) = &curr {
|
||||
let span = contains_unhygienic_crate_reference(tts);
|
||||
if span.is_some() {
|
||||
return span;
|
||||
}
|
||||
}
|
||||
prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
|
||||
if_chain! {
|
||||
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }) = tt;
|
||||
if symbol.as_str() == "crate";
|
||||
then { Some(*span) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
|
||||
if let TokenTree::Token(Token { kind: other, .. }) = tt {
|
||||
kind == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::tokenstream::TokenStream;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_in_cfg_test, is_in_test_function};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -15,14 +16,6 @@ declare_clippy_lint! {
|
||||
/// `dbg!` macro is intended as a debugging tool. It
|
||||
/// should not be in version control.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// * The lint level is unaffected by crate attributes. The level can still
|
||||
/// be set for functions, modules and other items. To change the level for
|
||||
/// the entire crate, please use command line flags. More information and a
|
||||
/// configuration example can be found in [clippy#6610].
|
||||
///
|
||||
/// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// // Bad
|
||||
@ -37,39 +30,71 @@ declare_clippy_lint! {
|
||||
"`dbg!` macro is intended as a debugging tool"
|
||||
}
|
||||
|
||||
declare_lint_pass!(DbgMacro => [DBG_MACRO]);
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DbgMacro {
|
||||
allow_dbg_in_tests: bool,
|
||||
}
|
||||
|
||||
impl EarlyLintPass for DbgMacro {
|
||||
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) {
|
||||
if mac.path == sym!(dbg) {
|
||||
if let Some(sugg) = tts_span(mac.args.inner_tokens()).and_then(|span| snippet_opt(cx, span)) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DBG_MACRO,
|
||||
mac.span(),
|
||||
"`dbg!` macro is intended as a debugging tool",
|
||||
"ensure to avoid having uses of it in version control",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DBG_MACRO,
|
||||
mac.span(),
|
||||
"`dbg!` macro is intended as a debugging tool",
|
||||
None,
|
||||
"ensure to avoid having uses of it in version control",
|
||||
);
|
||||
}
|
||||
}
|
||||
impl_lint_pass!(DbgMacro => [DBG_MACRO]);
|
||||
|
||||
impl DbgMacro {
|
||||
pub fn new(allow_dbg_in_tests: bool) -> Self {
|
||||
DbgMacro { allow_dbg_in_tests }
|
||||
}
|
||||
}
|
||||
|
||||
// Get span enclosing entire the token stream.
|
||||
fn tts_span(tts: TokenStream) -> Option<Span> {
|
||||
let mut cursor = tts.into_trees();
|
||||
let first = cursor.next()?.span();
|
||||
let span = cursor.last().map_or(first, |tree| first.to(tree.span()));
|
||||
Some(span)
|
||||
impl LateLintPass<'_> for DbgMacro {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
|
||||
if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
|
||||
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
|
||||
if self.allow_dbg_in_tests
|
||||
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = match expr.peel_drop_temps().kind {
|
||||
// dbg!()
|
||||
ExprKind::Block(_, _) => String::new(),
|
||||
// dbg!(1)
|
||||
ExprKind::Match(val, ..) => {
|
||||
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string()
|
||||
},
|
||||
// dbg!(2, 3)
|
||||
ExprKind::Tup(
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Match(first, ..),
|
||||
..
|
||||
},
|
||||
..,
|
||||
Expr {
|
||||
kind: ExprKind::Match(last, ..),
|
||||
..
|
||||
},
|
||||
],
|
||||
) => {
|
||||
let snippet = snippet_with_applicability(
|
||||
cx,
|
||||
first.span.source_callsite().to(last.span.source_callsite()),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
format!("({snippet})")
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DBG_MACRO,
|
||||
macro_call.span,
|
||||
"`dbg!` macro is intended as a debugging tool",
|
||||
"ensure to avoid having uses of it in version control",
|
||||
suggestion,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_with_macro_callsite;
|
||||
use clippy_utils::ty::{has_drop, is_copy};
|
||||
use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths};
|
||||
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
@ -88,6 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
|
||||
if !is_update_syntax_base(cx, expr);
|
||||
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
|
||||
if let QPath::Resolved(None, _path) = qpath;
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||
@ -95,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
then {
|
||||
// TODO: Work out a way to put "whatever the imported way of referencing
|
||||
// this type in this file" rather than a fully-qualified type.
|
||||
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
|
||||
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did()));
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEFAULT_TRAIT_ACCESS,
|
||||
@ -109,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
|
||||
// start from the `let mut _ = _::default();` and look at all the following
|
||||
// statements, see if they re-assign the fields of the binding
|
||||
@ -136,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
if let Some(adt) = binding_type.ty_adt_def();
|
||||
if adt.is_struct();
|
||||
let variant = adt.non_enum_variant();
|
||||
if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
|
||||
if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
|
||||
let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
|
||||
if variant
|
||||
.fields
|
||||
@ -215,7 +216,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
if let ty::Adt(adt_def, substs) = binding_type.kind();
|
||||
if !substs.is_empty();
|
||||
then {
|
||||
let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
|
||||
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
|
||||
let generic_args = substs.iter().collect::<Vec<_>>();
|
||||
let tys_str = generic_args
|
||||
.iter()
|
||||
@ -290,3 +291,16 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
|
||||
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(parent) = get_parent_expr(cx, expr);
|
||||
if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
|
||||
then {
|
||||
base.hir_id == expr.hir_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,14 +116,13 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
match &expr.kind {
|
||||
ExprKind::Call(func, args) => {
|
||||
if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
|
||||
for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) {
|
||||
// Push found arg type, then visit arg.
|
||||
self.ty_bounds.push(TyBound::Ty(bound));
|
||||
self.ty_bounds.push(TyBound::Ty(*bound));
|
||||
self.visit_expr(expr);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
@ -135,7 +134,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
|
||||
for (expr, bound) in iter::zip(*args, fn_sig.inputs()) {
|
||||
self.ty_bounds.push(TyBound::Ty(bound));
|
||||
self.ty_bounds.push(TyBound::Ty(*bound));
|
||||
self.visit_expr(expr);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
@ -148,7 +147,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
if_chain! {
|
||||
if let Some(adt_def) = ty.ty_adt_def();
|
||||
if adt_def.is_struct();
|
||||
if let Some(variant) = adt_def.variants.iter().next();
|
||||
if let Some(variant) = adt_def.variants().iter().next();
|
||||
then {
|
||||
let fields_def = &variant.fields;
|
||||
|
||||
@ -210,7 +209,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
|
||||
fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> {
|
||||
let node_ty = cx.typeck_results().node_type_opt(hir_id)?;
|
||||
// We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs.
|
||||
// We can't use `Ty::fn_sig` because it automatically performs substs, this may result in FNs.
|
||||
match node_ty.kind() {
|
||||
ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)),
|
||||
ty::FnPtr(fn_sig) => Some(*fn_sig),
|
||||
|
@ -25,7 +25,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let _x: u32 = unsafe {
|
||||
/// Foo { a: 0_i32 }.b // Undefined behaviour: `b` is allowed to be padding
|
||||
/// Foo { a: 0_i32 }.b // Undefined behavior: `b` is allowed to be padding
|
||||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
@ -39,7 +39,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let _x: u32 = unsafe {
|
||||
/// Foo { a: 0_i32 }.b // Now defined behaviour, this is just an i32 -> u32 transmute
|
||||
/// Foo { a: 0_i32 }.b // Now defined behavior, this is just an i32 -> u32 transmute
|
||||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -194,7 +194,6 @@ declare_deprecated_lint! {
|
||||
/// ### Deprecation reason
|
||||
/// The `avoid_breaking_exported_api` config option was added, which
|
||||
/// enables the `enum_variant_names` lint for public items.
|
||||
/// ```
|
||||
#[clippy::version = "1.54.0"]
|
||||
pub PUB_ENUM_VARIANT_NAMES,
|
||||
"set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items"
|
||||
|
@ -12,7 +12,7 @@ use rustc_hir::{
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
|
||||
@ -168,7 +168,7 @@ struct RefPat {
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// Skip path expressions from deref calls. e.g. `Deref::deref(e)`
|
||||
if Some(expr.hir_id) == self.skip_expr.take() {
|
||||
@ -448,7 +448,7 @@ fn try_parse_ref_op<'tcx>(
|
||||
// the reference.
|
||||
fn deref_method_same_type(result_ty: Ty<'_>, arg_ty: Ty<'_>) -> bool {
|
||||
match (result_ty.kind(), arg_ty.kind()) {
|
||||
(ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => TyS::same_type(result_ty, arg_ty),
|
||||
(ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => result_ty == arg_ty,
|
||||
|
||||
// The result type for a deref method is always a reference
|
||||
// Not matching the previous pattern means the argument type is not a reference
|
||||
@ -528,7 +528,7 @@ fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool {
|
||||
fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
|
||||
if let Some(Node::Expr(parent)) = parent {
|
||||
match parent.kind {
|
||||
ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
|
||||
// ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
|
||||
ExprKind::Field(..) => true,
|
||||
ExprKind::Call(f, _) => f.hir_id == child_id,
|
||||
_ => false,
|
||||
@ -580,7 +580,7 @@ fn find_adjustments<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) {
|
||||
match state {
|
||||
State::DerefMethod {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{is_automatically_derived, is_default_equivalent, peel_blocks};
|
||||
use clippy_utils::{is_default_equivalent, peel_blocks};
|
||||
use rustc_hir::{
|
||||
def::{DefKind, Res},
|
||||
Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind,
|
||||
@ -71,8 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
||||
self_ty,
|
||||
..
|
||||
}) = item.kind;
|
||||
if let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
if !is_automatically_derived(attrs);
|
||||
if !cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
|
||||
if !item.span.from_expansion();
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
|
||||
@ -81,6 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
||||
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
|
||||
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
|
||||
if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def();
|
||||
if let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
if !attrs.iter().any(|attr| attr.doc_str().is_some());
|
||||
if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
|
||||
if !child_attrs.iter().any(|attr| attr.doc_str().is_some());
|
||||
@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
||||
_ => false,
|
||||
};
|
||||
if should_emit {
|
||||
let path_string = cx.tcx.def_path_str(adt_def.did);
|
||||
let path_string = cx.tcx.def_path_str(adt_def.did());
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DERIVABLE_IMPLS,
|
||||
|
@ -1,8 +1,9 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::paths;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::{get_trait_def_id, is_automatically_derived, is_lint_allowed, match_def_path};
|
||||
use clippy_utils::{is_lint_allowed, match_def_path};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
|
||||
use rustc_hir::{
|
||||
BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
|
||||
@ -12,6 +13,7 @@ use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -100,8 +102,8 @@ declare_clippy_lint! {
|
||||
/// types.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// To avoid surprising behaviour, these traits should
|
||||
/// agree and the behaviour of `Copy` cannot be overridden. In almost all
|
||||
/// To avoid surprising behavior, these traits should
|
||||
/// agree and the behavior of `Copy` cannot be overridden. In almost all
|
||||
/// situations a `Copy` type should have a `Clone` implementation that does
|
||||
/// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
|
||||
/// gets you.
|
||||
@ -155,11 +157,44 @@ declare_clippy_lint! {
|
||||
"deriving `serde::Deserialize` on a type that has methods using `unsafe`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for types that derive `PartialEq` and could implement `Eq`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
|
||||
/// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
|
||||
/// in APIs that require `Eq` types. It also allows structs containing `T` to derive
|
||||
/// `Eq` themselves.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// #[derive(PartialEq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[derive(PartialEq, Eq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
style,
|
||||
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Derive => [
|
||||
EXPL_IMPL_CLONE_ON_COPY,
|
||||
DERIVE_HASH_XOR_EQ,
|
||||
DERIVE_ORD_XOR_PARTIAL_ORD,
|
||||
UNSAFE_DERIVE_DESERIALIZE
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Derive {
|
||||
@ -170,14 +205,14 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
|
||||
}) = item.kind
|
||||
{
|
||||
let ty = cx.tcx.type_of(item.def_id);
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let is_automatically_derived = is_automatically_derived(attrs);
|
||||
let is_automatically_derived = cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
|
||||
|
||||
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);
|
||||
|
||||
if is_automatically_derived {
|
||||
check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
|
||||
check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
|
||||
} else {
|
||||
check_copy_clone(cx, item, trait_ref, ty);
|
||||
}
|
||||
@ -196,11 +231,11 @@ fn check_hash_peq<'tcx>(
|
||||
if_chain! {
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if match_def_path(cx, def_id, &paths::HASH);
|
||||
if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
|
||||
then {
|
||||
// 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 = is_automatically_derived(cx.tcx.get_attrs(impl_id));
|
||||
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
|
||||
if peq_is_automatically_derived == hash_is_automatically_derived {
|
||||
return;
|
||||
@ -247,14 +282,14 @@ fn check_ord_partial_ord<'tcx>(
|
||||
ord_is_automatically_derived: bool,
|
||||
) {
|
||||
if_chain! {
|
||||
if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD);
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
|
||||
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
|
||||
if let Some(def_id) = &trait_ref.trait_def_id();
|
||||
if *def_id == ord_trait_def_id;
|
||||
then {
|
||||
// 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 = is_automatically_derived(cx.tcx.get_attrs(impl_id));
|
||||
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
|
||||
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
|
||||
return;
|
||||
@ -314,7 +349,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
|
||||
let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| {
|
||||
impls
|
||||
.iter()
|
||||
.any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
|
||||
.any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
|
||||
});
|
||||
if !has_copy_impl {
|
||||
return;
|
||||
@ -356,10 +391,10 @@ fn check_unsafe_derive_deserialize<'tcx>(
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id();
|
||||
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
|
||||
if let ty::Adt(def, _) = ty.kind();
|
||||
if let Some(local_def_id) = def.did.as_local();
|
||||
if let Some(local_def_id) = def.did().as_local();
|
||||
let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
|
||||
if cx.tcx.inherent_impls(def.did)
|
||||
if cx.tcx.inherent_impls(def.did())
|
||||
.iter()
|
||||
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
|
||||
.any(|imp| has_unsafe(cx, imp));
|
||||
@ -418,3 +453,36 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
|
||||
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
if_chain! {
|
||||
if let ty::Adt(adt, substs) = ty.kind();
|
||||
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
|
||||
if !implements_trait(cx, ty, eq_trait_def_id, substs);
|
||||
then {
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
for variant in adt.variants() {
|
||||
for field in &variant.fields {
|
||||
let ty = field.ty(cx.tcx, substs);
|
||||
|
||||
if !implements_trait(cx, ty, eq_trait_def_id, substs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
"consider deriving `Eq` as well",
|
||||
"PartialEq, Eq".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::fn_def_id;
|
||||
use clippy_utils::{fn_def_id, get_parent_expr, path_def_id};
|
||||
|
||||
use rustc_hir::{def::Res, def_id::DefIdMap, Expr};
|
||||
use rustc_hir::{def::Res, def_id::DefIdMap, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
||||
@ -77,14 +77,22 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
for (index, conf) in self.conf_disallowed.iter().enumerate() {
|
||||
let segs: Vec<_> = conf.path().split("::").collect();
|
||||
if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &segs) {
|
||||
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
|
||||
self.disallowed.insert(id, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let def_id = match fn_def_id(cx, expr) {
|
||||
let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::Call(receiver, _) = parent.kind
|
||||
&& receiver.hir_id == expr.hir_id
|
||||
{
|
||||
None
|
||||
} else {
|
||||
path_def_id(cx, expr)
|
||||
};
|
||||
let def_id = match uncalled_path.or_else(|| fn_def_id(cx, expr)) {
|
||||
Some(def_id) => def_id,
|
||||
None => return,
|
||||
};
|
||||
|
@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
|
||||
),
|
||||
};
|
||||
let segs: Vec<_> = path.split("::").collect();
|
||||
match clippy_utils::path_to_res(cx, &segs) {
|
||||
match clippy_utils::def_path_res(cx, &segs) {
|
||||
Res::Def(_, id) => {
|
||||
self.def_ids.insert(id, reason);
|
||||
},
|
||||
|
@ -11,7 +11,7 @@ use rustc_ast::token::CommentKind;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::emitter::EmitterWriter;
|
||||
use rustc_errors::{Applicability, Handler, SuggestionStyle};
|
||||
use rustc_errors::{Applicability, Handler, MultiSpan, SuggestionStyle};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{AnonConst, Expr};
|
||||
@ -25,7 +25,7 @@ use rustc_session::parse::ParseSess;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
|
||||
use rustc_span::source_map::{BytePos, FilePathMapping, SourceMap, Span};
|
||||
use rustc_span::{sym, FileName, Pos};
|
||||
use std::io;
|
||||
use std::ops::Range;
|
||||
@ -198,7 +198,7 @@ declare_clippy_lint! {
|
||||
"presence of `fn main() {` in code examples"
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[expect(clippy::module_name_repetitions)]
|
||||
#[derive(Clone)]
|
||||
pub struct DocMarkdown {
|
||||
valid_idents: FxHashSet<String>,
|
||||
@ -240,7 +240,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
|
||||
lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
|
||||
}
|
||||
},
|
||||
hir::ItemKind::Impl(ref impl_) => {
|
||||
hir::ItemKind::Impl(impl_) => {
|
||||
self.in_trait_impl = impl_.of_trait.is_some();
|
||||
},
|
||||
hir::ItemKind::Trait(_, unsafety, ..) => {
|
||||
@ -373,7 +373,7 @@ fn lint_for_missing_headers<'tcx>(
|
||||
/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
|
||||
/// need to keep track of
|
||||
/// the spans but this function is inspired from the later.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[must_use]
|
||||
pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
|
||||
// one-line comments lose their prefix
|
||||
@ -428,7 +428,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
|
||||
/// We don't want the parser to choke on intra doc links. Since we don't
|
||||
/// actually care about rendering them, just pretend that all broken links are
|
||||
/// point to a fake address.
|
||||
#[allow(clippy::unnecessary_wraps)] // we're following a type signature
|
||||
#[expect(clippy::unnecessary_wraps)] // we're following a type signature
|
||||
fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
|
||||
Some(("fake".into(), "fake".into()))
|
||||
}
|
||||
@ -621,16 +621,26 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
|
||||
let filename = FileName::anon_source_code(&code);
|
||||
|
||||
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let emitter = EmitterWriter::new(Box::new(io::sink()), None, false, false, false, None, false);
|
||||
let fallback_bundle =
|
||||
rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
|
||||
let emitter = EmitterWriter::new(
|
||||
Box::new(io::sink()),
|
||||
None,
|
||||
None,
|
||||
fallback_bundle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
let handler = Handler::with_emitter(false, None, Box::new(emitter));
|
||||
let sess = ParseSess::with_span_handler(handler, sm);
|
||||
|
||||
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
|
||||
Ok(p) => p,
|
||||
Err(errs) => {
|
||||
for mut err in errs {
|
||||
err.cancel();
|
||||
}
|
||||
drop(errs);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@ -639,12 +649,6 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
|
||||
loop {
|
||||
match parser.parse_item(ForceCollect::No) {
|
||||
Ok(Some(item)) => match &item.kind {
|
||||
// Tests with one of these items are ignored
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
| ItemKind::ExternCrate(..)
|
||||
| ItemKind::ForeignMod(..) => return false,
|
||||
// We found a main function ...
|
||||
ItemKind::Fn(box Fn {
|
||||
sig, body: Some(block), ..
|
||||
}) if item.ident.name == sym::main => {
|
||||
@ -663,12 +667,17 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Another function was found; this case is ignored too
|
||||
ItemKind::Fn(..) => return false,
|
||||
// Tests with one of these items are ignored
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
| ItemKind::ExternCrate(..)
|
||||
| ItemKind::ForeignMod(..)
|
||||
// Another function was found; this case is ignored
|
||||
| ItemKind::Fn(..) => return false,
|
||||
_ => {},
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(mut e) => {
|
||||
Err(e) => {
|
||||
e.cancel();
|
||||
return false;
|
||||
},
|
||||
@ -730,7 +739,7 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
|
||||
/// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
|
||||
/// Plurals are also excluded (`IDs` is ok).
|
||||
fn is_camel_case(s: &str) -> bool {
|
||||
if s.starts_with(|c: char| c.is_digit(10)) {
|
||||
if s.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ declare_clippy_lint! {
|
||||
declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]);
|
||||
|
||||
impl<'tcx> DoubleComparisons {
|
||||
#[allow(clippy::similar_names)]
|
||||
#[expect(clippy::similar_names)]
|
||||
fn check_binop(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
|
||||
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
|
||||
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
|
||||
|
@ -1,11 +1,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::is_must_use_func_call;
|
||||
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
|
||||
use rustc_hir::{Expr, ExprKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -103,6 +102,75 @@ declare_clippy_lint! {
|
||||
"calls to `std::mem::forget` with a value that implements Copy"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to `std::mem::drop` with a value that does not implement `Drop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Calling `std::mem::drop` is no different than dropping such a type. A different value may
|
||||
/// have been intended.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo;
|
||||
/// let x = Foo;
|
||||
/// std::mem::drop(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub DROP_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::drop` with a value which does not implement `Drop`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Calling `std::mem::forget` is no different than dropping such a type. A different value may
|
||||
/// have been intended.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo;
|
||||
/// let x = Foo;
|
||||
/// std::mem::forget(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub FORGET_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::forget` with a value which does not implement `Drop`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not catch cases if the user binds `std::mem::drop`
|
||||
/// to a different name and calls it that way.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// drop(std::mem::ManuallyDrop::new(S));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// unsafe {
|
||||
/// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
pub UNDROPPED_MANUALLY_DROPS,
|
||||
correctness,
|
||||
"use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
|
||||
}
|
||||
|
||||
const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \
|
||||
Dropping a reference does nothing";
|
||||
const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \
|
||||
@ -111,56 +179,65 @@ const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that imp
|
||||
Dropping a copy leaves the original intact";
|
||||
const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
|
||||
Forgetting a copy leaves the original intact";
|
||||
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
|
||||
Dropping such a type only extends its contained lifetimes";
|
||||
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
|
||||
Forgetting such a type is the same as dropping it";
|
||||
|
||||
declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]);
|
||||
declare_lint_pass!(DropForgetRef => [
|
||||
DROP_REF,
|
||||
FORGET_REF,
|
||||
DROP_COPY,
|
||||
FORGET_COPY,
|
||||
DROP_NON_DROP,
|
||||
FORGET_NON_DROP,
|
||||
UNDROPPED_MANUALLY_DROPS
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(path, args) = expr.kind;
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if args.len() == 1;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
then {
|
||||
let lint;
|
||||
let msg;
|
||||
let arg = &args[0];
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
|
||||
if let ty::Ref(..) = arg_ty.kind() {
|
||||
if match_def_path(cx, def_id, &paths::DROP) {
|
||||
lint = DROP_REF;
|
||||
msg = DROP_REF_SUMMARY.to_string();
|
||||
} else if match_def_path(cx, def_id, &paths::MEM_FORGET) {
|
||||
lint = FORGET_REF;
|
||||
msg = FORGET_REF_SUMMARY.to_string();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
span_lint_and_note(cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type `{}`", arg_ty));
|
||||
} else if is_copy(cx, arg_ty) {
|
||||
if match_def_path(cx, def_id, &paths::DROP) {
|
||||
lint = DROP_COPY;
|
||||
msg = DROP_COPY_SUMMARY.to_string();
|
||||
} else if match_def_path(cx, def_id, &paths::MEM_FORGET) {
|
||||
lint = FORGET_COPY;
|
||||
msg = FORGET_COPY_SUMMARY.to_string();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
span_lint_and_note(cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type {}", arg_ty));
|
||||
if let ExprKind::Call(path, [arg]) = expr.kind
|
||||
&& let ExprKind::Path(ref qpath) = path.kind
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
|
||||
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
|
||||
{
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
let (lint, msg) = match fn_name {
|
||||
sym::mem_drop if arg_ty.is_ref() => (DROP_REF, DROP_REF_SUMMARY),
|
||||
sym::mem_forget if arg_ty.is_ref() => (FORGET_REF, FORGET_REF_SUMMARY),
|
||||
sym::mem_drop if is_copy(cx, arg_ty) => (DROP_COPY, DROP_COPY_SUMMARY),
|
||||
sym::mem_forget if is_copy(cx, arg_ty) => (FORGET_COPY, FORGET_COPY_SUMMARY),
|
||||
sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNDROPPED_MANUALLY_DROPS,
|
||||
expr.span,
|
||||
"the inner value of this ManuallyDrop will not be dropped",
|
||||
None,
|
||||
"to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sym::mem_drop
|
||||
if !(arg_ty.needs_drop(cx.tcx, cx.param_env)
|
||||
|| is_must_use_func_call(cx, arg)
|
||||
|| is_must_use_ty(cx, arg_ty)) =>
|
||||
{
|
||||
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY)
|
||||
},
|
||||
sym::mem_forget if !arg_ty.needs_drop(cx.tcx, cx.param_env) => {
|
||||
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type `{}`", arg_ty),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
clippy_lints/src/duplicate_mod.rs
Normal file
102
clippy_lints/src/duplicate_mod.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{FileName, Span};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for files that are included as modules multiple times.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Loading a file as a module more than once causes it to be compiled
|
||||
/// multiple times, taking longer and putting duplicate content into the
|
||||
/// module tree.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// // lib.rs
|
||||
/// mod a;
|
||||
/// mod b;
|
||||
/// ```
|
||||
/// ```rust,ignore
|
||||
/// // a.rs
|
||||
/// #[path = "./b.rs"]
|
||||
/// mod b;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // lib.rs
|
||||
/// mod a;
|
||||
/// mod b;
|
||||
/// ```
|
||||
/// ```rust,ignore
|
||||
/// // a.rs
|
||||
/// use crate::b;
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub DUPLICATE_MOD,
|
||||
suspicious,
|
||||
"file loaded as module multiple times"
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, Ord, PartialEq, Eq)]
|
||||
struct Modules {
|
||||
local_path: PathBuf,
|
||||
spans: Vec<Span>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DuplicateMod {
|
||||
/// map from the canonicalized path to `Modules`, `BTreeMap` to make the
|
||||
/// order deterministic for tests
|
||||
modules: BTreeMap<PathBuf, Modules>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
|
||||
|
||||
impl EarlyLintPass for DuplicateMod {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, mod_spans)) = &item.kind
|
||||
&& let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
|
||||
&& let Some(local_path) = real.into_local_path()
|
||||
&& let Ok(absolute_path) = local_path.canonicalize()
|
||||
{
|
||||
let modules = self.modules.entry(absolute_path).or_insert(Modules {
|
||||
local_path,
|
||||
spans: Vec::new(),
|
||||
});
|
||||
modules.spans.push(item.span_with_attributes());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
|
||||
for Modules { local_path, spans } in self.modules.values() {
|
||||
if spans.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut multi_span = MultiSpan::from_spans(spans.clone());
|
||||
let (&first, duplicates) = spans.split_first().unwrap();
|
||||
|
||||
multi_span.push_span_label(first, "first loaded here");
|
||||
for &duplicate in duplicates {
|
||||
multi_span.push_span_label(duplicate, "loaded again here");
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DUPLICATE_MOD,
|
||||
multi_span,
|
||||
&format!("file is loaded as a module multiple times: `{}`", local_path.display()),
|
||||
None,
|
||||
"replace all but one `mod` item with `use` items",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::match_type;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Spanned;
|
||||
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::paths;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -46,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
|
||||
if let ExprKind::MethodCall(method_path, args, _) = left.kind;
|
||||
if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION);
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::Duration);
|
||||
if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
|
||||
then {
|
||||
let suggested_fn = match (method_path.ident.as_str(), divisor) {
|
||||
|
65
clippy_lints/src/empty_drop.rs
Normal file
65
clippy_lints/src/empty_drop.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use clippy_utils::{diagnostics::span_lint_and_sugg, peel_blocks};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for empty `Drop` implementations.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Empty `Drop` implementations have no effect when dropping an instance of the type. They are
|
||||
/// most likely useless. However, an empty `Drop` implementation prevents a type from being
|
||||
/// destructured, which might be the intention behind adding the implementation as a marker.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
///
|
||||
/// impl Drop for S {
|
||||
/// fn drop(&mut self) {}
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub EMPTY_DROP,
|
||||
restriction,
|
||||
"empty `Drop` implementations"
|
||||
}
|
||||
declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
|
||||
|
||||
impl LateLintPass<'_> for EmptyDrop {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if_chain! {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
items: [child],
|
||||
..
|
||||
}) = item.kind;
|
||||
if trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait();
|
||||
if let impl_item_hir = child.id.hir_id();
|
||||
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
|
||||
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
|
||||
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
|
||||
let func_expr = peel_blocks(func_expr);
|
||||
if let ExprKind::Block(block, _) = func_expr.kind;
|
||||
if block.stmts.is_empty() && block.expr.is_none();
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EMPTY_DROP,
|
||||
item.span,
|
||||
"empty drop implementation",
|
||||
"try removing this impl",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for EmptyEnum {
|
||||
if let ItemKind::Enum(..) = item.kind {
|
||||
let ty = cx.tcx.type_of(item.def_id);
|
||||
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
|
||||
if adt.variants.is_empty() {
|
||||
if adt.variants().is_empty() {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
EMPTY_ENUM,
|
||||
|
99
clippy_lints/src/empty_structs_with_brackets.rs
Normal file
99
clippy_lints/src/empty_structs_with_brackets.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use clippy_utils::{diagnostics::span_lint_and_then, source::snippet_opt};
|
||||
use rustc_ast::ast::{Item, ItemKind, VariantData};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Finds structs without fields (a so-called "empty struct") that are declared with brackets.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Empty brackets after a struct declaration can be omitted.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Cookie {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct Cookie;
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub EMPTY_STRUCTS_WITH_BRACKETS,
|
||||
restriction,
|
||||
"finds struct declarations with empty brackets"
|
||||
}
|
||||
declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
|
||||
|
||||
impl EarlyLintPass for EmptyStructsWithBrackets {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
let span_after_ident = item.span.with_lo(item.ident.span.hi());
|
||||
|
||||
if let ItemKind::Struct(var_data, _) = &item.kind
|
||||
&& has_brackets(var_data)
|
||||
&& has_no_fields(cx, var_data, span_after_ident) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EMPTY_STRUCTS_WITH_BRACKETS,
|
||||
span_after_ident,
|
||||
"found empty brackets on struct declaration",
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span_after_ident,
|
||||
"remove the brackets",
|
||||
";".to_string(),
|
||||
Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
|
||||
if !var_data.fields().is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ use rustc_errors::Applicability;
|
||||
use rustc_hir::{
|
||||
hir_id::HirIdSet,
|
||||
intravisit::{walk_expr, Visitor},
|
||||
Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp,
|
||||
Block, Expr, ExprKind, Guard, HirId, Let, Pat, Stmt, StmtKind, UnOp,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@ -63,7 +63,7 @@ declare_clippy_lint! {
|
||||
declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let (cond_expr, then_expr, else_expr) = match higher::If::hir(expr) {
|
||||
Some(higher::If { cond, then, r#else }) => (cond, then, r#else),
|
||||
@ -319,7 +319,7 @@ struct Insertion<'tcx> {
|
||||
/// `or_insert_with`.
|
||||
/// * Determine if there's any sub-expression that can't be placed in a closure.
|
||||
/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
struct InsertSearcher<'cx, 'tcx> {
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
/// The map expression used in the contains call.
|
||||
@ -478,7 +478,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
|
||||
let mut is_map_used = self.is_map_used;
|
||||
for arm in arms {
|
||||
self.visit_pat(arm.pat);
|
||||
if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
|
||||
if let Some(Guard::If(guard) | Guard::IfLet(&Let { init: guard, .. })) = arm.guard {
|
||||
self.visit_non_tail_expr(guard);
|
||||
}
|
||||
is_map_used |= self.visit_cond_arm(arm.body);
|
||||
|
@ -8,7 +8,6 @@ use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::util::IntTypeExt;
|
||||
use rustc_middle::ty::{self, IntTy, UintTy};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -37,7 +36,7 @@ declare_clippy_lint! {
|
||||
declare_lint_pass!(UnportableVariant => [ENUM_CLIKE_UNPORTABLE_VARIANT]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)]
|
||||
#[expect(clippy::cast_possible_wrap)]
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if cx.tcx.data_layout.pointer_size.bits() != 64 {
|
||||
return;
|
||||
@ -55,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
|
||||
if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
|
||||
if let ty::Adt(adt, _) = ty.kind() {
|
||||
if adt.is_enum() {
|
||||
ty = adt.repr.discr_type().to_ty(cx.tcx);
|
||||
ty = adt.repr().discr_type().to_ty(cx.tcx);
|
||||
}
|
||||
}
|
||||
match ty.kind() {
|
||||
|
@ -240,7 +240,7 @@ impl LateLintPass<'_> for EnumVariantNames {
|
||||
assert!(last.is_some());
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
#[expect(clippy::similar_names)]
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let item_name = item.ident.name.as_str();
|
||||
let item_camel = to_camel_case(item_name);
|
||||
@ -260,7 +260,7 @@ impl LateLintPass<'_> for EnumVariantNames {
|
||||
}
|
||||
// The `module_name_repetitions` lint should only trigger if the item has the module in its
|
||||
// name. Having the same name is accepted.
|
||||
if item.vis.node.is_pub() && item_camel.len() > mod_camel.len() {
|
||||
if cx.tcx.visibility(item.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();
|
||||
|
@ -6,11 +6,9 @@ use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{
|
||||
def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, Ty, TyKind,
|
||||
};
|
||||
use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, TyS};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -74,7 +72,7 @@ declare_clippy_lint! {
|
||||
declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
#[allow(clippy::similar_names, clippy::too_many_lines)]
|
||||
#[expect(clippy::similar_names, clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
|
||||
@ -140,7 +138,6 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
},
|
||||
};
|
||||
if let Some(trait_id) = trait_id {
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match (&left.kind, &right.kind) {
|
||||
// do not suggest to dereference literals
|
||||
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
|
||||
@ -279,7 +276,11 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
}
|
||||
}
|
||||
|
||||
fn in_impl<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, bin_op: DefId) -> Option<(&'tcx Ty<'tcx>, &'tcx Ty<'tcx>)> {
|
||||
fn in_impl<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
bin_op: DefId,
|
||||
) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
|
||||
if_chain! {
|
||||
if let Some(block) = get_enclosing_block(cx, e.hir_id);
|
||||
if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id());
|
||||
@ -301,10 +302,10 @@ fn in_impl<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, bin_op: DefId) -> Op
|
||||
}
|
||||
}
|
||||
|
||||
fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: &TyS<'_>, hir_ty: &Ty<'_>) -> bool {
|
||||
fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ty::Adt(adt_def, _) = middle_ty.kind();
|
||||
if let Some(local_did) = adt_def.did.as_local();
|
||||
if let Some(local_did) = adt_def.did().as_local();
|
||||
let item = cx.tcx.hir().expect_item(local_did);
|
||||
let middle_ty_id = item.def_id.to_def_id();
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
|
||||
|
@ -3,13 +3,14 @@ use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::usage::local_used_after_expr;
|
||||
use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id};
|
||||
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Expr, ExprKind, Param, PatKind, Unsafety};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
|
||||
use rustc_middle::ty::binding::BindingMode;
|
||||
use rustc_middle::ty::subst::Subst;
|
||||
use rustc_middle::ty::{self, ClosureKind, Ty, TypeFoldable};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@ -102,6 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
||||
let closure_ty = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
if_chain!(
|
||||
if !is_adjusted(cx, &body.value);
|
||||
if let ExprKind::Call(callee, args) = body.value.kind;
|
||||
if let ExprKind::Path(_) = callee.kind;
|
||||
if check_inputs(cx, body.params, args);
|
||||
@ -124,8 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
||||
if_chain! {
|
||||
if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
|
||||
if substs.as_closure().kind() == ClosureKind::FnMut;
|
||||
if get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
|
||||
|| path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee));
|
||||
if path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr));
|
||||
|
||||
then {
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
@ -144,11 +145,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
||||
);
|
||||
|
||||
if_chain!(
|
||||
if !is_adjusted(cx, &body.value);
|
||||
if let ExprKind::MethodCall(path, args, _) = body.value.kind;
|
||||
if check_inputs(cx, body.params, args);
|
||||
let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();
|
||||
let substs = cx.typeck_results().node_substs(body.value.hir_id);
|
||||
let call_ty = cx.tcx.type_of(method_def_id).subst(cx.tcx, substs);
|
||||
let call_ty = cx.tcx.bound_type_of(method_def_id).subst(cx.tcx, substs);
|
||||
if check_sig(cx, closure_ty, call_ty);
|
||||
then {
|
||||
span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
|
||||
@ -169,11 +171,17 @@ fn check_inputs(cx: &LateContext<'_>, params: &[Param<'_>], call_args: &[Expr<'_
|
||||
if params.len() != call_args.len() {
|
||||
return false;
|
||||
}
|
||||
let binding_modes = cx.typeck_results().pat_binding_modes();
|
||||
std::iter::zip(params, call_args).all(|(param, arg)| {
|
||||
match param.pat.kind {
|
||||
PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {},
|
||||
_ => return false,
|
||||
}
|
||||
// checks that parameters are not bound as `ref` or `ref mut`
|
||||
if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match *cx.typeck_results().expr_adjustments(arg) {
|
||||
[] => true,
|
||||
[
|
||||
@ -217,7 +225,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
|
||||
ty::ImplContainer(def_id) => {
|
||||
let ty = cx.tcx.type_of(def_id);
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did),
|
||||
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
},
|
||||
|
@ -4,8 +4,6 @@ use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for excessive
|
||||
|
@ -78,7 +78,10 @@ impl LateLintPass<'_> for ExhaustiveItems {
|
||||
if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
|
||||
then {
|
||||
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
|
||||
if v.fields().iter().any(|f| !f.vis.node.is_pub()) {
|
||||
if v.fields().iter().any(|f| {
|
||||
let def_id = cx.tcx.hir().local_def_id(f.hir_id);
|
||||
!cx.tcx.visibility(def_id).is_public()
|
||||
}) {
|
||||
// skip structs with private fields
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::FormatArgsExpn;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, match_function_call, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
@ -79,28 +80,22 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
||||
"print".into(),
|
||||
)
|
||||
};
|
||||
let msg = format!("use of `{}.unwrap()`", used);
|
||||
if let [write_output] = *format_args.format_string_parts {
|
||||
let mut write_output = write_output.to_string();
|
||||
if write_output.ends_with('\n') {
|
||||
write_output.pop();
|
||||
}
|
||||
|
||||
let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default());
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&msg,
|
||||
"try this",
|
||||
sugg,
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
} else {
|
||||
// We don't have a proper suggestion
|
||||
let help = format!("consider using `{}{}!` instead", prefix, sugg_mac);
|
||||
span_lint_and_help(cx, EXPLICIT_WRITE, expr.span, &msg, None, &help);
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args.inputs_span(),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&format!("use of `{}.unwrap()`", used),
|
||||
"try this",
|
||||
format!("{}{}!({})", prefix, sugg_mac, inputs_snippet),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ declare_clippy_lint! {
|
||||
/// // Good
|
||||
/// struct Foo(i32);
|
||||
///
|
||||
/// use std::convert::TryFrom;
|
||||
/// impl TryFrom<String> for Foo {
|
||||
/// type Error = ();
|
||||
/// fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
@ -86,9 +85,9 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h
|
||||
|
||||
// check for `unwrap`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
|
||||
let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
|
||||
if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
|
||||
|| is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
|
||||
let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
|
||||
if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
|
||||
|| is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
|
||||
{
|
||||
self.result.push(expr.span);
|
||||
}
|
||||
|
@ -1,166 +0,0 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
|
||||
use rustc_hir::CRATE_HIR_ID;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::DUMMY_SP;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// These prefixes and suffixes have no significant meaning.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # The `Cargo.toml` with feature name redundancy
|
||||
/// [features]
|
||||
/// default = ["use-abc", "with-def", "ghi-support"]
|
||||
/// use-abc = [] // redundant
|
||||
/// with-def = [] // redundant
|
||||
/// ghi-support = [] // redundant
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// [features]
|
||||
/// default = ["abc", "def", "ghi"]
|
||||
/// abc = []
|
||||
/// def = []
|
||||
/// ghi = []
|
||||
/// ```
|
||||
///
|
||||
#[clippy::version = "1.57.0"]
|
||||
pub REDUNDANT_FEATURE_NAMES,
|
||||
cargo,
|
||||
"usage of a redundant feature name"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for negative feature names with prefix `no-` or `not-`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Features are supposed to be additive, and negatively-named features violate it.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # The `Cargo.toml` with negative feature names
|
||||
/// [features]
|
||||
/// default = []
|
||||
/// no-abc = []
|
||||
/// not-def = []
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// [features]
|
||||
/// default = ["abc", "def"]
|
||||
/// abc = []
|
||||
/// def = []
|
||||
///
|
||||
/// ```
|
||||
#[clippy::version = "1.57.0"]
|
||||
pub NEGATIVE_FEATURE_NAMES,
|
||||
cargo,
|
||||
"usage of a negative feature name"
|
||||
}
|
||||
|
||||
declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]);
|
||||
|
||||
static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
|
||||
static SUFFIXES: [&str; 2] = ["-support", "_support"];
|
||||
|
||||
fn is_negative_prefix(s: &str) -> bool {
|
||||
s.starts_with("no")
|
||||
}
|
||||
|
||||
fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
|
||||
let is_negative = is_prefix && is_negative_prefix(substring);
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
if is_negative {
|
||||
NEGATIVE_FEATURE_NAMES
|
||||
} else {
|
||||
REDUNDANT_FEATURE_NAMES
|
||||
},
|
||||
DUMMY_SP,
|
||||
&format!(
|
||||
"the \"{}\" {} in the feature name \"{}\" is {}",
|
||||
substring,
|
||||
if is_prefix { "prefix" } else { "suffix" },
|
||||
feature,
|
||||
if is_negative { "negative" } else { "redundant" }
|
||||
),
|
||||
None,
|
||||
&format!(
|
||||
"consider renaming the feature to \"{}\"{}",
|
||||
if is_prefix {
|
||||
feature.strip_prefix(substring)
|
||||
} else {
|
||||
feature.strip_suffix(substring)
|
||||
}
|
||||
.unwrap(),
|
||||
if is_negative {
|
||||
", but make sure the feature adds functionality"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for FeatureName {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID)
|
||||
&& is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false);
|
||||
|
||||
for package in metadata.packages {
|
||||
let mut features: Vec<&String> = package.features.keys().collect();
|
||||
features.sort();
|
||||
for feature in features {
|
||||
let prefix_opt = {
|
||||
let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
|
||||
if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
|
||||
Some(PREFIXES[i - 1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(prefix) = prefix_opt {
|
||||
lint(cx, feature, prefix, true);
|
||||
}
|
||||
|
||||
let suffix_opt: Option<&str> = {
|
||||
let i = SUFFIXES.partition_point(|suffix| {
|
||||
suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
|
||||
});
|
||||
if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
|
||||
Some(SUFFIXES[i - 1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(suffix) = suffix_opt {
|
||||
lint(cx, feature, suffix, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefixes_sorted() {
|
||||
let mut sorted_prefixes = PREFIXES;
|
||||
sorted_prefixes.sort_unstable();
|
||||
assert_eq!(PREFIXES, sorted_prefixes);
|
||||
let mut sorted_suffixes = SUFFIXES;
|
||||
sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
|
||||
assert_eq!(SUFFIXES, sorted_suffixes);
|
||||
}
|
@ -20,7 +20,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// ### Known problems
|
||||
/// If the user can ensure that b is larger than a, the `.abs()` is
|
||||
/// technically unneccessary. However, it will make the code more robust and doesn't have any
|
||||
/// technically unnecessary. However, it will make the code more robust and doesn't have any
|
||||
/// large performance implications. If the abs call was deliberately left out for performance
|
||||
/// reasons, it is probably better to state this explicitly in the code, which then can be done
|
||||
/// with an allow.
|
||||
@ -69,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
|
||||
|
||||
if_chain! {
|
||||
|
||||
// left hand side is a substraction
|
||||
// left hand side is a subtraction
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub,
|
||||
@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
|
||||
if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
|
||||
if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
|
||||
|
||||
// values of the substractions on the left hand side are of the type float
|
||||
// values of the subtractions on the left hand side are of the type float
|
||||
let t_val_l = cx.typeck_results().expr_ty(val_l);
|
||||
let t_val_r = cx.typeck_results().expr_ty(val_r);
|
||||
if let ty::Float(_) = t_val_l.kind();
|
||||
|
@ -215,7 +215,7 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
// converted to an integer without loss of precision. For now we only check
|
||||
// ranges [-16777215, 16777216) for type f32 as whole number floats outside
|
||||
// this range are lossy and ambiguous.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
|
||||
match value {
|
||||
F32(num) if num.fract() == 0.0 => {
|
||||
|
@ -72,7 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
||||
if_chain! {
|
||||
if format_args.format_string_parts == [kw::Empty];
|
||||
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did),
|
||||
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::macros::{FormatArgsArg, FormatArgsExpn};
|
||||
use clippy_utils::is_diag_trait_item;
|
||||
use clippy_utils::macros::{is_format_macro, FormatArgsArg, FormatArgsExpn};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_diag_trait_item, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -65,21 +65,6 @@ declare_clippy_lint! {
|
||||
|
||||
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
|
||||
|
||||
const FORMAT_MACRO_PATHS: &[&[&str]] = &[
|
||||
&paths::FORMAT_ARGS_MACRO,
|
||||
&paths::ASSERT_EQ_MACRO,
|
||||
&paths::ASSERT_MACRO,
|
||||
&paths::ASSERT_NE_MACRO,
|
||||
&paths::EPRINT_MACRO,
|
||||
&paths::EPRINTLN_MACRO,
|
||||
&paths::PRINT_MACRO,
|
||||
&paths::PRINTLN_MACRO,
|
||||
&paths::WRITE_MACRO,
|
||||
&paths::WRITELN_MACRO,
|
||||
];
|
||||
|
||||
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro];
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if_chain! {
|
||||
@ -87,12 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
let expr_expn_data = expr.span.ctxt().outer_expn_data();
|
||||
let outermost_expn_data = outermost_expn_data(expr_expn_data);
|
||||
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
|
||||
if FORMAT_MACRO_PATHS
|
||||
.iter()
|
||||
.any(|path| match_def_path(cx, macro_def_id, path))
|
||||
|| FORMAT_MACRO_DIAG_ITEMS
|
||||
.iter()
|
||||
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id));
|
||||
if is_format_macro(cx, macro_def_id);
|
||||
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
|
||||
if let Some(args) = format_args.args();
|
||||
then {
|
||||
@ -211,7 +191,7 @@ where
|
||||
if overloaded_deref.is_some() {
|
||||
n_needed = n_total;
|
||||
}
|
||||
ty = target;
|
||||
ty = *target;
|
||||
} else {
|
||||
return (n_needed, ty);
|
||||
}
|
||||
|
253
clippy_lints/src/format_impl.rs
Normal file
253
clippy_lints/src/format_impl.rs
Normal file
@ -0,0 +1,253 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, symbol::kw, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
|
||||
/// which uses `self` as a parameter.
|
||||
/// This is typically done indirectly with the `write!` macro or with `to_string()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This will lead to infinite recursion and a stack overflow.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// struct Structure(i32);
|
||||
/// impl fmt::Display for Structure {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
/// write!(f, "{}", self.to_string())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// struct Structure(i32);
|
||||
/// impl fmt::Display for Structure {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
/// write!(f, "{}", self.0)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.48.0"]
|
||||
pub RECURSIVE_FORMAT_IMPL,
|
||||
correctness,
|
||||
"Format trait method called while implementing the same Format trait"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
|
||||
/// implementation of a formatting trait.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using a print macro is likely unintentional since formatting traits
|
||||
/// should write to the `Formatter`, not stdout/stderr.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use std::fmt::{Display, Error, Formatter};
|
||||
///
|
||||
/// struct S;
|
||||
/// impl Display for S {
|
||||
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
/// println!("S");
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::fmt::{Display, Error, Formatter};
|
||||
///
|
||||
/// struct S;
|
||||
/// impl Display for S {
|
||||
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
/// writeln!(f, "S");
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub PRINT_IN_FORMAT_IMPL,
|
||||
suspicious,
|
||||
"use of a print macro in a formatting trait impl"
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FormatTrait {
|
||||
/// e.g. `sym::Display`
|
||||
name: Symbol,
|
||||
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
|
||||
formatter_name: Option<Symbol>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatImpl {
|
||||
// Whether we are inside Display or Debug trait impl - None for neither
|
||||
format_trait_impl: Option<FormatTrait>,
|
||||
}
|
||||
|
||||
impl FormatImpl {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
format_trait_impl: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatImpl {
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
self.format_trait_impl = is_format_trait_impl(cx, impl_item);
|
||||
}
|
||||
|
||||
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
// Assume no nested Impl of Debug and Display within eachother
|
||||
if is_format_trait_impl(cx, impl_item).is_some() {
|
||||
self.format_trait_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(format_trait_impl) = self.format_trait_impl else { return };
|
||||
|
||||
if format_trait_impl.name == sym::Display {
|
||||
check_to_string_in_display(cx, expr);
|
||||
}
|
||||
|
||||
check_self_in_format_args(cx, expr, format_trait_impl);
|
||||
check_print_in_format_impl(cx, expr, format_trait_impl);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
// Get the hir_id of the object we are calling the method on
|
||||
if let ExprKind::MethodCall(path, [ref self_arg, ..], _) = expr.kind;
|
||||
// Is the method to_string() ?
|
||||
if path.ident.name == sym!(to_string);
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
|
||||
// Is the method is called on self
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
|
||||
if let [segment] = path.segments;
|
||||
if segment.ident.name == kw::SelfLower;
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
|
||||
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||||
if_chain! {
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
|
||||
if let macro_def_id = outer_macro.def_id;
|
||||
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
|
||||
if is_format_macro(cx, macro_def_id);
|
||||
if let Some(args) = format_args.args();
|
||||
then {
|
||||
for arg in args {
|
||||
if arg.format_trait != impl_trait.name {
|
||||
continue;
|
||||
}
|
||||
check_format_arg_self(cx, expr, &arg, impl_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||||
// Since the argument to fmt is itself a reference: &self
|
||||
let reference = peel_ref_operators(cx, arg.value);
|
||||
let map = cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTrait { name, .. } = impl_trait;
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
|
||||
if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
|
||||
then {
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{}!` in `{}` impl", name, impl_trait.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = impl_trait.formatter_name {
|
||||
format!("{}!({}, ..)", replacement, formatter_name)
|
||||
} else {
|
||||
format!("{}!(..)", replacement)
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
|
||||
if_chain! {
|
||||
if impl_item.ident.name == sym::fmt;
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
|
||||
if let Some(did) = trait_ref.trait_def_id();
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(did);
|
||||
if matches!(name, sym::Debug | sym::Display);
|
||||
then {
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let formatter_name = body.params.get(1)
|
||||
.and_then(|param| param.pat.simple_ident())
|
||||
.map(|ident| ident.name);
|
||||
|
||||
Some(FormatTrait {
|
||||
name,
|
||||
formatter_name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
77
clippy_lints/src/format_push_string.rs
Normal file
77
clippy_lints/src/format_push_string.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{match_def_path, paths, peel_hir_expr_refs};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects cases where the result of a `format!` call is
|
||||
/// appended to an existing `String`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Introduces an extra, avoidable heap allocation.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let mut s = String::new();
|
||||
/// s += &format!("0x{:X}", 1024);
|
||||
/// s.push_str(&format!("0x{:X}", 1024));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::fmt::Write as _; // import without risk of name clashing
|
||||
///
|
||||
/// let mut s = String::new();
|
||||
/// let _ = write!(s, "0x{:X}", 1024);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub FORMAT_PUSH_STRING,
|
||||
perf,
|
||||
"`format!(..)` appended to existing `String`"
|
||||
}
|
||||
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
|
||||
|
||||
fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
|
||||
}
|
||||
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
if let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id {
|
||||
cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatPushString {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let arg = match expr.kind {
|
||||
ExprKind::MethodCall(_, [_, arg], _) => {
|
||||
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) &&
|
||||
match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
|
||||
arg
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
ExprKind::AssignOp(op, left, arg)
|
||||
if op.node == BinOpKind::Add && is_string(cx, left) => {
|
||||
arg
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let (arg, _) = peel_hir_expr_refs(arg);
|
||||
if is_format(cx, arg) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
FORMAT_PUSH_STRING,
|
||||
expr.span,
|
||||
"`format!(..)` appended to existing `String`",
|
||||
None,
|
||||
"consider using `write!` to avoid the extra allocation",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::differing_macro_contexts;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
|
||||
@ -135,7 +134,7 @@ 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 !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() {
|
||||
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) {
|
||||
@ -165,7 +164,7 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
|
||||
if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion();
|
||||
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
|
||||
// span between BinOp LHS and RHS
|
||||
let binop_span = lhs.span.between(rhs.span);
|
||||
// if RHS is an UnOp
|
||||
@ -206,8 +205,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if_chain! {
|
||||
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
|
||||
if is_block(else_) || is_if(else_);
|
||||
if !differing_macro_contexts(then.span, else_.span);
|
||||
if !then.span.from_expansion() && !in_external_macro(cx.sess(), expr.span);
|
||||
if !then.span.from_expansion() && !else_.span.from_expansion();
|
||||
if !in_external_macro(cx.sess(), expr.span);
|
||||
|
||||
// workaround for rust-lang/rust#43081
|
||||
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
|
||||
@ -268,7 +267,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
for element in array {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
|
||||
if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span);
|
||||
if has_unary_equivalent(op.node) && lhs.span.ctxt() == op.span.ctxt();
|
||||
let space_span = lhs.span.between(op.span);
|
||||
if let Some(space_snippet) = snippet_opt(cx, space_span);
|
||||
let lint_span = lhs.span.with_lo(lhs.span.hi());
|
||||
@ -291,8 +290,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
|
||||
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
|
||||
if_chain! {
|
||||
if !differing_macro_contexts(first.span, second.span);
|
||||
if !first.span.from_expansion();
|
||||
if !first.span.from_expansion() && !second.span.from_expansion();
|
||||
if let ExprKind::If(cond_expr, ..) = &first.kind;
|
||||
if is_block(second) || is_if(second);
|
||||
|
||||
|
@ -55,7 +55,7 @@ impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FromOverInto {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
if !meets_msrv(self.msrv.as_ref(), &msrvs::RE_REBALANCING_COHERENCE) {
|
||||
if !meets_msrv(self.msrv, msrvs::RE_REBALANCING_COHERENCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ use clippy_utils::attrs::is_proc_macro;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::is_must_use_ty;
|
||||
use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
|
||||
use clippy_utils::{match_def_path, return_ty, trait_ref_of_method};
|
||||
|
||||
use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
|
||||
|
||||
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
|
||||
let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
|
||||
if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if let Some(attr) = attr {
|
||||
@ -44,7 +44,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
|
||||
let is_public = cx.access_levels.is_exported(item.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.def_id).is_none() {
|
||||
@ -67,7 +67,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
} else if let hir::TraitFn::Provided(eid) = *eid {
|
||||
@ -105,12 +105,7 @@ fn check_needless_must_use(
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
attr.span,
|
||||
"remove the attribute",
|
||||
"".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
||||
@ -189,11 +184,11 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m
|
||||
// primitive types are never mutable
|
||||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
|
||||
ty::Adt(adt, substs) => {
|
||||
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
||||
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
|
||||
tys.insert(adt.did()) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
||||
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did(), path))
|
||||
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
|
||||
},
|
||||
ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
||||
ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
||||
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
|
||||
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
|
||||
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
|
||||
|
@ -17,8 +17,8 @@ pub(super) fn check_fn<'tcx>(
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
let unsafety = match kind {
|
||||
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
|
||||
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
|
||||
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }) => unsafety,
|
||||
intravisit::FnKind::Method(_, sig) => sig.header.unsafety,
|
||||
intravisit::FnKind::Closure => return,
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@ use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use super::RESULT_UNIT_ERR;
|
||||
|
||||
pub(super) fn check_item(cx: &LateContext<'_>, item: &hir::Item<'_>) {
|
||||
if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind {
|
||||
if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public {
|
||||
|
@ -26,9 +26,8 @@ pub(super) fn check_fn(
|
||||
header: hir::FnHeader { abi: Abi::Rust, .. },
|
||||
..
|
||||
},
|
||||
_,
|
||||
)
|
||||
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => check_arg_number(
|
||||
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }) => check_arg_number(
|
||||
cx,
|
||||
decl,
|
||||
span.with_hi(decl.output.span().hi()),
|
||||
|
@ -5,7 +5,7 @@ use rustc_hir::{Body, FnDecl, HirId};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::subst::Subst;
|
||||
use rustc_middle::ty::{Opaque, PredicateKind::Trait};
|
||||
use rustc_middle::ty::{EarlyBinder, Opaque, PredicateKind::Trait};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt;
|
||||
@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
|
||||
let preds = cx.tcx.explicit_item_bounds(id);
|
||||
let mut is_future = false;
|
||||
for &(p, _span) in preds {
|
||||
let p = p.subst(cx.tcx, subst);
|
||||
let p = EarlyBinder(p).subst(cx.tcx, subst);
|
||||
if let Some(trait_pred) = p.to_opt_poly_trait_pred() {
|
||||
if Some(trait_pred.skip_binder().trait_ref.def_id) == cx.tcx.lang_items().future_trait() {
|
||||
is_future = true;
|
||||
|
69
clippy_lints/src/get_first.rs
Normal file
69
clippy_lints/src/get_first.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_slice_of_primitives, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Spanned;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for using `x.get(0)` instead of
|
||||
/// `x.first()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `x.first()` is easier to read and has the same
|
||||
/// result.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// // Bad
|
||||
/// let x = vec![2, 3, 5];
|
||||
/// let first_element = x.get(0);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// // Good
|
||||
/// let x = vec![2, 3, 5];
|
||||
/// let first_element = x.first();
|
||||
/// ```
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub GET_FIRST,
|
||||
style,
|
||||
"Using `x.get(0)` when `x.first()` is simpler"
|
||||
}
|
||||
declare_lint_pass!(GetFirst => [GET_FIRST]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for GetFirst {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::MethodCall(_, [struct_calling_on, method_arg], _) = &expr.kind;
|
||||
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if match_def_path(cx, expr_def_id, &paths::SLICE_GET);
|
||||
|
||||
if let Some(_) = is_slice_of_primitives(cx, struct_calling_on);
|
||||
if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = method_arg.kind;
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let slice_name = snippet_with_applicability(
|
||||
cx,
|
||||
struct_calling_on.span, "..",
|
||||
&mut applicability,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
GET_FIRST,
|
||||
expr.span,
|
||||
&format!("accessing first element with `{0}.get(0)`", slice_name),
|
||||
"try",
|
||||
format!("{}.first()", slice_name),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
//! lint on using `x.get(x.len() - 1)` instead of `x.last()`
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::SpanlessEq;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for using `x.get(x.len() - 1)` instead of
|
||||
/// `x.last()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `x.last()` is easier to read and has the same
|
||||
/// result.
|
||||
///
|
||||
/// Note that using `x[x.len() - 1]` is semantically different from
|
||||
/// `x.last()`. Indexing into the array will panic on out-of-bounds
|
||||
/// accesses, while `x.get()` and `x.last()` will return `None`.
|
||||
///
|
||||
/// There is another lint (get_unwrap) that covers the case of using
|
||||
/// `x.get(index).unwrap()` instead of `x[index]`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// // Bad
|
||||
/// let x = vec![2, 3, 5];
|
||||
/// let last_element = x.get(x.len() - 1);
|
||||
///
|
||||
/// // Good
|
||||
/// let x = vec![2, 3, 5];
|
||||
/// let last_element = x.last();
|
||||
/// ```
|
||||
#[clippy::version = "1.37.0"]
|
||||
pub GET_LAST_WITH_LEN,
|
||||
complexity,
|
||||
"Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
|
||||
}
|
||||
|
||||
declare_lint_pass!(GetLastWithLen => [GET_LAST_WITH_LEN]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
// Is a method call
|
||||
if let ExprKind::MethodCall(path, args, _) = expr.kind;
|
||||
|
||||
// Method name is "get"
|
||||
if path.ident.name == sym!(get);
|
||||
|
||||
// Argument 0 (the struct we're calling the method on) is a vector
|
||||
if let Some(struct_calling_on) = args.get(0);
|
||||
let struct_ty = cx.typeck_results().expr_ty(struct_calling_on);
|
||||
if is_type_diagnostic_item(cx, struct_ty, sym::Vec);
|
||||
|
||||
// Argument to "get" is a subtraction
|
||||
if let Some(get_index_arg) = args.get(1);
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub,
|
||||
..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = &get_index_arg.kind;
|
||||
|
||||
// LHS of subtraction is "x.len()"
|
||||
if let ExprKind::MethodCall(arg_lhs_path, lhs_args, _) = &lhs.kind;
|
||||
if arg_lhs_path.ident.name == sym::len;
|
||||
if let Some(arg_lhs_struct) = lhs_args.get(0);
|
||||
|
||||
// The two vectors referenced (x in x.get(...) and in x.len())
|
||||
if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct);
|
||||
|
||||
// RHS of subtraction is 1
|
||||
if let ExprKind::Lit(rhs_lit) = &rhs.kind;
|
||||
if let LitKind::Int(1, ..) = rhs_lit.node;
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let vec_name = snippet_with_applicability(
|
||||
cx,
|
||||
struct_calling_on.span, "vec",
|
||||
&mut applicability,
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
GET_LAST_WITH_LEN,
|
||||
expr.span,
|
||||
&format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name),
|
||||
"try",
|
||||
format!("{}.last()", vec_name),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user