diff --git a/.github/ISSUE_TEMPLATE/blank_issue.yml b/.github/ISSUE_TEMPLATE/blank_issue.yml index d610e8c7bc4..89884bfc859 100644 --- a/.github/ISSUE_TEMPLATE/blank_issue.yml +++ b/.github/ISSUE_TEMPLATE/blank_issue.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 68877efc9e1..b6f70a7f183 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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: diff --git a/.github/ISSUE_TEMPLATE/false_negative.yml b/.github/ISSUE_TEMPLATE/false_negative.yml index 9357ccc4f4e..25e436d30b9 100644 --- a/.github/ISSUE_TEMPLATE/false_negative.yml +++ b/.github/ISSUE_TEMPLATE/false_negative.yml @@ -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: diff --git a/.github/ISSUE_TEMPLATE/false_positive.yml b/.github/ISSUE_TEMPLATE/false_positive.yml index b7dd400ee73..561b65c93a7 100644 --- a/.github/ISSUE_TEMPLATE/false_positive.yml +++ b/.github/ISSUE_TEMPLATE/false_positive.yml @@ -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: diff --git a/.github/ISSUE_TEMPLATE/ice.yml b/.github/ISSUE_TEMPLATE/ice.yml index 2a5b8b3c891..81bd9c5e032 100644 --- a/.github/ISSUE_TEMPLATE/ice.yml +++ b/.github/ISSUE_TEMPLATE/ice.yml @@ -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: diff --git a/.github/deploy.sh b/.github/deploy.sh index 34225a54029..5a59f94ec91 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -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 diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 116ae031bb7..0e27cc927ac 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -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 diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 989667037c1..9b3fd3ddfeb 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -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 diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index fe8bce00fa8..22051093c9c 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -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. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b8be730be32..71d71d10359 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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' diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 56c00544c93..a179bfa7261 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b002407b6..3aaa79a1719 100644 --- a/CHANGELOG.md +++ b/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` or `Rc` + [#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 [`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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc663de8f79..6ab2bd59137 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index e445889a58f..d23d681df00 100644 --- a/Cargo.toml +++ b/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" } diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index d350d9a0018..b0d470a2124 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -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 diff --git a/clippy_dev/src/bless.rs b/clippy_dev/src/bless.rs index dcc2502e4c5..8e5c739afe0 100644 --- a/clippy_dev/src/bless.rs +++ b/clippy_dev/src/bless.rs @@ -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> = 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> = + 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 { +fn updated_since_clippy_build(entry: &DirEntry) -> Option { 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) } diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 59fde447547..81e807cf10c 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -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 diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index b8287980a4b..9e463aa741c 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -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) { + 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) { + 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)); } diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 30a241c8ba1..ee535b1d3be 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -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() diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 59658b42c79..07d19638788 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -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 { + 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::().ok()?, + patch.parse::().ok()? + )) } - - String::from("") + 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!( diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index b36e2a28ee4..d55b1a354d0 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -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) -> SystemTime { } #[allow(clippy::missing_errors_doc)] -pub fn validate_port(arg: &OsStr) -> Result<(), OsString> { - match arg.to_string_lossy().parse::() { - Ok(_port) => Ok(()), - Err(err) => Err(OsString::from(err.to_string())), - } +pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> { + arg.to_string_lossy().parse::().map(|_| ()) } diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs index a1e4dd103b8..f691ae4fa45 100644 --- a/clippy_dev/src/setup/mod.rs +++ b/clippy_dev/src/setup/mod.rs @@ -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 } } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index d368ef1f46a..5024e63bfa7 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,11 +1,13 @@ +use aho_corasick::AhoCorasickBuilder; +use core::fmt::Write as _; use itertools::Itertools; -use regex::Regex; -use std::collections::HashMap; +use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; +use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::fs; -use std::lazy::SyncLazy; -use std::path::Path; -use walkdir::WalkDir; +use std::io::{self, Read as _, Seek as _, Write as _}; +use std::path::{Path, PathBuf}; +use walkdir::{DirEntry, WalkDir}; use crate::clippy_project_root; @@ -13,37 +15,9 @@ const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev u // Use that command to update this file and do not edit by hand.\n\ // Manual edits will be overwritten.\n\n"; -static DEC_CLIPPY_LINT_RE: SyncLazy = SyncLazy::new(|| { - Regex::new( - r#"(?x) - declare_clippy_lint!\s*[\{(] - (?:\s+///.*)* - (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])? - \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* - (?P[a-z_]+)\s*,\s* - "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] -"#, - ) - .unwrap() -}); +const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; -static DEC_DEPRECATED_LINT_RE: SyncLazy = SyncLazy::new(|| { - Regex::new( - r#"(?x) - declare_deprecated_lint!\s*[{(]\s* - (?:\s+///.*)* - (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])? - \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* - "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] -"#, - ) - .unwrap() -}); -static NL_ESCAPE_RE: SyncLazy = SyncLazy::new(|| Regex::new(r#"\\\n\s*"#).unwrap()); - -static DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; - -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum UpdateMode { Check, Change, @@ -58,62 +32,66 @@ pub enum UpdateMode { /// # Panics /// /// Panics if a file path could not read from or then written to -#[allow(clippy::too_many_lines)] -pub fn run(update_mode: UpdateMode) { - let lint_list: Vec = gather_all().collect(); +pub fn update(update_mode: UpdateMode) { + let (lints, deprecated_lints, renamed_lints) = gather_all(); + generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints); +} - let internal_lints = Lint::internal_lints(&lint_list); - let deprecated_lints = Lint::deprecated_lints(&lint_list); - let usable_lints = Lint::usable_lints(&lint_list); +fn generate_lint_files( + update_mode: UpdateMode, + lints: &[Lint], + deprecated_lints: &[DeprecatedLint], + renamed_lints: &[RenamedLint], +) { + let internal_lints = Lint::internal_lints(lints); + let usable_lints = Lint::usable_lints(lints); let mut sorted_usable_lints = usable_lints.clone(); sorted_usable_lints.sort_by_key(|lint| lint.name.clone()); - let usable_lint_count = round_to_fifty(usable_lints.len()); - - let mut file_change = false; - - file_change |= replace_region_in_file( + replace_region_in_file( + update_mode, Path::new("README.md"), - &format!( - r#"\[There are over \d+ lints included in this crate!\]\({}\)"#, - DOCS_LINK - ), - "", - true, - update_mode == UpdateMode::Change, - || { - vec![format!( - "[There are over {} lints included in this crate!]({})", - usable_lint_count, DOCS_LINK - )] + "[There are over ", + " lints included in this crate!]", + |res| { + write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap(); }, - ) - .changed; + ); - file_change |= replace_region_in_file( + replace_region_in_file( + update_mode, Path::new("CHANGELOG.md"), - "", + "\n", "", - false, - update_mode == UpdateMode::Change, - || gen_changelog_lint_list(usable_lints.iter().chain(deprecated_lints.iter())), - ) - .changed; + |res| { + for lint in usable_lints + .iter() + .map(|l| &*l.name) + .chain(deprecated_lints.iter().map(|l| &*l.name)) + .chain( + renamed_lints + .iter() + .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)), + ) + .sorted() + { + writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap(); + } + }, + ); // This has to be in lib.rs, otherwise rustfmt doesn't work - file_change |= replace_region_in_file( + replace_region_in_file( + update_mode, Path::new("clippy_lints/src/lib.rs"), - "begin lints modules", - "end lints modules", - false, - update_mode == UpdateMode::Change, - || gen_modules_list(usable_lints.iter()), - ) - .changed; - - if file_change && update_mode == UpdateMode::Check { - exit_with_failure(); - } + "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n", + "// end lints modules, do not remove this comment, it’s used in `update_lints`", + |res| { + for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() { + writeln!(res, "mod {};", lint_mod).unwrap(); + } + }, + ); process_file( "clippy_lints/src/lib.register_lints.rs", @@ -123,7 +101,7 @@ pub fn run(update_mode: UpdateMode) { process_file( "clippy_lints/src/lib.deprecated.rs", update_mode, - &gen_deprecated(deprecated_lints.iter()), + &gen_deprecated(deprecated_lints), ); let all_group_lints = usable_lints.iter().filter(|l| { @@ -143,18 +121,21 @@ pub fn run(update_mode: UpdateMode) { &content, ); } + + let content = gen_deprecated_lints_test(deprecated_lints); + process_file("tests/ui/deprecated.rs", update_mode, &content); + + let content = gen_renamed_lints_test(renamed_lints); + process_file("tests/ui/rename.rs", update_mode, &content); } pub fn print_lints() { - let lint_list: Vec = gather_all().collect(); + let (lint_list, _, _) = gather_all(); let usable_lints = Lint::usable_lints(&lint_list); let usable_lint_count = usable_lints.len(); let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter()); for (lint_group, mut lints) in grouped_by_lint_group { - if lint_group == "Deprecated" { - continue; - } println!("\n## {}", lint_group); lints.sort_by_key(|l| l.name.clone()); @@ -167,6 +148,209 @@ pub fn print_lints() { println!("there are {} lints", usable_lint_count); } +/// Runs the `rename_lint` command. +/// +/// This does the following: +/// * Adds an entry to `renamed_lints.rs`. +/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`). +/// * Renames the lint struct to the new name. +/// * Renames the module containing the lint struct to the new name if it shares a name with the +/// lint. +/// +/// # Panics +/// Panics for the following conditions: +/// * If a file path could not read from or then written to +/// * If either lint name has a prefix +/// * If `old_name` doesn't name an existing lint. +/// * If `old_name` names a deprecated or renamed lint. +#[allow(clippy::too_many_lines)] +pub fn rename(old_name: &str, new_name: &str, uplift: bool) { + if let Some((prefix, _)) = old_name.split_once("::") { + panic!("`{}` should not contain the `{}` prefix", old_name, prefix); + } + if let Some((prefix, _)) = new_name.split_once("::") { + panic!("`{}` should not contain the `{}` prefix", new_name, prefix); + } + + let (mut lints, deprecated_lints, mut renamed_lints) = gather_all(); + let mut old_lint_index = None; + let mut found_new_name = false; + for (i, lint) in lints.iter().enumerate() { + if lint.name == old_name { + old_lint_index = Some(i); + } else if lint.name == new_name { + found_new_name = true; + } + } + let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name)); + + let lint = RenamedLint { + old_name: format!("clippy::{}", old_name), + new_name: if uplift { + new_name.into() + } else { + format!("clippy::{}", new_name) + }, + }; + + // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in + // case. + assert!( + !renamed_lints.iter().any(|l| lint.old_name == l.old_name), + "`{}` has already been renamed", + old_name + ); + assert!( + !deprecated_lints.iter().any(|l| lint.old_name == l.name), + "`{}` has already been deprecated", + old_name + ); + + // Update all lint level attributes. (`clippy::lint_name`) + for file in WalkDir::new(clippy_project_root()) + .into_iter() + .map(Result::unwrap) + .filter(|f| { + let name = f.path().file_name(); + let ext = f.path().extension(); + (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) + && name != Some(OsStr::new("rename.rs")) + && name != Some(OsStr::new("renamed_lints.rs")) + }) + { + rewrite_file(file.path(), |s| { + replace_ident_like(s, &[(&lint.old_name, &lint.new_name)]) + }); + } + + renamed_lints.push(lint); + renamed_lints.sort_by(|lhs, rhs| { + lhs.new_name + .starts_with("clippy::") + .cmp(&rhs.new_name.starts_with("clippy::")) + .reverse() + .then_with(|| lhs.old_name.cmp(&rhs.old_name)) + }); + + write_file( + Path::new("clippy_lints/src/renamed_lints.rs"), + &gen_renamed_lints_list(&renamed_lints), + ); + + if uplift { + write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); + println!( + "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.", + old_name + ); + } else if found_new_name { + write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); + println!( + "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.", + new_name + ); + } else { + // Rename the lint struct and source files sharing a name with the lint. + let lint = &mut lints[old_lint_index]; + let old_name_upper = old_name.to_uppercase(); + let new_name_upper = new_name.to_uppercase(); + lint.name = new_name.into(); + + // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist. + if try_rename_file( + Path::new(&format!("tests/ui/{}.rs", old_name)), + Path::new(&format!("tests/ui/{}.rs", new_name)), + ) { + try_rename_file( + Path::new(&format!("tests/ui/{}.stderr", old_name)), + Path::new(&format!("tests/ui/{}.stderr", new_name)), + ); + try_rename_file( + Path::new(&format!("tests/ui/{}.fixed", old_name)), + Path::new(&format!("tests/ui/{}.fixed", new_name)), + ); + } + + // Try to rename the file containing the lint if the file name matches the lint's name. + let replacements; + let replacements = if lint.module == old_name + && try_rename_file( + Path::new(&format!("clippy_lints/src/{}.rs", old_name)), + Path::new(&format!("clippy_lints/src/{}.rs", new_name)), + ) { + // Edit the module name in the lint list. Note there could be multiple lints. + for lint in lints.iter_mut().filter(|l| l.module == old_name) { + lint.module = new_name.into(); + } + replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; + replacements.as_slice() + } else if !lint.module.contains("::") + // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs` + && try_rename_file( + Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)), + Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)), + ) + { + // Edit the module name in the lint list. Note there could be multiple lints, or none. + let renamed_mod = format!("{}::{}", lint.module, old_name); + for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) { + lint.module = format!("{}::{}", lint.module, new_name); + } + replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; + replacements.as_slice() + } else { + replacements = [(&*old_name_upper, &*new_name_upper), ("", "")]; + &replacements[0..1] + }; + + // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being + // renamed. + for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { + rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); + } + + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("{} has been successfully renamed", old_name); + } + + println!("note: `cargo uitest` still needs to be run to update the test results"); +} + +/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there +/// were no replacements. +fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option { + fn is_ident_char(c: u8) -> bool { + matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') + } + + let searcher = AhoCorasickBuilder::new() + .dfa(true) + .match_kind(aho_corasick::MatchKind::LeftmostLongest) + .build_with_size::(replacements.iter().map(|&(x, _)| x.as_bytes())) + .unwrap(); + + let mut result = String::with_capacity(contents.len() + 1024); + let mut pos = 0; + let mut edited = false; + for m in searcher.find_iter(contents) { + let (old, new) = replacements[m.pattern()]; + result.push_str(&contents[pos..m.start()]); + result.push_str( + if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0)) + && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0)) + { + edited = true; + new + } else { + old + }, + ); + pos = m.end(); + } + result.push_str(&contents[pos..]); + edited.then(|| result) +} + fn round_to_fifty(count: usize) -> usize { count / 50 * 50 } @@ -193,24 +377,22 @@ fn exit_with_failure() { } /// Lint data parsed from the Clippy source code. -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] struct Lint { name: String, group: String, desc: String, - deprecation: Option, module: String, } impl Lint { #[must_use] - fn new(name: &str, group: &str, desc: &str, deprecation: Option<&str>, module: &str) -> Self { + fn new(name: &str, group: &str, desc: &str, module: &str) -> Self { Self { name: name.to_lowercase(), - group: group.to_string(), - desc: NL_ESCAPE_RE.replace(&desc.replace("\\\"", "\""), "").to_string(), - deprecation: deprecation.map(ToString::to_string), - module: module.to_string(), + group: group.into(), + desc: remove_line_splices(desc), + module: module.into(), } } @@ -219,7 +401,7 @@ impl Lint { fn usable_lints(lints: &[Self]) -> Vec { lints .iter() - .filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal")) + .filter(|l| !l.group.starts_with("internal")) .cloned() .collect() } @@ -230,12 +412,6 @@ impl Lint { lints.iter().filter(|l| l.group == "internal").cloned().collect() } - /// Returns all deprecated lints - #[must_use] - fn deprecated_lints(lints: &[Self]) -> Vec { - lints.iter().filter(|l| l.deprecation.is_some()).cloned().collect() - } - /// Returns the lints in a `HashMap`, grouped by the different lint groups #[must_use] fn by_lint_group(lints: impl Iterator) -> HashMap> { @@ -243,6 +419,33 @@ impl Lint { } } +#[derive(Clone, PartialEq, Eq, Debug)] +struct DeprecatedLint { + name: String, + reason: String, +} +impl DeprecatedLint { + fn new(name: &str, reason: &str) -> Self { + Self { + name: name.to_lowercase(), + reason: remove_line_splices(reason), + } + } +} + +struct RenamedLint { + old_name: String, + new_name: String, +} +impl RenamedLint { + fn new(old_name: &str, new_name: &str) -> Self { + Self { + old_name: remove_line_splices(old_name), + new_name: remove_line_splices(new_name), + } + } +} + /// Generates the code for registering a group fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator) -> String { let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect(); @@ -250,54 +453,35 @@ fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator(lints: impl Iterator) -> Vec { - lints - .map(|l| &l.module) - .unique() - .map(|module| format!("mod {};", module)) - .sorted() - .collect::>() -} - -/// Generates the list of lint links at the bottom of the CHANGELOG -#[must_use] -fn gen_changelog_lint_list<'a>(lints: impl Iterator) -> Vec { - lints - .sorted_by_key(|l| &l.name) - .map(|l| format!("[`{}`]: {}#{}", l.name, DOCS_LINK, l.name)) - .collect() -} - /// Generates the `register_removed` code #[must_use] -fn gen_deprecated<'a>(lints: impl Iterator) -> String { +fn gen_deprecated(lints: &[DeprecatedLint]) -> String { let mut output = GENERATED_FILE_COMMENT.to_string(); output.push_str("{\n"); - for Lint { name, deprecation, .. } in lints { - output.push_str(&format!( + for lint in lints { + let _ = write!( + output, concat!( " store.register_removed(\n", " \"clippy::{}\",\n", " \"{}\",\n", " );\n" ), - name, - deprecation.as_ref().expect("`lints` are deprecated") - )); + lint.name, lint.reason, + ); } output.push_str("}\n"); @@ -323,68 +507,207 @@ fn gen_register_lint_list<'a>( if !is_public { output.push_str(" #[cfg(feature = \"internal\")]\n"); } - output.push_str(&format!(" {}::{},\n", module_name, lint_name)); + let _ = writeln!(output, " {}::{},", module_name, lint_name); } output.push_str("])\n"); output } -/// Gathers all files in `src/clippy_lints` and gathers all lints inside -fn gather_all() -> impl Iterator { - lint_files().flat_map(|f| gather_from_file(&f)) -} - -fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator { - let content = fs::read_to_string(dir_entry.path()).unwrap(); - let path = dir_entry.path(); - let filename = path.file_stem().unwrap(); - let path_buf = path.with_file_name(filename); - let mut rel_path = path_buf - .strip_prefix(clippy_project_root().join("clippy_lints/src")) - .expect("only files in `clippy_lints/src` should be looked at"); - // If the lints are stored in mod.rs, we get the module name from - // the containing directory: - if filename == "mod" { - rel_path = rel_path.parent().unwrap(); +fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { + let mut res: String = GENERATED_FILE_COMMENT.into(); + for lint in lints { + writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); } - - let module = rel_path - .components() - .map(|c| c.as_os_str().to_str().unwrap()) - .collect::>() - .join("::"); - - parse_contents(&content, &module) + res.push_str("\nfn main() {}\n"); + res } -fn parse_contents(content: &str, module: &str) -> impl Iterator { - let lints = DEC_CLIPPY_LINT_RE - .captures_iter(content) - .map(|m| Lint::new(&m["name"], &m["cat"], &m["desc"], None, module)); - let deprecated = DEC_DEPRECATED_LINT_RE - .captures_iter(content) - .map(|m| Lint::new(&m["name"], "Deprecated", &m["desc"], Some(&m["desc"]), module)); - // Removing the `.collect::>().into_iter()` causes some lifetime issues due to the map - lints.chain(deprecated).collect::>().into_iter() +fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String { + let mut seen_lints = HashSet::new(); + let mut res: String = GENERATED_FILE_COMMENT.into(); + res.push_str("// run-rustfix\n\n"); + for lint in lints { + if seen_lints.insert(&lint.new_name) { + writeln!(res, "#![allow({})]", lint.new_name).unwrap(); + } + } + seen_lints.clear(); + for lint in lints { + if seen_lints.insert(&lint.old_name) { + writeln!(res, "#![warn({})]", lint.old_name).unwrap(); + } + } + res.push_str("\nfn main() {}\n"); + res } -/// Collects all .rs files in the `clippy_lints/src` directory -fn lint_files() -> impl Iterator { - // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories. - // Otherwise we would not collect all the lints, for example in `clippy_lints/src/methods/`. - let path = clippy_project_root().join("clippy_lints/src"); - WalkDir::new(path) - .into_iter() - .filter_map(Result::ok) +fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String { + const HEADER: &str = "\ + // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\ + #[rustfmt::skip]\n\ + pub static RENAMED_LINTS: &[(&str, &str)] = &[\n"; + + let mut res = String::from(HEADER); + for lint in lints { + writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap(); + } + res.push_str("];\n"); + res +} + +/// Gathers all lints defined in `clippy_lints/src` +fn gather_all() -> (Vec, Vec, Vec) { + let mut lints = Vec::with_capacity(1000); + let mut deprecated_lints = Vec::with_capacity(50); + let mut renamed_lints = Vec::with_capacity(50); + + for (rel_path, file) in clippy_lints_src_files() { + let path = file.path(); + let contents = + fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e)); + let module = rel_path + .components() + .map(|c| c.as_os_str().to_str().unwrap()) + .collect::>() + .join("::"); + + // If the lints are stored in mod.rs, we get the module name from + // the containing directory: + let module = if let Some(module) = module.strip_suffix("::mod.rs") { + module + } else { + module.strip_suffix(".rs").unwrap_or(&module) + }; + + match module { + "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), + "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), + _ => parse_contents(&contents, module, &mut lints), + } + } + (lints, deprecated_lints, renamed_lints) +} + +fn clippy_lints_src_files() -> impl Iterator { + let root_path = clippy_project_root().join("clippy_lints/src"); + let iter = WalkDir::new(&root_path).into_iter(); + iter.map(Result::unwrap) .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) + .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f)) } -/// Whether a file has had its text changed or not -#[derive(PartialEq, Debug)] -struct FileChange { - changed: bool, - new_lines: String, +macro_rules! match_tokens { + ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => { + { + $($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() { + _x + } else { + continue; + };)* + #[allow(clippy::unused_unit)] + { ($($($capture,)?)*) } + } + } +} + +/// Parse a source file looking for `declare_clippy_lint` macro invocations. +fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { + let mut offset = 0usize; + let mut iter = tokenize(contents).map(|t| { + let range = offset..offset + t.len; + offset = range.end; + (t.kind, &contents[range]) + }); + + while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") { + let mut iter = iter + .by_ref() + .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + // matches `!{` + match_tokens!(iter, Bang OpenBrace); + match iter.next() { + // #[clippy::version = "version"] pub + Some((TokenKind::Pound, _)) => { + match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident); + }, + // pub + Some((TokenKind::Ident, _)) => (), + _ => continue, + } + let (name, group, desc) = match_tokens!( + iter, + // LINT_NAME + Ident(name) Comma + // group, + Ident(group) Comma + // "description" } + Literal{..}(desc) CloseBrace + ); + lints.push(Lint::new(name, group, desc, module)); + } +} + +/// Parse a source file looking for `declare_deprecated_lint` macro invocations. +fn parse_deprecated_contents(contents: &str, lints: &mut Vec) { + let mut offset = 0usize; + let mut iter = tokenize(contents).map(|t| { + let range = offset..offset + t.len; + offset = range.end; + (t.kind, &contents[range]) + }); + while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") { + let mut iter = iter + .by_ref() + .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + let (name, reason) = match_tokens!( + iter, + // !{ + Bang OpenBrace + // #[clippy::version = "version"] + Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket + // pub LINT_NAME, + Ident Ident(name) Comma + // "description" + Literal{kind: LiteralKind::Str{..},..}(reason) + // } + CloseBrace + ); + lints.push(DeprecatedLint::new(name, reason)); + } +} + +fn parse_renamed_contents(contents: &str, lints: &mut Vec) { + for line in contents.lines() { + let mut offset = 0usize; + let mut iter = tokenize(line).map(|t| { + let range = offset..offset + t.len; + offset = range.end; + (t.kind, &line[range]) + }); + let (old_name, new_name) = match_tokens!( + iter, + // ("old_name", + Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma + // "new_name"), + Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma + ); + lints.push(RenamedLint::new(old_name, new_name)); + } +} + +/// Removes the line splices and surrounding quotes from a string literal +fn remove_line_splices(s: &str) -> String { + let s = s + .strip_prefix('r') + .unwrap_or(s) + .trim_matches('#') + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + .unwrap_or_else(|| panic!("expected quoted string, found `{}`", s)); + let mut res = String::with_capacity(s.len()); + unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, _| res.push_str(&s[range])); + res } /// Replaces a region in a file delimited by two lines matching regexes. @@ -396,144 +719,95 @@ struct FileChange { /// # Panics /// /// Panics if the path could not read or then written -fn replace_region_in_file( +fn replace_region_in_file( + update_mode: UpdateMode, path: &Path, start: &str, end: &str, - replace_start: bool, - write_back: bool, - replacements: F, -) -> FileChange -where - F: FnOnce() -> Vec, -{ - let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.display(), e)); - let file_change = replace_region_in_text(&contents, start, end, replace_start, replacements); + write_replacement: impl FnMut(&mut String), +) { + let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e)); + let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { + Ok(x) => x, + Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()), + }; - if write_back { - if let Err(e) = fs::write(path, file_change.new_lines.as_bytes()) { - panic!("Cannot write to {}: {}", path.display(), e); - } - } - file_change -} - -/// Replaces a region in a text delimited by two lines matching regexes. -/// -/// * `text` is the input text on which you want to perform the replacement -/// * `start` is a `&str` that describes the delimiter line before the region you want to replace. -/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. -/// * `end` is a `&str` that describes the delimiter line until where the replacement should happen. -/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. -/// * If `replace_start` is true, the `start` delimiter line is replaced as well. The `end` -/// delimiter line is never replaced. -/// * `replacements` is a closure that has to return a `Vec` which contains the new text. -/// -/// If you want to perform the replacement on files instead of already parsed text, -/// use `replace_region_in_file`. -/// -/// # Example -/// -/// ```ignore -/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end"; -/// let result = -/// replace_region_in_text(the_text, "replace_start", "replace_end", false, || { -/// vec!["a different".to_string(), "text".to_string()] -/// }) -/// .new_lines; -/// assert_eq!("replace_start\na different\ntext\nreplace_end", result); -/// ``` -/// -/// # Panics -/// -/// Panics if start or end is not valid regex -fn replace_region_in_text(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange -where - F: FnOnce() -> Vec, -{ - let replace_it = replacements(); - let mut in_old_region = false; - let mut found = false; - let mut new_lines = vec![]; - let start = Regex::new(start).unwrap(); - let end = Regex::new(end).unwrap(); - - for line in text.lines() { - if in_old_region { - if end.is_match(line) { - in_old_region = false; - new_lines.extend(replace_it.clone()); - new_lines.push(line.to_string()); + match update_mode { + UpdateMode::Check if contents != new_contents => exit_with_failure(), + UpdateMode::Check => (), + UpdateMode::Change => { + if let Err(e) = fs::write(path, new_contents.as_bytes()) { + panic!("Cannot write to `{}`: {}", path.display(), e); } - } else if start.is_match(line) { - if !replace_start { - new_lines.push(line.to_string()); + }, + } +} + +/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters +/// were found, or the missing delimiter if not. +fn replace_region_in_text<'a>( + text: &str, + start: &'a str, + end: &'a str, + mut write_replacement: impl FnMut(&mut String), +) -> Result { + let (text_start, rest) = text.split_once(start).ok_or(start)?; + let (_, text_end) = rest.split_once(end).ok_or(end)?; + + let mut res = String::with_capacity(text.len() + 4096); + res.push_str(text_start); + res.push_str(start); + write_replacement(&mut res); + res.push_str(end); + res.push_str(text_end); + + Ok(res) +} + +fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { + match fs::OpenOptions::new().create_new(true).write(true).open(new_name) { + Ok(file) => drop(file), + Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, + Err(e) => panic_file(e, new_name, "create"), + }; + match fs::rename(old_name, new_name) { + Ok(()) => true, + Err(e) => { + drop(fs::remove_file(new_name)); + if e.kind() == io::ErrorKind::NotFound { + false + } else { + panic_file(e, old_name, "rename"); } - in_old_region = true; - found = true; - } else { - new_lines.push(line.to_string()); - } + }, } +} - if !found { - // This happens if the provided regex in `clippy_dev/src/main.rs` does not match in the - // given text or file. Most likely this is an error on the programmer's side and the Regex - // is incorrect. - eprintln!("error: regex \n{:?}\ndoesn't match. You may have to update it.", start); - std::process::exit(1); +#[allow(clippy::needless_pass_by_value)] +fn panic_file(error: io::Error, name: &Path, action: &str) -> ! { + panic!("failed to {} file `{}`: {}", action, name.display(), error) +} + +fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option) { + let mut file = fs::OpenOptions::new() + .write(true) + .read(true) + .open(path) + .unwrap_or_else(|e| panic_file(e, path, "open")); + let mut buf = String::new(); + file.read_to_string(&mut buf) + .unwrap_or_else(|e| panic_file(e, path, "read")); + if let Some(new_contents) = f(&buf) { + file.rewind().unwrap_or_else(|e| panic_file(e, path, "write")); + file.write_all(new_contents.as_bytes()) + .unwrap_or_else(|e| panic_file(e, path, "write")); + file.set_len(new_contents.len() as u64) + .unwrap_or_else(|e| panic_file(e, path, "write")); } - - let mut new_lines = new_lines.join("\n"); - if text.ends_with('\n') { - new_lines.push('\n'); - } - let changed = new_lines != text; - FileChange { changed, new_lines } } -#[test] -fn test_parse_contents() { - let result: Vec = parse_contents( - r#" -declare_clippy_lint! { - #[clippy::version = "Hello Clippy!"] - pub PTR_ARG, - style, - "really long \ - text" -} - -declare_clippy_lint!{ - #[clippy::version = "Test version"] - pub DOC_MARKDOWN, - pedantic, - "single line" -} - -/// some doc comment -declare_deprecated_lint! { - #[clippy::version = "I'm a version"] - pub SHOULD_ASSERT_EQ, - "`assert!()` will be more flexible with RFC 2011" -} - "#, - "module_name", - ) - .collect(); - - let expected = vec![ - Lint::new("ptr_arg", "style", "really long text", None, "module_name"), - Lint::new("doc_markdown", "pedantic", "single line", None, "module_name"), - Lint::new( - "should_assert_eq", - "Deprecated", - "`assert!()` will be more flexible with RFC 2011", - Some("`assert!()` will be more flexible with RFC 2011"), - "module_name", - ), - ]; - assert_eq!(expected, result); +fn write_file(path: &Path, contents: &str) { + fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write")); } #[cfg(test)] @@ -541,55 +815,65 @@ mod tests { use super::*; #[test] - fn test_replace_region() { - let text = "\nabc\n123\n789\ndef\nghi"; - let expected = FileChange { - changed: true, - new_lines: "\nabc\nhello world\ndef\nghi".to_string(), - }; - let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || { - vec!["hello world".to_string()] - }); + fn test_parse_contents() { + static CONTENTS: &str = r#" + declare_clippy_lint! { + #[clippy::version = "Hello Clippy!"] + pub PTR_ARG, + style, + "really long \ + text" + } + + declare_clippy_lint!{ + #[clippy::version = "Test version"] + pub DOC_MARKDOWN, + pedantic, + "single line" + } + "#; + let mut result = Vec::new(); + parse_contents(CONTENTS, "module_name", &mut result); + + let expected = vec![ + Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"), + Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"), + ]; assert_eq!(expected, result); } #[test] - fn test_replace_region_with_start() { - let text = "\nabc\n123\n789\ndef\nghi"; - let expected = FileChange { - changed: true, - new_lines: "\nhello world\ndef\nghi".to_string(), - }; - let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || { - vec!["hello world".to_string()] - }); - assert_eq!(expected, result); - } + fn test_parse_deprecated_contents() { + static DEPRECATED_CONTENTS: &str = r#" + /// some doc comment + declare_deprecated_lint! { + #[clippy::version = "I'm a version"] + pub SHOULD_ASSERT_EQ, + "`assert!()` will be more flexible with RFC 2011" + } + "#; - #[test] - fn test_replace_region_no_changes() { - let text = "123\n456\n789"; - let expected = FileChange { - changed: false, - new_lines: "123\n456\n789".to_string(), - }; - let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, Vec::new); + let mut result = Vec::new(); + parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); + + let expected = vec![DeprecatedLint::new( + "should_assert_eq", + "\"`assert!()` will be more flexible with RFC 2011\"", + )]; assert_eq!(expected, result); } #[test] fn test_usable_lints() { let lints = vec![ - Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"), - Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"), - Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"), - Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"), + Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"), + Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"), ]; let expected = vec![Lint::new( "should_assert_eq2", "Not Deprecated", - "abc", - None, + "\"abc\"", "module_name", )]; assert_eq!(expected, Lint::usable_lints(&lints)); @@ -598,55 +882,30 @@ mod tests { #[test] fn test_by_lint_group() { let lints = vec![ - Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), - Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), - Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), ]; let mut expected: HashMap> = HashMap::new(); expected.insert( "group1".to_string(), vec![ - Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), - Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), ], ); expected.insert( "group2".to_string(), - vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")], + vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")], ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } - #[test] - fn test_gen_changelog_lint_list() { - let lints = vec![ - Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), - Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), - ]; - let expected = vec![ - format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK), - format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK), - ]; - assert_eq!(expected, gen_changelog_lint_list(lints.iter())); - } - #[test] fn test_gen_deprecated() { let lints = vec![ - Lint::new( - "should_assert_eq", - "group1", - "abc", - Some("has been superseded by should_assert_eq2"), - "module_name", - ), - Lint::new( - "another_deprecated", - "group2", - "abc", - Some("will be removed"), - "module_name", - ), + DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""), + DeprecatedLint::new("another_deprecated", "\"will be removed\""), ]; let expected = GENERATED_FILE_COMMENT.to_string() @@ -665,32 +924,15 @@ mod tests { .join("\n") + "\n"; - assert_eq!(expected, gen_deprecated(lints.iter())); - } - - #[test] - #[should_panic] - fn test_gen_deprecated_fail() { - let lints = vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")]; - let _deprecated_lints = gen_deprecated(lints.iter()); - } - - #[test] - fn test_gen_modules_list() { - let lints = vec![ - Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), - Lint::new("incorrect_stuff", "group3", "abc", None, "another_module"), - ]; - let expected = vec!["mod another_module;".to_string(), "mod module_name;".to_string()]; - assert_eq!(expected, gen_modules_list(lints.iter())); + assert_eq!(expected, gen_deprecated(&lints)); } #[test] fn test_gen_lint_group_list() { let lints = vec![ - Lint::new("abc", "group1", "abc", None, "module_name"), - Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), - Lint::new("internal", "internal_style", "abc", None, "module_name"), + Lint::new("abc", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), + Lint::new("internal", "internal_style", "\"abc\"", "module_name"), ]; let expected = GENERATED_FILE_COMMENT.to_string() + &[ diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 2053ca64ba2..0a3f04da357 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -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" diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index e109ee0009e..da1b646f477 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -87,9 +87,7 @@ impl ApproxConstant { let s = s.as_str(); if s.parse::().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, diff --git a/clippy_lints/src/arithmetic.rs b/clippy_lints/src/arithmetic.rs index e0c1d6ab6e1..c5948707c81 100644 --- a/clippy_lints/src/arithmetic.rs +++ b/clippy_lints/src/arithmetic.rs @@ -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) { diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs index 12c1bddf79d..4c2d3366483 100644 --- a/clippy_lints/src/assign_ops.rs +++ b/clippy_lints/src/assign_ops.rs @@ -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 diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index a58d12ddd6b..7105ce4b292 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -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::>(); 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) { 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) } diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 1cc3418d474..5b7c4591504 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -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) { - /// let guard = x.lock().unwrap(); + /// let mut guard = x.lock().unwrap(); /// *guard += 1; - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &Mutex) { + /// 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) { /// { - /// let guard = x.lock().unwrap(); + /// let mut guard = x.lock().unwrap(); /// *guard += 1; /// } - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &Mutex) { + /// { + /// 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) { /// let mut y = x.borrow_mut(); /// *y += 1; - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &RefCell) { + /// 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) { /// { /// let mut y = x.borrow_mut(); /// *y += 1; /// } - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &RefCell) { + /// { + /// 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, + def_ids: FxHashMap, +} + +impl AwaitHolding { + pub(crate) fn new(conf_invalid_types: Vec) -> 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) diff --git a/clippy_lints/src/bit_mask.rs b/clippy_lints/src/bit_mask.rs index ca4af66cad1..dc7e400fdc2 100644 --- a/clippy_lints/src/bit_mask.rs +++ b/clippy_lints/src/bit_mask.rs @@ -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, ); - }); - } + }, + ); } } } diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs index c4956bacf43..4c4dd85d518 100644 --- a/clippy_lints/src/blocks_in_if_conditions.rs +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -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 diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index f7449c8dc72..0adb6327164 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -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()) diff --git a/clippy_lints/src/borrow_as_ptr.rs b/clippy_lints/src/borrow_as_ptr.rs index 9f8eb488c29..0993adbae2e 100644 --- a/clippy_lints/src/borrow_as_ptr.rs +++ b/clippy_lints/src/borrow_as_ptr.rs @@ -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; } diff --git a/clippy_lints/src/bytes_count_to_len.rs b/clippy_lints/src/bytes_count_to_len.rs new file mode 100644 index 00000000000..d70dbf5b239 --- /dev/null +++ b/clippy_lints/src/bytes_count_to_len.rs @@ -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 + ); + } + }; + } +} diff --git a/clippy_lints/src/cargo/common_metadata.rs b/clippy_lints/src/cargo/common_metadata.rs new file mode 100644 index 00000000000..e0442dda479 --- /dev/null +++ b/clippy_lints/src/cargo/common_metadata.rs @@ -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>(value: &Option) -> 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) +} diff --git a/clippy_lints/src/cargo/feature_name.rs b/clippy_lints/src/cargo/feature_name.rs new file mode 100644 index 00000000000..79a469a4258 --- /dev/null +++ b/clippy_lints/src/cargo/feature_name.rs @@ -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); +} diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs new file mode 100644 index 00000000000..abe95c6663f --- /dev/null +++ b/clippy_lints/src/cargo/mod.rs @@ -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)); + } + }, + } + } + } +} diff --git a/clippy_lints/src/cargo/multiple_crate_versions.rs b/clippy_lints/src/cargo/multiple_crate_versions.rs new file mode 100644 index 00000000000..76fd0819a39 --- /dev/null +++ b/clippy_lints/src/cargo/multiple_crate_versions.rs @@ -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)) +} diff --git a/clippy_lints/src/cargo/wildcard_dependencies.rs b/clippy_lints/src/cargo/wildcard_dependencies.rs new file mode 100644 index 00000000000..7fa6acbf557 --- /dev/null +++ b/clippy_lints/src/cargo/wildcard_dependencies.rs @@ -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), + ); + } + } + } +} diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs deleted file mode 100644 index 23f79fdc682..00000000000 --- a/clippy_lints/src/cargo_common_metadata.rs +++ /dev/null @@ -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>(value: &Option) -> 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"); - } - } - } - } -} diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs index e71f110820c..7af200708ff 100644 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -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); } }, diff --git a/clippy_lints/src/casts/cast_abs_to_unsigned.rs b/clippy_lints/src/casts/cast_abs_to_unsigned.rs new file mode 100644 index 00000000000..6bac6bf83f8 --- /dev/null +++ b/clippy_lints/src/casts/cast_abs_to_unsigned.rs @@ -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, +) { + 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, + ); + } + } +} diff --git a/clippy_lints/src/casts/cast_enum_constructor.rs b/clippy_lints/src/casts/cast_enum_constructor.rs new file mode 100644 index 00000000000..1973692e105 --- /dev/null +++ b/clippy_lints/src/casts/cast_enum_constructor.rs @@ -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", + ); + } +} diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 4a95bed1148..938458e30ca 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -16,7 +16,7 @@ pub(super) fn check( cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, - msrv: &Option, + msrv: Option, ) { 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, + msrv: Option, ) -> 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)) }, diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index ea74d5acbda..64f87c80f8d 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -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 { 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); diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 079b7ff0675..d476a1a7646 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -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::::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 } diff --git a/clippy_lints/src/casts/cast_slice_different_sizes.rs b/clippy_lints/src/casts/cast_slice_different_sizes.rs new file mode 100644 index 00000000000..027c660ce3b --- /dev/null +++ b/clippy_lints/src/casts/cast_slice_different_sizes.rs @@ -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) { + // 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> { + 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> { + 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 + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index aee1e50b94a..daf3b7b4ce4 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -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, } @@ -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); diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index fb04f93fbcf..46d45d09661 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -12,8 +12,8 @@ use rustc_semver::RustcVersion; use super::PTR_AS_PTR; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option) { - if !meets_msrv(msrv.as_ref(), &msrvs::POINTER_CAST) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Option) { + if !meets_msrv(msrv, msrvs::POINTER_CAST) { return; } diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 470c8c7ea26..af56ec11ef8 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -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(); diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 00fd0b3473b..5a4f20f0990 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -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() + } +} diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 31cc3698592..7eeaaa01921 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -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, diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 85f95237549..317c4bfb322 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -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| { diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index f03f34e5a4b..3227e6e86af 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -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, ); } diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index c71e9f10f79..ec55009f347 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -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"); }, ); diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 8b79f1600ae..e6a0162fd02 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -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"); } diff --git a/clippy_lints/src/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs new file mode 100644 index 00000000000..fc141b4a6e3 --- /dev/null +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -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 { + 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 { + 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 + } +} diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 5a0b60fdfbc..17deccf8c39 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -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 { - 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, + ); + } + } } diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 3070588483c..243dfd3a461 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -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 ::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::>(); 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 + } + } +} diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index fb201d2c012..3d9f9ed41ce 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -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> { 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), diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 9b5da0bd8a6..d559ad423df 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -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 /// }; /// } /// ``` diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index bba27576c89..5d5ea0f49c8 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -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" diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c0adab790f0..ea4c0207bb0 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -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>) -> bool { fn is_auto_borrow_position(parent: Option>, 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 { diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index eccb18982f3..34a5f8444de 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -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, diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 6d3df260ca2..fe99f4a8d55 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -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, + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[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, + ) + } + } +} diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 73c00d97020..53973ab792a 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -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, }; diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index ea4b49b46fe..14f89edce61 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -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); }, diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index a00361e6062..aaec88f50c7 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -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, @@ -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, 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; } diff --git a/clippy_lints/src/double_comparison.rs b/clippy_lints/src/double_comparison.rs index 176092e5b28..be95375789d 100644 --- a/clippy_lints/src/double_comparison.rs +++ b/clippy_lints/src/double_comparison.rs @@ -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)) => { diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index a8f8e3d8a42..25014bfa1a5 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -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`, 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), + ); } } } diff --git a/clippy_lints/src/duplicate_mod.rs b/clippy_lints/src/duplicate_mod.rs new file mode 100644 index 00000000000..c6c7b959d4f --- /dev/null +++ b/clippy_lints/src/duplicate_mod.rs @@ -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, +} + +#[derive(Default)] +pub struct DuplicateMod { + /// map from the canonicalized path to `Modules`, `BTreeMap` to make the + /// order deterministic for tests + modules: BTreeMap, +} + +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", + ); + } + } +} diff --git a/clippy_lints/src/duration_subsec.rs b/clippy_lints/src/duration_subsec.rs index 24e32c09f44..09318f74527 100644 --- a/clippy_lints/src/duration_subsec.rs +++ b/clippy_lints/src/duration_subsec.rs @@ -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) { diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs new file mode 100644 index 00000000000..325ae2356c1 --- /dev/null +++ b/clippy_lints/src/empty_drop.rs @@ -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 + ); + } + } + } +} diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index af9e65e6361..b5d6b3c7524 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -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, diff --git a/clippy_lints/src/empty_structs_with_brackets.rs b/clippy_lints/src/empty_structs_with_brackets.rs new file mode 100644 index 00000000000..8430e7b4c82 --- /dev/null +++ b/clippy_lints/src/empty_structs_with_brackets.rs @@ -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)); + } +} diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 1ae2e20c1e0..c5a987842c3 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -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); diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 3b6661c817b..10be245b362 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -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() { diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 1f4353fa4f7..e029b8e8537 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -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(); diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index 24d7613e6f8..afb5d32f953 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -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; diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 263bff4873c..530d6d4de35 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -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(), } }, diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs index 245a4fc12fd..7a81fb37e84 100644 --- a/clippy_lints/src/excessive_bools.rs +++ b/clippy_lints/src/excessive_bools.rs @@ -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 diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index b0f50b5c144..173d41b4b05 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -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; } diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index f326fd83d18..3e2217c28da 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -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, + ) } } } diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 574678b5542..9f868df3ad0 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -32,7 +32,6 @@ declare_clippy_lint! { /// // Good /// struct Foo(i32); /// - /// use std::convert::TryFrom; /// impl TryFrom for Foo { /// type Error = (); /// fn try_from(s: String) -> Result { @@ -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); } diff --git a/clippy_lints/src/feature_name.rs b/clippy_lints/src/feature_name.rs deleted file mode 100644 index dc6bef52ddd..00000000000 --- a/clippy_lints/src/feature_name.rs +++ /dev/null @@ -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); -} diff --git a/clippy_lints/src/float_equality_without_abs.rs b/clippy_lints/src/float_equality_without_abs.rs index ca8886228de..98aee7592ae 100644 --- a/clippy_lints/src/float_equality_without_abs.rs +++ b/clippy_lints/src/float_equality_without_abs.rs @@ -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(); diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 79ce53f7a5f..42503c26de1 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -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 { match value { F32(num) if num.fract() == 0.0 => { diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 395c920c997..64c41b56587 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -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, }; diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 17b0749a4a9..1e6feaac26c 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -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); } diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs new file mode 100644 index 00000000000..ef8be9e878f --- /dev/null +++ b/clippy_lints/src/format_impl.rs @@ -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, +} + +#[derive(Default)] +pub struct FormatImpl { + // Whether we are inside Display or Debug trait impl - None for neither + format_trait_impl: Option, +} + +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 { + 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 + } + } +} diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs new file mode 100644 index 00000000000..ee15ae9f59a --- /dev/null +++ b/clippy_lints/src/format_push_string.rs @@ -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", + ); + } + } +} diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index ae18f8081bc..57964b8d48e 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -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); diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index c2f52605151..5d25c1d0634 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -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; } diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 3e3718b9445..6672a6cb0b5 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -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) diff --git a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs index 830e3b32cfa..565a1c871d7 100644 --- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -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, }; diff --git a/clippy_lints/src/functions/result_unit_err.rs b/clippy_lints/src/functions/result_unit_err.rs index 120fcb2619c..2e63a1f920d 100644 --- a/clippy_lints/src/functions/result_unit_err.rs +++ b/clippy_lints/src/functions/result_unit_err.rs @@ -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 { diff --git a/clippy_lints/src/functions/too_many_arguments.rs b/clippy_lints/src/functions/too_many_arguments.rs index 3af960491ed..5c8d8b8e755 100644 --- a/clippy_lints/src/functions/too_many_arguments.rs +++ b/clippy_lints/src/functions/too_many_arguments.rs @@ -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()), diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 43911a313d5..5c46d6c7df7 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -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; diff --git a/clippy_lints/src/get_first.rs b/clippy_lints/src/get_first.rs new file mode 100644 index 00000000000..0748ab45252 --- /dev/null +++ b/clippy_lints/src/get_first.rs @@ -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, + ); + } + } + } +} diff --git a/clippy_lints/src/get_last_with_len.rs b/clippy_lints/src/get_last_with_len.rs deleted file mode 100644 index df29d9308e7..00000000000 --- a/clippy_lints/src/get_last_with_len.rs +++ /dev/null @@ -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, - ); - } - } - } -} diff --git a/clippy_lints/src/identity_op.rs b/clippy_lints/src/identity_op.rs index f824f20ca40..419ea5a6811 100644 --- a/clippy_lints/src/identity_op.rs +++ b/clippy_lints/src/identity_op.rs @@ -1,14 +1,14 @@ -use clippy_utils::source::snippet; -use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind}; +use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{clip, unsext}; +use rustc_errors::Applicability; +use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use clippy_utils::consts::{constant_simple, Constant}; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::{clip, unsext}; - declare_clippy_lint! { /// ### What it does /// Checks for identity operations, e.g., `x + 0`. @@ -31,35 +31,83 @@ declare_clippy_lint! { declare_lint_pass!(IdentityOp => [IDENTITY_OP]); impl<'tcx> LateLintPass<'tcx> for IdentityOp { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { return; } - if let ExprKind::Binary(cmp, left, right) = e.kind { - if is_allowed(cx, cmp, left, right) { - return; - } - match cmp.node { - BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - check(cx, left, 0, e.span, right.span); - check(cx, right, 0, e.span, left.span); - }, - BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => check(cx, right, 0, e.span, left.span), - BinOpKind::Mul => { - check(cx, left, 1, e.span, right.span); - check(cx, right, 1, e.span, left.span); - }, - BinOpKind::Div => check(cx, right, 1, e.span, left.span), - BinOpKind::BitAnd => { - check(cx, left, -1, e.span, right.span); - check(cx, right, -1, e.span, left.span); - }, - _ => (), + if let ExprKind::Binary(cmp, left, right) = &expr.kind { + if !is_allowed(cx, *cmp, left, right) { + match cmp.node { + BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { + check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check(cx, right, 0, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { + check(cx, right, 0, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Mul => { + check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check(cx, right, 1, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded), + BinOpKind::BitAnd => { + check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check(cx, right, -1, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), + _ => (), + } } } } } +#[derive(Copy, Clone)] +enum Parens { + Needed, + Unneeded, +} + +/// Checks if `left op right` needs parenthesis when reduced to `right` +/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced +/// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be +/// interpreted as a statement +/// +/// See #8724 +fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> Parens { + match right.kind { + ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => { + // ensure we're checking against the leftmost expression of `right` + // + // ~~~ `lhs` + // 0 + {4} * 2 + // ~~~~~~~ `right` + return needs_parenthesis(cx, binary, lhs); + }, + ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {}, + _ => return Parens::Unneeded, + } + + let mut prev_id = binary.hir_id; + for (_, node) in cx.tcx.hir().parent_iter(binary.hir_id) { + if let Node::Expr(expr) = node + && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) = expr.kind + && lhs.hir_id == prev_id + { + // keep going until we find a node that encompasses left of `binary` + prev_id = expr.hir_id; + continue; + } + + match node { + Node::Block(_) | Node::Stmt(_) => break, + _ => return Parens::Unneeded, + }; + } + + Parens::Needed +} + fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool { // This lint applies to integers !cx.typeck_results().expr_ty(left).peel_refs().is_integral() @@ -70,7 +118,19 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_ && constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))) } -fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) { +fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { + let lhs_const = constant_full_int(cx, cx.typeck_results(), left); + let rhs_const = constant_full_int(cx, cx.typeck_results(), right); + if match (lhs_const, rhs_const) { + (Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(), + (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv, + _ => return, + } { + span_ineffective_operation(cx, span, arg, Parens::Unneeded); + } +} + +fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) { if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), @@ -83,15 +143,27 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) { 1 => v == 1, _ => unreachable!(), } { - span_lint( - cx, - IDENTITY_OP, - span, - &format!( - "the operation is ineffective. Consider reducing it to `{}`", - snippet(cx, arg, "..") - ), - ); + span_ineffective_operation(cx, span, arg, parens); } } } + +fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span, parens: Parens) { + let mut applicability = Applicability::MachineApplicable; + let expr_snippet = snippet_with_applicability(cx, arg, "..", &mut applicability); + + let suggestion = match parens { + Parens::Needed => format!("({expr_snippet})"), + Parens::Unneeded => expr_snippet.into_owned(), + }; + + span_lint_and_sugg( + cx, + IDENTITY_OP, + span, + "this operation has no effect", + "consider reducing it to", + suggestion, + applicability, + ); +} diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 9525c163ece..b8d227855d9 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -57,7 +57,7 @@ impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv.as_ref(), &msrvs::BOOL_THEN) { + if !meets_msrv(self.msrv, msrvs::BOOL_THEN) { return; } diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index eed25e9bc0e..4f9680f60fe 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -1,14 +1,14 @@ use std::borrow::Cow; use std::collections::BTreeMap; -use rustc_errors::DiagnosticBuilder; +use rustc_errors::Diagnostic; use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor}; use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{Ty, TyS, TypeckResults}; +use rustc_middle::ty::{Ty, TypeckResults}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::sym; @@ -17,7 +17,6 @@ use rustc_typeck::hir_ty_to_ty; use if_chain::if_chain; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; -use clippy_utils::differing_macro_contexts; use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::ty::is_type_diagnostic_item; @@ -63,13 +62,13 @@ declare_clippy_lint! { declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]); impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { - #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] + #[expect(clippy::cast_possible_truncation, clippy::too_many_lines)] fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { use rustc_span::BytePos; fn suggestion<'tcx>( cx: &LateContext<'tcx>, - diag: &mut DiagnosticBuilder<'_>, + diag: &mut Diagnostic, generics_span: Span, generics_suggestion_span: Span, target: &ImplicitHasherType<'_>, @@ -118,12 +117,12 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { } match item.kind { - ItemKind::Impl(ref impl_) => { + ItemKind::Impl(impl_) => { let mut vis = ImplicitHasherTypeVisitor::new(cx); vis.visit_ty(impl_.self_ty); for target in &vis.found { - if differing_macro_contexts(item.span, target.span()) { + if item.span.ctxt() != target.span().ctxt() { return; } @@ -156,7 +155,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { ); } }, - ItemKind::Fn(ref sig, ref generics, body_id) => { + ItemKind::Fn(ref sig, generics, body_id) => { let body = cx.tcx.hir().body(body_id); for ty in sig.decl.inputs { @@ -346,7 +345,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; if let Some(ty_did) = ty_path.res.opt_def_id(); then { - if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) { + if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) { return; } diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index d650d6e9a85..647947d5d30 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -164,7 +164,7 @@ fn lint_implicit_returns( }) .visit_block(block); if add_return { - #[allow(clippy::option_if_let_else)] + #[expect(clippy::option_if_let_else)] if let Some(span) = call_site_span { lint_return(cx, span); LintLocation::Parent @@ -196,7 +196,7 @@ fn lint_implicit_returns( _ => { - #[allow(clippy::option_if_let_else)] + #[expect(clippy::option_if_let_else)] if let Some(span) = call_site_span { lint_return(cx, span); LintLocation::Parent diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 6515975fbff..ae4158662d4 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -3,7 +3,7 @@ use clippy_utils::{higher, peel_blocks_with_stmt, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{lang_items::LangItem, BinOpKind, Expr, ExprKind, QPath}; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -82,14 +82,6 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { // Get the variable name let var_name = ares_path.segments[0].ident.name.as_str(); - const INT_TYPES: [LangItem; 5] = [ - LangItem::I8, - LangItem::I16, - LangItem::I32, - LangItem::I64, - LangItem::Isize - ]; - match cond_num_val.kind { ExprKind::Lit(ref cond_lit) => { // Check if the constant is zero @@ -105,8 +97,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { if name.ident.as_str() == "MIN"; if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(const_id); - let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok()); - if int_ids.any(|int_id| int_id == impl_id); + if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl + if cx.tcx.type_of(impl_id).is_integral(); then { print_lint_and_sugg(cx, var_name, expr) } @@ -118,8 +110,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { if name.ident.as_str() == "min_value"; if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(func_id); - let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok()); - if int_ids.any(|int_id| int_id == impl_id); + if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl + if cx.tcx.type_of(impl_id).is_integral(); then { print_lint_and_sugg(cx, var_name, expr) } diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index 3d44a669d8f..14b22d2b50d 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -7,6 +7,7 @@ use rustc_hir::{self as hir, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Symbol; +use std::fmt::Write as _; declare_clippy_lint! { /// ### What it does @@ -71,7 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { let ty = cx.typeck_results().expr_ty(expr); 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(); if fields.iter().all(|f| f.is_shorthand); then { let mut def_order_map = FxHashMap::default(); @@ -89,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { let mut fields_snippet = String::new(); let (last_ident, idents) = ordered_fields.split_last().unwrap(); for ident in idents { - fields_snippet.push_str(&format!("{}, ", ident)); + let _ = write!(fields_snippet, "{}, ", ident); } fields_snippet.push_str(&last_ident.to_string()); diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 66765210698..9ce5b8e17a9 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -4,7 +4,7 @@ use clippy_utils::higher::IfLet; use clippy_utils::ty::is_copy; use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local}; use if_chain::if_chain; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; @@ -14,7 +14,6 @@ use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::Ident, Span}; -use std::convert::TryInto; declare_clippy_lint! { /// ### What it does @@ -75,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice { if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some(); if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr); if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id); - if meets_msrv(self.msrv.as_ref(), &msrvs::SLICE_PATTERNS); + if meets_msrv(self.msrv, msrvs::SLICE_PATTERNS); let found_slices = find_slice_values(cx, let_pat); if !found_slices.is_empty(); @@ -92,9 +91,9 @@ impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice { extract_msrv_attr!(LateContext); } -fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxHashMap { +fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap { let mut removed_pat: FxHashSet = FxHashSet::default(); - let mut slices: FxHashMap = FxHashMap::default(); + let mut slices: FxIndexMap = FxIndexMap::default(); pat.walk_always(|pat| { if let hir::PatKind::Binding(binding, value_hir_id, ident, sub_pat) = pat.kind { // We'll just ignore mut and ref mut for simplicity sake right now @@ -116,9 +115,9 @@ fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxHashMap( cx: &'a LateContext<'tcx>, - slice_lint_info: FxHashMap, + slice_lint_info: FxIndexMap, max_suggested_slice: u64, scope: &'tcx hir::Expr<'tcx>, -) -> FxHashMap { +) -> FxIndexMap { let mut visitor = SliceIndexLintingVisitor { cx, slice_lint_info, @@ -225,7 +224,7 @@ fn filter_lintable_slices<'a, 'tcx>( struct SliceIndexLintingVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - slice_lint_info: FxHashMap, + slice_lint_info: FxIndexMap, max_suggested_slice: u64, } diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 9ead4bb27a5..4ba7477add8 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -96,6 +96,10 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING] impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if cx.tcx.hir().is_inside_const_context(expr.hir_id) { + return; + } + if let ExprKind::Index(array, index) = &expr.kind { let ty = cx.typeck_results().expr_ty(array).peel_refs(); if let Some(range) = higher::Range::hir(index) { @@ -151,6 +155,10 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { } else { // Catchall non-range index, i.e., [n] or [n << m] if let ty::Array(..) = ty.kind() { + // Index is a const block. + if let ExprKind::ConstBlock(..) = index.kind { + return; + } // Index is a constant uint. if let Some(..) = constant(cx, cx.typeck_results(), index) { // Let rustc's `const_err` lint handle constant `usize` indexing on arrays. diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 3008e86ef8b..b2b9889f5dc 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths}; +use clippy_utils::{higher, match_def_path, path_def_id, paths}; use rustc_hir::{BorrowKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -167,13 +167,9 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { }, ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)), ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e), - ExprKind::Call(path, _) => { - if let ExprKind::Path(ref qpath) = path.kind { - is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into() - } else { - Finite - } - }, + ExprKind::Call(path, _) => path_def_id(cx, path) + .map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT)) + .into(), ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(), _ => Finite, } @@ -233,9 +229,12 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { } } if method.ident.name == sym!(last) && args.len() == 1 { - let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR).map_or(false, |id| { - !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[]) - }); + let not_double_ended = cx + .tcx + .get_diagnostic_item(sym::DoubleEndedIterator) + .map_or(false, |id| { + !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[]) + }); if not_double_ended { return is_infinite(cx, &args[0]); } diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 254d3f8a4d0..6a031a627df 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -119,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { let id = cx.tcx.hir().local_def_id_to_hir_id(id); if let Node::Item(&Item { - kind: ItemKind::Impl(ref impl_item), + kind: ItemKind::Impl(impl_item), span, .. }) = cx.tcx.hir().get(id) diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index 9284e002409..7e1548531f1 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -49,6 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields { && fields .iter() .all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit)) + && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..)) { let expr_spans = fields .iter() diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index df69d3dcc51..dd7177e0131 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -1,7 +1,7 @@ //! checks for `#[inline]` on trait methods without bodies use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::sugg::DiagnosticBuilderExt; +use clippy_utils::sugg::DiagnosticExt; use rustc_ast::ast::Attribute; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 3716d36ad88..8db7b307ddb 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -52,7 +52,7 @@ enum Side { } impl IntPlusOne { - #[allow(clippy::cast_sign_loss)] + #[expect(clippy::cast_sign_loss)] fn check_lit(lit: &Lit, target_value: i128) -> bool { if let LitKind::Int(value, ..) = lit.kind { return value == (target_value as u128); diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 80260e4cd83..27db6388136 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -53,9 +53,9 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { if let ItemKind::Const(hir_ty, _) = &item.kind; let ty = hir_ty_to_ty(cx.tcx, hir_ty); if let ty::Array(element_type, cst) = ty.kind(); - if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val(); if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); - if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; then { diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 0191713f60d..0f3889a2936 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -81,37 +81,33 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { if let ItemKind::Enum(ref def, _) = 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.len() <= 1 { + if adt.variants().len() <= 1 { return; } - let mut variants_size: Vec = adt - .variants - .iter() - .enumerate() - .map(|(i, variant)| { - let mut fields_size = Vec::new(); - let size: u64 = variant - .fields - .iter() - .enumerate() - .filter_map(|(i, f)| { - let ty = cx.tcx.type_of(f.did); - // don't count generics by filtering out everything - // that does not have a layout - cx.layout_of(ty).ok().map(|l| { - let size = l.size.bytes(); - fields_size.push(FieldInfo { ind: i, size }); - size - }) - }) - .sum(); - VariantInfo { - ind: i, - size, - fields_size, + let mut variants_size: Vec = Vec::new(); + for (i, variant) in adt.variants().iter().enumerate() { + let mut fields_size = Vec::new(); + for (i, f) in variant.fields.iter().enumerate() { + let ty = cx.tcx.type_of(f.did); + // don't lint variants which have a field of generic type. + match cx.layout_of(ty) { + Ok(l) => { + let fsize = l.size.bytes(); + fields_size.push(FieldInfo { ind: i, size: fsize }); + }, + Err(_) => { + return; + }, } - }) - .collect(); + } + let size: u64 = fields_size.iter().map(|info| info.size).sum(); + + variants_size.push(VariantInfo { + ind: i, + size, + fields_size, + }); + } variants_size.sort_by(|a, b| (b.size.cmp(&a.size))); diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs new file mode 100644 index 00000000000..8bef13c682d --- /dev/null +++ b/clippy_lints/src/large_include_file.rs @@ -0,0 +1,86 @@ +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::is_lint_allowed; +use clippy_utils::macros::root_macro_call_first_node; +use rustc_ast::LitKind; +use rustc_hir::Expr; +use rustc_hir::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 + /// Checks for the inclusion of large files via `include_bytes!()` + /// and `include_str!()` + /// + /// ### Why is this bad? + /// Including large files can increase the size of the binary + /// + /// ### Example + /// ```rust,ignore + /// let included_str = include_str!("very_large_file.txt"); + /// let included_bytes = include_bytes!("very_large_file.txt"); + /// ``` + /// + /// Instead, you can load the file at runtime: + /// ```rust,ignore + /// use std::fs; + /// + /// let string = fs::read_to_string("very_large_file.txt")?; + /// let bytes = fs::read("very_large_file.txt")?; + /// ``` + #[clippy::version = "1.62.0"] + pub LARGE_INCLUDE_FILE, + restriction, + "including a large file" +} + +pub struct LargeIncludeFile { + max_file_size: u64, +} + +impl LargeIncludeFile { + #[must_use] + pub fn new(max_file_size: u64) -> Self { + Self { max_file_size } + } +} + +impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); + +impl LateLintPass<'_> for LargeIncludeFile { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if_chain! { + if let Some(macro_call) = root_macro_call_first_node(cx, expr); + if !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id); + if cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) + || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id); + if let ExprKind::Lit(lit) = &expr.kind; + then { + let len = match &lit.node { + // include_bytes + LitKind::ByteStr(bstr) => bstr.len(), + // include_str + LitKind::Str(sym, _) => sym.as_str().len(), + _ => return, + }; + + if len as u64 <= self.max_file_size { + return; + } + + span_lint_and_note( + cx, + LARGE_INCLUDE_FILE, + expr.span, + "attempted to include a large file", + None, + &format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + ), + ); + } + } + } +} diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 1cc2c28c04a..57b0d709acd 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -43,9 +43,9 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { if_chain! { if let ExprKind::Repeat(_, _) = expr.kind; if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind(); - if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val(); if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); - if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; then { span_lint_and_help( diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 530b0a90ebd..dabbb8375f0 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -10,7 +10,7 @@ use rustc_hir::{ ItemKind, Mutability, Node, TraitItemRef, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, AssocKind, FnSig, Ty, TyS}; +use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{ source_map::{Span, Spanned, Symbol}, @@ -248,13 +248,13 @@ enum LenOutput<'tcx> { fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option> { match *sig.output().kind() { ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral), - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) => { - subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did)) + ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => { + subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())) }, - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did) => subs + ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => subs .type_at(0) .is_integral() - .then(|| LenOutput::Result(adt.did, subs.type_at(1))), + .then(|| LenOutput::Result(adt.did(), subs.type_at(1))), _ => None, } } @@ -263,9 +263,9 @@ impl LenOutput<'_> { fn matches_is_empty_output(self, ty: Ty<'_>) -> bool { match (self, ty.kind()) { (_, &ty::Bool) => true, - (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(), - (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => { - subs.type_at(0).is_bool() && TyS::same_type(subs.type_at(1), err_ty) + (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(), + (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did() => { + subs.type_at(0).is_bool() && subs.type_at(1) == err_ty }, _ => false, } @@ -294,7 +294,7 @@ impl LenOutput<'_> { /// Checks if the given signature matches the expectations for `is_empty` fn check_is_empty_sig(sig: FnSig<'_>, self_kind: ImplicitSelfKind, len_output: LenOutput<'_>) -> bool { match &**sig.inputs_and_output { - [arg, res] if len_output.matches_is_empty_output(res) => { + [arg, res] if len_output.matches_is_empty_output(*res) => { matches!( (arg.kind(), self_kind), (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) @@ -488,7 +488,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { .any(|item| is_is_empty(cx, item)) }), ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id), - ty::Adt(id, _) => has_is_empty_impl(cx, id.did), + ty::Adt(id, _) => has_is_empty_impl(cx, id.did()), ty::Array(..) | ty::Slice(..) | ty::Str => true, _ => false, } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 4721b7f2b47..a028b41db77 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -14,6 +14,9 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::USELESS_ATTRIBUTE), + LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), + LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(bit_mask::BAD_BIT_MASK), LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(blacklisted_name::BLACKLISTED_NAME), @@ -21,7 +24,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(booleans::LOGIC_BUG), LintId::of(booleans::NONMINIMAL_BOOL), + LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), + LintId::of(casts::CAST_ABS_TO_UNSIGNED), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), + LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), + LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::FN_TO_NUMERIC_CAST), LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), @@ -32,11 +40,13 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(comparison_chain::COMPARISON_CHAIN), LintId::of(copies::IFS_SAME_COND), LintId::of(copies::IF_SAME_THEN_ELSE), + LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), LintId::of(dereference::NEEDLESS_BORROW), LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derive::DERIVE_HASH_XOR_EQ), LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD), + LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ), LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(doc::MISSING_SAFETY_DOC), @@ -44,9 +54,13 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(double_comparison::DOUBLE_COMPARISONS), LintId::of(double_parens::DOUBLE_PARENS), LintId::of(drop_forget_ref::DROP_COPY), + LintId::of(drop_forget_ref::DROP_NON_DROP), LintId::of(drop_forget_ref::DROP_REF), LintId::of(drop_forget_ref::FORGET_COPY), + LintId::of(drop_forget_ref::FORGET_NON_DROP), LintId::of(drop_forget_ref::FORGET_REF), + LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), + LintId::of(duplicate_mod::DUPLICATE_MOD), LintId::of(duration_subsec::DURATION_SUBSEC), LintId::of(entry::MAP_ENTRY), LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), @@ -57,14 +71,15 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(erasing_op::ERASING_OP), LintId::of(escape::BOXED_LOCAL), LintId::of(eta_reduction::REDUNDANT_CLOSURE), - LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION), - LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(explicit_write::EXPLICIT_WRITE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), LintId::of(float_literal::EXCESSIVE_PRECISION), LintId::of(format::USELESS_FORMAT), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), + LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), + LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), @@ -76,7 +91,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), LintId::of(functions::RESULT_UNIT_ERR), LintId::of(functions::TOO_MANY_ARGUMENTS), - LintId::of(get_last_with_len::GET_LAST_WITH_LEN), + LintId::of(get_first::GET_FIRST), LintId::of(identity_op::IDENTITY_OP), LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), @@ -104,6 +119,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(loops::ITER_NEXT_LOOP), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_MEMCPY), + LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::MUT_RANGE_BOUND), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(loops::NEEDLESS_RANGE_LOOP), @@ -131,6 +147,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(matches::MATCH_OVERLAPPING_ARM), LintId::of(matches::MATCH_REF_PATS), LintId::of(matches::MATCH_SINGLE_BINDING), + LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::REDUNDANT_PATTERN_MATCHING), LintId::of(matches::SINGLE_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), @@ -143,13 +160,16 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::CHARS_NEXT_CMP), LintId::of(methods::CLONE_DOUBLE_REF), LintId::of(methods::CLONE_ON_COPY), + LintId::of(methods::ERR_EXPECT), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), LintId::of(methods::FLAT_MAP_IDENTITY), + LintId::of(methods::GET_LAST_WITH_LEN), LintId::of(methods::INSPECT_FOR_EACH), LintId::of(methods::INTO_ITER_ON_REF), + LintId::of(methods::IS_DIGIT_ASCII_RADIX), LintId::of(methods::ITERATOR_STEP_BY_ZERO), LintId::of(methods::ITER_CLONED_COLLECT), LintId::of(methods::ITER_COUNT), @@ -166,13 +186,17 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::MAP_FLATTEN), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::NEEDLESS_OPTION_AS_DEREF), + LintId::of(methods::NEEDLESS_OPTION_TAKE), LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::NEW_RET_NO_SELF), + LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::OK_EXPECT), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::OR_FUN_CALL), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SHOULD_IMPLEMENT_TRAIT), @@ -184,6 +208,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::SUSPICIOUS_SPLITN), LintId::of(methods::UNINIT_ASSUMED_INIT), LintId::of(methods::UNNECESSARY_FILTER_MAP), + LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::UNNECESSARY_FOLD), LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS), LintId::of(methods::UNNECESSARY_TO_OWNED), @@ -205,6 +230,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(misc_early::REDUNDANT_PATTERN), LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), LintId::of(misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION), LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), @@ -213,7 +239,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), - LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), @@ -241,6 +266,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ranges::MANUAL_RANGE_CONTAINS), LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), + LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), @@ -254,11 +280,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), LintId::of(serde_api::SERDE_API_MISUSE), + LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE), LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE), LintId::of(strings::STRING_FROM_UTF8_AS_BYTES), + LintId::of(strings::TRIM_SPLIT_WHITESPACE), LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -267,7 +294,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME), - LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY), LintId::of(transmute::CROSSPOINTER_TRANSMUTE), LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS), LintId::of(transmute::TRANSMUTE_BYTES_TO_STR), @@ -280,21 +306,21 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), - LintId::of(try_err::TRY_ERR), LintId::of(types::BORROWED_BOX), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), LintId::of(types::TYPE_COMPLEXITY), LintId::of(types::VEC_BOX), - LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), LintId::of(unit_hash::UNIT_HASH), LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), + LintId::of(unit_types::LET_UNIT_VALUE), LintId::of(unit_types::UNIT_ARG), LintId::of(unit_types::UNIT_CMP), LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS), LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_io_amount::UNUSED_IO_AMOUNT), diff --git a/clippy_lints/src/lib.register_cargo.rs b/clippy_lints/src/lib.register_cargo.rs index 1809f2cc7d4..c890523fe5a 100644 --- a/clippy_lints/src/lib.register_cargo.rs +++ b/clippy_lints/src/lib.register_cargo.rs @@ -3,9 +3,9 @@ // Manual edits will be overwritten. store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ - LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA), - LintId::of(feature_name::NEGATIVE_FEATURE_NAMES), - LintId::of(feature_name::REDUNDANT_FEATURE_NAMES), - LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), - LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES), + LintId::of(cargo::CARGO_COMMON_METADATA), + LintId::of(cargo::MULTIPLE_CRATE_VERSIONS), + LintId::of(cargo::NEGATIVE_FEATURE_NAMES), + LintId::of(cargo::REDUNDANT_FEATURE_NAMES), + LintId::of(cargo::WILDCARD_DEPENDENCIES), ]) diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index bd5ff613447..d5dfcd10a66 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -5,17 +5,16 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![ LintId::of(attrs::DEPRECATED_CFG_ATTR), LintId::of(booleans::NONMINIMAL_BOOL), + LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::UNNECESSARY_CAST), LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(double_comparison::DOUBLE_COMPARISONS), LintId::of(double_parens::DOUBLE_PARENS), LintId::of(duration_subsec::DURATION_SUBSEC), - LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION), LintId::of(explicit_write::EXPLICIT_WRITE), LintId::of(format::USELESS_FORMAT), LintId::of(functions::TOO_MANY_ARGUMENTS), - LintId::of(get_last_with_len::GET_LAST_WITH_LEN), LintId::of(identity_op::IDENTITY_OP), LintId::of(int_plus_one::INT_PLUS_ONE), LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES), @@ -30,12 +29,14 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(matches::MATCH_AS_REF), LintId::of(matches::MATCH_SINGLE_BINDING), + LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), LintId::of(methods::BIND_INSTEAD_OF_MAP), LintId::of(methods::CLONE_ON_COPY), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), LintId::of(methods::FLAT_MAP_IDENTITY), + LintId::of(methods::GET_LAST_WITH_LEN), LintId::of(methods::INSPECT_FOR_EACH), LintId::of(methods::ITER_COUNT), LintId::of(methods::MANUAL_FILTER_MAP), @@ -43,21 +44,25 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::MANUAL_SPLIT_ONCE), LintId::of(methods::MAP_FLATTEN), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::NEEDLESS_OPTION_AS_DEREF), + LintId::of(methods::NEEDLESS_OPTION_TAKE), LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), + LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::USELESS_ASREF), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), LintId::of(misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION), LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), LintId::of(needless_bool::BOOL_COMPARISON), LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), - LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 4217fd3a3ea..6bf2c4bbaed 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -13,6 +13,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(booleans::LOGIC_BUG), LintId::of(casts::CAST_REF_TO_MUT), + LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), LintId::of(copies::IFS_SAME_COND), LintId::of(copies::IF_SAME_THEN_ELSE), LintId::of(derive::DERIVE_HASH_XOR_EQ), @@ -21,9 +22,11 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(drop_forget_ref::DROP_REF), LintId::of(drop_forget_ref::FORGET_COPY), LintId::of(drop_forget_ref::FORGET_REF), + LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), LintId::of(eq_op::EQ_OP), LintId::of(erasing_op::ERASING_OP), + LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), LintId::of(if_let_mutex::IF_LET_MUTEX), @@ -57,11 +60,9 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(swap::ALMOST_SWAPPED), - LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), - LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), LintId::of(unit_hash::UNIT_HASH), diff --git a/clippy_lints/src/lib.register_internal.rs b/clippy_lints/src/lib.register_internal.rs index 7d4c7d2adb5..4778f4fdfa7 100644 --- a/clippy_lints/src/lib.register_internal.rs +++ b/clippy_lints/src/lib.register_internal.rs @@ -14,6 +14,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![ LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS), LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM), LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE), + LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL), LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA), LintId::of(utils::internal_lints::PRODUCE_ICE), LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 42c530428f3..1f2132cf620 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -26,6 +26,8 @@ store.register_lints(&[ #[cfg(feature = "internal")] utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE, #[cfg(feature = "internal")] + utils::internal_lints::MISSING_MSRV_ATTR_IMPL, + #[cfg(feature = "internal")] utils::internal_lints::OUTER_EXPN_EXPN_DATA, #[cfg(feature = "internal")] utils::internal_lints::PRODUCE_ICE, @@ -42,6 +44,7 @@ store.register_lints(&[ assign_ops::ASSIGN_OP_PATTERN, assign_ops::MISREFACTORED_ASSIGN_OP, async_yields_async::ASYNC_YIELDS_ASYNC, + attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON, attrs::BLANKET_CLIPPY_RESTRICTION_LINTS, attrs::DEPRECATED_CFG_ATTR, attrs::DEPRECATED_SEMVER, @@ -49,6 +52,7 @@ store.register_lints(&[ attrs::INLINE_ALWAYS, attrs::MISMATCHED_TARGET_OS, attrs::USELESS_ATTRIBUTE, + await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE, await_holding_invalid::AWAIT_HOLDING_LOCK, await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, bit_mask::BAD_BIT_MASK, @@ -61,8 +65,16 @@ store.register_lints(&[ booleans::NONMINIMAL_BOOL, borrow_as_ptr::BORROW_AS_PTR, bytecount::NAIVE_BYTECOUNT, - cargo_common_metadata::CARGO_COMMON_METADATA, + bytes_count_to_len::BYTES_COUNT_TO_LEN, + cargo::CARGO_COMMON_METADATA, + cargo::MULTIPLE_CRATE_VERSIONS, + cargo::NEGATIVE_FEATURE_NAMES, + cargo::REDUNDANT_FEATURE_NAMES, + cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::CAST_ABS_TO_UNSIGNED, + casts::CAST_ENUM_CONSTRUCTOR, + casts::CAST_ENUM_TRUNCATION, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, casts::CAST_POSSIBLE_WRAP, @@ -70,6 +82,7 @@ store.register_lints(&[ casts::CAST_PTR_ALIGNMENT, casts::CAST_REF_TO_MUT, casts::CAST_SIGN_LOSS, + casts::CAST_SLICE_DIFFERENT_SIZES, casts::CHAR_LIT_AS_U8, casts::FN_TO_NUMERIC_CAST, casts::FN_TO_NUMERIC_CAST_ANY, @@ -87,6 +100,7 @@ store.register_lints(&[ copies::IF_SAME_THEN_ELSE, copies::SAME_FUNCTIONS_IN_IF_CONDITION, copy_iterator::COPY_ITERATOR, + crate_in_macro_def::CRATE_IN_MACRO_DEF, create_dir::CREATE_DIR, dbg_macro::DBG_MACRO, default::DEFAULT_TRAIT_ACCESS, @@ -99,6 +113,7 @@ store.register_lints(&[ derivable_impls::DERIVABLE_IMPLS, derive::DERIVE_HASH_XOR_EQ, derive::DERIVE_ORD_XOR_PARTIAL_ORD, + derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ, derive::EXPL_IMPL_CLONE_ON_COPY, derive::UNSAFE_DERIVE_DESERIALIZE, disallowed_methods::DISALLOWED_METHODS, @@ -113,12 +128,18 @@ store.register_lints(&[ double_comparison::DOUBLE_COMPARISONS, double_parens::DOUBLE_PARENS, drop_forget_ref::DROP_COPY, + drop_forget_ref::DROP_NON_DROP, drop_forget_ref::DROP_REF, drop_forget_ref::FORGET_COPY, + drop_forget_ref::FORGET_NON_DROP, drop_forget_ref::FORGET_REF, + drop_forget_ref::UNDROPPED_MANUALLY_DROPS, + duplicate_mod::DUPLICATE_MOD, duration_subsec::DURATION_SUBSEC, else_if_without_else::ELSE_IF_WITHOUT_ELSE, + empty_drop::EMPTY_DROP, empty_enum::EMPTY_ENUM, + empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS, entry::MAP_ENTRY, enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT, enum_variants::ENUM_VARIANT_NAMES, @@ -131,8 +152,6 @@ store.register_lints(&[ escape::BOXED_LOCAL, eta_reduction::REDUNDANT_CLOSURE, eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS, - eval_order_dependence::DIVERGING_SUB_EXPRESSION, - eval_order_dependence::EVAL_ORDER_DEPENDENCE, excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS, excessive_bools::STRUCT_EXCESSIVE_BOOLS, exhaustive_items::EXHAUSTIVE_ENUMS, @@ -140,8 +159,6 @@ store.register_lints(&[ exit::EXIT, explicit_write::EXPLICIT_WRITE, fallible_impl_from::FALLIBLE_IMPL_FROM, - feature_name::NEGATIVE_FEATURE_NAMES, - feature_name::REDUNDANT_FEATURE_NAMES, float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, float_literal::EXCESSIVE_PRECISION, float_literal::LOSSY_FLOAT_LITERAL, @@ -150,6 +167,9 @@ store.register_lints(&[ format::USELESS_FORMAT, format_args::FORMAT_IN_FORMAT_ARGS, format_args::TO_STRING_IN_FORMAT_ARGS, + format_impl::PRINT_IN_FORMAT_IMPL, + format_impl::RECURSIVE_FORMAT_IMPL, + format_push_string::FORMAT_PUSH_STRING, formatting::POSSIBLE_MISSING_COMMA, formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING, formatting::SUSPICIOUS_ELSE_FORMATTING, @@ -164,7 +184,7 @@ store.register_lints(&[ functions::TOO_MANY_ARGUMENTS, functions::TOO_MANY_LINES, future_not_send::FUTURE_NOT_SEND, - get_last_with_len::GET_LAST_WITH_LEN, + get_first::GET_FIRST, identity_op::IDENTITY_OP, if_let_mutex::IF_LET_MUTEX, if_not_else::IF_NOT_ELSE, @@ -190,6 +210,7 @@ store.register_lints(&[ iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR, large_const_arrays::LARGE_CONST_ARRAYS, large_enum_variant::LARGE_ENUM_VARIANT, + large_include_file::LARGE_INCLUDE_FILE, large_stack_arrays::LARGE_STACK_ARRAYS, len_zero::COMPARISON_TO_EMPTY, len_zero::LEN_WITHOUT_IS_EMPTY, @@ -215,6 +236,7 @@ store.register_lints(&[ loops::ITER_NEXT_LOOP, loops::MANUAL_FLATTEN, loops::MANUAL_MEMCPY, + loops::MISSING_SPIN_LOOP, loops::MUT_RANGE_BOUND, loops::NEEDLESS_COLLECT, loops::NEEDLESS_RANGE_LOOP, @@ -251,6 +273,7 @@ store.register_lints(&[ matches::MATCH_SINGLE_BINDING, matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, matches::MATCH_WILD_ERR_ARM, + matches::NEEDLESS_MATCH, matches::REDUNDANT_PATTERN_MATCHING, matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, matches::SINGLE_MATCH, @@ -269,6 +292,7 @@ store.register_lints(&[ methods::CLONE_DOUBLE_REF, methods::CLONE_ON_COPY, methods::CLONE_ON_REF_PTR, + methods::ERR_EXPECT, methods::EXPECT_FUN_CALL, methods::EXPECT_USED, methods::EXTEND_WITH_DRAIN, @@ -279,11 +303,13 @@ store.register_lints(&[ methods::FLAT_MAP_IDENTITY, methods::FLAT_MAP_OPTION, methods::FROM_ITER_INSTEAD_OF_COLLECT, + methods::GET_LAST_WITH_LEN, methods::GET_UNWRAP, methods::IMPLICIT_CLONE, methods::INEFFICIENT_TO_STRING, methods::INSPECT_FOR_EACH, methods::INTO_ITER_ON_REF, + methods::IS_DIGIT_ASCII_RADIX, methods::ITERATOR_STEP_BY_ZERO, methods::ITER_CLONED_COLLECT, methods::ITER_COUNT, @@ -292,6 +318,7 @@ store.register_lints(&[ methods::ITER_NTH_ZERO, methods::ITER_OVEREAGER_CLONED, methods::ITER_SKIP_NEXT, + methods::ITER_WITH_DRAIN, methods::MANUAL_FILTER_MAP, methods::MANUAL_FIND_MAP, methods::MANUAL_SATURATING_ARITHMETIC, @@ -301,13 +328,17 @@ store.register_lints(&[ methods::MAP_FLATTEN, methods::MAP_IDENTITY, methods::MAP_UNWRAP_OR, + methods::NEEDLESS_OPTION_AS_DEREF, + methods::NEEDLESS_OPTION_TAKE, methods::NEEDLESS_SPLITN, methods::NEW_RET_NO_SELF, + methods::NO_EFFECT_REPLACE, methods::OK_EXPECT, methods::OPTION_AS_REF_DEREF, methods::OPTION_FILTER_MAP, methods::OPTION_MAP_OR_NONE, methods::OR_FUN_CALL, + methods::OR_THEN_UNWRAP, methods::RESULT_MAP_OR_INTO_OPTION, methods::SEARCH_IS_SOME, methods::SHOULD_IMPLEMENT_TRAIT, @@ -319,7 +350,9 @@ store.register_lints(&[ methods::SUSPICIOUS_SPLITN, methods::UNINIT_ASSUMED_INIT, methods::UNNECESSARY_FILTER_MAP, + methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, + methods::UNNECESSARY_JOIN, methods::UNNECESSARY_LAZY_EVALUATIONS, methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, @@ -351,10 +384,11 @@ store.register_lints(&[ missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS, missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES, missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS, + mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION, + mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION, module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, modulo_arithmetic::MODULO_ARITHMETIC, - multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, mut_mutex_lock::MUT_MUTEX_LOCK, @@ -370,7 +404,6 @@ store.register_lints(&[ needless_continue::NEEDLESS_CONTINUE, needless_for_each::NEEDLESS_FOR_EACH, needless_late_init::NEEDLESS_LATE_INIT, - needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF, needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, needless_question_mark::NEEDLESS_QUESTION_MARK, needless_update::NEEDLESS_UPDATE, @@ -389,6 +422,7 @@ store.register_lints(&[ non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY, nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES, octal_escapes::OCTAL_ESCAPES, + only_used_in_recursion::ONLY_USED_IN_RECURSION, open_options::NONSENSICAL_OPEN_OPTIONS, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, @@ -410,17 +444,20 @@ store.register_lints(&[ ptr::PTR_ARG, ptr_eq::PTR_EQ, ptr_offset_with_cast::PTR_OFFSET_WITH_CAST, + pub_use::PUB_USE, question_mark::QUESTION_MARK, ranges::MANUAL_RANGE_CONTAINS, ranges::RANGE_MINUS_ONE, ranges::RANGE_PLUS_ONE, ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, + rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT, redundant_clone::REDUNDANT_CLONE, redundant_closure_call::REDUNDANT_CLOSURE_CALL, redundant_else::REDUNDANT_ELSE, redundant_field_names::REDUNDANT_FIELD_NAMES, redundant_pub_crate::REDUNDANT_PUB_CRATE, + redundant_slicing::DEREF_BY_SLICING, redundant_slicing::REDUNDANT_SLICING, redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, ref_option_ref::REF_OPTION_REF, @@ -439,6 +476,7 @@ store.register_lints(&[ shadow::SHADOW_REUSE, shadow::SHADOW_SAME, shadow::SHADOW_UNRELATED, + significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE, single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES, single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, @@ -451,6 +489,7 @@ store.register_lints(&[ strings::STRING_SLICE, strings::STRING_TO_STRING, strings::STR_TO_STRING, + strings::TRIM_SPLIT_WHITESPACE, strlen_on_c_strings::STRLEN_ON_C_STRINGS, suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS, suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL, @@ -460,7 +499,6 @@ store.register_lints(&[ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, temporary_assignment::TEMPORARY_ASSIGNMENT, to_digit_is_some::TO_DIGIT_IS_SOME, - to_string_in_display::TO_STRING_IN_DISPLAY, trailing_empty_array::TRAILING_EMPTY_ARRAY, trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS, trait_bounds::TYPE_REPETITION_IN_BOUNDS, @@ -474,6 +512,7 @@ store.register_lints(&[ transmute::TRANSMUTE_NUM_TO_BYTES, transmute::TRANSMUTE_PTR_TO_PTR, transmute::TRANSMUTE_PTR_TO_REF, + transmute::TRANSMUTE_UNDEFINED_REPR, transmute::UNSOUND_COLLECTION_TRANSMUTE, transmute::USELESS_TRANSMUTE, transmute::WRONG_TRANSMUTE, @@ -489,7 +528,6 @@ store.register_lints(&[ types::TYPE_COMPLEXITY, types::VEC_BOX, undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS, - undropped_manually_drops::UNDROPPED_MANUALLY_DROPS, unicode::INVISIBLE_CHARACTERS, unicode::NON_ASCII_LITERAL, unicode::UNICODE_NOT_NFC, @@ -501,6 +539,7 @@ store.register_lints(&[ unit_types::UNIT_CMP, unnamed_address::FN_ADDRESS_COMPARISONS, unnamed_address::VTABLE_ADDRESS_COMPARISONS, + unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, @@ -508,6 +547,7 @@ store.register_lints(&[ unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, unused_async::UNUSED_ASYNC, unused_io_amount::UNUSED_IO_AMOUNT, + unused_rounding::UNUSED_ROUNDING, unused_self::UNUSED_SELF, unused_unit::UNUSED_UNIT, unwrap::PANICKING_UNWRAP, @@ -520,7 +560,6 @@ store.register_lints(&[ vec_init_then_push::VEC_INIT_THEN_PUSH, vec_resize_to_zero::VEC_RESIZE_TO_ZERO, verbose_file_reads::VERBOSE_FILE_READS, - wildcard_dependencies::WILDCARD_DEPENDENCIES, wildcard_imports::ENUM_GLOB_USE, wildcard_imports::WILDCARD_IMPORTS, write::PRINTLN_EMPTY_STRING, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index a7353790100..10808af363d 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -13,12 +13,14 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(future_not_send::FUTURE_NOT_SEND), LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(methods::ITER_WITH_DRAIN), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutex_atomic::MUTEX_ATOMIC), LintId::of(mutex_atomic::MUTEX_INTEGER), LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(option_if_let_else::OPTION_IF_LET_ELSE), LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE), @@ -26,6 +28,10 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(strings::STRING_LIT_AS_BYTES), LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY), + LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), + LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::USELESS_TRANSMUTE), + LintId::of(unused_rounding::UNUSED_ROUNDING), LintId::of(use_self::USE_SELF), ]) diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index de72420706b..5684972b8be 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,8 +4,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), - LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(bit_mask::VERBOSE_BIT_MASK), LintId::of(borrow_as_ptr::BORROW_AS_PTR), LintId::of(bytecount::NAIVE_BYTECOUNT), @@ -66,6 +64,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(methods::IMPLICIT_CLONE), LintId::of(methods::INEFFICIENT_TO_STRING), LintId::of(methods::MAP_UNWRAP_OR), + LintId::of(methods::UNNECESSARY_JOIN), LintId::of(misc::FLOAT_CMP), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mut_mut::MUT_MUT), @@ -84,14 +83,12 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(ref_option_ref::REF_OPTION_REF), LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE), LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED), + LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE), LintId::of(strings::STRING_ADD_ASSIGN), - LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), - LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS), LintId::of(transmute::TRANSMUTE_PTR_TO_PTR), LintId::of(types::LINKEDLIST), LintId::of(types::OPTION_OPTION), LintId::of(unicode::UNICODE_NOT_NFC), - LintId::of(unit_types::LET_UNIT_VALUE), LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(unused_async::UNUSED_ASYNC), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index c44ef124bfa..82431863e6c 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -7,9 +7,11 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(escape::BOXED_LOCAL), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(loops::MANUAL_MEMCPY), + LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), @@ -22,7 +24,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(misc::CMP_OWNED), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), LintId::of(vec::USELESS_VEC), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 5a89fdb05a9..a6d3a06dc16 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(as_conversions::AS_CONVERSIONS), LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX), LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), + LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON), LintId::of(casts::FN_TO_NUMERIC_CAST_ANY), LintId::of(create_dir::CREATE_DIR), LintId::of(dbg_macro::DBG_MACRO), @@ -15,6 +16,8 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION), LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS), LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE), + LintId::of(empty_drop::EMPTY_DROP), + LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS), LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS), LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS), LintId::of(exit::EXIT), @@ -24,6 +27,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(indexing_slicing::INDEXING_SLICING), LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL), LintId::of(integer_division::INTEGER_DIVISION), + LintId::of(large_include_file::LARGE_INCLUDE_FILE), LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE), LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION), LintId::of(map_err_ignore::MAP_ERR_IGNORE), @@ -42,6 +46,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS), LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES), LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS), + LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION), LintId::of(module_style::MOD_MODULE_FILES), LintId::of(module_style::SELF_NAMED_MODULE_FILES), LintId::of(modulo_arithmetic::MODULO_ARITHMETIC), @@ -51,6 +56,8 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(panic_unimplemented::UNIMPLEMENTED), LintId::of(panic_unimplemented::UNREACHABLE), LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH), + LintId::of(pub_use::PUB_USE), + LintId::of(redundant_slicing::DEREF_BY_SLICING), LintId::of(same_name_method::SAME_NAME_METHOD), LintId::of(shadow::SHADOW_REUSE), LintId::of(shadow::SHADOW_SAME), @@ -60,6 +67,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(strings::STRING_SLICE), LintId::of(strings::STRING_TO_STRING), LintId::of(strings::STR_TO_STRING), + LintId::of(try_err::TRY_ERR), LintId::of(types::RC_BUFFER), LintId::of(types::RC_MUTEX), LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 05211476ff2..ea2e1082458 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -16,6 +16,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(comparison_chain::COMPARISON_CHAIN), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), LintId::of(dereference::NEEDLESS_BORROW), + LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ), LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(doc::MISSING_SAFETY_DOC), @@ -30,6 +31,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(functions::DOUBLE_MUST_USE), LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::RESULT_UNIT_ERR), + LintId::of(get_first::GET_FIRST), LintId::of(inherent_to_string::INHERENT_TO_STRING), LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS), LintId::of(len_zero::COMPARISON_TO_EMPTY), @@ -59,7 +61,9 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::BYTES_NTH), LintId::of(methods::CHARS_LAST_CMP), LintId::of(methods::CHARS_NEXT_CMP), + LintId::of(methods::ERR_EXPECT), LintId::of(methods::INTO_ITER_ON_REF), + LintId::of(methods::IS_DIGIT_ASCII_RADIX), LintId::of(methods::ITER_CLONED_COLLECT), LintId::of(methods::ITER_NEXT_SLICE), LintId::of(methods::ITER_NTH_ZERO), @@ -103,9 +107,11 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(returns::NEEDLESS_RETURN), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), + LintId::of(strings::TRIM_SPLIT_WHITESPACE), LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME), - LintId::of(try_err::TRY_ERR), + LintId::of(unit_types::LET_UNIT_VALUE), + LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_unit::UNUSED_UNIT), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 10f8ae4b7f7..20bf5a245b1 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -5,17 +5,30 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), - LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), + LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), + LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), + LintId::of(casts::CAST_ABS_TO_UNSIGNED), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), + LintId::of(casts::CAST_ENUM_TRUNCATION), + LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), + LintId::of(drop_forget_ref::DROP_NON_DROP), + LintId::of(drop_forget_ref::FORGET_NON_DROP), + LintId::of(duplicate_mod::DUPLICATE_MOD), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING), LintId::of(loops::EMPTY_LOOP), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::MUT_RANGE_BOUND), + LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::SUSPICIOUS_MAP), LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(octal_escapes::OCTAL_ESCAPES), + LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), + LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), ]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 644c2418289..d611600cf71 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,11 +1,14 @@ // error-pattern:cargo-clippy +#![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(drain_filter)] #![feature(iter_intersperse)] +#![feature(let_chains)] #![feature(let_else)] +#![feature(lint_reasons)] #![feature(once_cell)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] @@ -17,11 +20,15 @@ #![warn(rust_2018_idioms, unused_lifetimes)] // warn on rustc internal lints #![warn(rustc::internal)] +// Disable this rustc lint for now, as it was also done in rustc +#![allow(rustc::potential_query_instability)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; +extern crate rustc_attr; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; @@ -157,6 +164,8 @@ mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; +mod renamed_lints; + // begin lints modules, do not remove this comment, it’s used in `update_lints` mod absurd_extreme_comparisons; mod approx_const; @@ -175,7 +184,8 @@ mod bool_assert_comparison; mod booleans; mod borrow_as_ptr; mod bytecount; -mod cargo_common_metadata; +mod bytes_count_to_len; +mod cargo; mod case_sensitive_file_extension_comparisons; mod casts; mod checked_conversions; @@ -185,6 +195,7 @@ mod collapsible_match; mod comparison_chain; mod copies; mod copy_iterator; +mod crate_in_macro_def; mod create_dir; mod dbg_macro; mod default; @@ -201,9 +212,12 @@ mod doc_link_with_quotes; mod double_comparison; mod double_parens; mod drop_forget_ref; +mod duplicate_mod; mod duration_subsec; mod else_if_without_else; +mod empty_drop; mod empty_enum; +mod empty_structs_with_brackets; mod entry; mod enum_clike; mod enum_variants; @@ -212,24 +226,24 @@ mod equatable_if_let; mod erasing_op; mod escape; mod eta_reduction; -mod eval_order_dependence; mod excessive_bools; mod exhaustive_items; mod exit; mod explicit_write; mod fallible_impl_from; -mod feature_name; mod float_equality_without_abs; mod float_literal; mod floating_point_arithmetic; mod format; mod format_args; +mod format_impl; +mod format_push_string; mod formatting; mod from_over_into; mod from_str_radix_10; mod functions; mod future_not_send; -mod get_last_with_len; +mod get_first; mod identity_op; mod if_let_mutex; mod if_not_else; @@ -252,6 +266,7 @@ mod items_after_statements; mod iter_not_returning_iterator; mod large_const_arrays; mod large_enum_variant; +mod large_include_file; mod large_stack_arrays; mod len_zero; mod let_if_seq; @@ -286,9 +301,9 @@ mod missing_const_for_fn; mod missing_doc; mod missing_enforced_import_rename; mod missing_inline; +mod mixed_read_write_in_expression; mod module_style; mod modulo_arithmetic; -mod multiple_crate_versions; mod mut_key; mod mut_mut; mod mut_mutex_lock; @@ -302,7 +317,6 @@ mod needless_borrowed_ref; mod needless_continue; mod needless_for_each; mod needless_late_init; -mod needless_option_as_deref; mod needless_pass_by_value; mod needless_question_mark; mod needless_update; @@ -316,6 +330,7 @@ mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; mod nonstandard_macro_braces; mod octal_escapes; +mod only_used_in_recursion; mod open_options; mod option_env_unwrap; mod option_if_let_else; @@ -330,8 +345,10 @@ mod precedence; mod ptr; mod ptr_eq; mod ptr_offset_with_cast; +mod pub_use; mod question_mark; mod ranges; +mod rc_clone_in_vec_init; mod redundant_clone; mod redundant_closure_call; mod redundant_else; @@ -351,6 +368,7 @@ mod self_named_constructors; mod semicolon_if_nothing_returned; mod serde_api; mod shadow; +mod significant_drop_in_scrutinee; mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; @@ -364,7 +382,6 @@ mod swap; mod tabs_in_doc_comments; mod temporary_assignment; mod to_digit_is_some; -mod to_string_in_display; mod trailing_empty_array; mod trait_bounds; mod transmute; @@ -372,13 +389,13 @@ mod transmuting_null; mod try_err; mod types; mod undocumented_unsafe_blocks; -mod undropped_manually_drops; mod unicode; mod uninit_vec; mod unit_hash; mod unit_return_expecting_ord; mod unit_types; mod unnamed_address; +mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; mod unnecessary_sort_by; mod unnecessary_wraps; @@ -386,6 +403,7 @@ mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; +mod unused_rounding; mod unused_self; mod unused_unit; mod unwrap; @@ -397,7 +415,6 @@ mod vec; mod vec_init_then_push; mod vec_resize_to_zero; mod verbose_file_reads; -mod wildcard_dependencies; mod wildcard_imports; mod write; mod zero_div_zero; @@ -405,7 +422,7 @@ mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` pub use crate::utils::conf::Conf; -use crate::utils::conf::TryConf; +use crate::utils::conf::{format_error, TryConf}; /// Register all pre expansion lints /// @@ -430,7 +447,6 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se store.register_pre_expansion_pass(|| Box::new(write::Write::default())); store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv })); - store.register_pre_expansion_pass(|| Box::new(dbg_macro::DbgMacro)); } #[doc(hidden)] @@ -451,7 +467,7 @@ pub fn read_conf(sess: &Session) -> Conf { sess.struct_err(&format!( "error reading Clippy's configuration file `{}`: {}", file_name.display(), - error + format_error(error) )) .emit(); } @@ -462,7 +478,7 @@ pub fn read_conf(sess: &Session) -> Conf { /// Register all lints and lint groups with the rustc plugin registry /// /// Used in `./src/driver.rs`. -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { register_removed_non_tool_lints(store); @@ -497,7 +513,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: { store.register_early_pass(|| Box::new(utils::internal_lints::ClippyLintsInternal)); store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce)); - store.register_late_pass(|| Box::new(utils::inspector::DeepCodeInspector)); store.register_late_pass(|| Box::new(utils::internal_lints::CollapsibleCalls)); store.register_late_pass(|| Box::new(utils::internal_lints::CompilerLintFunctions::new())); store.register_late_pass(|| Box::new(utils::internal_lints::IfChainStyle)); @@ -506,10 +521,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default())); store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem)); store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass)); + store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl)); } + store.register_late_pass(|| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|| Box::new(utils::author::Author)); - store.register_late_pass(|| Box::new(await_holding_invalid::AwaitHolding)); + let await_holding_invalid_types = conf.await_holding_invalid_types.clone(); + store.register_late_pass(move || { + Box::new(await_holding_invalid::AwaitHolding::new( + await_holding_invalid_types.clone(), + )) + }); store.register_late_pass(|| Box::new(serde_api::SerdeApi)); let vec_box_size_threshold = conf.vec_box_size_threshold; let type_complexity_threshold = conf.type_complexity_threshold; @@ -531,7 +553,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(ptr::Ptr)); store.register_late_pass(|| Box::new(ptr_eq::PtrEq)); store.register_late_pass(|| Box::new(needless_bool::NeedlessBool)); - store.register_late_pass(|| Box::new(needless_option_as_deref::OptionNeedlessDeref)); store.register_late_pass(|| Box::new(needless_bool::BoolComparison)); store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|| Box::new(misc::MiscLints)); @@ -567,10 +588,20 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: }); let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; + let allow_expect_in_tests = conf.allow_expect_in_tests; + let allow_unwrap_in_tests = conf.allow_unwrap_in_tests; store.register_late_pass(move || Box::new(approx_const::ApproxConstant::new(msrv))); - store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv))); + store.register_late_pass(move || { + Box::new(methods::Methods::new( + avoid_breaking_exported_api, + msrv, + allow_expect_in_tests, + allow_unwrap_in_tests, + )) + }); store.register_late_pass(move || Box::new(matches::Matches::new(msrv))); - store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv))); + store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv))); + store.register_late_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv))); store.register_late_pass(move || Box::new(manual_strip::ManualStrip::new(msrv))); store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv))); store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv))); @@ -623,7 +654,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(strings::StringLitAsBytes)); store.register_late_pass(|| Box::new(derive::Derive)); store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls)); - store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen)); store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef)); store.register_late_pass(|| Box::new(empty_enum::EmptyEnum)); store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons)); @@ -652,7 +682,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(arithmetic::Arithmetic::default())); store.register_late_pass(|| Box::new(assign_ops::AssignOps)); store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq)); - store.register_late_pass(|| Box::new(eval_order_dependence::EvalOrderDependence)); + store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new())); store.register_late_pass(|| Box::new(missing_inline::MissingInline)); store.register_late_pass(move || Box::new(exhaustive_items::ExhaustiveItems)); @@ -706,7 +736,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic)); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - store.register_late_pass(|| Box::new(to_string_in_display::ToStringInDisplay::new())); + store.register_late_pass(|| Box::new(format_impl::FormatImpl::new())); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); @@ -723,10 +753,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|| Box::new(create_dir::CreateDir)); store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - let cargo_ignore_publish = conf.cargo_ignore_publish; - store.register_late_pass(move || Box::new(cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish))); - store.register_late_pass(|| Box::new(multiple_crate_versions::MultipleCrateVersions)); - store.register_late_pass(|| Box::new(wildcard_dependencies::WildcardDependencies)); let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions; store.register_early_pass(move || { Box::new(literal_representation::LiteralDigitGrouping::new( @@ -814,7 +840,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone()))); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); - store.register_late_pass(|| Box::new(undropped_manually_drops::UndroppedManuallyDrops)); + store.register_late_pass(|| Box::new(empty_drop::EmptyDrop)); store.register_late_pass(|| Box::new(strings::StrToString)); store.register_late_pass(|| Box::new(strings::StringToString)); store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues)); @@ -841,7 +867,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts))); store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings)); store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors)); - store.register_late_pass(move || Box::new(feature_name::FeatureName)); store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move || Box::new(manual_assert::ManualAssert)); let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send; @@ -850,7 +875,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: enable_raw_pointer_heuristic_for_send, )) }); - store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default())); + store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks)); store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch)); store.register_late_pass(move || Box::new(format_args::FormatArgs)); store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray)); @@ -863,6 +888,29 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes)); + store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); + store.register_late_pass(|| Box::new(significant_drop_in_scrutinee::SignificantDropInScrutinee)); + let allow_dbg_in_tests = conf.allow_dbg_in_tests; + store.register_late_pass(move || Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests))); + let cargo_ignore_publish = conf.cargo_ignore_publish; + store.register_late_pass(move || { + Box::new(cargo::Cargo { + ignore_publish: cargo_ignore_publish, + }) + }); + store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); + store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets)); + store.register_late_pass(|| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); + store.register_early_pass(|| Box::new(pub_use::PubUse)); + store.register_late_pass(|| Box::new(format_push_string::FormatPushString)); + store.register_late_pass(|| Box::new(bytes_count_to_len::BytesCountToLen)); + let max_include_file_size = conf.max_include_file_size; + store.register_late_pass(move || Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size))); + store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace)); + store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); + store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default())); + store.register_late_pass(|| Box::new(get_first::GetFirst)); + store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); // add lints here, do not remove this comment, it's used in `new_lint` } @@ -914,42 +962,9 @@ fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { /// /// Used in `./src/driver.rs`. pub fn register_renamed(ls: &mut rustc_lint::LintStore) { - // NOTE: when renaming a lint, add a corresponding test to tests/ui/rename.rs - ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions"); - ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); - ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); - ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); - ls.register_renamed("clippy::option_and_then_some", "clippy::bind_instead_of_map"); - ls.register_renamed("clippy::box_vec", "clippy::box_collection"); - ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"); - ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"); - ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"); - ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"); - ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"); - ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used"); - ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used"); - ls.register_renamed("clippy::option_expect_used", "clippy::expect_used"); - ls.register_renamed("clippy::result_expect_used", "clippy::expect_used"); - ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"); - ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"); - ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion"); - ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters"); - ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str"); - ls.register_renamed("clippy::if_let_some_result", "clippy::match_result_ok"); - ls.register_renamed("clippy::disallowed_type", "clippy::disallowed_types"); - ls.register_renamed("clippy::disallowed_method", "clippy::disallowed_methods"); - ls.register_renamed("clippy::ref_in_deref", "clippy::needless_borrow"); - - // uplifted lints - ls.register_renamed("clippy::invalid_ref", "invalid_value"); - ls.register_renamed("clippy::into_iter_on_array", "array_into_iter"); - ls.register_renamed("clippy::unused_label", "unused_labels"); - ls.register_renamed("clippy::drop_bounds", "drop_bounds"); - ls.register_renamed("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"); - ls.register_renamed("clippy::panic_params", "non_fmt_panics"); - ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints"); - ls.register_renamed("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"); - ls.register_renamed("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"); + for (old_name, new_name) in renamed_lints::RENAMED_LINTS { + ls.register_renamed(old_name, new_name); + } } // only exists to let the dogfood integration test works. diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index b09c23f31e9..51d5b510ab9 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -1,16 +1,19 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::trait_ref_of_method; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter}; use rustc_hir::intravisit::{ - walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty, Visitor, + walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, + walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor, }; use rustc_hir::FnRetTy::Return; use rustc_hir::{ - BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, - ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, - TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate, + BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, + TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::nested_filter as middle_nested_filter; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, Ident, Symbol}; @@ -82,8 +85,10 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); impl<'tcx> LateLintPass<'tcx> for Lifetimes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Fn(ref sig, ref generics, id) = item.kind { + if let ItemKind::Fn(ref sig, generics, id) = item.kind { check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true); + } else if let ItemKind::Impl(impl_) = item.kind { + report_extra_impl_lifetimes(cx, impl_); } } @@ -95,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { sig.decl, Some(id), None, - &item.generics, + item.generics, item.span, report_extra_lifetimes, ); @@ -108,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { TraitFn::Required(sig) => (None, Some(sig)), TraitFn::Provided(id) => (Some(id), None), }; - check_fn_inner(cx, sig.decl, body, trait_sig, &item.generics, item.span, true); + check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true); } } } @@ -130,7 +135,7 @@ fn check_fn_inner<'tcx>( span: Span, report_extra_lifetimes: bool, ) { - if span.from_expansion() || has_where_lifetimes(cx, &generics.where_clause) { + if span.from_expansion() || has_where_lifetimes(cx, generics) { return; } @@ -139,28 +144,35 @@ fn check_fn_inner<'tcx>( .iter() .filter(|param| matches!(param.kind, GenericParamKind::Type { .. })); for typ in types { - for bound in typ.bounds { - let mut visitor = RefVisitor::new(cx); - walk_param_bound(&mut visitor, bound); - if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) { - return; + for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) { + if pred.origin == PredicateOrigin::WhereClause { + // has_where_lifetimes checked that this predicate contains no lifetime. + continue; } - if let GenericBound::Trait(ref trait_ref, _) = *bound { - let params = &trait_ref - .trait_ref - .path - .segments - .last() - .expect("a path must have at least one segment") - .args; - if let Some(params) = *params { - let lifetimes = params.args.iter().filter_map(|arg| match arg { - GenericArg::Lifetime(lt) => Some(lt), - _ => None, - }); - for bound in lifetimes { - if bound.name != LifetimeName::Static && !bound.is_elided() { - return; + + for bound in pred.bounds { + let mut visitor = RefVisitor::new(cx); + walk_param_bound(&mut visitor, bound); + if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) { + return; + } + if let GenericBound::Trait(ref trait_ref, _) = *bound { + let params = &trait_ref + .trait_ref + .path + .segments + .last() + .expect("a path must have at least one segment") + .args; + if let Some(params) = *params { + let lifetimes = params.args.iter().filter_map(|arg| match arg { + GenericArg::Lifetime(lt) => Some(lt), + _ => None, + }); + for bound in lifetimes { + if bound.name != LifetimeName::Static && !bound.is_elided() { + return; + } } } } @@ -194,8 +206,7 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: visitor.visit_ty(self_ty); !visitor.all_lts().is_empty() - } - else { + } else { false } } @@ -322,9 +333,7 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet { let mut allowed_lts = FxHashSet::default(); for par in named_generics.iter() { if let GenericParamKind::Lifetime { .. } = par.kind { - if par.bounds.is_empty() { - allowed_lts.insert(RefLt::Named(par.name.ident().name)); - } + allowed_lts.insert(RefLt::Named(par.name.ident().name)); } } allowed_lts.insert(RefLt::Unnamed); @@ -445,8 +454,8 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { /// Are any lifetimes mentioned in the `where` clause? If so, we don't try to /// reason about elision. -fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereClause<'_>) -> bool { - for predicate in where_clause.predicates { +fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_>) -> bool { + for predicate in generics.predicates { match *predicate { WherePredicate::RegionPredicate(..) => return true, WherePredicate::BoundPredicate(ref pred) => { @@ -481,11 +490,29 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl false } -struct LifetimeChecker { +struct LifetimeChecker<'cx, 'tcx, F> { + cx: &'cx LateContext<'tcx>, map: FxHashMap, + phantom: std::marker::PhantomData, } -impl<'tcx> Visitor<'tcx> for LifetimeChecker { +impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> { + fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap) -> LifetimeChecker<'cx, 'tcx, F> { + Self { + cx, + map, + phantom: std::marker::PhantomData, + } + } +} + +impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F> +where + F: NestedFilter<'tcx>, +{ + type Map = rustc_middle::hir::map::Map<'tcx>; + type NestedFilter = F; + // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { self.map.remove(&lifetime.name.ident().name); @@ -501,6 +528,10 @@ impl<'tcx> Visitor<'tcx> for LifetimeChecker { walk_generic_param(self, param); } } + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } } fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) { @@ -512,7 +543,7 @@ fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, _ => None, }) .collect(); - let mut checker = LifetimeChecker { map: hs }; + let mut checker = LifetimeChecker::::new(cx, hs); walk_generics(&mut checker, generics); walk_fn_decl(&mut checker, func); @@ -527,6 +558,32 @@ fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, } } +fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) { + let hs = impl_ + .generics + .params + .iter() + .filter_map(|par| match par.kind { + GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)), + _ => None, + }) + .collect(); + let mut checker = LifetimeChecker::::new(cx, hs); + + walk_generics(&mut checker, impl_.generics); + if let Some(ref trait_ref) = impl_.of_trait { + walk_trait_ref(&mut checker, trait_ref); + } + walk_ty(&mut checker, impl_.self_ty); + for item in impl_.items { + walk_impl_item_ref(&mut checker, item); + } + + for &v in checker.map.values() { + span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl"); + } +} + struct BodyLifetimeChecker { lifetimes_used_in_body: bool, } diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index b7430f49229..9998712b852 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -42,17 +42,12 @@ declare_clippy_lint! { /// This is most probably a typo /// /// ### Known problems - /// - Recommends a signed suffix, even though the number might be too big and an unsigned - /// suffix is required + /// - Does not match on integers too large to fit in the corresponding unsigned type /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers /// /// ### Example - /// ```rust - /// // Probably mistyped - /// 2_32; - /// - /// // Good - /// 2_i32; + /// `2_32` => `2_i32` + /// `250_8 => `250_u8` /// ``` #[clippy::version = "1.30.0"] pub MISTYPED_LITERAL_SUFFIXES, @@ -209,7 +204,6 @@ impl WarningType { } } -#[allow(clippy::module_name_repetitions)] #[derive(Copy, Clone)] pub struct LiteralDigitGrouping { lint_fraction_readability: bool, @@ -310,18 +304,47 @@ impl LiteralDigitGrouping { return true; } - let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent { - (exponent, &["32", "64"][..], 'f') + let (part, mistyped_suffixes, is_float) = if let Some((_, exponent)) = &mut num_lit.exponent { + (exponent, &["32", "64"][..], true) } else if num_lit.fraction.is_some() { - (&mut num_lit.integer, &["32", "64"][..], 'f') + return true; } else { - (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i') + (&mut num_lit.integer, &["8", "16", "32", "64"][..], false) }; let mut split = part.rsplit('_'); let last_group = split.next().expect("At least one group"); if split.next().is_some() && mistyped_suffixes.contains(&last_group) { - *part = &part[..part.len() - last_group.len()]; + let main_part = &part[..part.len() - last_group.len()]; + let missing_char; + if is_float { + missing_char = 'f'; + } else { + let radix = match num_lit.radix { + Radix::Binary => 2, + Radix::Octal => 8, + Radix::Decimal => 10, + Radix::Hexadecimal => 16, + }; + if let Ok(int) = u64::from_str_radix(&main_part.replace('_', ""), radix) { + missing_char = match (last_group, int) { + ("8", i) if i8::try_from(i).is_ok() => 'i', + ("16", i) if i16::try_from(i).is_ok() => 'i', + ("32", i) if i32::try_from(i).is_ok() => 'i', + ("64", i) if i64::try_from(i).is_ok() => 'i', + ("8", u) if u8::try_from(u).is_ok() => 'u', + ("16", u) if u16::try_from(u).is_ok() => 'u', + ("32", u) if u32::try_from(u).is_ok() => 'u', + ("64", _) => 'u', + _ => { + return true; + }, + } + } else { + return true; + } + } + *part = main_part; let mut sugg = num_lit.format(); sugg.push('_'); sugg.push(missing_char); @@ -408,7 +431,7 @@ impl LiteralDigitGrouping { } } -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] #[derive(Copy, Clone)] pub struct DecimalLiteralRepresentation { threshold: u64, diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index e0150990cfe..fc50e8addcc 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::{Expr, Pat}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, UintTy}; +use rustc_middle::ty::{self, Ty, UintTy}; // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero @@ -36,7 +36,7 @@ pub(super) fn check<'tcx>( then { let mut applicability = Applicability::MachineApplicable; - let int_name = match ty.map(ty::TyS::kind) { + let int_name = match ty.map(Ty::kind) { // usize or inferred Some(ty::Uint(UintTy::Usize)) | None => { span_lint_and_sugg( diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs index 17246cc5426..175e2b382e3 100644 --- a/clippy_lints/src/loops/explicit_into_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -5,13 +5,12 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::symbol::sym; pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) { let self_ty = cx.typeck_results().expr_ty(self_arg); let self_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg); - if !(TyS::same_type(self_ty, self_ty_adjusted) && is_trait_method(cx, call_expr, sym::IntoIterator)) { + if !(self_ty == self_ty_adjusted && is_trait_method(cx, call_expr, sym::IntoIterator)) { return; } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 5ac69d106ce..5f5beccd030 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -6,7 +6,7 @@ use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) { @@ -22,7 +22,7 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m mutbl: Mutability::Not, }, ); - TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) + receiver_ty_adjusted == ref_receiver_ty }, _ => false, }; diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index ef0221639aa..b31015d195b 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -334,9 +334,9 @@ struct Start<'hir> { fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { match ty.kind() { - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did) => Some(subs.type_at(0)), - ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, subty), - ty::Slice(ty) | ty::Array(ty, _) => Some(ty), + ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did()) => Some(subs.type_at(0)), + ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, *subty), + ty::Slice(ty) | ty::Array(ty, _) => Some(*ty), _ => None, } } diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs new file mode 100644 index 00000000000..0696afa3922 --- /dev/null +++ b/clippy_lints/src/loops/missing_spin_loop.rs @@ -0,0 +1,56 @@ +use super::MISSING_SPIN_LOOP; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_no_std_crate; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::sym; + +fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + match &cond.kind { + ExprKind::Block( + Block { + stmts: [], + expr: Some(e), + .. + }, + _, + ) + | ExprKind::Unary(_, e) => unpack_cond(e), + ExprKind::Binary(_, l, r) => { + let l = unpack_cond(l); + if let ExprKind::MethodCall(..) = l.kind { + l + } else { + unpack_cond(r) + } + }, + _ => cond, + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind; + if let ExprKind::MethodCall(method, [callee, ..], _) = unpack_cond(cond).kind; + if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name); + if let ty::Adt(def, _substs) = cx.typeck_results().expr_ty(callee).kind(); + if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()); + then { + span_lint_and_sugg( + cx, + MISSING_SPIN_LOOP, + body.span, + "busy-waiting loop should at least have a spin loop hint", + "try this", + (if is_no_std_crate(cx) { + "{ core::hint::spin_loop() }" + } else { + "{ std::hint::spin_loop() }" + }).into(), + Applicability::MachineApplicable + ); + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 5bc32acf56e..75d771f992a 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -7,6 +7,7 @@ mod for_loops_over_fallibles; mod iter_next_loop; mod manual_flatten; mod manual_memcpy; +mod missing_spin_loop; mod mut_range_bound; mod needless_collect; mod needless_range_loop; @@ -560,6 +561,42 @@ declare_clippy_lint! { "for loops over `Option`s or `Result`s with a single expression can be simplified" } +declare_clippy_lint! { + /// ### What it does + /// Check for empty spin loops + /// + /// ### Why is this bad? + /// The loop body should have something like `thread::park()` or at least + /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve + /// energy. Perhaps even better use an actual lock, if possible. + /// + /// ### Known problems + /// This lint doesn't currently trigger on `while let` or + /// `loop { match .. { .. } }` loops, which would be considered idiomatic in + /// combination with e.g. `AtomicBool::compare_exchange_weak`. + /// + /// ### Example + /// + /// ```ignore + /// use core::sync::atomic::{AtomicBool, Ordering}; + /// let b = AtomicBool::new(true); + /// // give a ref to `b` to another thread,wait for it to become false + /// while b.load(Ordering::Acquire) {}; + /// ``` + /// Use instead: + /// ```rust,no_run + ///# use core::sync::atomic::{AtomicBool, Ordering}; + ///# let b = AtomicBool::new(true); + /// while b.load(Ordering::Acquire) { + /// std::hint::spin_loop() + /// } + /// ``` + #[clippy::version = "1.59.0"] + pub MISSING_SPIN_LOOP, + perf, + "An empty busy waiting loop" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -579,10 +616,10 @@ declare_lint_pass!(Loops => [ WHILE_IMMUTABLE_CONDITION, SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, + MISSING_SPIN_LOOP, ]); impl<'tcx> LateLintPass<'tcx> for Loops { - #[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let for_loop = higher::ForLoop::hir(expr); if let Some(higher::ForLoop { @@ -628,6 +665,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let Some(higher::While { condition, body }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); + missing_spin_loop::check(cx, condition, body); } needless_collect::check(expr, cx); diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index f57dcc2f5c4..ddaffc75188 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -6,15 +6,15 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::intravisit::{walk_block, walk_expr, Visitor}; use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_span::sym; -use rustc_span::{MultiSpan, Span}; +use rustc_span::Span; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; @@ -102,7 +102,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo // Suggest replacing iter_call with iter_replacement, and removing stmt let mut span = MultiSpan::from_span(method_name.ident.span); - span.push_span_label(iter_call.span, "the iterator could be used here instead".into()); + span.push_span_label(iter_call.span, "the iterator could be used here instead"); span_lint_hir_and_then( cx, super::NEEDLESS_COLLECT, @@ -334,8 +334,8 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>( } } -fn get_captured_ids(cx: &LateContext<'_>, ty: &'_ TyS<'_>) -> HirIdSet { - fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: &'_ TyS<'_>, set: &mut HirIdSet) { +fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet { + fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) { match ty.kind() { ty::Adt(_, generics) => { for generic in *generics { diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 9d335073e4f..09f9c05b4fc 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -19,7 +19,7 @@ use std::mem; /// Checks for looping over a range and then indexing a sequence with it. /// The iteratee must be a range literal. -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, @@ -59,7 +59,7 @@ pub(super) fn check<'tcx>( if let Some(indexed_extent) = indexed_extent { let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id); let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { return; } @@ -262,7 +262,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { match res { Res::Local(hir_id) => { let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); - let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); + let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id).unwrap(); if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, @@ -273,7 +273,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { } return false; // no need to walk further *on the variable* } - Res::Def(DefKind::Static | DefKind::Const, ..) => { + Res::Def(DefKind::Static (_)| DefKind::Const, ..) => { if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index a0b2302662e..70a118d6b35 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -168,14 +168,16 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { .operands .iter() .map(|(o, _)| match o { - InlineAsmOperand::In { expr, .. } - | InlineAsmOperand::InOut { expr, .. } - | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { + never_loop_expr(expr, main_loop_id) + }, InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) }, - InlineAsmOperand::Const { .. } => NeverLoopResult::Otherwise, + InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) .fold(NeverLoopResult::Otherwise, combine_both), ExprKind::Struct(_, _, None) diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index 15f419e4410..a0bd7ad0ac6 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -1,11 +1,13 @@ use super::SINGLE_ELEMENT_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::single_segment_path; -use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::source::{indent_of, snippet_with_applicability}; use if_chain::if_chain; +use rustc_ast::util::parser::PREC_PREFIX; +use rustc_ast::Mutability; use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{is_range_literal, BorrowKind, Expr, ExprKind, Pat}; use rustc_lint::LateContext; +use rustc_span::edition::Edition; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, @@ -14,26 +16,76 @@ pub(super) fn check<'tcx>( body: &'tcx Expr<'_>, expr: &'tcx Expr<'_>, ) { - let arg_expr = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg, - ExprKind::MethodCall(method, args, _) if args.len() == 1 && method.ident.name == rustc_span::sym::iter => { - &args[0] - }, + let (arg_expression, prefix) = match arg.kind { + ExprKind::AddrOf( + BorrowKind::Ref, + Mutability::Not, + Expr { + kind: ExprKind::Array([arg]), + .. + }, + ) => (arg, "&"), + ExprKind::AddrOf( + BorrowKind::Ref, + Mutability::Mut, + Expr { + kind: ExprKind::Array([arg]), + .. + }, + ) => (arg, "&mut "), + ExprKind::MethodCall( + method, + [ + Expr { + kind: ExprKind::Array([arg]), + .. + }, + ], + _, + ) if method.ident.name == rustc_span::sym::iter => (arg, "&"), + ExprKind::MethodCall( + method, + [ + Expr { + kind: ExprKind::Array([arg]), + .. + }, + ], + _, + ) if method.ident.name.as_str() == "iter_mut" => (arg, "&mut "), + ExprKind::MethodCall( + method, + [ + Expr { + kind: ExprKind::Array([arg]), + .. + }, + ], + _, + ) if method.ident.name == rustc_span::sym::into_iter => (arg, ""), + // Only check for arrays edition 2021 or later, as this case will trigger a compiler error otherwise. + ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""), _ => return, }; if_chain! { - if let PatKind::Binding(.., target, _) = pat.kind; - if let ExprKind::Array([arg_expression]) = arg_expr.kind; - if let ExprKind::Path(ref list_item) = arg_expression.kind; - if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); if let ExprKind::Block(block, _) = body.kind; if !block.stmts.is_empty(); - then { - let mut block_str = snippet(cx, block.span, "..").into_owned(); + let mut applicability = Applicability::MachineApplicable; + let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability); + let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability); + let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned(); block_str.remove(0); block_str.pop(); + let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)); + // Reference iterator from `&(mut) []` or `[].iter(_mut)()`. + if !prefix.is_empty() && ( + // Precedence of internal expression is less than or equal to precedence of `&expr`. + arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression) + ) { + arg_snip = format!("({arg_snip})").into(); + } span_lint_and_sugg( cx, @@ -41,8 +93,8 @@ pub(super) fn check<'tcx>( expr.span, "for loop over a single element", "try", - format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), - Applicability::MachineApplicable + format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"), + applicability, ) } } diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index eac0f03b142..4801a84eb92 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -7,13 +7,13 @@ use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor} use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Symbol}; use rustc_typeck::hir_ty_to_ty; use std::iter::Iterator; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] enum IncrementVisitorVarState { Initial, // Not examined yet IncrOnce, // Incremented exactly once, may be a loop counter @@ -332,18 +332,21 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic } else { // (&x).into_iter() ==> x.iter() // (&mut x).into_iter() ==> x.iter_mut() - match &arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) - if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() => - { - let meth_name = match mutability { + let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); + match &arg_ty.kind() { + ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, *inner_ty).is_some() => { + let method_name = match mutbl { Mutability::Mut => "iter_mut", Mutability::Not => "iter", }; + let caller = match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner, + _ => arg, + }; format!( "{}.{}()", - sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(), - meth_name, + sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(), + method_name, ) }, _ => format!( diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index 5dcfed65c78..a63422d2a36 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -104,7 +104,7 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { Res::Local(hir_id) => { self.ids.insert(hir_id); }, - Res::Def(DefKind::Static, def_id) => { + Res::Def(DefKind::Static(_), def_id) => { let mutable = self.cx.tcx.is_mutable_static(def_id); self.def_ids.insert(def_id, mutable); }, diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 20a8294a0d1..82760607ba2 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -239,7 +239,7 @@ fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tc v.uses_iter } -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &Expr<'_>) -> bool { struct AfterLoopVisitor<'a, 'b, 'tcx> { cx: &'a LateContext<'tcx>, diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index 76c5cfadc2c..da806918be0 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -49,7 +49,7 @@ impl MacroRefData { } #[derive(Default)] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub struct MacroUseImports { /// the actual import path used and the span of the attribute above it. imports: Vec<(String, Span)>, @@ -135,7 +135,6 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports { self.push_unique_macro_pat_ty(cx, ty.span); } } - #[allow(clippy::too_many_lines)] fn check_crate_post(&mut self, cx: &LateContext<'_>) { let mut used = FxHashMap::default(); let mut check_dup = vec![]; diff --git a/clippy_lints/src/main_recursion.rs b/clippy_lints/src/main_recursion.rs index fad8fa467d4..20333c150e3 100644 --- a/clippy_lints/src/main_recursion.rs +++ b/clippy_lints/src/main_recursion.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Apart from special setups (which we could detect following attributes like #![no_std]), - /// recursing into main() seems like an unintuitive antipattern we should be able to detect. + /// recursing into main() seems like an unintuitive anti-pattern we should be able to detect. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 809aa168a7a..60bbcde4f1d 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_opt; -use clippy_utils::{match_def_path, meets_msrv, msrvs, paths}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{get_parent_expr, meets_msrv, msrvs}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; @@ -8,6 +8,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -23,7 +24,7 @@ declare_clippy_lint! { /// ``` /// Use instead: /// ```rust - /// usize::BITS; + /// usize::BITS as usize; /// ``` #[clippy::version = "1.60.0"] pub MANUAL_BITS, @@ -47,7 +48,7 @@ impl_lint_pass!(ManualBits => [MANUAL_BITS]); impl<'tcx> LateLintPass<'tcx> for ManualBits { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv.as_ref(), &msrvs::MANUAL_BITS) { + if !meets_msrv(self.msrv, msrvs::MANUAL_BITS) { return; } @@ -58,16 +59,19 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_)); if let ExprKind::Lit(lit) = &other_expr.kind; if let LitKind::Int(8, _) = lit.node; - then { + let mut app = Applicability::MachineApplicable; + let ty_snip = snippet_with_applicability(cx, real_ty.span, "..", &mut app); + let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS")); + span_lint_and_sugg( cx, MANUAL_BITS, expr.span, "usage of `mem::size_of::()` to obtain the size of `T` in bits", "consider using", - format!("{}::BITS", snippet_opt(cx, real_ty.span).unwrap()), - Applicability::MachineApplicable, + sugg, + app, ); } } @@ -99,7 +103,7 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option< if let Some(GenericArg::Type(real_ty)) = args.args.get(0); if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::MEM_SIZE_OF); + if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id); then { cx.typeck_results().node_substs(count_func.hir_id).types().next().map(|resolved_ty| (real_ty, resolved_ty)) } else { @@ -107,3 +111,36 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option< } } } + +fn create_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, base_sugg: String) -> String { + if let Some(parent_expr) = get_parent_expr(cx, expr) { + if is_ty_conversion(parent_expr) { + return base_sugg; + } + + // These expressions have precedence over casts, the suggestion therefore + // needs to be wrapped into parentheses + match parent_expr.kind { + ExprKind::Unary(..) | ExprKind::AddrOf(..) | ExprKind::MethodCall(..) => { + return format!("({base_sugg} as usize)"); + }, + _ => {}, + } + } + + format!("{base_sugg} as usize") +} + +fn is_ty_conversion(expr: &Expr<'_>) -> bool { + if let ExprKind::Cast(..) = expr.kind { + true + } else if let ExprKind::MethodCall(path, [_], _) = expr.kind + && path.ident.name == rustc_span::sym::try_into + { + // This is only called for `usize` which implements `TryInto`. Therefore, + // we don't have to check here if `self` implements the `TryInto` trait. + true + } else { + false + } +} diff --git a/clippy_lints/src/manual_map.rs b/clippy_lints/src/manual_map.rs index 8475e367b09..230ae029ed9 100644 --- a/clippy_lints/src/manual_map.rs +++ b/clippy_lints/src/manual_map.rs @@ -46,7 +46,7 @@ declare_clippy_lint! { declare_lint_pass!(ManualMap => [MANUAL_MAP]); impl<'tcx> LateLintPass<'tcx> for ManualMap { - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) { Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else), diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 33d1bb2985f..80845ace3f9 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -1,13 +1,16 @@ -use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; -use clippy_utils::{meets_msrv, msrvs}; -use if_chain::if_chain; -use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind}; +use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs}; +use rustc_ast::ast::{self, VisibilityKind}; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath}; +use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::DefIdTree; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -58,129 +61,160 @@ declare_clippy_lint! { "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]" } -#[derive(Clone)] -pub struct ManualNonExhaustive { +#[expect(clippy::module_name_repetitions)] +pub struct ManualNonExhaustiveStruct { msrv: Option, } -impl ManualNonExhaustive { +impl ManualNonExhaustiveStruct { #[must_use] pub fn new(msrv: Option) -> Self { Self { msrv } } } -impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]); +impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]); -impl EarlyLintPass for ManualNonExhaustive { - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) { +#[expect(clippy::module_name_repetitions)] +pub struct ManualNonExhaustiveEnum { + msrv: Option, + constructed_enum_variants: FxHashSet<(DefId, DefId)>, + potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>, +} + +impl ManualNonExhaustiveEnum { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + constructed_enum_variants: FxHashSet::default(), + potential_enums: Vec::new(), + } + } +} + +impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]); + +impl EarlyLintPass for ManualNonExhaustiveStruct { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) { return; } - match &item.kind { - ItemKind::Enum(def, _) => { - check_manual_non_exhaustive_enum(cx, item, &def.variants); - }, - ItemKind::Struct(variant_data, _) => { - if let VariantData::Unit(..) = variant_data { - return; - } - - check_manual_non_exhaustive_struct(cx, item, variant_data); - }, - _ => {}, + if let ast::ItemKind::Struct(variant_data, _) = &item.kind { + let (fields, delimiter) = match variant_data { + ast::VariantData::Struct(fields, _) => (&**fields, '{'), + ast::VariantData::Tuple(fields, _) => (&**fields, '('), + ast::VariantData::Unit(_) => return, + }; + if fields.len() <= 1 { + return; + } + let mut iter = fields.iter().filter_map(|f| match f.vis.kind { + VisibilityKind::Public => None, + VisibilityKind::Inherited => Some(Ok(f)), + _ => Some(Err(())), + }); + if let Some(Ok(field)) = iter.next() + && iter.next().is_none() + && field.ty.kind.is_unit() + && field.ident.map_or(true, |name| name.as_str().starts_with('_')) + { + span_lint_and_then( + cx, + MANUAL_NON_EXHAUSTIVE, + item.span, + "this seems like a manual implementation of the non-exhaustive pattern", + |diag| { + if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive)) + && let header_span = cx.sess().source_map().span_until_char(item.span, delimiter) + && let Some(snippet) = snippet_opt(cx, header_span) + { + diag.span_suggestion( + header_span, + "add the attribute", + format!("#[non_exhaustive] {}", snippet), + Applicability::Unspecified, + ); + } + diag.span_help(field.span, "remove this field"); + } + ); + } } } extract_msrv_attr!(EarlyContext); } -fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) { - fn is_non_exhaustive_marker(variant: &Variant) -> bool { - matches!(variant.data, VariantData::Unit(_)) - && variant.ident.as_str().starts_with('_') - && is_doc_hidden(&variant.attrs) +impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) { + return; + } + + if let hir::ItemKind::Enum(def, _) = &item.kind + && def.variants.len() > 1 + { + let mut iter = def.variants.iter().filter_map(|v| { + let id = cx.tcx.hir().local_def_id(v.id); + (matches!(v.data, hir::VariantData::Unit(_)) + && v.ident.as_str().starts_with('_') + && is_doc_hidden(cx.tcx.hir().attrs(v.id))) + .then(|| (id, v.span)) + }); + if let Some((id, span)) = iter.next() + && iter.next().is_none() + { + self.potential_enums.push((item.def_id, id, item.span, span)); + } + } } - let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v)); - if_chain! { - if let Some(marker) = markers.next(); - if markers.count() == 0 && variants.len() > 1; - then { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind + && let [.., name] = p.segments + && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res + && name.ident.as_str().starts_with('_') + { + let variant_id = cx.tcx.parent(id); + let enum_id = cx.tcx.parent(variant_id); + + self.constructed_enum_variants.insert((enum_id, variant_id)); + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + for &(enum_id, _, enum_span, variant_span) in + self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| { + !self + .constructed_enum_variants + .contains(&(enum_id.to_def_id(), variant_id.to_def_id())) + && !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id)) + }) + { span_lint_and_then( cx, MANUAL_NON_EXHAUSTIVE, - item.span, + enum_span, "this seems like a manual implementation of the non-exhaustive pattern", |diag| { - if_chain! { - if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive)); - let header_span = cx.sess().source_map().span_until_char(item.span, '{'); - if let Some(snippet) = snippet_opt(cx, header_span); - then { + if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive() + && let header_span = cx.sess().source_map().span_until_char(enum_span, '{') + && let Some(snippet) = snippet_opt(cx, header_span) + { diag.span_suggestion( header_span, "add the attribute", format!("#[non_exhaustive] {}", snippet), Applicability::Unspecified, ); - } } - diag.span_help(marker.span, "remove this variant"); - }); - } - } -} - -fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) { - fn is_private(field: &FieldDef) -> bool { - matches!(field.vis.kind, VisibilityKind::Inherited) - } - - fn is_non_exhaustive_marker(field: &FieldDef) -> bool { - is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_')) - } - - fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span { - let delimiter = match data { - VariantData::Struct(..) => '{', - VariantData::Tuple(..) => '(', - VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"), - }; - - cx.sess().source_map().span_until_char(item.span, delimiter) - } - - let fields = data.fields(); - let private_fields = fields.iter().filter(|f| is_private(f)).count(); - let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count(); - - if_chain! { - if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1; - if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f)); - then { - span_lint_and_then( - cx, - MANUAL_NON_EXHAUSTIVE, - item.span, - "this seems like a manual implementation of the non-exhaustive pattern", - |diag| { - if_chain! { - if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive)); - let header_span = find_header_span(cx, item, data); - if let Some(snippet) = snippet_opt(cx, header_span); - then { - diag.span_suggestion( - header_span, - "add the attribute", - format!("#[non_exhaustive] {}", snippet), - Applicability::Unspecified, - ); - } - } - diag.span_help(marker.span, "remove this field"); - }); + diag.span_help(variant_span, "remove this variant"); + }, + ); } } + + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index aacabf303a7..dfb3efc4e28 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -68,7 +68,7 @@ enum StripKind { impl<'tcx> LateLintPass<'tcx> for ManualStrip { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) { + if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) { return; } diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index 3f8eeb736fb..a13d191375b 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -100,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { let obj_ty = cx.typeck_results().expr_ty(obj); if let ty::Ref(_, ty, mutability) = obj_ty.kind() { if matches!(mutability, Mutability::Not) { - let copy = is_copy(cx, ty); + let copy = is_copy(cx, *ty); self.lint_explicit_closure(cx, e.span, args[0].span, copy); } } else { @@ -143,15 +143,11 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { impl MapClone { fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) { let mut applicability = Applicability::MachineApplicable; - let message = if is_copy { - "you are using an explicit closure for copying elements" + + let (message, sugg_method) = if is_copy && meets_msrv(self.msrv, msrvs::ITERATOR_COPIED) { + ("you are using an explicit closure for copying elements", "copied") } else { - "you are using an explicit closure for cloning elements" - }; - let sugg_method = if is_copy && meets_msrv(self.msrv.as_ref(), &msrvs::ITERATOR_COPIED) { - "copied" - } else { - "cloned" + ("you are using an explicit closure for cloning elements", "cloned") }; span_lint_and_sugg( diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 0f6ac478432..f552d5c1afa 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{iter_input_pats, method_chain_args}; use if_chain::if_chain; @@ -217,36 +217,33 @@ fn lint_map_unit_fn(cx: &LateContext<'_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr let fn_arg = &map_args[1]; if is_unit_function(cx, fn_arg) { + let mut applicability = Applicability::MachineApplicable; let msg = suggestion_msg("function", map_type); let suggestion = format!( "if let {0}({binding}) = {1} {{ {2}({binding}) }}", variant, - snippet(cx, var_arg.span, "_"), - snippet(cx, fn_arg.span, "_"), + snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), + snippet_with_applicability(cx, fn_arg.span, "_", &mut applicability), binding = let_binding_name(cx, var_arg) ); span_lint_and_then(cx, lint, expr.span, &msg, |diag| { - diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable); + diag.span_suggestion(stmt.span, "try this", suggestion, applicability); }); } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) { let msg = suggestion_msg("closure", map_type); span_lint_and_then(cx, lint, expr.span, &msg, |diag| { if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + let mut applicability = Applicability::MachineApplicable; let suggestion = format!( "if let {0}({1}) = {2} {{ {3} }}", variant, - snippet(cx, binding.pat.span, "_"), - snippet(cx, var_arg.span, "_"), - snippet(cx, reduced_expr_span, "_") - ); - diag.span_suggestion( - stmt.span, - "try this", - suggestion, - Applicability::MachineApplicable, // snippet + snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability), + snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), + snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0, ); + diag.span_suggestion(stmt.span, "try this", suggestion, applicability); } else { let suggestion = format!( "if let {0}({1}) = {2} {{ ... }}", diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index 77a4917ec58..3349b85f134 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -24,7 +24,7 @@ declare_clippy_lint! { /// vec.push(value) /// } /// - /// if let Some(valie) = iter.next().ok() { + /// if let Some(value) = iter.next().ok() { /// vec.push(value) /// } /// ``` @@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { if_chain! { if let ExprKind::MethodCall(ok_path, [ref result_types_0, ..], _) = let_expr.kind; //check is expr.ok() has type Result.ok(, _) if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation - if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized; + if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() method use std::marker::Sized; if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result); if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some"; diff --git a/clippy_lints/src/match_str_case_mismatch.rs b/clippy_lints/src/match_str_case_mismatch.rs index 85aec93670b..d97a878825a 100644 --- a/clippy_lints/src/match_str_case_mismatch.rs +++ b/clippy_lints/src/match_str_case_mismatch.rs @@ -21,7 +21,6 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let text = "Foo"; - /// /// match &*text.to_ascii_lowercase() { /// "foo" => {}, /// "Bar" => {}, @@ -31,7 +30,6 @@ declare_clippy_lint! { /// Use instead: /// ```rust /// # let text = "Foo"; - /// /// match &*text.to_ascii_lowercase() { /// "foo" => {}, /// "bar" => {}, diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs deleted file mode 100644 index e0cbadeb645..00000000000 --- a/clippy_lints/src/matches.rs +++ /dev/null @@ -1,2437 +0,0 @@ -use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt}; -use clippy_utils::diagnostics::{ - multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, -}; -use clippy_utils::macros::{is_panic, root_macro_call}; -use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; -use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs}; -use clippy_utils::visitors::is_local_used; -use clippy_utils::{ - get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, - path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, - strip_pat_refs, -}; -use clippy_utils::{higher, peel_blocks_with_stmt}; -use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; -use core::iter::{once, ExactSizeIterator}; -use if_chain::if_chain; -use rustc_ast::ast::{Attribute, LitKind}; -use rustc_errors::Applicability; -use rustc_hir::def::{CtorKind, DefKind, Res}; -use rustc_hir::LangItem::{OptionNone, OptionSome}; -use rustc_hir::{ - self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, - Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, -}; -use rustc_hir::{HirIdMap, HirIdSet}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, TyS, VariantDef}; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::{Span, Spanned}; -use rustc_span::{sym, symbol::kw}; -use std::cmp::{max, Ordering}; -use std::collections::hash_map::Entry; - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches with a single arm where an `if let` - /// will usually suffice. - /// - /// ### Why is this bad? - /// Just readability – `if let` nests less than a `match`. - /// - /// ### Example - /// ```rust - /// # fn bar(stool: &str) {} - /// # let x = Some("abc"); - /// // Bad - /// match x { - /// Some(ref foo) => bar(foo), - /// _ => (), - /// } - /// - /// // Good - /// if let Some(ref foo) = x { - /// bar(foo); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SINGLE_MATCH, - style, - "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches with two arms where an `if let else` will - /// usually suffice. - /// - /// ### Why is this bad? - /// Just readability – `if let` nests less than a `match`. - /// - /// ### Known problems - /// Personal style preferences may differ. - /// - /// ### Example - /// Using `match`: - /// - /// ```rust - /// # fn bar(foo: &usize) {} - /// # let other_ref: usize = 1; - /// # let x: Option<&usize> = Some(&1); - /// match x { - /// Some(ref foo) => bar(foo), - /// _ => bar(&other_ref), - /// } - /// ``` - /// - /// Using `if let` with `else`: - /// - /// ```rust - /// # fn bar(foo: &usize) {} - /// # let other_ref: usize = 1; - /// # let x: Option<&usize> = Some(&1); - /// if let Some(ref foo) = x { - /// bar(foo); - /// } else { - /// bar(&other_ref); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SINGLE_MATCH_ELSE, - pedantic, - "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches where all arms match a reference, - /// suggesting to remove the reference and deref the matched expression - /// instead. It also checks for `if let &foo = bar` blocks. - /// - /// ### Why is this bad? - /// It just makes the code less readable. That reference - /// destructuring adds nothing to the code. - /// - /// ### Example - /// ```rust,ignore - /// // Bad - /// match x { - /// &A(ref y) => foo(y), - /// &B => bar(), - /// _ => frob(&x), - /// } - /// - /// // Good - /// match *x { - /// A(ref y) => foo(y), - /// B => bar(), - /// _ => frob(x), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_REF_PATS, - style, - "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches where match expression is a `bool`. It - /// suggests to replace the expression with an `if...else` block. - /// - /// ### Why is this bad? - /// It makes the code less readable. - /// - /// ### Example - /// ```rust - /// # fn foo() {} - /// # fn bar() {} - /// let condition: bool = true; - /// match condition { - /// true => foo(), - /// false => bar(), - /// } - /// ``` - /// Use if/else instead: - /// ```rust - /// # fn foo() {} - /// # fn bar() {} - /// let condition: bool = true; - /// if condition { - /// foo(); - /// } else { - /// bar(); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_BOOL, - pedantic, - "a `match` on a boolean expression instead of an `if..else` block" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for overlapping match arms. - /// - /// ### Why is this bad? - /// It is likely to be an error and if not, makes the code - /// less obvious. - /// - /// ### Example - /// ```rust - /// let x = 5; - /// match x { - /// 1..=10 => println!("1 ... 10"), - /// 5..=15 => println!("5 ... 15"), - /// _ => (), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_OVERLAPPING_ARM, - style, - "a `match` with overlapping arms" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for arm which matches all errors with `Err(_)` - /// and take drastic actions like `panic!`. - /// - /// ### Why is this bad? - /// It is generally a bad practice, similar to - /// catching all exceptions in java with `catch(Exception)` - /// - /// ### Example - /// ```rust - /// let x: Result = Ok(3); - /// match x { - /// Ok(_) => println!("ok"), - /// Err(_) => panic!("err"), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_WILD_ERR_ARM, - pedantic, - "a `match` with `Err(_)` arm and take drastic actions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for match which is used to add a reference to an - /// `Option` value. - /// - /// ### Why is this bad? - /// Using `as_ref()` or `as_mut()` instead is shorter. - /// - /// ### Example - /// ```rust - /// let x: Option<()> = None; - /// - /// // Bad - /// let r: Option<&()> = match x { - /// None => None, - /// Some(ref v) => Some(v), - /// }; - /// - /// // Good - /// let r: Option<&()> = x.as_ref(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_AS_REF, - complexity, - "a `match` on an Option value instead of using `as_ref()` or `as_mut`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard enum matches using `_`. - /// - /// ### Why is this bad? - /// New enum variants added by library updates can be missed. - /// - /// ### Known problems - /// Suggested replacements may be incorrect if guards exhaustively cover some - /// variants, and also may not use correct path to enum if it's not present in the current scope. - /// - /// ### Example - /// ```rust - /// # enum Foo { A(usize), B(usize) } - /// # let x = Foo::B(1); - /// // Bad - /// match x { - /// Foo::A(_) => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match x { - /// Foo::A(_) => {}, - /// Foo::B(_) => {}, - /// } - /// ``` - #[clippy::version = "1.34.0"] - pub WILDCARD_ENUM_MATCH_ARM, - restriction, - "a wildcard enum match arm using `_`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard enum matches for a single variant. - /// - /// ### Why is this bad? - /// New enum variants added by library updates can be missed. - /// - /// ### Known problems - /// Suggested replacements may not use correct path to enum - /// if it's not present in the current scope. - /// - /// ### Example - /// ```rust - /// # enum Foo { A, B, C } - /// # let x = Foo::B; - /// // Bad - /// match x { - /// Foo::A => {}, - /// Foo::B => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match x { - /// Foo::A => {}, - /// Foo::B => {}, - /// Foo::C => {}, - /// } - /// ``` - #[clippy::version = "1.45.0"] - pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - pedantic, - "a wildcard enum match for a single variant" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard pattern used with others patterns in same match arm. - /// - /// ### Why is this bad? - /// Wildcard pattern already covers any other pattern as it will match anyway. - /// It makes the code less readable, especially to spot wildcard pattern use in match arm. - /// - /// ### Example - /// ```rust - /// // Bad - /// match "foo" { - /// "a" => {}, - /// "bar" | _ => {}, - /// } - /// - /// // Good - /// match "foo" { - /// "a" => {}, - /// _ => {}, - /// } - /// ``` - #[clippy::version = "1.42.0"] - pub WILDCARD_IN_OR_PATTERNS, - complexity, - "a wildcard pattern used with others patterns in same match arm" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches being used to destructure a single-variant enum - /// or tuple struct where a `let` will suffice. - /// - /// ### Why is this bad? - /// Just readability – `let` doesn't nest, whereas a `match` does. - /// - /// ### Example - /// ```rust - /// enum Wrapper { - /// Data(i32), - /// } - /// - /// let wrapper = Wrapper::Data(42); - /// - /// let data = match wrapper { - /// Wrapper::Data(i) => i, - /// }; - /// ``` - /// - /// The correct use would be: - /// ```rust - /// enum Wrapper { - /// Data(i32), - /// } - /// - /// let wrapper = Wrapper::Data(42); - /// let Wrapper::Data(data) = wrapper; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INFALLIBLE_DESTRUCTURING_MATCH, - style, - "a `match` statement with a single infallible arm instead of a `let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for useless match that binds to only one value. - /// - /// ### Why is this bad? - /// Readability and needless complexity. - /// - /// ### Known problems - /// Suggested replacements may be incorrect when `match` - /// is actually binding temporary value, bringing a 'dropped while borrowed' error. - /// - /// ### Example - /// ```rust - /// # let a = 1; - /// # let b = 2; - /// - /// // Bad - /// match (a, b) { - /// (c, d) => { - /// // useless match - /// } - /// } - /// - /// // Good - /// let (c, d) = (a, b); - /// ``` - #[clippy::version = "1.43.0"] - pub MATCH_SINGLE_BINDING, - complexity, - "a match with a single binding instead of using `let` statement" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. - /// - /// ### Why is this bad? - /// Correctness and readability. It's like having a wildcard pattern after - /// matching all enum variants explicitly. - /// - /// ### Example - /// ```rust - /// # struct A { a: i32 } - /// let a = A { a: 5 }; - /// - /// // Bad - /// match a { - /// A { a: 5, .. } => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match a { - /// A { a: 5 } => {}, - /// _ => {}, - /// } - /// ``` - #[clippy::version = "1.43.0"] - pub REST_PAT_IN_FULLY_BOUND_STRUCTS, - restriction, - "a match on a struct that binds all fields but still uses the wildcard pattern" -} - -declare_clippy_lint! { - /// ### What it does - /// Lint for redundant pattern matching over `Result`, `Option`, - /// `std::task::Poll` or `std::net::IpAddr` - /// - /// ### Why is this bad? - /// It's more concise and clear to just use the proper - /// utility function - /// - /// ### Known problems - /// This will change the drop order for the matched type. Both `if let` and - /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the - /// value before entering the block. For most types this change will not matter, but for a few - /// types this will not be an acceptable change (e.g. locks). See the - /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about - /// drop order. - /// - /// ### Example - /// ```rust - /// # use std::task::Poll; - /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - /// if let Ok(_) = Ok::(42) {} - /// if let Err(_) = Err::(42) {} - /// if let None = None::<()> {} - /// if let Some(_) = Some(42) {} - /// if let Poll::Pending = Poll::Pending::<()> {} - /// if let Poll::Ready(_) = Poll::Ready(42) {} - /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} - /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} - /// match Ok::(42) { - /// Ok(_) => true, - /// Err(_) => false, - /// }; - /// ``` - /// - /// The more idiomatic use would be: - /// - /// ```rust - /// # use std::task::Poll; - /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - /// if Ok::(42).is_ok() {} - /// if Err::(42).is_err() {} - /// if None::<()>.is_none() {} - /// if Some(42).is_some() {} - /// if Poll::Pending::<()>.is_pending() {} - /// if Poll::Ready(42).is_ready() {} - /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} - /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} - /// Ok::(42).is_ok(); - /// ``` - #[clippy::version = "1.31.0"] - pub REDUNDANT_PATTERN_MATCHING, - style, - "use the proper utility function avoiding an `if let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `match` or `if let` expressions producing a - /// `bool` that could be written using `matches!` - /// - /// ### Why is this bad? - /// Readability and needless complexity. - /// - /// ### Known problems - /// This lint falsely triggers, if there are arms with - /// `cfg` attributes that remove an arm evaluating to `false`. - /// - /// ### Example - /// ```rust - /// let x = Some(5); - /// - /// // Bad - /// let a = match x { - /// Some(0) => true, - /// _ => false, - /// }; - /// - /// let a = if let Some(0) = x { - /// true - /// } else { - /// false - /// }; - /// - /// // Good - /// let a = matches!(x, Some(0)); - /// ``` - #[clippy::version = "1.47.0"] - pub MATCH_LIKE_MATCHES_MACRO, - style, - "a match that could be written with the matches! macro" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `match` with identical arm bodies. - /// - /// ### Why is this bad? - /// This is probably a copy & paste error. If arm bodies - /// are the same on purpose, you can factor them - /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). - /// - /// ### Known problems - /// False positive possible with order dependent `match` - /// (see issue - /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). - /// - /// ### Example - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => bar(), // <= oops - /// } - /// ``` - /// - /// This should probably be - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => baz(), // <= fixed - /// } - /// ``` - /// - /// or if the original code was not a typo: - /// ```rust,ignore - /// match foo { - /// Bar | Baz => bar(), // <= shows the intent better - /// Quz => quz(), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_SAME_ARMS, - pedantic, - "`match` with identical arm bodies" -} - -#[derive(Default)] -pub struct Matches { - msrv: Option, - infallible_destructuring_match_linted: bool, -} - -impl Matches { - #[must_use] - pub fn new(msrv: Option) -> Self { - Self { - msrv, - ..Matches::default() - } - } -} - -impl_lint_pass!(Matches => [ - SINGLE_MATCH, - MATCH_REF_PATS, - MATCH_BOOL, - SINGLE_MATCH_ELSE, - MATCH_OVERLAPPING_ARM, - MATCH_WILD_ERR_ARM, - MATCH_AS_REF, - WILDCARD_ENUM_MATCH_ARM, - MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - WILDCARD_IN_OR_PATTERNS, - MATCH_SINGLE_BINDING, - INFALLIBLE_DESTRUCTURING_MATCH, - REST_PAT_IN_FULLY_BOUND_STRUCTS, - REDUNDANT_PATTERN_MATCHING, - MATCH_LIKE_MATCHES_MACRO, - MATCH_SAME_ARMS, -]); - -impl<'tcx> LateLintPass<'tcx> for Matches { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - redundant_pattern_match::check(cx, expr); - - if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { - if !check_match_like_matches(cx, expr) { - lint_match_arms(cx, expr); - } - } else { - lint_match_arms(cx, expr); - } - - if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { - check_single_match(cx, ex, arms, expr); - check_match_bool(cx, ex, arms, expr); - check_overlapping_arms(cx, ex, arms); - check_wild_err_arm(cx, ex, arms); - check_wild_enum_match(cx, ex, arms); - check_match_as_ref(cx, ex, arms, expr); - check_wild_in_or_pats(cx, arms); - - if self.infallible_destructuring_match_linted { - self.infallible_destructuring_match_linted = false; - } else { - check_match_single_binding(cx, ex, arms, expr); - } - } - if let ExprKind::Match(ex, arms, _) = expr.kind { - check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); - } - } - - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { - if_chain! { - if !local.span.from_expansion(); - if let Some(expr) = local.init; - if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; - if arms.len() == 1 && arms[0].guard.is_none(); - if let PatKind::TupleStruct( - QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; - if args.len() == 1; - if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; - let body = peel_blocks(arms[0].body); - if path_to_local_id(body, arg); - - then { - let mut applicability = Applicability::MachineApplicable; - self.infallible_destructuring_match_linted = true; - span_lint_and_sugg( - cx, - INFALLIBLE_DESTRUCTURING_MATCH, - local.span, - "you seem to be trying to use `match` to destructure a single infallible pattern. \ - Consider using `let`", - "try this", - format!( - "let {}({}) = {};", - snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), - snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), - snippet_with_applicability(cx, target.span, "..", &mut applicability), - ), - applicability, - ); - } - } - } - - fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { - if_chain! { - if !pat.span.from_expansion(); - if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; - if let Some(def_id) = path.res.opt_def_id(); - let ty = cx.tcx.type_of(def_id); - if let ty::Adt(def, _) = ty.kind(); - if def.is_struct() || def.is_union(); - if fields.len() == def.non_enum_variant().fields.len(); - - then { - span_lint_and_help( - cx, - REST_PAT_IN_FULLY_BOUND_STRUCTS, - pat.span, - "unnecessary use of `..` pattern in struct binding. All fields were already bound", - None, - "consider removing `..` from this binding", - ); - } - } - } - - extract_msrv_attr!(LateContext); -} - -#[rustfmt::skip] -fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - if expr.span.from_expansion() { - // Don't lint match expressions present in - // macro_rules! block - return; - } - if let PatKind::Or(..) = arms[0].pat.kind { - // don't lint for or patterns for now, this makes - // the lint noisy in unnecessary situations - return; - } - let els = arms[1].body; - let els = if is_unit_expr(peel_blocks(els)) { - None - } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { - if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { - // single statement/expr "else" block, don't lint - return; - } - // block with 2+ statements or 1 expr and 1+ statement - Some(els) - } else { - // not a block, don't lint - return; - }; - - let ty = cx.typeck_results().expr_ty(ex); - if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { - check_single_match_single_pattern(cx, ex, arms, expr, els); - check_single_match_opt_like(cx, ex, arms, expr, ty, els); - } - } -} - -fn check_single_match_single_pattern( - cx: &LateContext<'_>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - els: Option<&Expr<'_>>, -) { - if is_wild(arms[1].pat) { - report_single_match_single_pattern(cx, ex, arms, expr, els); - } -} - -fn report_single_match_single_pattern( - cx: &LateContext<'_>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - els: Option<&Expr<'_>>, -) { - let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; - let els_str = els.map_or(String::new(), |els| { - format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) - }); - - let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); - let (msg, sugg) = if_chain! { - if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; - let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); - if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait(); - if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait(); - if ty.is_integral() || ty.is_char() || ty.is_str() - || (implements_trait(cx, ty, spe_trait_id, &[]) - && implements_trait(cx, ty, pe_trait_id, &[ty.into()])); - then { - // scrutinee derives PartialEq and the pattern is a constant. - let pat_ref_count = match pat.kind { - // string literals are already a reference. - PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, - _ => pat_ref_count, - }; - // References are only implicitly added to the pattern, so no overflow here. - // e.g. will work: match &Some(_) { Some(_) => () } - // will not: match Some(_) { &Some(_) => () } - let ref_count_diff = ty_ref_count - pat_ref_count; - - // Try to remove address of expressions first. - let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); - let ref_count_diff = ref_count_diff - removed; - - let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; - let sugg = format!( - "if {} == {}{} {}{}", - snippet(cx, ex.span, ".."), - // PartialEq for different reference counts may not exist. - "&".repeat(ref_count_diff), - snippet(cx, arms[0].pat.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), - els_str, - ); - (msg, sugg) - } else { - let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; - let sugg = format!( - "if let {} = {} {}{}", - snippet(cx, arms[0].pat.span, ".."), - snippet(cx, ex.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), - els_str, - ); - (msg, sugg) - } - }; - - span_lint_and_sugg( - cx, - lint, - expr.span, - msg, - "try this", - sugg, - Applicability::HasPlaceholders, - ); -} - -fn check_single_match_opt_like<'a>( - cx: &LateContext<'a>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - ty: Ty<'a>, - els: Option<&Expr<'_>>, -) { - // list of candidate `Enum`s we know will never get any more members - let candidates = &[ - (&paths::COW, "Borrowed"), - (&paths::COW, "Cow::Borrowed"), - (&paths::COW, "Cow::Owned"), - (&paths::COW, "Owned"), - (&paths::OPTION, "None"), - (&paths::RESULT, "Err"), - (&paths::RESULT, "Ok"), - ]; - - // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive - // match with the second branch, without enum variants in matches. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { - return; - } - - let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } - - let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool { - let (path, ty) = path_info; - for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, ty, ty_path) { - return true; - } - } - false - }; - if paths_and_types.iter().all(in_candidate_enum) { - report_single_match_single_pattern(cx, ex, arms, expr, els); - } -} - -/// Collects paths and their types from the given patterns. Returns true if the given pattern could -/// be simplified, false otherwise. -fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { - match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) => inner.iter().all(|p| { - let p_ty = cx.typeck_results().pat_ty(p); - collect_pat_paths(acc, cx, p, p_ty) - }), - PatKind::TupleStruct(ref path, ..) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push((ident.to_string(), ty)); - true - }, - PatKind::Path(ref path) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - _ => false, - } -} - -/// Returns true if the given arm of pattern matching contains wildcard patterns. -fn contains_only_wilds(pat: &Pat<'_>) -> bool { - match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), - _ => false, - } -} - -/// Returns true if the given patterns forms only exhaustive matches that don't contain enum -/// patterns without a wildcard. -fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { - match (&left.kind, &right.kind) { - (PatKind::Wild, _) | (_, PatKind::Wild) => true, - (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { - // We don't actually know the position and the presence of the `..` (dotdot) operator - // in the arms, so we need to evaluate the correct offsets here in order to iterate in - // both arms at the same time. - let len = max( - left_in.len() + { - if left_pos.is_some() { 1 } else { 0 } - }, - right_in.len() + { - if right_pos.is_some() { 1 } else { 0 } - }, - ); - let mut left_pos = left_pos.unwrap_or(usize::MAX); - let mut right_pos = right_pos.unwrap_or(usize::MAX); - let mut left_dot_space = 0; - let mut right_dot_space = 0; - for i in 0..len { - let mut found_dotdot = false; - if i == left_pos { - left_dot_space += 1; - if left_dot_space < len - left_in.len() { - left_pos += 1; - } - found_dotdot = true; - } - if i == right_pos { - right_dot_space += 1; - if right_dot_space < len - right_in.len() { - right_pos += 1; - } - found_dotdot = true; - } - if found_dotdot { - continue; - } - if !contains_only_wilds(&left_in[i - left_dot_space]) - && !contains_only_wilds(&right_in[i - right_dot_space]) - { - return false; - } - } - true - }, - _ => false, - } -} - -fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - // Type of expression is `bool`. - if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { - span_lint_and_then( - cx, - MATCH_BOOL, - expr.span, - "you seem to be trying to match on a boolean expression", - move |diag| { - if arms.len() == 2 { - // no guards - let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind { - if let ExprKind::Lit(ref lit) = arm_bool.kind { - match lit.node { - LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), - LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), - _ => None, - } - } else { - None - } - } else { - None - }; - - if let Some((true_expr, false_expr)) = exprs { - let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { - (false, false) => Some(format!( - "if {} {} else {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)), - expr_block(cx, false_expr, None, "..", Some(expr.span)) - )), - (false, true) => Some(format!( - "if {} {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)) - )), - (true, false) => { - let test = Sugg::hir(cx, ex, ".."); - Some(format!( - "if {} {}", - !test, - expr_block(cx, false_expr, None, "..", Some(expr.span)) - )) - }, - (true, true) => None, - }; - - if let Some(sugg) = sugg { - diag.span_suggestion( - expr.span, - "consider using an `if`/`else` expression", - sugg, - Applicability::HasPlaceholders, - ); - } - } - } - }, - ); - } -} - -fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { - if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { - let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); - if !ranges.is_empty() { - if let Some((start, end)) = overlapping(&ranges) { - span_lint_and_note( - cx, - MATCH_OVERLAPPING_ARM, - start.span, - "some ranges overlap", - Some(end.span), - "overlaps with this", - ); - } - } - } -} - -fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { - let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); - if is_type_diagnostic_item(cx, ex_ty, sym::Result) { - for arm in arms { - if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { - let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); - if path_str == "Err" { - let mut matching_wild = inner.iter().any(is_wild); - let mut ident_bind_name = kw::Underscore; - if !matching_wild { - // Looking for unused bindings (i.e.: `_e`) - for pat in inner.iter() { - if let PatKind::Binding(_, id, ident, None) = pat.kind { - if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) { - ident_bind_name = ident.name; - matching_wild = true; - } - } - } - } - if_chain! { - if matching_wild; - if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span); - if is_panic(cx, macro_call.def_id); - then { - // `Err(_)` or `Err(_e)` arm with `panic!` found - span_lint_and_note(cx, - MATCH_WILD_ERR_ARM, - arm.pat.span, - &format!("`Err({})` matches all errors", ident_bind_name), - None, - "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", - ); - } - } - } - } - } - } -} - -enum CommonPrefixSearcher<'a> { - None, - Path(&'a [PathSegment<'a>]), - Mixed, -} -impl<'a> CommonPrefixSearcher<'a> { - fn with_path(&mut self, path: &'a [PathSegment<'a>]) { - match path { - [path @ .., _] => self.with_prefix(path), - [] => (), - } - } - - fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) { - match self { - Self::None => *self = Self::Path(path), - Self::Path(self_path) - if path - .iter() - .map(|p| p.ident.name) - .eq(self_path.iter().map(|p| p.ident.name)) => {}, - Self::Path(_) => *self = Self::Mixed, - Self::Mixed => (), - } - } -} - -fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool { - let attrs = cx.tcx.get_attrs(variant_def.def_id); - clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs) -} - -#[allow(clippy::too_many_lines)] -fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { - let ty = cx.typeck_results().expr_ty(ex).peel_refs(); - let adt_def = match ty.kind() { - ty::Adt(adt_def, _) - if adt_def.is_enum() - && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) => - { - adt_def - }, - _ => return, - }; - - // First pass - check for violation, but don't do much book-keeping because this is hopefully - // the uncommon case, and the book-keeping is slightly expensive. - let mut wildcard_span = None; - let mut wildcard_ident = None; - let mut has_non_wild = false; - for arm in arms { - match peel_hir_pat_refs(arm.pat).0.kind { - PatKind::Wild => wildcard_span = Some(arm.pat.span), - PatKind::Binding(_, _, ident, None) => { - wildcard_span = Some(arm.pat.span); - wildcard_ident = Some(ident); - }, - _ => has_non_wild = true, - } - } - let wildcard_span = match wildcard_span { - Some(x) if has_non_wild => x, - _ => return, - }; - - // Accumulate the variants which should be put in place of the wildcard because they're not - // already covered. - let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x)); - let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect(); - - let mut path_prefix = CommonPrefixSearcher::None; - for arm in arms { - // Guards mean that this case probably isn't exhaustively covered. Technically - // this is incorrect, as we should really check whether each variant is exhaustively - // covered by the set of guards that cover it, but that's really hard to do. - recurse_or_patterns(arm.pat, |pat| { - let path = match &peel_hir_pat_refs(pat).0.kind { - PatKind::Path(path) => { - #[allow(clippy::match_same_arms)] - let id = match cx.qpath_res(path, pat.hir_id) { - Res::Def( - DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst, - _, - ) => return, - Res::Def(_, id) => id, - _ => return, - }; - if arm.guard.is_none() { - missing_variants.retain(|e| e.ctor_def_id != Some(id)); - } - path - }, - PatKind::TupleStruct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) { - missing_variants.retain(|e| e.ctor_def_id != Some(id)); - } - } - path - }, - PatKind::Struct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) { - missing_variants.retain(|e| e.def_id != id); - } - } - path - }, - _ => return, - }; - match path { - QPath::Resolved(_, path) => path_prefix.with_path(path.segments), - QPath::TypeRelative( - hir::Ty { - kind: TyKind::Path(QPath::Resolved(_, path)), - .. - }, - _, - ) => path_prefix.with_prefix(path.segments), - _ => (), - } - }); - } - - let format_suggestion = |variant: &VariantDef| { - format!( - "{}{}{}{}", - if let Some(ident) = wildcard_ident { - format!("{} @ ", ident.name) - } else { - String::new() - }, - if let CommonPrefixSearcher::Path(path_prefix) = path_prefix { - let mut s = String::new(); - for seg in path_prefix { - s.push_str(seg.ident.as_str()); - s.push_str("::"); - } - s - } else { - let mut s = cx.tcx.def_path_str(adt_def.did); - s.push_str("::"); - s - }, - variant.name, - match variant.ctor_kind { - CtorKind::Fn if variant.fields.len() == 1 => "(_)", - CtorKind::Fn => "(..)", - CtorKind::Const => "", - CtorKind::Fictive => "{ .. }", - } - ) - }; - - match missing_variants.as_slice() { - [] => (), - [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg( - cx, - MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - wildcard_span, - "wildcard matches only a single variant and will also match any future added variants", - "try this", - format_suggestion(x), - Applicability::MaybeIncorrect, - ), - variants => { - let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); - let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden { - suggestions.push("_".into()); - "wildcard matches known variants and will also match future added variants" - } else { - "wildcard match will also match any future added variants" - }; - - span_lint_and_sugg( - cx, - WILDCARD_ENUM_MATCH_ARM, - wildcard_span, - message, - "try this", - suggestions.join(" | "), - Applicability::MaybeIncorrect, - ); - }, - }; -} - -fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) -where - 'b: 'a, - I: Clone + Iterator>, -{ - if !has_multiple_ref_pats(pats.clone()) { - return; - } - - let (first_sugg, msg, title); - let span = ex.span.source_callsite(); - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind { - first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); - msg = "try"; - title = "you don't need to add `&` to both the expression and the patterns"; - } else { - first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); - msg = "instead of prefixing all patterns with `&`, you can dereference the expression"; - title = "you don't need to add `&` to all patterns"; - } - - let remaining_suggs = pats.filter_map(|pat| { - if let PatKind::Ref(refp, _) = pat.kind { - Some((pat.span, snippet(cx, refp.span, "..").to_string())) - } else { - None - } - }); - - span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { - if !expr.span.from_expansion() { - multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs)); - } - }); -} - -fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - let arm_ref: Option = if is_none_arm(cx, &arms[0]) { - is_ref_some_arm(cx, &arms[1]) - } else if is_none_arm(cx, &arms[1]) { - is_ref_some_arm(cx, &arms[0]) - } else { - None - }; - if let Some(rb) = arm_ref { - let suggestion = if rb == BindingAnnotation::Ref { - "as_ref" - } else { - "as_mut" - }; - - let output_ty = cx.typeck_results().expr_ty(expr); - let input_ty = cx.typeck_results().expr_ty(ex); - - let cast = if_chain! { - if let ty::Adt(_, substs) = input_ty.kind(); - let input_ty = substs.type_at(0); - if let ty::Adt(_, substs) = output_ty.kind(); - let output_ty = substs.type_at(0); - if let ty::Ref(_, output_ty, _) = *output_ty.kind(); - if input_ty != output_ty; - then { - ".map(|x| x as _)" - } else { - "" - } - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - MATCH_AS_REF, - expr.span, - &format!("use `{}()` instead", suggestion), - "try this", - format!( - "{}.{}(){}", - snippet_with_applicability(cx, ex.span, "_", &mut applicability), - suggestion, - cast, - ), - applicability, - ); - } - } -} - -fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { - for arm in arms { - if let PatKind::Or(fields) = arm.pat.kind { - // look for multiple fields in this arm that contains at least one Wild pattern - if fields.len() > 1 && fields.iter().any(is_wild) { - span_lint_and_help( - cx, - WILDCARD_IN_OR_PATTERNS, - arm.pat.span, - "wildcard pattern covers any other pattern as it will match anyway", - None, - "consider handling `_` separately", - ); - } - } - } -} - -/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` -fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - if let Some(higher::IfLet { - let_pat, - let_expr, - if_then, - if_else: Some(if_else), - }) = higher::IfLet::hir(cx, expr) - { - return find_matches_sugg( - cx, - let_expr, - IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), - expr, - true, - ); - } - - if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind { - return find_matches_sugg( - cx, - scrut, - arms.iter().map(|arm| { - ( - cx.tcx.hir().attrs(arm.hir_id), - Some(arm.pat), - arm.body, - arm.guard.as_ref(), - ) - }), - expr, - false, - ); - } - - false -} - -/// Lint a `match` or `if let` for replacement by `matches!` -fn find_matches_sugg<'a, 'b, I>( - cx: &LateContext<'_>, - ex: &Expr<'_>, - mut iter: I, - expr: &Expr<'_>, - is_if_let: bool, -) -> bool -where - 'b: 'a, - I: Clone - + DoubleEndedIterator - + ExactSizeIterator - + Iterator< - Item = ( - &'a [Attribute], - Option<&'a Pat<'b>>, - &'a Expr<'b>, - Option<&'a Guard<'b>>, - ), - >, -{ - if_chain! { - if iter.len() >= 2; - if cx.typeck_results().expr_ty(expr).is_bool(); - if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); - let iter_without_last = iter.clone(); - if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); - if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); - if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); - if b0 != b1; - if first_guard.is_none() || iter.len() == 0; - if first_attrs.is_empty(); - if iter - .all(|arm| { - find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() - }); - then { - if let Some(last_pat) = last_pat_opt { - if !is_wild(last_pat) { - return false; - } - } - - // The suggestion may be incorrect, because some arms can have `cfg` attributes - // evaluated into `false` and so such arms will be stripped before. - let mut applicability = Applicability::MaybeIncorrect; - let pat = { - use itertools::Itertools as _; - iter_without_last - .filter_map(|arm| { - let pat_span = arm.1?.span; - Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) - }) - .join(" | ") - }; - let pat_and_guard = if let Some(Guard::If(g)) = first_guard { - format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) - } else { - pat - }; - - // strip potential borrows (#6503), but only if the type is a reference - let mut ex_new = ex; - if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { - if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { - ex_new = ex_inner; - } - }; - span_lint_and_sugg( - cx, - MATCH_LIKE_MATCHES_MACRO, - expr.span, - &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }), - "try this", - format!( - "{}matches!({}, {})", - if b0 { "" } else { "!" }, - snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), - pat_and_guard, - ), - applicability, - ); - true - } else { - false - } - } -} - -/// Extract a `bool` or `{ bool }` -fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { - match ex { - ExprKind::Lit(Spanned { - node: LitKind::Bool(b), .. - }) => Some(*b), - ExprKind::Block( - rustc_hir::Block { - stmts: &[], - expr: Some(exp), - .. - }, - _, - ) if is_if_let => { - if let ExprKind::Lit(Spanned { - node: LitKind::Bool(b), .. - }) = exp.kind - { - Some(b) - } else { - None - } - }, - _ => None, - } -} - -#[allow(clippy::too_many_lines)] -fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { - return; - } - - // HACK: - // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here - // to prevent false positives as there is currently no better way to detect if code was excluded by - // a macro. See PR #6435 - if_chain! { - if let Some(match_snippet) = snippet_opt(cx, expr.span); - if let Some(arm_snippet) = snippet_opt(cx, arms[0].span); - if let Some(ex_snippet) = snippet_opt(cx, ex.span); - let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, ""); - if rest_snippet.contains("=>"); - then { - // The code it self contains another thick arrow "=>" - // -> Either another arm or a comment - return; - } - } - - let matched_vars = ex.span; - let bind_names = arms[0].pat.span; - let match_body = peel_blocks(arms[0].body); - let mut snippet_body = if match_body.span.from_expansion() { - Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() - } else { - snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() - }; - - // Do we need to add ';' to suggestion ? - match match_body.kind { - ExprKind::Block(block, _) => { - // macro + expr_ty(body) == () - if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() { - snippet_body.push(';'); - } - }, - _ => { - // expr_ty(body) == () - if cx.typeck_results().expr_ty(match_body).is_unit() { - snippet_body.push(';'); - } - }, - } - - let mut applicability = Applicability::MaybeIncorrect; - match arms[0].pat.kind { - PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { - // If this match is in a local (`let`) stmt - let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) { - ( - parent_let_node.span, - format!( - "let {} = {};\n{}let {} = {};", - snippet_with_applicability(cx, bind_names, "..", &mut applicability), - snippet_with_applicability(cx, matched_vars, "..", &mut applicability), - " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), - snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability), - snippet_body - ), - ) - } else { - // If we are in closure, we need curly braces around suggestion - let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); - let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string()); - if let Some(parent_expr) = get_parent_expr(cx, expr) { - if let ExprKind::Closure(..) = parent_expr.kind { - cbrace_end = format!("\n{}}}", indent); - // Fix body indent due to the closure - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{}", indent); - } - } - // If the parent is already an arm, and the body is another match statement, - // we need curly braces around suggestion - let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id); - if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) { - if let ExprKind::Match(..) = arm.body.kind { - cbrace_end = format!("\n{}}}", indent); - // Fix body indent due to the match - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{}", indent); - } - } - ( - expr.span, - format!( - "{}let {} = {};\n{}{}{}", - cbrace_start, - snippet_with_applicability(cx, bind_names, "..", &mut applicability), - snippet_with_applicability(cx, matched_vars, "..", &mut applicability), - indent, - snippet_body, - cbrace_end - ), - ) - }; - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - target_span, - "this match could be written as a `let` statement", - "consider using `let` statement", - sugg, - applicability, - ); - }, - PatKind::Wild => { - if ex.can_have_side_effects() { - let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0)); - let sugg = format!( - "{};\n{}{}", - snippet_with_applicability(cx, ex.span, "..", &mut applicability), - indent, - snippet_body - ); - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - expr.span, - "this match could be replaced by its scrutinee and body", - "consider using the scrutinee and body instead", - sugg, - applicability, - ); - } else { - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - expr.span, - "this match could be replaced by its body itself", - "consider using the match body instead", - snippet_body, - Applicability::MachineApplicable, - ); - } - }, - _ => (), - } -} - -/// Returns true if the `ex` match expression is in a local (`let`) statement -fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { - let map = &cx.tcx.hir(); - if_chain! { - if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); - if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); - then { - return Some(parent_let_expr); - } - } - None -} - -/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges. -fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { - arms.iter() - .filter_map(|arm| { - if let Arm { pat, guard: None, .. } = *arm { - if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { - let lhs_const = match lhs { - Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, - None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, - }; - let rhs_const = match rhs { - Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, - None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, - }; - - let lhs_val = lhs_const.int_value(cx, ty)?; - let rhs_val = rhs_const.int_value(cx, ty)?; - - let rhs_bound = match range_end { - RangeEnd::Included => EndBound::Included(rhs_val), - RangeEnd::Excluded => EndBound::Excluded(rhs_val), - }; - return Some(SpannedRange { - span: pat.span, - node: (lhs_val, rhs_bound), - }); - } - - if let PatKind::Lit(value) = pat.kind { - let value = constant_full_int(cx, cx.typeck_results(), value)?; - return Some(SpannedRange { - span: pat.span, - node: (value, EndBound::Included(value)), - }); - } - } - None - }) - .collect() -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum EndBound { - Included(T), - Excluded(T), -} - -#[derive(Debug, Eq, PartialEq)] -struct SpannedRange { - pub span: Span, - pub node: (T, EndBound), -} - -// Checks if arm has the form `None => None` -fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone)) -} - -// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) -fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { - if_chain! { - if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; - if is_lang_ctor(cx, qpath, OptionSome); - if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; - if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; - if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; - if let ExprKind::Path(ref some_path) = e.kind; - if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1; - if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; - if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; - then { - return Some(rb) - } - } - None -} - -fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool -where - 'b: 'a, - I: Iterator>, -{ - let mut ref_count = 0; - for opt in pats.map(|pat| match pat.kind { - PatKind::Ref(..) => Some(true), // &-patterns - PatKind::Wild => Some(false), // an "anything" wildcard is also fine - _ => None, // any other pattern is not fine - }) { - if let Some(inner) = opt { - if inner { - ref_count += 1; - } - } else { - return false; - } - } - ref_count > 1 -} - -fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> -where - T: Copy + Ord, -{ - #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] - enum BoundKind { - EndExcluded, - Start, - EndIncluded, - } - - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); - - impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { - fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { - let RangeBound(self_value, self_kind, _) = *self; - (self_value, self_kind).cmp(&(*other_value, *other_kind)) - } - } - - let mut values = Vec::with_capacity(2 * ranges.len()); - - for r @ SpannedRange { node: (start, end), .. } in ranges { - values.push(RangeBound(*start, BoundKind::Start, r)); - values.push(match end { - EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r), - EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r), - }); - } - - values.sort(); - - let mut started = vec![]; - - for RangeBound(_, kind, range) in values { - match kind { - BoundKind::Start => started.push(range), - BoundKind::EndExcluded | BoundKind::EndIncluded => { - let mut overlap = None; - - while let Some(last_started) = started.pop() { - if last_started == range { - break; - } - overlap = Some(last_started); - } - - if let Some(first_overlapping) = overlap { - return Some((range, first_overlapping)); - } - }, - } - } - - None -} - -mod redundant_pattern_match { - use super::REDUNDANT_PATTERN_MATCHING; - use clippy_utils::diagnostics::span_lint_and_then; - use clippy_utils::higher; - use clippy_utils::source::snippet; - use clippy_utils::sugg::Sugg; - use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type}; - use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; - use if_chain::if_chain; - use rustc_ast::ast::LitKind; - use rustc_data_structures::fx::FxHashSet; - use rustc_errors::Applicability; - use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; - use rustc_hir::{ - intravisit::{walk_expr, Visitor}, - Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp, - }; - use rustc_lint::LateContext; - use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; - use rustc_span::sym; - - pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some(higher::IfLet { - if_else, - let_pat, - let_expr, - .. - }) = higher::IfLet::hir(cx, expr) - { - find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some()); - } - if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind { - find_sugg_for_match(cx, expr, op, arms); - } - if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { - find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); - } - } - - /// Checks if the drop order for a type matters. Some std types implement drop solely to - /// deallocate memory. For these types, and composites containing them, changing the drop order - /// won't result in any observable side effects. - fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) - } - - fn type_needs_ordered_drop_inner<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - seen: &mut FxHashSet>, - ) -> bool { - if !seen.insert(ty) { - return false; - } - if !ty.needs_drop(cx.tcx, cx.param_env) { - false - } else if !cx - .tcx - .lang_items() - .drop_trait() - .map_or(false, |id| implements_trait(cx, ty, id, &[])) - { - // This type doesn't implement drop, so no side effects here. - // Check if any component type has any. - match ty.kind() { - ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), - ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen), - ty::Adt(adt, subs) => adt - .all_fields() - .map(|f| f.ty(cx.tcx, subs)) - .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), - _ => true, - } - } - // Check for std types which implement drop, but only for memory allocation. - else if is_type_diagnostic_item(cx, ty, sym::Vec) - || is_type_lang_item(cx, ty, LangItem::OwnedBox) - || is_type_diagnostic_item(cx, ty, sym::Rc) - || is_type_diagnostic_item(cx, ty, sym::Arc) - || is_type_diagnostic_item(cx, ty, sym::cstring_type) - || is_type_diagnostic_item(cx, ty, sym::BTreeMap) - || is_type_diagnostic_item(cx, ty, sym::LinkedList) - || match_type(cx, ty, &paths::WEAK_RC) - || match_type(cx, ty, &paths::WEAK_ARC) - { - // Check all of the generic arguments. - if let ty::Adt(_, subs) = ty.kind() { - subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)) - } else { - true - } - } else { - true - } - } - - // Extract the generic arguments out of a type - fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option> { - if_chain! { - if let ty::Adt(_, subs) = ty.kind(); - if let Some(sub) = subs.get(index); - if let GenericArgKind::Type(sub_ty) = sub.unpack(); - then { - Some(sub_ty) - } else { - None - } - } - } - - // Checks if there are any temporaries created in the given expression for which drop order - // matters. - fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - struct V<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - res: bool, - } - impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - // Taking the reference of a value leaves a temporary - // e.g. In `&String::new()` the string is a temporary value. - // Remaining fields are temporary values - // e.g. In `(String::new(), 0).1` the string is a temporary value. - ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => { - if !matches!(expr.kind, ExprKind::Path(_)) { - if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) { - self.res = true; - } else { - self.visit_expr(expr); - } - } - }, - // the base type is alway taken by reference. - // e.g. In `(vec![0])[0]` the vector is a temporary value. - ExprKind::Index(base, index) => { - if !matches!(base.kind, ExprKind::Path(_)) { - if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) { - self.res = true; - } else { - self.visit_expr(base); - } - } - self.visit_expr(index); - }, - // Method calls can take self by reference. - // e.g. In `String::new().len()` the string is a temporary value. - ExprKind::MethodCall(_, [self_arg, args @ ..], _) => { - if !matches!(self_arg.kind, ExprKind::Path(_)) { - let self_by_ref = self - .cx - .typeck_results() - .type_dependent_def_id(expr.hir_id) - .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref()); - if self_by_ref - && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) - { - self.res = true; - } else { - self.visit_expr(self_arg); - } - } - args.iter().for_each(|arg| self.visit_expr(arg)); - }, - // Either explicitly drops values, or changes control flow. - ExprKind::DropTemps(_) - | ExprKind::Ret(_) - | ExprKind::Break(..) - | ExprKind::Yield(..) - | ExprKind::Block(Block { expr: None, .. }, _) - | ExprKind::Loop(..) => (), - - // Only consider the final expression. - ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr), - - _ => walk_expr(self, expr), - } - } - } - - let mut v = V { cx, res: false }; - v.visit_expr(expr); - v.res - } - - fn find_sugg_for_if_let<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - let_pat: &Pat<'_>, - let_expr: &'tcx Expr<'_>, - keyword: &'static str, - has_else: bool, - ) { - // also look inside refs - let mut kind = &let_pat.kind; - // if we have &None for example, peel it so we can detect "if let None = x" - if let PatKind::Ref(inner, _mutability) = kind { - kind = &inner.kind; - } - let op_ty = cx.typeck_results().expr_ty(let_expr); - // Determine which function should be used, and the type contained by the corresponding - // variant. - let (good_method, inner_ty) = match kind { - PatKind::TupleStruct(ref path, [sub_pat], _) => { - if let PatKind::Wild = sub_pat.kind { - if is_lang_ctor(cx, path, ResultOk) { - ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, ResultErr) { - ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, OptionSome) { - ("is_some()", op_ty) - } else if is_lang_ctor(cx, path, PollReady) { - ("is_ready()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) { - ("is_ipv4()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) { - ("is_ipv6()", op_ty) - } else { - return; - } - } else { - return; - } - }, - PatKind::Path(ref path) => { - let method = if is_lang_ctor(cx, path, OptionNone) { - "is_none()" - } else if is_lang_ctor(cx, path, PollPending) { - "is_pending()" - } else { - return; - }; - // `None` and `Pending` don't have an inner type. - (method, cx.tcx.types.unit) - }, - _ => return, - }; - - // If this is the last expression in a block or there is an else clause then the whole - // type needs to be considered, not just the inner type of the branch being matched on. - // Note the last expression in a block is dropped after all local bindings. - let check_ty = if has_else - || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..))))) - { - op_ty - } else { - inner_ty - }; - - // All temporaries created in the scrutinee expression are dropped at the same time as the - // scrutinee would be, so they have to be considered as well. - // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held - // for the duration if body. - let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr); - - // check that `while_let_on_iterator` lint does not trigger - if_chain! { - if keyword == "while"; - if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind; - if method_path.ident.name == sym::next; - if is_trait_method(cx, let_expr, sym::Iterator); - then { - return; - } - } - - let result_expr = match &let_expr.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - ExprKind::Unary(UnOp::Deref, deref) => deref, - _ => let_expr, - }; - - span_lint_and_then( - cx, - REDUNDANT_PATTERN_MATCHING, - let_pat.span, - &format!("redundant pattern matching, consider using `{}`", good_method), - |diag| { - // if/while let ... = ... { ... } - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - let expr_span = expr.span; - - // if/while let ... = ... { ... } - // ^^^ - let op_span = result_expr.span.source_callsite(); - - // if/while let ... = ... { ... } - // ^^^^^^^^^^^^^^^^^^^ - let span = expr_span.until(op_span.shrink_to_hi()); - - let app = if needs_drop { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - - let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") - .maybe_par() - .to_string(); - - diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app); - - if needs_drop { - diag.note("this will change drop order of the result, as well as all temporaries"); - diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important"); - } - }, - ); - } - - fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { - if arms.len() == 2 { - let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); - - let found_good_method = match node_pair { - ( - PatKind::TupleStruct(ref path_left, patterns_left, _), - PatKind::TupleStruct(ref path_right, patterns_right, _), - ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { - if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::RESULT_OK, - &paths::RESULT_ERR, - "is_ok()", - "is_err()", - ) - .or_else(|| { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::IPADDR_V4, - &paths::IPADDR_V6, - "is_ipv4()", - "is_ipv6()", - ) - }) - } else { - None - } - }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) - | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) - if patterns.len() == 1 => - { - if let PatKind::Wild = patterns[0].kind { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::OPTION_SOME, - &paths::OPTION_NONE, - "is_some()", - "is_none()", - ) - .or_else(|| { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::POLL_READY, - &paths::POLL_PENDING, - "is_ready()", - "is_pending()", - ) - }) - } else { - None - } - }, - _ => None, - }; - - if let Some(good_method) = found_good_method { - let span = expr.span.to(op.span); - let result_expr = match &op.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - _ => op, - }; - span_lint_and_then( - cx, - REDUNDANT_PATTERN_MATCHING, - expr.span, - &format!("redundant pattern matching, consider using `{}`", good_method), - |diag| { - diag.span_suggestion( - span, - "try this", - format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method), - Applicability::MaybeIncorrect, // snippet - ); - }, - ); - } - } - } - - #[allow(clippy::too_many_arguments)] - fn find_good_method_for_match<'a>( - cx: &LateContext<'_>, - arms: &[Arm<'_>], - path_left: &QPath<'_>, - path_right: &QPath<'_>, - expected_left: &[&str], - expected_right: &[&str], - should_be_left: &'a str, - should_be_right: &'a str, - ) -> Option<&'a str> { - let body_node_pair = if is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_left) - && is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_right) - { - (&(*arms[0].body).kind, &(*arms[1].body).kind) - } else if is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_left) - && is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_right) - { - (&(*arms[1].body).kind, &(*arms[0].body).kind) - } else { - return None; - }; - - match body_node_pair { - (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), - _ => None, - }, - _ => None, - } - } -} - -#[test] -fn test_overlapping() { - use rustc_span::source_map::DUMMY_SP; - - let sp = |s, e| SpannedRange { - span: DUMMY_SP, - node: (s, e), - }; - - assert_eq!(None, overlapping::(&[])); - assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))])); - assert_eq!( - None, - overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))]) - ); - assert_eq!( - None, - overlapping(&[ - sp(1, EndBound::Included(4)), - sp(5, EndBound::Included(6)), - sp(10, EndBound::Included(11)) - ],) - ); - assert_eq!( - Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))), - overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))]) - ); - assert_eq!( - Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))), - overlapping(&[ - sp(1, EndBound::Included(4)), - sp(5, EndBound::Included(6)), - sp(6, EndBound::Included(11)) - ],) - ); -} - -/// Implementation of `MATCH_SAME_ARMS`. -fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { - if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind { - let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(arm.body); - h.finish() - }; - - let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { - let min_index = usize::min(lindex, rindex); - let max_index = usize::max(lindex, rindex); - - let mut local_map: HirIdMap = HirIdMap::default(); - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - if_chain! { - if let Some(a_id) = path_to_local(a); - if let Some(b_id) = path_to_local(b); - let entry = match local_map.entry(a_id) { - Entry::Vacant(entry) => entry, - // check if using the same bindings as before - Entry::Occupied(entry) => return *entry.get() == b_id, - }; - // the names technically don't have to match; this makes the lint more conservative - if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); - if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); - if pat_contains_local(lhs.pat, a_id); - if pat_contains_local(rhs.pat, b_id); - then { - entry.insert(b_id); - true - } else { - false - } - } - }; - // Arms with a guard are ignored, those can’t always be merged together - // This is also the case for arms in-between each there is an arm with a guard - (min_index..=max_index).all(|index| arms[index].guard.is_none()) - && SpanlessEq::new(cx) - .expr_fallback(eq_fallback) - .eq_expr(lhs.body, rhs.body) - // these checks could be removed to allow unused bindings - && bindings_eq(lhs.pat, local_map.keys().copied().collect()) - && bindings_eq(rhs.pat, local_map.values().copied().collect()) - }; - - let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); - for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { - span_lint_and_then( - cx, - MATCH_SAME_ARMS, - j.body.span, - "this `match` has identical arm bodies", - |diag| { - diag.span_note(i.body.span, "same as this"); - - // Note: this does not use `span_suggestion` on purpose: - // there is no clean way - // to remove the other arm. Building a span and suggest to replace it to "" - // makes an even more confusing error message. Also in order not to make up a - // span for the whole pattern, the suggestion is only shown when there is only - // one pattern. The user should know about `|` if they are already using it… - - let lhs = snippet(cx, i.pat.span, ""); - let rhs = snippet(cx, j.pat.span, ""); - - if let PatKind::Wild = j.pat.kind { - // if the last arm is _, then i could be integrated into _ - // note that i.pat cannot be _, because that would mean that we're - // hiding all the subsequent arms, and rust won't compile - diag.span_note( - i.body.span, - &format!( - "`{}` has the same arm body as the `_` wildcard, consider removing it", - lhs - ), - ); - } else { - diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,)) - .help("...or consider changing the match arm bodies"); - } - }, - ); - } - } -} - -fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { - let mut result = false; - pat.walk_short(|p| { - result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); - !result - }); - result -} - -/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa -fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool { - let mut result = true; - pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); - result && ids.is_empty() -} diff --git a/clippy_lints/src/matches/infallible_destructuring_match.rs b/clippy_lints/src/matches/infallible_destructuring_match.rs new file mode 100644 index 00000000000..2472acb6f6e --- /dev/null +++ b/clippy_lints/src/matches/infallible_destructuring_match.rs @@ -0,0 +1,44 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs}; +use rustc_errors::Applicability; +use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath}; +use rustc_lint::LateContext; + +use super::INFALLIBLE_DESTRUCTURING_MATCH; + +pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool { + if_chain! { + if !local.span.from_expansion(); + if let Some(expr) = local.init; + if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; + if arms.len() == 1 && arms[0].guard.is_none(); + if let PatKind::TupleStruct( + QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; + if args.len() == 1; + if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; + let body = peel_blocks(arms[0].body); + if path_to_local_id(body, arg); + + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + INFALLIBLE_DESTRUCTURING_MATCH, + local.span, + "you seem to be trying to use `match` to destructure a single infallible pattern. \ + Consider using `let`", + "try this", + format!( + "let {}({}) = {};", + snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), + snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), + snippet_with_applicability(cx, target.span, "..", &mut applicability), + ), + applicability, + ); + return true; + } + } + false +} diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs new file mode 100644 index 00000000000..d914eba0171 --- /dev/null +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -0,0 +1,85 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{is_lang_ctor, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, LangItem, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MATCH_AS_REF; + +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + let arm_ref: Option = if is_none_arm(cx, &arms[0]) { + is_ref_some_arm(cx, &arms[1]) + } else if is_none_arm(cx, &arms[1]) { + is_ref_some_arm(cx, &arms[0]) + } else { + None + }; + if let Some(rb) = arm_ref { + let suggestion = if rb == BindingAnnotation::Ref { + "as_ref" + } else { + "as_mut" + }; + + let output_ty = cx.typeck_results().expr_ty(expr); + let input_ty = cx.typeck_results().expr_ty(ex); + + let cast = if_chain! { + if let ty::Adt(_, substs) = input_ty.kind(); + let input_ty = substs.type_at(0); + if let ty::Adt(_, substs) = output_ty.kind(); + let output_ty = substs.type_at(0); + if let ty::Ref(_, output_ty, _) = *output_ty.kind(); + if input_ty != output_ty; + then { + ".map(|x| x as _)" + } else { + "" + } + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MATCH_AS_REF, + expr.span, + &format!("use `{}()` instead", suggestion), + "try this", + format!( + "{}.{}(){}", + snippet_with_applicability(cx, ex.span, "_", &mut applicability), + suggestion, + cast, + ), + applicability, + ); + } + } +} + +// Checks if arm has the form `None => None` +fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, LangItem::OptionNone)) +} + +// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) +fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { + if_chain! { + if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; + if is_lang_ctor(cx, qpath, LangItem::OptionSome); + if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; + if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; + if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; + if let ExprKind::Path(ref some_path) = e.kind; + if is_lang_ctor(cx, some_path, LangItem::OptionSome) && args.len() == 1; + if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; + if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; + then { + return Some(rb) + } + } + None +} diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs new file mode 100644 index 00000000000..90c50b994d2 --- /dev/null +++ b/clippy_lints/src/matches/match_bool.rs @@ -0,0 +1,75 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_unit_expr; +use clippy_utils::source::{expr_block, snippet}; +use clippy_utils::sugg::Sugg; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MATCH_BOOL; + +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + // Type of expression is `bool`. + if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { + span_lint_and_then( + cx, + MATCH_BOOL, + expr.span, + "you seem to be trying to match on a boolean expression", + move |diag| { + if arms.len() == 2 { + // no guards + let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind { + if let ExprKind::Lit(ref lit) = arm_bool.kind { + match lit.node { + LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), + LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), + _ => None, + } + } else { + None + } + } else { + None + }; + + if let Some((true_expr, false_expr)) = exprs { + let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { + (false, false) => Some(format!( + "if {} {} else {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)), + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )), + (false, true) => Some(format!( + "if {} {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)) + )), + (true, false) => { + let test = Sugg::hir(cx, ex, ".."); + Some(format!( + "if {} {}", + !test, + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )) + }, + (true, true) => None, + }; + + if let Some(sugg) = sugg { + diag.span_suggestion( + expr.span, + "consider using an `if`/`else` expression", + sugg, + Applicability::HasPlaceholders, + ); + } + } + } + }, + ); + } +} diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs new file mode 100644 index 00000000000..2e1f7646eb4 --- /dev/null +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -0,0 +1,169 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{higher, is_wild}; +use rustc_ast::{Attribute, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Spanned; + +use super::MATCH_LIKE_MATCHES_MACRO; + +/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(higher::IfLet { + let_pat, + let_expr, + if_then, + if_else: Some(if_else), + }) = higher::IfLet::hir(cx, expr) + { + find_matches_sugg( + cx, + let_expr, + IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), + expr, + true, + ); + } +} + +pub(super) fn check_match<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + scrutinee: &'tcx Expr<'_>, + arms: &'tcx [Arm<'tcx>], +) -> bool { + find_matches_sugg( + cx, + scrutinee, + arms.iter().map(|arm| { + ( + cx.tcx.hir().attrs(arm.hir_id), + Some(arm.pat), + arm.body, + arm.guard.as_ref(), + ) + }), + e, + false, + ) +} + +/// Lint a `match` or `if let` for replacement by `matches!` +fn find_matches_sugg<'a, 'b, I>( + cx: &LateContext<'_>, + ex: &Expr<'_>, + mut iter: I, + expr: &Expr<'_>, + is_if_let: bool, +) -> bool +where + 'b: 'a, + I: Clone + + DoubleEndedIterator + + ExactSizeIterator + + Iterator< + Item = ( + &'a [Attribute], + Option<&'a Pat<'b>>, + &'a Expr<'b>, + Option<&'a Guard<'b>>, + ), + >, +{ + if_chain! { + if iter.len() >= 2; + if cx.typeck_results().expr_ty(expr).is_bool(); + if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); + let iter_without_last = iter.clone(); + if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); + if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); + if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); + if b0 != b1; + if first_guard.is_none() || iter.len() == 0; + if first_attrs.is_empty(); + if iter + .all(|arm| { + find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() + }); + then { + if let Some(last_pat) = last_pat_opt { + if !is_wild(last_pat) { + return false; + } + } + + // The suggestion may be incorrect, because some arms can have `cfg` attributes + // evaluated into `false` and so such arms will be stripped before. + let mut applicability = Applicability::MaybeIncorrect; + let pat = { + use itertools::Itertools as _; + iter_without_last + .filter_map(|arm| { + let pat_span = arm.1?.span; + Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) + }) + .join(" | ") + }; + let pat_and_guard = if let Some(Guard::If(g)) = first_guard { + format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) + } else { + pat + }; + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = ex; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { + if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { + ex_new = ex_inner; + } + }; + span_lint_and_sugg( + cx, + MATCH_LIKE_MATCHES_MACRO, + expr.span, + &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }), + "try this", + format!( + "{}matches!({}, {})", + if b0 { "" } else { "!" }, + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), + pat_and_guard, + ), + applicability, + ); + true + } else { + false + } + } +} + +/// Extract a `bool` or `{ bool }` +fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { + match ex { + ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) => Some(*b), + ExprKind::Block( + rustc_hir::Block { + stmts: &[], + expr: Some(exp), + .. + }, + _, + ) if is_if_let => { + if let ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) = exp.kind + { + Some(b) + } else { + None + } + }, + _ => None, + } +} diff --git a/clippy_lints/src/matches/match_ref_pats.rs b/clippy_lints/src/matches/match_ref_pats.rs new file mode 100644 index 00000000000..80f964ba1b7 --- /dev/null +++ b/clippy_lints/src/matches/match_ref_pats.rs @@ -0,0 +1,66 @@ +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::source::snippet; +use clippy_utils::sugg::Sugg; +use core::iter::once; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; +use rustc_lint::LateContext; + +use super::MATCH_REF_PATS; + +pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) +where + 'b: 'a, + I: Clone + Iterator>, +{ + if !has_multiple_ref_pats(pats.clone()) { + return; + } + + let (first_sugg, msg, title); + let span = ex.span.source_callsite(); + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind { + first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); + msg = "try"; + title = "you don't need to add `&` to both the expression and the patterns"; + } else { + first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); + msg = "instead of prefixing all patterns with `&`, you can dereference the expression"; + title = "you don't need to add `&` to all patterns"; + } + + let remaining_suggs = pats.filter_map(|pat| { + if let PatKind::Ref(refp, _) = pat.kind { + Some((pat.span, snippet(cx, refp.span, "..").to_string())) + } else { + None + } + }); + + span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { + if !expr.span.from_expansion() { + multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs)); + } + }); +} + +fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool +where + 'b: 'a, + I: Iterator>, +{ + let mut ref_count = 0; + for opt in pats.map(|pat| match pat.kind { + PatKind::Ref(..) => Some(true), // &-patterns + PatKind::Wild => Some(false), // an "anything" wildcard is also fine + _ => None, // any other pattern is not fine + }) { + if let Some(inner) = opt { + if inner { + ref_count += 1; + } + } else { + return false; + } + } + ref_count > 1 +} diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs new file mode 100644 index 00000000000..a96a7fe55f3 --- /dev/null +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -0,0 +1,419 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; +use core::cmp::Ordering; +use core::iter; +use core::slice; +use rustc_arena::DroplessArena; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Symbol; +use std::collections::hash_map::Entry; + +use super::MATCH_SAME_ARMS; + +#[expect(clippy::too_many_lines)] +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { + let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(arm.body); + h.finish() + }; + + let arena = DroplessArena::default(); + let normalized_pats: Vec<_> = arms + .iter() + .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat)) + .collect(); + + // The furthest forwards a pattern can move without semantic changes + let forwards_blocking_idxs: Vec<_> = normalized_pats + .iter() + .enumerate() + .map(|(i, pat)| { + normalized_pats[i + 1..] + .iter() + .enumerate() + .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j)) + .unwrap_or(normalized_pats.len()) + }) + .collect(); + + // The furthest backwards a pattern can move without semantic changes + let backwards_blocking_idxs: Vec<_> = normalized_pats + .iter() + .enumerate() + .map(|(i, pat)| { + normalized_pats[..i] + .iter() + .enumerate() + .rev() + .zip(forwards_blocking_idxs[..i].iter().copied().rev()) + .skip_while(|&(_, forward_block)| forward_block > i) + .find_map(|((j, other), forward_block)| { + (forward_block == i || pat.has_overlapping_values(other)).then(|| j) + }) + .unwrap_or(0) + }) + .collect(); + + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { + let min_index = usize::min(lindex, rindex); + let max_index = usize::max(lindex, rindex); + + let mut local_map: HirIdMap = HirIdMap::default(); + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + if_chain! { + if let Some(a_id) = path_to_local(a); + if let Some(b_id) = path_to_local(b); + let entry = match local_map.entry(a_id) { + Entry::Vacant(entry) => entry, + // check if using the same bindings as before + Entry::Occupied(entry) => return *entry.get() == b_id, + }; + // the names technically don't have to match; this makes the lint more conservative + if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); + if cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b); + if pat_contains_local(lhs.pat, a_id); + if pat_contains_local(rhs.pat, b_id); + then { + entry.insert(b_id); + true + } else { + false + } + } + }; + // Arms with a guard are ignored, those can’t always be merged together + // If both arms overlap with an arm in between then these can't be merged either. + !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index) + && lhs.guard.is_none() + && rhs.guard.is_none() + && SpanlessEq::new(cx) + .expr_fallback(eq_fallback) + .eq_expr(lhs.body, rhs.body) + // these checks could be removed to allow unused bindings + && bindings_eq(lhs.pat, local_map.keys().copied().collect()) + && bindings_eq(rhs.pat, local_map.values().copied().collect()) + }; + + let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); + for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { + if matches!(arm2.pat.kind, PatKind::Wild) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + arm1.span, + "this match arm has an identical body to the `_` wildcard arm", + |diag| { + diag.span_suggestion( + arm1.span, + "try removing the arm", + String::new(), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(arm2.span, "`_` wildcard arm here"); + }, + ); + } else { + let back_block = backwards_blocking_idxs[j]; + let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) { + (arm1, arm2) + } else { + (arm2, arm1) + }; + + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + keep_arm.span, + "this match arm has an identical body to another arm", + |diag| { + let move_pat_snip = snippet(cx, move_arm.pat.span, ""); + let keep_pat_snip = snippet(cx, keep_arm.pat.span, ""); + + diag.span_suggestion( + keep_arm.pat.span, + "try merging the arm patterns", + format!("{} | {}", keep_pat_snip, move_pat_snip), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(move_arm.span, "other arm here"); + }, + ); + } + } +} + +#[derive(Clone, Copy)] +enum NormalizedPat<'a> { + Wild, + Struct(Option, &'a [(Symbol, Self)]), + Tuple(Option, &'a [Self]), + Or(&'a [Self]), + Path(Option), + LitStr(Symbol), + LitBytes(&'a [u8]), + LitInt(u128), + LitBool(bool), + Range(PatRange), + /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise + /// the first value contains everything before the `..` wildcard pattern, and the second value + /// contains everything afterwards. Note that either side, or both sides, may contain zero + /// patterns. + Slice(&'a [Self], Option<&'a [Self]>), +} + +#[derive(Clone, Copy)] +struct PatRange { + start: u128, + end: u128, + bounds: RangeEnd, +} +impl PatRange { + fn contains(&self, x: u128) -> bool { + x >= self.start + && match self.bounds { + RangeEnd::Included => x <= self.end, + RangeEnd::Excluded => x < self.end, + } + } + + fn overlaps(&self, other: &Self) -> bool { + // Note: Empty ranges are impossible, so this is correct even though it would return true if an + // empty exclusive range were to reside within an inclusive range. + (match self.bounds { + RangeEnd::Included => self.end >= other.start, + RangeEnd::Excluded => self.end > other.start, + } && match other.bounds { + RangeEnd::Included => self.start <= other.end, + RangeEnd::Excluded => self.start < other.end, + }) + } +} + +/// Iterates over the pairs of fields with matching names. +fn iter_matching_struct_fields<'a>( + left: &'a [(Symbol, NormalizedPat<'a>)], + right: &'a [(Symbol, NormalizedPat<'a>)], +) -> impl Iterator, &'a NormalizedPat<'a>)> + 'a { + struct Iter<'a>( + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + ); + impl<'a> Iterator for Iter<'a> { + type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>); + fn next(&mut self) -> Option { + // Note: all the fields in each slice are sorted by symbol value. + let mut left = self.0.next()?; + let mut right = self.1.next()?; + loop { + match left.0.cmp(&right.0) { + Ordering::Equal => return Some((&left.1, &right.1)), + Ordering::Less => left = self.0.next()?, + Ordering::Greater => right = self.1.next()?, + } + } + } + } + Iter(left.iter(), right.iter()) +} + +#[expect(clippy::similar_names)] +impl<'a> NormalizedPat<'a> { + #[expect(clippy::too_many_lines)] + fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self { + match pat.kind { + PatKind::Wild | PatKind::Binding(.., None) => Self::Wild, + PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => { + Self::from_pat(cx, arena, pat) + }, + PatKind::Struct(ref path, fields, _) => { + let fields = + arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat)))); + fields.sort_by_key(|&(name, _)| name); + Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields) + }, + PatKind::TupleStruct(ref path, pats, wild_idx) => { + let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() { + Some(x) => x, + None => return Self::Wild, + }; + let (var_id, variant) = if adt.is_enum() { + match cx.qpath_res(path, pat.hir_id).opt_def_id() { + Some(x) => (Some(x), adt.variant_with_ctor_id(x)), + None => return Self::Wild, + } + } else { + (None, adt.non_enum_variant()) + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(var_id, pats) + }, + PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))), + PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()), + PatKind::Tuple(pats, wild_idx) => { + let field_count = match cx.typeck_results().pat_ty(pat).kind() { + ty::Tuple(subs) => subs.len(), + _ => return Self::Wild, + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(None, pats) + }, + PatKind::Lit(e) => match &e.kind { + // TODO: Handle negative integers. They're currently treated as a wild match. + ExprKind::Lit(lit) => match lit.node { + LitKind::Str(sym, _) => Self::LitStr(sym), + LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes), + LitKind::Byte(val) => Self::LitInt(val.into()), + LitKind::Char(val) => Self::LitInt(val.into()), + LitKind::Int(val, _) => Self::LitInt(val), + LitKind::Bool(val) => Self::LitBool(val), + LitKind::Float(..) | LitKind::Err(_) => Self::Wild, + }, + _ => Self::Wild, + }, + PatKind::Range(start, end, bounds) => { + // TODO: Handle negative integers. They're currently treated as a wild match. + let start = match start { + None => 0, + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => val, + LitKind::Char(val) => val.into(), + LitKind::Byte(val) => val.into(), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + let (end, bounds) = match end { + None => (u128::MAX, RangeEnd::Included), + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => (val, bounds), + LitKind::Char(val) => (val.into(), bounds), + LitKind::Byte(val) => (val.into(), bounds), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + Self::Range(PatRange { start, end, bounds }) + }, + PatKind::Slice(front, wild_pat, back) => Self::Slice( + arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))), + wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))), + ), + } + } + + /// Checks if two patterns overlap in the values they can match assuming they are for the same + /// type. + fn has_overlapping_values(&self, other: &Self) -> bool { + match (*self, *other) { + (Self::Wild, _) | (_, Self::Wild) => true, + (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => { + pats.iter().any(|pat| pat.has_overlapping_values(other)) + }, + (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => { + if lpath != rpath { + return false; + } + iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + }, + (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => { + if lpath != rpath { + return false; + } + lpats + .iter() + .zip(rpats.iter()) + .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + }, + (Self::Path(x), Self::Path(y)) => x == y, + (Self::LitStr(x), Self::LitStr(y)) => x == y, + (Self::LitBytes(x), Self::LitBytes(y)) => x == y, + (Self::LitInt(x), Self::LitInt(y)) => x == y, + (Self::LitBool(x), Self::LitBool(y)) => x == y, + (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), + (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), + (Self::Slice(lpats, None), Self::Slice(rpats, None)) => { + lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y)) + }, + (Self::Slice(pats, None), Self::Slice(front, Some(back))) + | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { + // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater + // then the minium length required will be greater than the length of `pats`. + if pats.len() < front.len() + back.len() { + return false; + } + pats[..front.len()] + .iter() + .zip(front.iter()) + .chain(pats[pats.len() - back.len()..].iter().zip(back.iter())) + .all(|(x, y)| x.has_overlapping_values(y)) + }, + (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront + .iter() + .zip(rfront.iter()) + .chain(lback.iter().rev().zip(rback.iter().rev())) + .all(|(x, y)| x.has_overlapping_values(y)), + + // Enums can mix unit variants with tuple/struct variants. These can never overlap. + (Self::Path(_), Self::Tuple(..) | Self::Struct(..)) + | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false, + + // Tuples can be matched like a struct. + (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => { + // TODO: check fields here. + x == y + }, + + // TODO: Lit* with Path, Range with Path, LitBytes with Slice + _ => true, + } + } +} + +fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { + let mut result = false; + pat.walk_short(|p| { + result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); + !result + }); + result +} + +/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa +fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool { + let mut result = true; + pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); + result && ids.is_empty() +} diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs new file mode 100644 index 00000000000..a59711d4cac --- /dev/null +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -0,0 +1,216 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::macros::HirNode; +use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::MATCH_SINGLE_BINDING; + +enum AssignmentExpr { + Assign { span: Span, match_span: Span }, + Local { span: Span, pat_span: Span }, +} + +#[expect(clippy::too_many_lines)] +pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) { + if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { + return; + } + + let matched_vars = ex.span; + let bind_names = arms[0].pat.span; + let match_body = peel_blocks(arms[0].body); + let mut snippet_body = if match_body.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() + } else { + snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() + }; + + // Do we need to add ';' to suggestion ? + match match_body.kind { + ExprKind::Block(block, _) => { + // macro + expr_ty(body) == () + if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() { + snippet_body.push(';'); + } + }, + _ => { + // expr_ty(body) == () + if cx.typeck_results().expr_ty(match_body).is_unit() { + snippet_body.push(';'); + } + }, + } + + let mut applicability = Applicability::MaybeIncorrect; + match arms[0].pat.kind { + PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { + let (target_span, sugg) = match opt_parent_assign_span(cx, ex) { + Some(AssignmentExpr::Assign { span, match_span }) => { + let sugg = sugg_with_curlies( + cx, + (ex, expr), + (bind_names, matched_vars), + &*snippet_body, + &mut applicability, + Some(span), + ); + + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + span.to(match_span), + "this assignment could be simplified", + "consider removing the `match` expression", + sugg, + applicability, + ); + + return; + }, + Some(AssignmentExpr::Local { span, pat_span }) => ( + span, + format!( + "let {} = {};\n{}let {} = {};", + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), + snippet_with_applicability(cx, pat_span, "..", &mut applicability), + snippet_body + ), + ), + None => { + let sugg = sugg_with_curlies( + cx, + (ex, expr), + (bind_names, matched_vars), + &*snippet_body, + &mut applicability, + None, + ); + (expr.span, sugg) + }, + }; + + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + target_span, + "this match could be written as a `let` statement", + "consider using a `let` statement", + sugg, + applicability, + ); + }, + PatKind::Wild => { + if ex.can_have_side_effects() { + let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0)); + let sugg = format!( + "{};\n{}{}", + snippet_with_applicability(cx, ex.span, "..", &mut applicability), + indent, + snippet_body + ); + + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its scrutinee and body", + "consider using the scrutinee and body instead", + sugg, + applicability, + ); + } else { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } +} + +/// Returns true if the `ex` match expression is in a local (`let`) or assign expression +fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option { + let map = &cx.tcx.hir(); + + if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) { + return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) { + Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local { + span: parent_let_expr.span, + pat_span: parent_let_expr.pat.span(), + }), + Some(Node::Expr(Expr { + kind: ExprKind::Assign(parent_assign_expr, match_expr, _), + .. + })) => Some(AssignmentExpr::Assign { + span: parent_assign_expr.span, + match_span: match_expr.span, + }), + _ => None, + }; + } + + None +} + +fn sugg_with_curlies<'a>( + cx: &LateContext<'a>, + (ex, match_expr): (&Expr<'a>, &Expr<'a>), + (bind_names, matched_vars): (Span, Span), + snippet_body: &str, + applicability: &mut Applicability, + assignment: Option, +) -> String { + let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); + + let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); + if let Some(parent_expr) = get_parent_expr(cx, match_expr) { + if let ExprKind::Closure(..) = parent_expr.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + } + + // If the parent is already an arm, and the body is another match statement, + // we need curly braces around suggestion + let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id); + if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) { + if let ExprKind::Match(..) = arm.body.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the match + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + } + + let assignment_str = assignment.map_or_else(String::new, |span| { + let mut s = snippet(cx, span, "..").to_string(); + s.push_str(" = "); + s + }); + + format!( + "{}let {} = {};\n{}{}{}{}", + cbrace_start, + snippet_with_applicability(cx, bind_names, "..", applicability), + snippet_with_applicability(cx, matched_vars, "..", applicability), + indent, + assignment_str, + snippet_body, + cbrace_end + ) +} diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs new file mode 100644 index 00000000000..6f8d766aef7 --- /dev/null +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -0,0 +1,196 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns}; +use rustc_errors::Applicability; +use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::{Arm, Expr, PatKind, PathSegment, QPath, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, VariantDef}; +use rustc_span::sym; + +use super::{MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_ENUM_MATCH_ARM}; + +#[expect(clippy::too_many_lines)] +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + let ty = cx.typeck_results().expr_ty(ex).peel_refs(); + let adt_def = match ty.kind() { + ty::Adt(adt_def, _) + if adt_def.is_enum() + && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) => + { + adt_def + }, + _ => return, + }; + + // First pass - check for violation, but don't do much book-keeping because this is hopefully + // the uncommon case, and the book-keeping is slightly expensive. + let mut wildcard_span = None; + let mut wildcard_ident = None; + let mut has_non_wild = false; + for arm in arms { + match peel_hir_pat_refs(arm.pat).0.kind { + PatKind::Wild => wildcard_span = Some(arm.pat.span), + PatKind::Binding(_, _, ident, None) => { + wildcard_span = Some(arm.pat.span); + wildcard_ident = Some(ident); + }, + _ => has_non_wild = true, + } + } + let wildcard_span = match wildcard_span { + Some(x) if has_non_wild => x, + _ => return, + }; + + // Accumulate the variants which should be put in place of the wildcard because they're not + // already covered. + let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x)); + let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect(); + + let mut path_prefix = CommonPrefixSearcher::None; + for arm in arms { + // Guards mean that this case probably isn't exhaustively covered. Technically + // this is incorrect, as we should really check whether each variant is exhaustively + // covered by the set of guards that cover it, but that's really hard to do. + recurse_or_patterns(arm.pat, |pat| { + let path = match &peel_hir_pat_refs(pat).0.kind { + PatKind::Path(path) => { + let id = match cx.qpath_res(path, pat.hir_id) { + Res::Def( + DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst, + _, + ) => return, + Res::Def(_, id) => id, + _ => return, + }; + if arm.guard.is_none() { + missing_variants.retain(|e| e.ctor_def_id != Some(id)); + } + path + }, + PatKind::TupleStruct(path, patterns, ..) => { + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { + if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) { + missing_variants.retain(|e| e.ctor_def_id != Some(id)); + } + } + path + }, + PatKind::Struct(path, patterns, ..) => { + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { + if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) { + missing_variants.retain(|e| e.def_id != id); + } + } + path + }, + _ => return, + }; + match path { + QPath::Resolved(_, path) => path_prefix.with_path(path.segments), + QPath::TypeRelative( + Ty { + kind: TyKind::Path(QPath::Resolved(_, path)), + .. + }, + _, + ) => path_prefix.with_prefix(path.segments), + _ => (), + } + }); + } + + let format_suggestion = |variant: &VariantDef| { + format!( + "{}{}{}{}", + if let Some(ident) = wildcard_ident { + format!("{} @ ", ident.name) + } else { + String::new() + }, + if let CommonPrefixSearcher::Path(path_prefix) = path_prefix { + let mut s = String::new(); + for seg in path_prefix { + s.push_str(seg.ident.as_str()); + s.push_str("::"); + } + s + } else { + let mut s = cx.tcx.def_path_str(adt_def.did()); + s.push_str("::"); + s + }, + variant.name, + match variant.ctor_kind { + CtorKind::Fn if variant.fields.len() == 1 => "(_)", + CtorKind::Fn => "(..)", + CtorKind::Const => "", + CtorKind::Fictive => "{ .. }", + } + ) + }; + + match missing_variants.as_slice() { + [] => (), + [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + "wildcard matches only a single variant and will also match any future added variants", + "try this", + format_suggestion(x), + Applicability::MaybeIncorrect, + ), + variants => { + let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); + let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden { + suggestions.push("_".into()); + "wildcard matches known variants and will also match future added variants" + } else { + "wildcard match will also match any future added variants" + }; + + span_lint_and_sugg( + cx, + WILDCARD_ENUM_MATCH_ARM, + wildcard_span, + message, + "try this", + suggestions.join(" | "), + Applicability::MaybeIncorrect, + ); + }, + }; +} + +enum CommonPrefixSearcher<'a> { + None, + Path(&'a [PathSegment<'a>]), + Mixed, +} +impl<'a> CommonPrefixSearcher<'a> { + fn with_path(&mut self, path: &'a [PathSegment<'a>]) { + match path { + [path @ .., _] => self.with_prefix(path), + [] => (), + } + } + + fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) { + match self { + Self::None => *self = Self::Path(path), + Self::Path(self_path) + if path + .iter() + .map(|p| p.ident.name) + .eq(self_path.iter().map(|p| p.ident.name)) => {}, + Self::Path(_) => *self = Self::Mixed, + Self::Mixed => (), + } + } +} + +fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool { + cx.tcx.is_doc_hidden(variant_def.def_id) || cx.tcx.has_attr(variant_def.def_id, sym::unstable) +} diff --git a/clippy_lints/src/matches/match_wild_err_arm.rs b/clippy_lints/src/matches/match_wild_err_arm.rs new file mode 100644 index 00000000000..bc16f17b619 --- /dev/null +++ b/clippy_lints/src/matches/match_wild_err_arm.rs @@ -0,0 +1,51 @@ +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::macros::{is_panic, root_macro_call}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::visitors::is_local_used; +use clippy_utils::{is_wild, peel_blocks_with_stmt}; +use rustc_hir::{Arm, Expr, PatKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::{kw, sym}; + +use super::MATCH_WILD_ERR_ARM; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { + let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); + if is_type_diagnostic_item(cx, ex_ty, sym::Result) { + for arm in arms { + if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { + let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); + if path_str == "Err" { + let mut matching_wild = inner.iter().any(is_wild); + let mut ident_bind_name = kw::Underscore; + if !matching_wild { + // Looking for unused bindings (i.e.: `_e`) + for pat in inner.iter() { + if let PatKind::Binding(_, id, ident, None) = pat.kind { + if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) { + ident_bind_name = ident.name; + matching_wild = true; + } + } + } + } + if_chain! { + if matching_wild; + if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span); + if is_panic(cx, macro_call.def_id); + then { + // `Err(_)` or `Err(_e)` arm with `panic!` found + span_lint_and_note(cx, + MATCH_WILD_ERR_ARM, + arm.pat.span, + &format!("`Err({})` matches all errors", ident_bind_name), + None, + "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", + ); + } + } + } + } + } + } +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs new file mode 100644 index 00000000000..3d8391bce2b --- /dev/null +++ b/clippy_lints/src/matches/mod.rs @@ -0,0 +1,796 @@ +use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context}; +use clippy_utils::{meets_msrv, msrvs}; +use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat}; +use rustc_lexer::{tokenize, TokenKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{Span, SpanData, SyntaxContext}; + +mod infallible_destructuring_match; +mod match_as_ref; +mod match_bool; +mod match_like_matches; +mod match_ref_pats; +mod match_same_arms; +mod match_single_binding; +mod match_wild_enum; +mod match_wild_err_arm; +mod needless_match; +mod overlapping_arms; +mod redundant_pattern_match; +mod rest_pat_in_fully_bound_struct; +mod single_match; +mod wild_in_or_pats; + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches with a single arm where an `if let` + /// will usually suffice. + /// + /// ### Why is this bad? + /// Just readability – `if let` nests less than a `match`. + /// + /// ### Example + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); + /// // Bad + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => (), + /// } + /// + /// // Good + /// if let Some(ref foo) = x { + /// bar(foo); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SINGLE_MATCH, + style, + "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches with two arms where an `if let else` will + /// usually suffice. + /// + /// ### Why is this bad? + /// Just readability – `if let` nests less than a `match`. + /// + /// ### Known problems + /// Personal style preferences may differ. + /// + /// ### Example + /// Using `match`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => bar(&other_ref), + /// } + /// ``` + /// + /// Using `if let` with `else`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// if let Some(ref foo) = x { + /// bar(foo); + /// } else { + /// bar(&other_ref); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SINGLE_MATCH_ELSE, + pedantic, + "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches where all arms match a reference, + /// suggesting to remove the reference and deref the matched expression + /// instead. It also checks for `if let &foo = bar` blocks. + /// + /// ### Why is this bad? + /// It just makes the code less readable. That reference + /// destructuring adds nothing to the code. + /// + /// ### Example + /// ```rust,ignore + /// // Bad + /// match x { + /// &A(ref y) => foo(y), + /// &B => bar(), + /// _ => frob(&x), + /// } + /// + /// // Good + /// match *x { + /// A(ref y) => foo(y), + /// B => bar(), + /// _ => frob(x), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_REF_PATS, + style, + "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches where match expression is a `bool`. It + /// suggests to replace the expression with an `if...else` block. + /// + /// ### Why is this bad? + /// It makes the code less readable. + /// + /// ### Example + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// match condition { + /// true => foo(), + /// false => bar(), + /// } + /// ``` + /// Use if/else instead: + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// if condition { + /// foo(); + /// } else { + /// bar(); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_BOOL, + pedantic, + "a `match` on a boolean expression instead of an `if..else` block" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for overlapping match arms. + /// + /// ### Why is this bad? + /// It is likely to be an error and if not, makes the code + /// less obvious. + /// + /// ### Example + /// ```rust + /// let x = 5; + /// match x { + /// 1..=10 => println!("1 ... 10"), + /// 5..=15 => println!("5 ... 15"), + /// _ => (), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_OVERLAPPING_ARM, + style, + "a `match` with overlapping arms" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for arm which matches all errors with `Err(_)` + /// and take drastic actions like `panic!`. + /// + /// ### Why is this bad? + /// It is generally a bad practice, similar to + /// catching all exceptions in java with `catch(Exception)` + /// + /// ### Example + /// ```rust + /// let x: Result = Ok(3); + /// match x { + /// Ok(_) => println!("ok"), + /// Err(_) => panic!("err"), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_WILD_ERR_ARM, + pedantic, + "a `match` with `Err(_)` arm and take drastic actions" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for match which is used to add a reference to an + /// `Option` value. + /// + /// ### Why is this bad? + /// Using `as_ref()` or `as_mut()` instead is shorter. + /// + /// ### Example + /// ```rust + /// let x: Option<()> = None; + /// + /// // Bad + /// let r: Option<&()> = match x { + /// None => None, + /// Some(ref v) => Some(v), + /// }; + /// + /// // Good + /// let r: Option<&()> = x.as_ref(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_AS_REF, + complexity, + "a `match` on an Option value instead of using `as_ref()` or `as_mut`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard enum matches using `_`. + /// + /// ### Why is this bad? + /// New enum variants added by library updates can be missed. + /// + /// ### Known problems + /// Suggested replacements may be incorrect if guards exhaustively cover some + /// variants, and also may not use correct path to enum if it's not present in the current scope. + /// + /// ### Example + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); + /// // Bad + /// match x { + /// Foo::A(_) => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A(_) => {}, + /// Foo::B(_) => {}, + /// } + /// ``` + #[clippy::version = "1.34.0"] + pub WILDCARD_ENUM_MATCH_ARM, + restriction, + "a wildcard enum match arm using `_`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard enum matches for a single variant. + /// + /// ### Why is this bad? + /// New enum variants added by library updates can be missed. + /// + /// ### Known problems + /// Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// ### Example + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// // Bad + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + #[clippy::version = "1.45.0"] + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard pattern used with others patterns in same match arm. + /// + /// ### Why is this bad? + /// Wildcard pattern already covers any other pattern as it will match anyway. + /// It makes the code less readable, especially to spot wildcard pattern use in match arm. + /// + /// ### Example + /// ```rust + /// // Bad + /// match "foo" { + /// "a" => {}, + /// "bar" | _ => {}, + /// } + /// + /// // Good + /// match "foo" { + /// "a" => {}, + /// _ => {}, + /// } + /// ``` + #[clippy::version = "1.42.0"] + pub WILDCARD_IN_OR_PATTERNS, + complexity, + "a wildcard pattern used with others patterns in same match arm" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches being used to destructure a single-variant enum + /// or tuple struct where a `let` will suffice. + /// + /// ### Why is this bad? + /// Just readability – `let` doesn't nest, whereas a `match` does. + /// + /// ### Example + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// + /// let data = match wrapper { + /// Wrapper::Data(i) => i, + /// }; + /// ``` + /// + /// The correct use would be: + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// let Wrapper::Data(data) = wrapper; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INFALLIBLE_DESTRUCTURING_MATCH, + style, + "a `match` statement with a single infallible arm instead of a `let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for useless match that binds to only one value. + /// + /// ### Why is this bad? + /// Readability and needless complexity. + /// + /// ### Known problems + /// Suggested replacements may be incorrect when `match` + /// is actually binding temporary value, bringing a 'dropped while borrowed' error. + /// + /// ### Example + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// + /// // Bad + /// match (a, b) { + /// (c, d) => { + /// // useless match + /// } + /// } + /// + /// // Good + /// let (c, d) = (a, b); + /// ``` + #[clippy::version = "1.43.0"] + pub MATCH_SINGLE_BINDING, + complexity, + "a match with a single binding instead of using `let` statement" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. + /// + /// ### Why is this bad? + /// Correctness and readability. It's like having a wildcard pattern after + /// matching all enum variants explicitly. + /// + /// ### Example + /// ```rust + /// # struct A { a: i32 } + /// let a = A { a: 5 }; + /// + /// // Bad + /// match a { + /// A { a: 5, .. } => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match a { + /// A { a: 5 } => {}, + /// _ => {}, + /// } + /// ``` + #[clippy::version = "1.43.0"] + pub REST_PAT_IN_FULLY_BOUND_STRUCTS, + restriction, + "a match on a struct that binds all fields but still uses the wildcard pattern" +} + +declare_clippy_lint! { + /// ### What it does + /// Lint for redundant pattern matching over `Result`, `Option`, + /// `std::task::Poll` or `std::net::IpAddr` + /// + /// ### Why is this bad? + /// It's more concise and clear to just use the proper + /// utility function + /// + /// ### Known problems + /// This will change the drop order for the matched type. Both `if let` and + /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the + /// value before entering the block. For most types this change will not matter, but for a few + /// types this will not be an acceptable change (e.g. locks). See the + /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about + /// drop order. + /// + /// ### Example + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if let Ok(_) = Ok::(42) {} + /// if let Err(_) = Err::(42) {} + /// if let None = None::<()> {} + /// if let Some(_) = Some(42) {} + /// if let Poll::Pending = Poll::Pending::<()> {} + /// if let Poll::Ready(_) = Poll::Ready(42) {} + /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} + /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} + /// match Ok::(42) { + /// Ok(_) => true, + /// Err(_) => false, + /// }; + /// ``` + /// + /// The more idiomatic use would be: + /// + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if Ok::(42).is_ok() {} + /// if Err::(42).is_err() {} + /// if None::<()>.is_none() {} + /// if Some(42).is_some() {} + /// if Poll::Pending::<()>.is_pending() {} + /// if Poll::Ready(42).is_ready() {} + /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + /// Ok::(42).is_ok(); + /// ``` + #[clippy::version = "1.31.0"] + pub REDUNDANT_PATTERN_MATCHING, + style, + "use the proper utility function avoiding an `if let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `match` or `if let` expressions producing a + /// `bool` that could be written using `matches!` + /// + /// ### Why is this bad? + /// Readability and needless complexity. + /// + /// ### Known problems + /// This lint falsely triggers, if there are arms with + /// `cfg` attributes that remove an arm evaluating to `false`. + /// + /// ### Example + /// ```rust + /// let x = Some(5); + /// + /// // Bad + /// let a = match x { + /// Some(0) => true, + /// _ => false, + /// }; + /// + /// let a = if let Some(0) = x { + /// true + /// } else { + /// false + /// }; + /// + /// // Good + /// let a = matches!(x, Some(0)); + /// ``` + #[clippy::version = "1.47.0"] + pub MATCH_LIKE_MATCHES_MACRO, + style, + "a match that could be written with the matches! macro" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `match` with identical arm bodies. + /// + /// ### Why is this bad? + /// This is probably a copy & paste error. If arm bodies + /// are the same on purpose, you can factor them + /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). + /// + /// ### Known problems + /// False positive possible with order dependent `match` + /// (see issue + /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). + /// + /// ### Example + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => bar(), // <= oops + /// } + /// ``` + /// + /// This should probably be + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => baz(), // <= fixed + /// } + /// ``` + /// + /// or if the original code was not a typo: + /// ```rust,ignore + /// match foo { + /// Bar | Baz => bar(), // <= shows the intent better + /// Quz => quz(), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_SAME_ARMS, + pedantic, + "`match` with identical arm bodies" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result` + /// when function signatures are the same. + /// + /// ### Why is this bad? + /// This `match` block does nothing and might not be what the coder intended. + /// + /// ### Example + /// ```rust,ignore + /// fn foo() -> Result<(), i32> { + /// match result { + /// Ok(val) => Ok(val), + /// Err(err) => Err(err), + /// } + /// } + /// + /// fn bar() -> Option { + /// if let Some(val) = option { + /// Some(val) + /// } else { + /// None + /// } + /// } + /// ``` + /// + /// Could be replaced as + /// + /// ```rust,ignore + /// fn foo() -> Result<(), i32> { + /// result + /// } + /// + /// fn bar() -> Option { + /// option + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub NEEDLESS_MATCH, + complexity, + "`match` or match-like `if let` that are unnecessary" +} + +#[derive(Default)] +pub struct Matches { + msrv: Option, + infallible_destructuring_match_linted: bool, +} + +impl Matches { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + ..Matches::default() + } + } +} + +impl_lint_pass!(Matches => [ + SINGLE_MATCH, + MATCH_REF_PATS, + MATCH_BOOL, + SINGLE_MATCH_ELSE, + MATCH_OVERLAPPING_ARM, + MATCH_WILD_ERR_ARM, + MATCH_AS_REF, + WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + WILDCARD_IN_OR_PATTERNS, + MATCH_SINGLE_BINDING, + INFALLIBLE_DESTRUCTURING_MATCH, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + REDUNDANT_PATTERN_MATCHING, + MATCH_LIKE_MATCHES_MACRO, + MATCH_SAME_ARMS, + NEEDLESS_MATCH, +]); + +impl<'tcx> LateLintPass<'tcx> for Matches { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Match(ex, arms, source) = expr.kind { + if !span_starts_with(cx, expr.span, "match") { + return; + } + if !contains_cfg_arm(cx, expr, ex, arms) { + if source == MatchSource::Normal { + if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO) + && match_like_matches::check_match(cx, expr, ex, arms)) + { + match_same_arms::check(cx, arms); + } + + redundant_pattern_match::check_match(cx, expr, ex, arms); + single_match::check(cx, ex, arms, expr); + match_bool::check(cx, ex, arms, expr); + overlapping_arms::check(cx, ex, arms); + match_wild_enum::check(cx, ex, arms); + match_as_ref::check(cx, ex, arms, expr); + needless_match::check_match(cx, ex, arms, expr); + + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + match_single_binding::check(cx, ex, arms, expr); + } + } + match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); + } + + // These don't depend on a relationship between multiple arms + match_wild_err_arm::check(cx, ex, arms); + wild_in_or_pats::check(cx, arms); + } else { + if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) { + match_like_matches::check(cx, expr); + } + redundant_pattern_match::check(cx, expr); + needless_match::check(cx, expr); + } + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local); + } + + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + rest_pat_in_fully_bound_struct::check(cx, pat); + } + + extract_msrv_attr!(LateContext); +} + +/// Checks if there are any arms with a `#[cfg(..)]` attribute. +fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool { + let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else { + // Shouldn't happen, but treat this as though a `cfg` attribute were found + return true; + }; + + let start = scrutinee_span.hi(); + let mut arm_spans = arms.iter().map(|arm| { + let data = arm.span.data(); + (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi)) + }); + let end = e.span.hi(); + + // Walk through all the non-code space before each match arm. The space trailing the final arm is + // handled after the `try_fold` e.g. + // + // match foo { + // _________^- everything between the scrutinee and arm1 + //| arm1 => (), + //|---^___________^ everything before arm2 + //| #[cfg(feature = "enabled")] + //| arm2 => some_code(), + //|---^____________________^ everything before arm3 + //| // some comment about arm3 + //| arm3 => some_code(), + //|---^____________________^ everything after arm3 + //| #[cfg(feature = "disabled")] + //| arm4 = some_code(), + //|}; + //|^ + let found = arm_spans.try_fold(start, |start, range| { + let Some((end, next_start)) = range else { + // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were + // found. + return Err(()); + }; + let span = SpanData { + lo: start, + hi: end, + ctxt: SyntaxContext::root(), + parent: None, + } + .span(); + (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(()) + }); + match found { + Ok(start) => { + let span = SpanData { + lo: start, + hi: end, + ctxt: SyntaxContext::root(), + parent: None, + } + .span(); + span_contains_cfg(cx, span) + }, + Err(()) => true, + } +} + +/// Checks if the given span contains a `#[cfg(..)]` attribute +fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { + let Some(snip) = snippet_opt(cx, s) else { + // Assume true. This would require either an invalid span, or one which crosses file boundaries. + return true; + }; + let mut pos = 0usize; + let mut iter = tokenize(&snip).map(|t| { + let start = pos; + pos += t.len; + (t.kind, start..pos) + }); + + // Search for the token sequence [`#`, `[`, `cfg`] + while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) { + let mut iter = iter.by_ref().skip_while(|(t, _)| { + matches!( + t, + TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } + ) + }); + if matches!(iter.next(), Some((TokenKind::OpenBracket, _))) + && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg") + { + return true; + } + } + false +} diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs new file mode 100644 index 00000000000..f920ad4651f --- /dev/null +++ b/clippy_lints/src/matches/needless_match.rs @@ -0,0 +1,209 @@ +use super::NEEDLESS_MATCH; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts}; +use clippy_utils::{ + eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over, + peel_blocks_with_stmt, +}; +use rustc_errors::Applicability; +use rustc_hir::LangItem::OptionNone; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; +use rustc_typeck::hir_ty_to_ty; + +pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_MATCH, + expr.span, + "this match expression is unnecessary", + "replace it with", + snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(), + applicability, + ); + } +} + +/// Check for nop `if let` expression that assembled as unnecessary match +/// +/// ```rust,ignore +/// if let Some(a) = option { +/// Some(a) +/// } else { +/// None +/// } +/// ``` +/// OR +/// ```rust,ignore +/// if let SomeEnum::A = some_enum { +/// SomeEnum::A +/// } else if let SomeEnum::B = some_enum { +/// SomeEnum::B +/// } else { +/// some_enum +/// } +/// ``` +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) { + if let Some(ref if_let) = higher::IfLet::hir(cx, ex) { + if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_MATCH, + ex.span, + "this if-let expression is unnecessary", + "replace it with", + snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(), + applicability, + ); + } + } +} + +fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool { + for arm in arms { + let arm_expr = peel_blocks_with_stmt(arm.body); + if let PatKind::Wild = arm.pat.kind { + return eq_expr_value(cx, match_expr, strip_return(arm_expr)); + } else if !pat_same_as_expr(arm.pat, arm_expr) { + return false; + } + } + + true +} + +fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool { + if let Some(if_else) = if_let.if_else { + if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) { + return false; + } + + // Recursively check for each `else if let` phrase, + if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) { + return check_if_let(cx, nested_if_let); + } + + if matches!(if_else.kind, ExprKind::Block(..)) { + let else_expr = peel_blocks_with_stmt(if_else); + if matches!(else_expr.kind, ExprKind::Block(..)) { + return false; + } + let ret = strip_return(else_expr); + let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr); + if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) { + if let ExprKind::Path(ref qpath) = ret.kind { + return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret); + } + return false; + } + return eq_expr_value(cx, if_let.let_expr, ret); + } + } + + false +} + +/// Strip `return` keyword if the expression type is `ExprKind::Ret`. +fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { + if let ExprKind::Ret(Some(ret)) = expr.kind { + ret + } else { + expr + } +} + +/// Manually check for coercion casting by checking if the type of the match operand or let expr +/// differs with the assigned local variable or the function return type. +fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool { + if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) { + match p_node { + // Compare match_expr ty with local in `let local = match match_expr {..}` + Node::Local(local) => { + let results = cx.typeck_results(); + return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr)); + }, + // compare match_expr ty with RetTy in `fn foo() -> RetTy` + Node::Item(..) => { + if let Some(fn_decl) = p_node.fn_decl() { + if let FnRetTy::Return(ret_ty) = fn_decl.output { + return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr)); + } + } + }, + // check the parent expr for this whole block `{ match match_expr {..} }` + Node::Block(block) => { + if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) { + return expr_ty_matches_p_ty(cx, expr, block_parent_expr); + } + }, + // recursively call on `if xxx {..}` etc. + Node::Expr(p_expr) => { + return expr_ty_matches_p_ty(cx, expr, p_expr); + }, + _ => {}, + } + } + false +} + +fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { + let expr = strip_return(expr); + match (&pat.kind, &expr.kind) { + // Example: `Some(val) => Some(val)` + (PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => { + if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind { + return over(path.segments, call_path.segments, |pat_seg, call_seg| { + pat_seg.ident.name == call_seg.ident.name + }) && same_non_ref_symbols(tuple_params, call_params); + } + }, + // Example: `val => val` + ( + PatKind::Binding(annot, _, pat_ident, _), + ExprKind::Path(QPath::Resolved( + _, + Path { + segments: [first_seg, ..], + .. + }, + )), + ) => { + return !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut) + && pat_ident.name == first_seg.ident.name; + }, + // Example: `Custom::TypeA => Custom::TypeB`, or `None => None` + (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => { + return over(p_path.segments, e_path.segments, |p_seg, e_seg| { + p_seg.ident.name == e_seg.ident.name + }); + }, + // Example: `5 => 5` + (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => { + if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind { + return pat_spanned.node == expr_spanned.node; + } + }, + _ => {}, + } + + false +} + +fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool { + if pats.len() != exprs.len() { + return false; + } + + for i in 0..pats.len() { + if !pat_same_as_expr(&pats[i], &exprs[i]) { + return false; + } + } + + true +} diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs new file mode 100644 index 00000000000..c0b3e95b185 --- /dev/null +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -0,0 +1,179 @@ +use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt}; +use clippy_utils::diagnostics::span_lint_and_note; +use core::cmp::Ordering; +use rustc_hir::{Arm, Expr, PatKind, RangeEnd}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::Span; + +use super::MATCH_OVERLAPPING_ARM; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { + if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { + let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); + if !ranges.is_empty() { + if let Some((start, end)) = overlapping(&ranges) { + span_lint_and_note( + cx, + MATCH_OVERLAPPING_ARM, + start.span, + "some ranges overlap", + Some(end.span), + "overlaps with this", + ); + } + } + } +} + +/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges. +fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { + arms.iter() + .filter_map(|arm| { + if let Arm { pat, guard: None, .. } = *arm { + if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { + let lhs_const = match lhs { + Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, + None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, + }; + let rhs_const = match rhs { + Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, + None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, + }; + let lhs_val = lhs_const.int_value(cx, ty)?; + let rhs_val = rhs_const.int_value(cx, ty)?; + let rhs_bound = match range_end { + RangeEnd::Included => EndBound::Included(rhs_val), + RangeEnd::Excluded => EndBound::Excluded(rhs_val), + }; + return Some(SpannedRange { + span: pat.span, + node: (lhs_val, rhs_bound), + }); + } + + if let PatKind::Lit(value) = pat.kind { + let value = constant_full_int(cx, cx.typeck_results(), value)?; + return Some(SpannedRange { + span: pat.span, + node: (value, EndBound::Included(value)), + }); + } + } + None + }) + .collect() +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EndBound { + Included(T), + Excluded(T), +} + +#[derive(Debug, Eq, PartialEq)] +struct SpannedRange { + pub span: Span, + pub node: (T, EndBound), +} + +fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> +where + T: Copy + Ord, +{ + #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] + enum BoundKind { + EndExcluded, + Start, + EndIncluded, + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); + + impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { + fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { + let RangeBound(self_value, self_kind, _) = *self; + (self_value, self_kind).cmp(&(*other_value, *other_kind)) + } + } + + let mut values = Vec::with_capacity(2 * ranges.len()); + + for r @ SpannedRange { node: (start, end), .. } in ranges { + values.push(RangeBound(*start, BoundKind::Start, r)); + values.push(match end { + EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r), + EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r), + }); + } + + values.sort(); + + let mut started = vec![]; + + for RangeBound(_, kind, range) in values { + match kind { + BoundKind::Start => started.push(range), + BoundKind::EndExcluded | BoundKind::EndIncluded => { + let mut overlap = None; + + while let Some(last_started) = started.pop() { + if last_started == range { + break; + } + overlap = Some(last_started); + } + + if let Some(first_overlapping) = overlap { + return Some((range, first_overlapping)); + } + }, + } + } + + None +} + +#[test] +fn test_overlapping() { + use rustc_span::source_map::DUMMY_SP; + + let sp = |s, e| SpannedRange { + span: DUMMY_SP, + node: (s, e), + }; + + assert_eq!(None, overlapping::(&[])); + assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))])); + assert_eq!( + None, + overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))]) + ); + assert_eq!( + None, + overlapping(&[ + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(10, EndBound::Included(11)) + ],) + ); + assert_eq!( + Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))), + overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))]) + ); + assert_eq!( + Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))), + overlapping(&[ + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(6, EndBound::Included(11)) + ],) + ); +} diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs new file mode 100644 index 00000000000..1a8b9d15f37 --- /dev/null +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -0,0 +1,378 @@ +use super::REDUNDANT_PATTERN_MATCHING; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::{higher, match_def_path}; +use clippy_utils::{is_lang_ctor, is_trait_method, paths}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{OptionNone, PollPending}; +use rustc_hir::{ + intravisit::{walk_expr, Visitor}, + Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp, +}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; +use rustc_span::sym; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(higher::IfLet { + if_else, + let_pat, + let_expr, + .. + }) = higher::IfLet::hir(cx, expr) + { + find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some()); + } else if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { + find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); + } +} + +// Extract the generic arguments out of a type +fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option> { + if_chain! { + if let ty::Adt(_, subs) = ty.kind(); + if let Some(sub) = subs.get(index); + if let GenericArgKind::Type(sub_ty) = sub.unpack(); + then { + Some(sub_ty) + } else { + None + } + } +} + +// Checks if there are any temporaries created in the given expression for which drop order +// matters. +fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + struct V<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + res: bool, + } + impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + // Taking the reference of a value leaves a temporary + // e.g. In `&String::new()` the string is a temporary value. + // Remaining fields are temporary values + // e.g. In `(String::new(), 0).1` the string is a temporary value. + ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => { + if !matches!(expr.kind, ExprKind::Path(_)) { + if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) { + self.res = true; + } else { + self.visit_expr(expr); + } + } + }, + // the base type is alway taken by reference. + // e.g. In `(vec![0])[0]` the vector is a temporary value. + ExprKind::Index(base, index) => { + if !matches!(base.kind, ExprKind::Path(_)) { + if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) { + self.res = true; + } else { + self.visit_expr(base); + } + } + self.visit_expr(index); + }, + // Method calls can take self by reference. + // e.g. In `String::new().len()` the string is a temporary value. + ExprKind::MethodCall(_, [self_arg, args @ ..], _) => { + if !matches!(self_arg.kind, ExprKind::Path(_)) { + let self_by_ref = self + .cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref()); + if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) { + self.res = true; + } else { + self.visit_expr(self_arg); + } + } + args.iter().for_each(|arg| self.visit_expr(arg)); + }, + // Either explicitly drops values, or changes control flow. + ExprKind::DropTemps(_) + | ExprKind::Ret(_) + | ExprKind::Break(..) + | ExprKind::Yield(..) + | ExprKind::Block(Block { expr: None, .. }, _) + | ExprKind::Loop(..) => (), + + // Only consider the final expression. + ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr), + + _ => walk_expr(self, expr), + } + } + } + + let mut v = V { cx, res: false }; + v.visit_expr(expr); + v.res +} + +fn find_sugg_for_if_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + let_pat: &Pat<'_>, + let_expr: &'tcx Expr<'_>, + keyword: &'static str, + has_else: bool, +) { + // also look inside refs + // if we have &None for example, peel it so we can detect "if let None = x" + let check_pat = match let_pat.kind { + PatKind::Ref(inner, _mutability) => inner, + _ => let_pat, + }; + let op_ty = cx.typeck_results().expr_ty(let_expr); + // Determine which function should be used, and the type contained by the corresponding + // variant. + let (good_method, inner_ty) = match check_pat.kind { + PatKind::TupleStruct(ref qpath, [sub_pat], _) => { + if let PatKind::Wild = sub_pat.kind { + let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); + let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return }; + let lang_items = cx.tcx.lang_items(); + if Some(id) == lang_items.result_ok_variant() { + ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) + } else if Some(id) == lang_items.result_err_variant() { + ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) + } else if Some(id) == lang_items.option_some_variant() { + ("is_some()", op_ty) + } else if Some(id) == lang_items.poll_ready_variant() { + ("is_ready()", op_ty) + } else if match_def_path(cx, id, &paths::IPADDR_V4) { + ("is_ipv4()", op_ty) + } else if match_def_path(cx, id, &paths::IPADDR_V6) { + ("is_ipv6()", op_ty) + } else { + return; + } + } else { + return; + } + }, + PatKind::Path(ref path) => { + let method = if is_lang_ctor(cx, path, OptionNone) { + "is_none()" + } else if is_lang_ctor(cx, path, PollPending) { + "is_pending()" + } else { + return; + }; + // `None` and `Pending` don't have an inner type. + (method, cx.tcx.types.unit) + }, + _ => return, + }; + + // If this is the last expression in a block or there is an else clause then the whole + // type needs to be considered, not just the inner type of the branch being matched on. + // Note the last expression in a block is dropped after all local bindings. + let check_ty = if has_else + || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..))))) + { + op_ty + } else { + inner_ty + }; + + // All temporaries created in the scrutinee expression are dropped at the same time as the + // scrutinee would be, so they have to be considered as well. + // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held + // for the duration if body. + let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr); + + // check that `while_let_on_iterator` lint does not trigger + if_chain! { + if keyword == "while"; + if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind; + if method_path.ident.name == sym::next; + if is_trait_method(cx, let_expr, sym::Iterator); + then { + return; + } + } + + let result_expr = match &let_expr.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + ExprKind::Unary(UnOp::Deref, deref) => deref, + _ => let_expr, + }; + + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + let_pat.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + // if/while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let expr_span = expr.span; + + // if/while let ... = ... { ... } + // ^^^ + let op_span = result_expr.span.source_callsite(); + + // if/while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^ + let span = expr_span.until(op_span.shrink_to_hi()); + + let app = if needs_drop { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + + let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") + .maybe_par() + .to_string(); + + diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app); + + if needs_drop { + diag.note("this will change drop order of the result, as well as all temporaries"); + diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important"); + } + }, + ); +} + +pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { + if arms.len() == 2 { + let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); + + let found_good_method = match node_pair { + ( + PatKind::TupleStruct(ref path_left, patterns_left, _), + PatKind::TupleStruct(ref path_right, patterns_right, _), + ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::RESULT_OK, + &paths::RESULT_ERR, + "is_ok()", + "is_err()", + ) + .or_else(|| { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::IPADDR_V4, + &paths::IPADDR_V6, + "is_ipv4()", + "is_ipv6()", + ) + }) + } else { + None + } + }, + (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) + | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) + if patterns.len() == 1 => + { + if let PatKind::Wild = patterns[0].kind { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::OPTION_SOME, + &paths::OPTION_NONE, + "is_some()", + "is_none()", + ) + .or_else(|| { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::POLL_READY, + &paths::POLL_PENDING, + "is_ready()", + "is_pending()", + ) + }) + } else { + None + } + }, + _ => None, + }; + + if let Some(good_method) = found_good_method { + let span = expr.span.to(op.span); + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + expr.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + diag.span_suggestion( + span, + "try this", + format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method), + Applicability::MaybeIncorrect, // snippet + ); + }, + ); + } + } +} + +#[expect(clippy::too_many_arguments)] +fn find_good_method_for_match<'a>( + cx: &LateContext<'_>, + arms: &[Arm<'_>], + path_left: &QPath<'_>, + path_right: &QPath<'_>, + expected_left: &[&str], + expected_right: &[&str], + should_be_left: &'a str, + should_be_right: &'a str, +) -> Option<&'a str> { + let left_id = cx + .typeck_results() + .qpath_res(path_left, arms[0].pat.hir_id) + .opt_def_id()?; + let right_id = cx + .typeck_results() + .qpath_res(path_right, arms[1].pat.hir_id) + .opt_def_id()?; + let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[0].body).kind, &(*arms[1].body).kind) + } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[1].body).kind, &(*arms[0].body).kind) + } else { + return None; + }; + + match body_node_pair { + (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { + (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), + (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + _ => None, + }, + _ => None, + } +} diff --git a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs new file mode 100644 index 00000000000..0aadb482acd --- /dev/null +++ b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -0,0 +1,30 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir::{Pat, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::REST_PAT_IN_FULLY_BOUND_STRUCTS; + +pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { + if_chain! { + if !pat.span.from_expansion(); + if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; + if let Some(def_id) = path.res.opt_def_id(); + let ty = cx.tcx.type_of(def_id); + if let ty::Adt(def, _) = ty.kind(); + if def.is_struct() || def.is_union(); + if fields.len() == def.non_enum_variant().fields.len(); + if !def.non_enum_variant().is_field_list_non_exhaustive(); + + then { + span_lint_and_help( + cx, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + pat.span, + "unnecessary use of `..` pattern in struct binding. All fields were already bound", + None, + "consider removing `..` from this binding", + ); + } + } +} diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs new file mode 100644 index 00000000000..0c4cb45d147 --- /dev/null +++ b/clippy_lints/src/matches/single_match.rs @@ -0,0 +1,269 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{expr_block, snippet}; +use clippy_utils::ty::{implements_trait, match_type, peel_mid_ty_refs}; +use clippy_utils::{ + is_lint_allowed, is_unit_expr, is_wild, paths, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, +}; +use core::cmp::max; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; + +#[rustfmt::skip] +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + if expr.span.from_expansion() { + // Don't lint match expressions present in + // macro_rules! block + return; + } + if let PatKind::Or(..) = arms[0].pat.kind { + // don't lint for or patterns for now, this makes + // the lint noisy in unnecessary situations + return; + } + let els = arms[1].body; + let els = if is_unit_expr(peel_blocks(els)) { + None + } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { + if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { + // single statement/expr "else" block, don't lint + return; + } + // block with 2+ statements or 1 expr and 1+ statement + Some(els) + } else { + // not a block, don't lint + return; + }; + + let ty = cx.typeck_results().expr_ty(ex); + if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { + check_single_pattern(cx, ex, arms, expr, els); + check_opt_like(cx, ex, arms, expr, ty, els); + } + } +} + +fn check_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + if is_wild(arms[1].pat) { + report_single_pattern(cx, ex, arms, expr, els); + } +} + +fn report_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; + let els_str = els.map_or(String::new(), |els| { + format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) + }); + + let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); + let (msg, sugg) = if_chain! { + if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; + let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); + if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait(); + if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait(); + if ty.is_integral() || ty.is_char() || ty.is_str() + || (implements_trait(cx, ty, spe_trait_id, &[]) + && implements_trait(cx, ty, pe_trait_id, &[ty.into()])); + then { + // scrutinee derives PartialEq and the pattern is a constant. + let pat_ref_count = match pat.kind { + // string literals are already a reference. + PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, + _ => pat_ref_count, + }; + // References are only implicitly added to the pattern, so no overflow here. + // e.g. will work: match &Some(_) { Some(_) => () } + // will not: match Some(_) { &Some(_) => () } + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + let ref_count_diff = ref_count_diff - removed; + + let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; + let sugg = format!( + "if {} == {}{} {}{}", + snippet(cx, ex.span, ".."), + // PartialEq for different reference counts may not exist. + "&".repeat(ref_count_diff), + snippet(cx, arms[0].pat.span, ".."), + expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } else { + let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; + let sugg = format!( + "if let {} = {} {}{}", + snippet(cx, arms[0].pat.span, ".."), + snippet(cx, ex.span, ".."), + expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } + }; + + span_lint_and_sugg( + cx, + lint, + expr.span, + msg, + "try this", + sugg, + Applicability::HasPlaceholders, + ); +} + +fn check_opt_like<'a>( + cx: &LateContext<'a>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + ty: Ty<'a>, + els: Option<&Expr<'_>>, +) { + // list of candidate `Enum`s we know will never get any more members + let candidates = &[ + (&paths::COW, "Borrowed"), + (&paths::COW, "Cow::Borrowed"), + (&paths::COW, "Cow::Owned"), + (&paths::COW, "Owned"), + (&paths::OPTION, "None"), + (&paths::RESULT, "Err"), + (&paths::RESULT, "Ok"), + ]; + + // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive + // match with the second branch, without enum variants in matches. + if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { + return; + } + + let mut paths_and_types = Vec::new(); + if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { + return; + } + + let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool { + let (path, ty) = path_info; + for &(ty_path, pat_path) in candidates { + if path == pat_path && match_type(cx, *ty, ty_path) { + return true; + } + } + false + }; + if paths_and_types.iter().all(in_candidate_enum) { + report_single_pattern(cx, ex, arms, expr, els); + } +} + +/// Collects paths and their types from the given patterns. Returns true if the given pattern could +/// be simplified, false otherwise. +fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) => inner.iter().all(|p| { + let p_ty = cx.typeck_results().pat_ty(p); + collect_pat_paths(acc, cx, p, p_ty) + }), + PatKind::TupleStruct(ref path, ..) => { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + }); + acc.push((path, ty)); + true + }, + PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { + acc.push((ident.to_string(), ty)); + true + }, + PatKind::Path(ref path) => { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + }); + acc.push((path, ty)); + true + }, + _ => false, + } +} + +/// Returns true if the given arm of pattern matching contains wildcard patterns. +fn contains_only_wilds(pat: &Pat<'_>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), + _ => false, + } +} + +/// Returns true if the given patterns forms only exhaustive matches that don't contain enum +/// patterns without a wildcard. +fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { + match (&left.kind, &right.kind) { + (PatKind::Wild, _) | (_, PatKind::Wild) => true, + (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { + // We don't actually know the position and the presence of the `..` (dotdot) operator + // in the arms, so we need to evaluate the correct offsets here in order to iterate in + // both arms at the same time. + let len = max( + left_in.len() + { + if left_pos.is_some() { 1 } else { 0 } + }, + right_in.len() + { + if right_pos.is_some() { 1 } else { 0 } + }, + ); + let mut left_pos = left_pos.unwrap_or(usize::MAX); + let mut right_pos = right_pos.unwrap_or(usize::MAX); + let mut left_dot_space = 0; + let mut right_dot_space = 0; + for i in 0..len { + let mut found_dotdot = false; + if i == left_pos { + left_dot_space += 1; + if left_dot_space < len - left_in.len() { + left_pos += 1; + } + found_dotdot = true; + } + if i == right_pos { + right_dot_space += 1; + if right_dot_space < len - right_in.len() { + right_pos += 1; + } + found_dotdot = true; + } + if found_dotdot { + continue; + } + if !contains_only_wilds(&left_in[i - left_dot_space]) + && !contains_only_wilds(&right_in[i - right_dot_space]) + { + return false; + } + } + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/matches/wild_in_or_pats.rs b/clippy_lints/src/matches/wild_in_or_pats.rs new file mode 100644 index 00000000000..459513e65bf --- /dev/null +++ b/clippy_lints/src/matches/wild_in_or_pats.rs @@ -0,0 +1,24 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_wild; +use rustc_hir::{Arm, PatKind}; +use rustc_lint::LateContext; + +use super::WILDCARD_IN_OR_PATTERNS; + +pub(crate) fn check(cx: &LateContext<'_>, arms: &[Arm<'_>]) { + for arm in arms { + if let PatKind::Or(fields) = arm.pat.kind { + // look for multiple fields in this arm that contains at least one Wild pattern + if fields.len() > 1 && fields.iter().any(is_wild) { + span_lint_and_help( + cx, + WILDCARD_IN_OR_PATTERNS, + arm.pat.span, + "wildcard pattern covers any other pattern as it will match anyway", + None, + "consider handling `_` separately", + ); + } + } + } +} diff --git a/clippy_lints/src/mem_forget.rs b/clippy_lints/src/mem_forget.rs index 5ffcfd4d264..d6c235b5a69 100644 --- a/clippy_lints/src/mem_forget.rs +++ b/clippy_lints/src/mem_forget.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{match_def_path, paths}; use rustc_hir::{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 @@ -32,7 +32,7 @@ impl<'tcx> LateLintPass<'tcx> for MemForget { if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.kind { if let ExprKind::Path(ref qpath) = path_expr.kind { if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() { - if match_def_path(cx, def_id, &paths::MEM_FORGET) { + if cx.tcx.is_diagnostic_item(sym::mem_forget, def_id) { let forgot_ty = cx.typeck_results().expr_ty(first_arg); if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) { diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index a184806d021..41073d40f3d 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths}; +use clippy_utils::{is_default_equivalent, is_lang_ctor, meets_msrv, msrvs}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; @@ -249,12 +249,12 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace { if let ExprKind::Call(func, func_args) = expr.kind; if let ExprKind::Path(ref func_qpath) = func.kind; if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::MEM_REPLACE); + if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id); if let [dest, src] = func_args; then { check_replace_option_with_none(cx, src, dest, expr.span); check_replace_with_uninit(cx, src, dest, expr.span); - if meets_msrv(self.msrv.as_ref(), &msrvs::MEM_TAKE) { + if meets_msrv(self.msrv, msrvs::MEM_TAKE) { check_replace_with_default(cx, src, dest, expr.span); } } diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index ce958b8ac9f..b88ec0963f2 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -42,7 +42,7 @@ pub(crate) trait BindInsteadOfMap { fn no_op_msg(cx: &LateContext<'_>) -> Option { let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?; - let item_id = cx.tcx.parent(variant_id)?; + let item_id = cx.tcx.parent(variant_id); Some(format!( "using `{}.{}({})`, which is a no-op", cx.tcx.item_name(item_id), @@ -53,7 +53,7 @@ pub(crate) trait BindInsteadOfMap { fn lint_msg(cx: &LateContext<'_>) -> Option { let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?; - let item_id = cx.tcx.parent(variant_id)?; + let item_id = cx.tcx.parent(variant_id); Some(format!( "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`", cx.tcx.item_name(item_id), @@ -145,7 +145,7 @@ pub(crate) trait BindInsteadOfMap { if_chain! { if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def(); if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM); - if Some(adt.did) == cx.tcx.parent(vid); + if adt.did() == cx.tcx.parent(vid); then {} else { return false; } } @@ -182,7 +182,7 @@ pub(crate) trait BindInsteadOfMap { fn is_variant(cx: &LateContext<'_>, res: Res) -> bool { if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) { - return cx.tcx.parent(id) == Some(variant_id); + return cx.tcx.parent(id) == variant_id; } } false diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index 76eaedea8a0..44857d61fef 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E cx, BYTES_NTH, expr.span, - &format!("called `.byte().nth()` on a `{}`", caller_type), + &format!("called `.bytes().nth()` on a `{}`", caller_type), "try", format!( "{}.as_bytes().get({})", diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs index 514c4118765..f7b79f0839b 100644 --- a/clippy_lints/src/methods/chars_cmp.rs +++ b/clippy_lints/src/methods/chars_cmp.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{method_chain_args, single_segment_path}; +use clippy_utils::{method_chain_args, path_def_id}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_lint::Lint; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_middle::ty::{self, DefIdTree}; /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. pub(super) fn check( @@ -19,11 +18,9 @@ pub(super) fn check( ) -> bool { if_chain! { if let Some(args) = method_chain_args(info.chain, chain_methods); - if let hir::ExprKind::Call(fun, arg_char) = info.other.kind; - if arg_char.len() == 1; - if let hir::ExprKind::Path(ref qpath) = fun.kind; - if let Some(segment) = single_segment_path(qpath); - if segment.ident.name == sym::Some; + if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind; + if let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id)); + if Some(id) == cx.tcx.lang_items().option_some_variant(); then { let mut applicability = Applicability::MachineApplicable; let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs(); @@ -42,7 +39,7 @@ pub(super) fn check( if info.eq { "" } else { "!" }, snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), suggest, - snippet_with_applicability(cx, arg_char[0].span, "..", &mut applicability)), + snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)), applicability, ); diff --git a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs b/clippy_lints/src/methods/chars_cmp_with_unwrap.rs index 4275857757f..a7c0e43923e 100644 --- a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs +++ b/clippy_lints/src/methods/chars_cmp_with_unwrap.rs @@ -32,7 +32,7 @@ pub(super) fn check<'tcx>( if info.eq { "" } else { "!" }, snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), suggest, - c), + c.escape_default()), applicability, ); diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints/src/methods/cloned_instead_of_copied.rs index 6fe69b8f01f..e9aeab2d5b6 100644 --- a/clippy_lints/src/methods/cloned_instead_of_copied.rs +++ b/clippy_lints/src/methods/cloned_instead_of_copied.rs @@ -10,16 +10,16 @@ use rustc_span::{sym, Span}; use super::CLONED_INSTEAD_OF_COPIED; -pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option<&RustcVersion>) { +pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option) { let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); let inner_ty = match recv_ty.kind() { // `Option` -> `T` ty::Adt(adt, subst) - if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && meets_msrv(msrv, &msrvs::OPTION_COPIED) => + if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, msrvs::OPTION_COPIED) => { subst.type_at(0) }, - _ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) => { + _ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) => { match get_iterator_item_ty(cx, recv_ty) { // ::Item Some(ty) => ty, @@ -30,7 +30,7 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, }; match inner_ty.kind() { // &T where T: Copy - ty::Ref(_, ty, _) if is_copy(cx, ty) => {}, + ty::Ref(_, ty, _) if is_copy(cx, *ty) => {}, _ => return, }; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs new file mode 100644 index 00000000000..570a1b87358 --- /dev/null +++ b/clippy_lints/src/methods/err_expect.rs @@ -0,0 +1,60 @@ +use super::ERR_EXPECT; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::implements_trait; +use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_middle::ty::Ty; +use rustc_semver::RustcVersion; +use rustc_span::{sym, Span}; + +pub(super) fn check( + cx: &LateContext<'_>, + _expr: &rustc_hir::Expr<'_>, + recv: &rustc_hir::Expr<'_>, + msrv: Option, + expect_span: Span, + err_span: Span, +) { + if_chain! { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); + // Test the version to make sure the lint can be showed (expect_err has been + // introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982) + if meets_msrv(msrv, msrvs::EXPECT_ERR); + + // Grabs the `Result` type + let result_type = cx.typeck_results().expr_ty(recv); + // Tests if the T type in a `Result` is not None + if let Some(data_type) = get_data_type(cx, result_type); + // Tests if the T type in a `Result` implements debug + if has_debug_impl(data_type, cx); + + then { + span_lint_and_sugg( + cx, + ERR_EXPECT, + err_span.to(expect_span), + "called `.err().expect()` on a `Result` value", + "try", + "expect_err".to_string(), + Applicability::MachineApplicable + ); + } + }; +} + +/// Given a `Result` type, return its data (`T`). +fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { + match ty.kind() { + ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(), + _ => None, + } +} + +/// Given a type, very if the Debug trait has been impl'd +fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + cx.tcx + .get_diagnostic_item(sym::Debug) + .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) +} diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index d813edab687..6f2307d8f18 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -73,7 +73,7 @@ pub(super) fn check<'tcx>( match cx.qpath_res(p, fun.hir_id) { hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( cx.tcx.fn_sig(def_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) + ty::Ref(re, ..) if re.is_static(), ), _ => false, } @@ -87,13 +87,13 @@ pub(super) fn check<'tcx>( .map_or(false, |method_id| { matches!( cx.tcx.fn_sig(method_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) + ty::Ref(re, ..) if re.is_static() ) }) }, hir::ExprKind::Path(ref p) => matches!( cx.qpath_res(p, arg.hir_id), - hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) + hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static(_), _) ), _ => false, } diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs index 55be513c5bb..fbc3348f185 100644 --- a/clippy_lints/src/methods/expect_used.rs +++ b/clippy_lints/src/methods/expect_used.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_in_test_function; use clippy_utils::ty::is_type_diagnostic_item; use rustc_hir as hir; use rustc_lint::LateContext; @@ -7,7 +8,7 @@ use rustc_span::sym; use super::EXPECT_USED; /// lint use of `expect()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_expect_in_tests: bool) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { @@ -18,6 +19,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr None }; + if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { + return; + } + if let Some((lint, kind, none_value)) = mess { span_lint_and_help( cx, diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index ba1af9f3d62..558cb6bd64e 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -8,7 +8,6 @@ use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; use std::borrow::Cow; @@ -120,9 +119,9 @@ pub(super) fn check<'tcx>( if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind; if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); - if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did) { + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) { Some(false) - } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did) { + } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) { Some(true) } else { None @@ -149,7 +148,7 @@ pub(super) fn check<'tcx>( if_chain! { if path_to_local_id(a_path, filter_param_id); if path_to_local_id(b, map_param_id); - if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + if cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b); then { return true; } diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints/src/methods/filter_map_next.rs index f0d69a1f42e..38ec4d8e3ab 100644 --- a/clippy_lints/src/methods/filter_map_next.rs +++ b/clippy_lints/src/methods/filter_map_next.rs @@ -14,10 +14,10 @@ pub(super) fn check<'tcx>( expr: &'tcx hir::Expr<'_>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, - msrv: Option<&RustcVersion>, + msrv: Option, ) { if is_trait_method(cx, expr, sym::Iterator) { - if !meets_msrv(msrv, &msrvs::ITERATOR_FIND_MAP) { + if !meets_msrv(msrv, msrvs::ITERATOR_FIND_MAP) { return; } diff --git a/clippy_lints/src/methods/get_last_with_len.rs b/clippy_lints/src/methods/get_last_with_len.rs new file mode 100644 index 00000000000..23368238ef5 --- /dev/null +++ b/clippy_lints/src/methods/get_last_with_len.rs @@ -0,0 +1,55 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::SpanlessEq; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +use super::GET_LAST_WITH_LEN; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { + // Argument to "get" is a subtraction + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + lhs, + rhs, + ) = arg.kind + + // LHS of subtraction is "x.len()" + && let ExprKind::MethodCall(lhs_path, [lhs_recv], _) = &lhs.kind + && lhs_path.ident.name == sym::len + + // RHS of subtraction is 1 + && let ExprKind::Lit(rhs_lit) = &rhs.kind + && let LitKind::Int(1, ..) = rhs_lit.node + + // check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)` + && SpanlessEq::new(cx).eq_expr(recv, lhs_recv) + && !recv.can_have_side_effects() + { + let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() { + ty::Adt(def, _) if cx.tcx.is_diagnostic_item(sym::VecDeque, def.did()) => "back", + ty::Slice(_) => "last", + _ => return, + }; + + let mut applicability = Applicability::MachineApplicable; + let recv_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability); + + span_lint_and_sugg( + cx, + GET_LAST_WITH_LEN, + expr.span, + &format!("accessing last element with `{recv_snippet}.get({recv_snippet}.len() - 1)`"), + "try", + format!("{recv_snippet}.{method}()"), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 865e7702b71..9651a52be4e 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -6,7 +6,6 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::sym; use super::IMPLICIT_CLONE; @@ -18,8 +17,8 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv let return_type = cx.typeck_results().expr_ty(expr); let input_type = cx.typeck_results().expr_ty(recv); let (input_type, ref_count) = peel_mid_ty_refs(input_type); - if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); - if TyS::same_type(return_type, input_type); + if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did())); + if return_type == input_type; then { let mut app = Applicability::MachineApplicable; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; @@ -49,12 +48,11 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr), "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned), "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path), - "to_vec" => { - cx.tcx - .impl_of_method(method_def_id) - .map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl()) - == Some(true) - }, + "to_vec" => cx + .tcx + .impl_of_method(method_def_id) + .filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()) + .is_some(), _ => false, } } diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints/src/methods/inefficient_to_string.rs index c0f66feb48a..06ead144afa 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints/src/methods/inefficient_to_string.rs @@ -60,7 +60,7 @@ fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { } if let ty::Adt(adt, substs) = ty.kind() { - match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str() + match_def_path(cx, adt.did(), &paths::COW) && substs.type_at(1).is_str() } else { false } diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs new file mode 100644 index 00000000000..aa176dcc8b4 --- /dev/null +++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs @@ -0,0 +1,50 @@ +//! Lint for `c.is_digit(10)` + +use super::IS_DIGIT_ASCII_RADIX; +use clippy_utils::{ + consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs, + source::snippet_with_applicability, +}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + self_arg: &'tcx Expr<'_>, + radix: &'tcx Expr<'_>, + msrv: Option, +) { + if !meets_msrv(msrv, msrvs::IS_ASCII_DIGIT) { + return; + } + + if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() { + return; + } + + if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) { + let (num, replacement) = match radix_val { + FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"), + FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"), + _ => return, + }; + let mut applicability = Applicability::MachineApplicable; + + span_lint_and_sugg( + cx, + IS_DIGIT_ASCII_RADIX, + expr.span, + &format!("use of `char::is_digit` with literal radix of {}", num), + "try", + format!( + "{}.{}()", + snippet_with_applicability(cx, self_arg.span, "..", &mut applicability), + replacement + ), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints/src/methods/iter_next_slice.rs index d053ff56756..b8d1dabe007 100644 --- a/clippy_lints/src/methods/iter_next_slice.rs +++ b/clippy_lints/src/methods/iter_next_slice.rs @@ -34,13 +34,18 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal if let ast::LitKind::Int(start_idx, _) = start_lit.node; then { let mut applicability = Applicability::MachineApplicable; + let suggest = if start_idx == 0 { + format!("{}.first()", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability)) + } else { + format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx) + }; span_lint_and_sugg( cx, ITER_NEXT_SLICE, expr.span, "using `.iter().next()` on a Slice without end index", "try calling", - format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), + suggest, applicability, ); } @@ -55,7 +60,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal "using `.iter().next()` on an array", "try calling", format!( - "{}.get(0)", + "{}.first()", snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) ), applicability, diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index ca33bfc643d..54c9ca435a4 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; -use clippy_utils::ty::{get_iterator_item_ty, is_copy}; +use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy}; use itertools::Itertools; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty; +use rustc_span::sym; use std::ops::Not; use super::ITER_OVEREAGER_CLONED; @@ -20,13 +21,20 @@ pub(super) fn check<'tcx>( map_arg: &[hir::Expr<'_>], ) { // Check if it's iterator and get type associated with `Item`. - let inner_ty = match get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)) { - Some(ty) => ty, - _ => return, + let inner_ty = if_chain! { + if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + let recv_ty = cx.typeck_results().expr_ty(recv); + if implements_trait(cx, recv_ty, iterator_trait_id, &[]); + if let Some(inner_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)); + then { + inner_ty + } else { + return; + } }; match inner_ty.kind() { - ty::Ref(_, ty, _) if !is_copy(cx, ty) => {}, + ty::Ref(_, ty, _) if !is_copy(cx, *ty) => {}, _ => return, }; diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs new file mode 100644 index 00000000000..152072e09c7 --- /dev/null +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -0,0 +1,47 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::higher::Range; +use clippy_utils::is_integer_const; +use rustc_ast::ast::RangeLimits; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use rustc_span::Span; + +use super::ITER_WITH_DRAIN; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { + if !matches!(recv.kind, ExprKind::Field(..)) + && let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() + && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) + && matches!(ty_name, sym::Vec | sym::VecDeque) + && let Some(range) = Range::hir(arg) + && is_full_range(cx, recv, range) + { + span_lint_and_sugg( + cx, + ITER_WITH_DRAIN, + span.with_hi(expr.span.hi()), + &format!("`drain(..)` used on a `{}`", ty_name), + "try this", + "into_iter()".to_string(), + Applicability::MaybeIncorrect, + ); + }; +} + +fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool { + range.start.map_or(true, |e| is_integer_const(cx, e, 0)) + && range.end.map_or(true, |e| { + if range.limits == RangeLimits::HalfOpen + && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind + && let ExprKind::MethodCall(name, [self_arg], _) = e.kind + && name.ident.name == sym::len + && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind + { + container_path.res == path.res + } else { + false + } + }) +} diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index 4307cbf0050..0fe510beaa0 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_qpath_def_path; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{match_def_path, path_def_id}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; @@ -93,12 +93,12 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option, e: &Expr<'_>) -> Option { } else { let ty = cx.typeck_results().expr_ty(e); if is_type_diagnostic_item(cx, ty, sym::String) - || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, TyS::is_str)) - || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, TyS::is_str)) + || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, Ty::is_str)) + || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, Ty::is_str)) { Some(RepeatKind::String) } else { diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index 6782f64f2ca..f447940ea3b 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -1,83 +1,73 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_sugg_for_edges; use clippy_utils::is_trait_method; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::symbol::sym; +use rustc_span::{symbol::sym, Span}; use super::MAP_FLATTEN; /// lint use of `map().flatten()` for `Iterators` and 'Options' -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - map_arg: &'tcx hir::Expr<'_>, -) { - // lint if caller of `.map().flatten()` is an Iterator - if is_trait_method(cx, expr, sym::Iterator) { - let map_closure_ty = cx.typeck_results().expr_ty(map_arg); - let is_map_to_option = match map_closure_ty.kind() { - ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { - let map_closure_sig = match map_closure_ty.kind() { - ty::Closure(_, substs) => substs.as_closure().sig(), - _ => map_closure_ty.fn_sig(cx.tcx), - }; - let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); - is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) - }, - _ => false, - }; - - let method_to_use = if is_map_to_option { - // `(...).map(...)` has type `impl Iterator> - "filter_map" - } else { - // `(...).map(...)` has type `impl Iterator> - "flat_map" - }; - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".{0}({1})", method_to_use, func_snippet); - span_lint_and_sugg( +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { + if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) { + let mut applicability = Applicability::MachineApplicable; + let help_msgs = [ + &format!("try replacing `map` with `{}`", method_to_use), + "and remove the `.flatten()`", + ]; + let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability); + span_lint_and_sugg_for_edges( cx, MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - "called `map(..).flatten()` on an `Iterator`", - &format!("try using `{}` instead", method_to_use), - hint, - Applicability::MachineApplicable, + expr.span.with_lo(map_span.lo()), + &format!("called `map(..).flatten()` on `{}`", caller_ty_name), + &help_msgs, + format!("{}({})", method_to_use, closure_snippet), + applicability, ); } - - // lint if caller of `.map().flatten()` is an Option or Result - let caller_type = match cx.typeck_results().expr_ty(recv).kind() { - ty::Adt(adt, _) => { - if cx.tcx.is_diagnostic_item(sym::Option, adt.did) { - "Option" - } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did) { - "Result" - } else { - return; - } - }, - _ => { - return; - }, - }; - - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".and_then({})", func_snippet); - let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type); - span_lint_and_sugg( - cx, - MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - &lint_info, - "try using `and_then` instead", - hint, - Applicability::MachineApplicable, - ); +} + +fn try_get_caller_ty_name_and_method_name( + cx: &LateContext<'_>, + expr: &Expr<'_>, + caller_expr: &Expr<'_>, + map_arg: &Expr<'_>, +) -> Option<(&'static str, &'static str)> { + if is_trait_method(cx, expr, sym::Iterator) { + if is_map_to_option(cx, map_arg) { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "filter_map")) + } else { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "flat_map")) + } + } else { + if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { + if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { + return Some(("Option", "and_then")); + } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { + return Some(("Result", "and_then")); + } + } + None + } +} + +fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool { + let map_closure_ty = cx.typeck_results().expr_ty(map_arg); + match map_closure_ty.kind() { + ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { + let map_closure_sig = match map_closure_ty.kind() { + ty::Closure(_, substs) => substs.as_closure().sig(), + _ => map_closure_ty.fn_sig(cx.tcx), + }; + let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); + is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) + }, + _ => false, + } } diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints/src/methods/map_identity.rs index f112b500d3d..862a9578e6f 100644 --- a/clippy_lints/src/methods/map_identity.rs +++ b/clippy_lints/src/methods/map_identity.rs @@ -13,6 +13,7 @@ pub(super) fn check( expr: &hir::Expr<'_>, caller: &hir::Expr<'_>, map_arg: &hir::Expr<'_>, + name: &str, _map_span: Span, ) { let caller_ty = cx.typeck_results().expr_ty(caller); @@ -29,7 +30,7 @@ pub(super) fn check( MAP_IDENTITY, sugg_span, "unnecessary map of the identity function", - "remove the call to `map`", + &format!("remove the call to `{}`", name), String::new(), Applicability::MachineApplicable, ) diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index 9ec84e76519..4a8e7ce4ddb 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -19,13 +19,13 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'_>, map_arg: &'tcx hir::Expr<'_>, unwrap_arg: &'tcx hir::Expr<'_>, - msrv: Option<&RustcVersion>, + msrv: Option, ) -> bool { // lint if the caller of `map()` is an `Option` let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); - if is_result && !meets_msrv(msrv, &msrvs::RESULT_MAP_OR_ELSE) { + if is_result && !meets_msrv(msrv, msrvs::RESULT_MAP_OR_ELSE) { return false; } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 18d4867b7eb..b820b740930 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -9,6 +9,7 @@ mod chars_next_cmp_with_unwrap; mod clone_on_copy; mod clone_on_ref_ptr; mod cloned_instead_of_copied; +mod err_expect; mod expect_fun_call; mod expect_used; mod extend_with_drain; @@ -20,11 +21,13 @@ mod filter_next; mod flat_map_identity; mod flat_map_option; mod from_iter_instead_of_collect; +mod get_last_with_len; mod get_unwrap; mod implicit_clone; mod inefficient_to_string; mod inspect_for_each; mod into_iter_on_ref; +mod is_digit_ascii_radix; mod iter_cloned_collect; mod iter_count; mod iter_next_slice; @@ -32,6 +35,7 @@ mod iter_nth; mod iter_nth_zero; mod iter_overeager_cloned; mod iter_skip_next; +mod iter_with_drain; mod iterator_step_by_zero; mod manual_saturating_arithmetic; mod manual_str_repeat; @@ -39,11 +43,15 @@ mod map_collect_result_unit; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod needless_option_as_deref; +mod needless_option_take; +mod no_effect_replace; mod ok_expect; mod option_as_ref_deref; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; +mod or_then_unwrap; mod search_is_some; mod single_char_add_str; mod single_char_insert_string; @@ -58,6 +66,7 @@ mod uninit_assumed_init; mod unnecessary_filter_map; mod unnecessary_fold; mod unnecessary_iter_cloned; +mod unnecessary_join; mod unnecessary_lazy_eval; mod unnecessary_to_owned; mod unwrap_or_else_default; @@ -78,7 +87,7 @@ use rustc_hir::def::Res; use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, TraitRef, Ty, TyS}; +use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Span}; @@ -289,15 +298,15 @@ declare_clippy_lint! { /// Checks for methods with certain name prefixes and which /// doesn't match how self is taken. The actual rules are: /// - /// |Prefix |Postfix |`self` taken | `self` type | - /// |-------|------------|-----------------------|--------------| - /// |`as_` | none |`&self` or `&mut self` | any | - /// |`from_`| none | none | any | - /// |`into_`| none |`self` | any | - /// |`is_` | none |`&self` or none | any | - /// |`to_` | `_mut` |`&mut self` | any | - /// |`to_` | not `_mut` |`self` | `Copy` | - /// |`to_` | not `_mut` |`&self` | not `Copy` | + /// |Prefix |Postfix |`self` taken | `self` type | + /// |-------|------------|-------------------------------|--------------| + /// |`as_` | none |`&self` or `&mut self` | any | + /// |`from_`| none | none | any | + /// |`into_`| none |`self` | any | + /// |`is_` | none |`&mut self` or `&self` or none | any | + /// |`to_` | `_mut` |`&mut self` | any | + /// |`to_` | not `_mut` |`self` | `Copy` | + /// |`to_` | not `_mut` |`&self` | not `Copy` | /// /// Note: Clippy doesn't trigger methods with `to_` prefix in: /// - Traits definition. @@ -359,6 +368,29 @@ declare_clippy_lint! { "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.err().expect()` calls on the `Result` type. + /// + /// ### Why is this bad? + /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`. + /// + /// ### Example + /// ```should_panic + /// let x: Result = Ok(10); + /// x.err().expect("Testing err().expect()"); + /// ``` + /// Use instead: + /// ```should_panic + /// let x: Result = Ok(10); + /// x.expect_err("Testing expect_err"); + /// ``` + #[clippy::version = "1.61.0"] + pub ERR_EXPECT, + style, + r#"using `.err().expect("")` when `.expect_err("")` can be used"# +} + declare_clippy_lint! { /// ### What it does /// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and @@ -777,6 +809,42 @@ declare_clippy_lint! { "using any `*or` method with a function call, which suggests `*or_else`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); + /// let value = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # let result: Result<&str, &str> = Err("error"); + /// let value = result.unwrap_or(fallback); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub OR_THEN_UNWRAP, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, @@ -1118,6 +1186,63 @@ declare_clippy_lint! { "using `.skip(x).next()` on an iterator" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration. + /// + /// ### Why is this bad? + /// `.into_iter()` is simpler with better performance. + /// + /// ### Example + /// ```rust + /// # use std::collections::HashSet; + /// let mut foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.drain(..).collect(); + /// ``` + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; + /// let foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.into_iter().collect(); + /// ``` + #[clippy::version = "1.61.0"] + pub ITER_WITH_DRAIN, + nursery, + "replace `.drain(..)` with `.into_iter()`" +} + +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_clippy_lint! { /// ### What it does /// Checks for use of `.get().unwrap()` (or @@ -1176,7 +1301,7 @@ declare_clippy_lint! { #[clippy::version = "1.55.0"] pub EXTEND_WITH_DRAIN, perf, - "using vec.append(&mut vec) to move the full range of a vecor to another" + "using vec.append(&mut vec) to move the full range of a vector to another" } declare_clippy_lint! { @@ -1309,7 +1434,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `filter_map` calls which could be replaced by `filter` or `map`. + /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. /// More specifically it checks if the closure provided is only performing one of the /// filter or map operations and suggests the appropriate option. /// @@ -1337,6 +1462,36 @@ declare_clippy_lint! { "using `filter_map` when a more succinct alternative exists" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `find_map` calls that could be replaced by `find` or `map`. More + /// specifically it checks if the closure provided is only performing one of the + /// find or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```rust + /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).find(|&x| x > 2); + /// ``` + /// + /// ```rust + /// let _ = (0..4).find_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1).next(); + /// ``` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_FIND_MAP, + complexity, + "using `find_map` when a more succinct alternative exists" +} + declare_clippy_lint! { /// ### What it does /// Checks for `into_iter` calls on references which should be replaced by `iter` @@ -1442,7 +1597,7 @@ declare_clippy_lint! { #[clippy::version = "1.39.0"] pub MANUAL_SATURATING_ARITHMETIC, style, - "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`" + "`.checked_add/sub(x).unwrap_or(MAX/MIN)`" } declare_clippy_lint! { @@ -1655,8 +1810,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// use std::iter::FromIterator; - /// /// let five_fives = std::iter::repeat(5).take(5); /// /// let v = Vec::from_iter(five_fives); @@ -1888,13 +2041,27 @@ declare_clippy_lint! { /// ### Example /// ```rust,ignore /// // Bad - /// let (key, value) = _.splitn(2, '=').next_tuple()?; - /// let value = _.splitn(2, '=').nth(1)?; + /// let s = "key=value=add"; + /// let (key, value) = s.splitn(2, '=').next_tuple()?; + /// let value = s.splitn(2, '=').nth(1)?; /// - /// // Good - /// let (key, value) = _.split_once('=')?; - /// let value = _.split_once('=')?.1; + /// let mut parts = s.splitn(2, '='); + /// let key = parts.next()?; + /// let value = parts.next()?; /// ``` + /// Use instead: + /// ```rust,ignore + /// // Good + /// let s = "key=value=add"; + /// let (key, value) = s.split_once('=')?; + /// let value = s.split_once('=')?.1; + /// + /// let (key, value) = s.split_once('=')?; + /// ``` + /// + /// ### Limitations + /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` + /// in two separate `let` statements that immediately follow the `splitn()` #[clippy::version = "1.57.0"] pub MANUAL_SPLIT_ONCE, complexity, @@ -1956,17 +2123,150 @@ declare_clippy_lint! { "unnecessary calls to `to_owned`-like functions" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `.collect::>().join("")` on iterators. + /// + /// ### Why is this bad? + /// `.collect::()` is more concise and might be more performant + /// + /// ### Example + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); + /// println!("{}", output); + /// ``` + /// The correct use would be: + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + /// println!("{}", output); + /// ``` + /// ### Known problems + /// While `.collect::()` is sometimes more performant, there are cases where + /// using `.collect::()` over `.collect::>().join("")` + /// will prevent loop unrolling and will result in a negative performance impact. + /// + /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, + /// with aarch64 tending to producing faster assembly in more cases when using `.collect::()` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_JOIN, + pedantic, + "using `.collect::>().join(\"\")` on an iterator" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`, + /// for example, `Option<&T>::as_deref()` returns the same type. + /// + /// ### Why is this bad? + /// Redundant code and improving readability. + /// + /// ### Example + /// ```rust + /// let a = Some(&1); + /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> + /// ``` + /// Could be written as: + /// ```rust + /// let a = Some(&1); + /// let b = a; + /// ``` + #[clippy::version = "1.57.0"] + pub NEEDLESS_OPTION_AS_DEREF, + complexity, + "no-op use of `deref` or `deref_mut` method to `Option`." +} + +declare_clippy_lint! { + /// ### What it does + /// Finds usages of [`char::is_digit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that + /// can be replaced with [`is_ascii_digit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or + /// [`is_ascii_hexdigit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). + /// + /// ### Why is this bad? + /// `is_digit(..)` is slower and requires specifying the radix. + /// + /// ### Example + /// ```rust + /// let c: char = '6'; + /// c.is_digit(10); + /// c.is_digit(16); + /// ``` + /// Use instead: + /// ```rust + /// let c: char = '6'; + /// c.is_ascii_digit(); + /// c.is_ascii_hexdigit(); + /// ``` + #[clippy::version = "1.61.0"] + pub IS_DIGIT_ASCII_RADIX, + style, + "use of `char::is_digit(..)` with literal radix of 10 or 16" +} + +declare_clippy_lint! { + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// let x = Some(3); + /// x.as_ref().take(); + /// ``` + /// Use instead: + /// ```rust + /// let x = Some(3); + /// x.as_ref(); + /// ``` + #[clippy::version = "1.61.0"] + pub NEEDLESS_OPTION_TAKE, + complexity, + "using `.as_ref().take()` on a temporary value" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `replace` statements which have no effect. + /// + /// ### Why is this bad? + /// It's either a mistake or confusing. + /// + /// ### Example + /// ```rust + /// "1234".replace("12", "12"); + /// "1234".replacen("12", "12", 1); + /// ``` + #[clippy::version = "1.62.0"] + pub NO_EFFECT_REPLACE, + suspicious, + "replace with no effect" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, + allow_expect_in_tests: bool, + allow_unwrap_in_tests: bool, } impl Methods { #[must_use] - pub fn new(avoid_breaking_exported_api: bool, msrv: Option) -> Self { + pub fn new( + avoid_breaking_exported_api: bool, + msrv: Option, + allow_expect_in_tests: bool, + allow_unwrap_in_tests: bool, + ) -> Self { Self { avoid_breaking_exported_api, msrv, + allow_expect_in_tests, + allow_unwrap_in_tests, } } } @@ -1983,6 +2283,7 @@ impl_lint_pass!(Methods => [ OPTION_MAP_OR_NONE, BIND_INSTEAD_OF_MAP, OR_FUN_CALL, + OR_THEN_UNWRAP, EXPECT_FUN_CALL, CHARS_NEXT_CMP, CHARS_LAST_CMP, @@ -2015,11 +2316,14 @@ impl_lint_pass!(Methods => [ BYTES_NTH, ITER_SKIP_NEXT, GET_UNWRAP, + GET_LAST_WITH_LEN, STRING_EXTEND_CHARS, ITER_CLONED_COLLECT, + ITER_WITH_DRAIN, USELESS_ASREF, UNNECESSARY_FOLD, UNNECESSARY_FILTER_MAP, + UNNECESSARY_FIND_MAP, INTO_ITER_ON_REF, SUSPICIOUS_MAP, UNINIT_ASSUMED_INIT, @@ -2038,6 +2342,12 @@ impl_lint_pass!(Methods => [ MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN, UNNECESSARY_TO_OWNED, + UNNECESSARY_JOIN, + ERR_EXPECT, + NEEDLESS_OPTION_AS_DEREF, + IS_DIGIT_ASCII_RADIX, + NEEDLESS_OPTION_TAKE, + NO_EFFECT_REPLACE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2057,7 +2367,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { return; } - check_methods(cx, expr, self.msrv.as_ref()); + self.check_methods(cx, expr); match expr.kind { hir::ExprKind::Call(func, args) => { @@ -2073,7 +2383,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { single_char_add_str::check(cx, expr, args); into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args); single_char_pattern::check(cx, expr, method_call.ident.name, args); - unnecessary_to_owned::check(cx, expr, method_call.ident.name, args); + unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv); }, hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { @@ -2106,7 +2416,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let method_sig = cx.tcx.fn_sig(impl_item.def_id); let method_sig = cx.tcx.erase_late_bound_regions(method_sig); - let first_arg_ty = &method_sig.inputs().iter().next(); + let first_arg_ty = method_sig.inputs().iter().next(); // check conventions w.r.t. conversion method names and predicates if let Some(first_arg_ty) = first_arg_ty; @@ -2119,7 +2429,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if name == method_config.method_name && sig.decl.inputs.len() == method_config.param_count && method_config.output_type.matches(&sig.decl.output) && - method_config.self_kind.matches(cx, self_ty, first_arg_ty) && + method_config.self_kind.matches(cx, self_ty, *first_arg_ty) && fn_header_equals(method_config.fn_header, sig.header) && method_config.lifetime_param_cond(impl_item) { @@ -2151,7 +2461,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { cx, name, self_ty, - first_arg_ty, + *first_arg_ty, first_arg.pat.span, implements_trait, false @@ -2198,7 +2508,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } } - if name == "new" && !TyS::same_type(ret_ty, self_ty) { + if name == "new" && ret_ty != self_ty { span_lint( cx, NEW_RET_NO_SELF, @@ -2256,174 +2566,205 @@ impl<'tcx> LateLintPass<'tcx> for Methods { extract_msrv_attr!(LateContext); } -#[allow(clippy::too_many_lines)] -fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) { - if let Some((name, [recv, args @ ..], span)) = method_call(expr) { - match (name, args) { - ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => { - zst_offset::check(cx, expr, recv); - }, - ("and_then", [arg]) => { - let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg); - let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg); - if !biom_option_linted && !biom_result_linted { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); - } - }, - ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), - ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), - ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), - ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, msrv), - ("collect", []) => match method_call(recv) { - Some((name @ ("cloned" | "copied"), [recv2], _)) => { - iter_cloned_collect::check(cx, name, expr, recv2); +impl Methods { + #[allow(clippy::too_many_lines)] + fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some((name, [recv, args @ ..], span)) = method_call(expr) { + match (name, args) { + ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => { + zst_offset::check(cx, expr, recv); }, - Some(("map", [m_recv, m_arg], _)) => { - map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv); - }, - Some(("take", [take_self_arg, take_arg], _)) => { - if meets_msrv(msrv, &msrvs::STR_REPEAT) { - manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); + ("and_then", [arg]) => { + let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg); + let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg); + if !biom_option_linted && !biom_result_linted { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); } }, - _ => {}, - }, - (name @ "count", args @ []) => match method_call(recv) { - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), - Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { - iter_count::check(cx, expr, recv2, name2); + ("as_deref" | "as_deref_mut", []) => { + needless_option_as_deref::check(cx, expr, recv, name); }, - Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), - _ => {}, - }, - ("expect", [_]) => match method_call(recv) { - Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv), - _ => expect_used::check(cx, expr, recv), - }, - ("extend", [arg]) => { - string_extend_chars::check(cx, expr, recv, arg); - extend_with_drain::check(cx, expr, recv, arg); - }, - ("filter_map", [arg]) => { - unnecessary_filter_map::check(cx, expr, arg); - filter_map_identity::check(cx, expr, arg, span); - }, - ("flat_map", [arg]) => { - flat_map_identity::check(cx, expr, arg, span); - flat_map_option::check(cx, expr, arg, span); - }, - (name @ "flatten", args @ []) => match method_call(recv) { - Some(("map", [recv, map_arg], _)) => map_flatten::check(cx, expr, recv, map_arg), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), - _ => {}, - }, - ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span), - ("for_each", [_]) => { - if let Some(("inspect", [_, _], span2)) = method_call(recv) { - inspect_for_each::check(cx, expr, span2); - } - }, - ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), - ("is_file", []) => filetype_is_file::check(cx, expr, recv), - ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), - ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), - ("last", args @ []) | ("skip", args @ [_]) => { - if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); + ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), + ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), + ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), + ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv), + ("collect", []) => match method_call(recv) { + Some((name @ ("cloned" | "copied"), [recv2], _)) => { + iter_cloned_collect::check(cx, name, expr, recv2); + }, + Some(("map", [m_recv, m_arg], _)) => { + map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv); + }, + Some(("take", [take_self_arg, take_arg], _)) => { + if meets_msrv(self.msrv, msrvs::STR_REPEAT) { + manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); + } + }, + _ => {}, + }, + (name @ "count", args @ []) => match method_call(recv) { + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { + iter_count::check(cx, expr, recv2, name2); + }, + Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), + _ => {}, + }, + ("drain", [arg]) => { + iter_with_drain::check(cx, expr, recv, span, arg); + }, + ("expect", [_]) => match method_call(recv) { + Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv), + Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span), + _ => expect_used::check(cx, expr, recv, self.allow_expect_in_tests), + }, + ("extend", [arg]) => { + string_extend_chars::check(cx, expr, recv, arg); + extend_with_drain::check(cx, expr, recv, arg); + }, + ("filter_map", [arg]) => { + unnecessary_filter_map::check(cx, expr, arg, name); + filter_map_identity::check(cx, expr, arg, span); + }, + ("find_map", [arg]) => { + unnecessary_filter_map::check(cx, expr, arg, name); + }, + ("flat_map", [arg]) => { + flat_map_identity::check(cx, expr, arg, span); + flat_map_option::check(cx, expr, arg, span); + }, + (name @ "flatten", args @ []) => match method_call(recv) { + Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + _ => {}, + }, + ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span), + ("for_each", [_]) => { + if let Some(("inspect", [_, _], span2)) = method_call(recv) { + inspect_for_each::check(cx, expr, span2); } - } - }, - ("map", [m_arg]) => { - if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) { - match (name, args) { - ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv), - ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv), - ("filter", [f_arg]) => { - filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false); + }, + ("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg), + ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("is_file", []) => filetype_is_file::check(cx, expr, recv), + ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv), + ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), + ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("join", [join_arg]) => { + if let Some(("collect", _, span)) = method_call(recv) { + unnecessary_join::check(cx, expr, recv, join_arg, span); + } + }, + ("last", args @ []) | ("skip", args @ [_]) => { + if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv2, name, args); + } + } + }, + (name @ ("map" | "map_err"), [m_arg]) => { + if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) { + match (name, args) { + ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv), + ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv), + ("filter", [f_arg]) => { + filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false); + }, + ("find", [f_arg]) => { + filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true); + }, + _ => {}, + } + } + map_identity::check(cx, expr, recv, m_arg, name, span); + }, + ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), + (name @ "next", args @ []) => { + if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { + match (name2, args2) { + ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg), + ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), + ("iter", []) => iter_next_slice::check(cx, expr, recv2), + ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg), + ("skip_while", [_]) => skip_while_next::check(cx, expr), + _ => {}, + } + } + }, + ("nth", args @ [n_arg]) => match method_call(recv) { + Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), + Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), + _ => iter_nth_zero::check(cx, expr, recv, n_arg), + }, + ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("or_else", [arg]) => { + if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); + } + }, + ("splitn" | "rsplitn", [count_arg, pat_arg]) => { + if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { + suspicious_splitn::check(cx, name, expr, recv, count); + str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv); + } + }, + ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => { + if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { + suspicious_splitn::check(cx, name, expr, recv, count); + } + }, + ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), + ("take", args @ [_arg]) => { + if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv2, name, args); + } + } + }, + ("take", []) => needless_option_take::check(cx, expr, recv), + ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { + implicit_clone::check(cx, name, expr, recv); + }, + ("unwrap", []) => { + match method_call(recv) { + Some(("get", [recv, get_arg], _)) => { + get_unwrap::check(cx, expr, recv, get_arg, false); + }, + Some(("get_mut", [recv, get_arg], _)) => { + get_unwrap::check(cx, expr, recv, get_arg, true); + }, + Some(("or", [recv, or_arg], or_span)) => { + or_then_unwrap::check(cx, expr, recv, or_arg, or_span); }, - ("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true), _ => {}, } - } - map_identity::check(cx, expr, recv, m_arg, span); - }, - ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), - (name @ "next", args @ []) => { - if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { - match (name2, args2) { - ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args), - ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg), - ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, msrv), - ("iter", []) => iter_next_slice::check(cx, expr, recv2), - ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg), - ("skip_while", [_]) => skip_while_next::check(cx, expr), - _ => {}, - } - } - }, - ("nth", args @ [n_arg]) => match method_call(recv) { - Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), - Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), - Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), - _ => iter_nth_zero::check(cx, expr, recv, n_arg), - }, - ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), - ("or_else", [arg]) => { - if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); - } - }, - ("splitn" | "rsplitn", [count_arg, pat_arg]) => { - if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { - suspicious_splitn::check(cx, name, expr, recv, count); - if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) { - str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg); - } - if count >= 2 { - str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count); - } - } - }, - ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => { - if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { - suspicious_splitn::check(cx, name, expr, recv, count); - } - }, - ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), - ("take", args @ [_arg]) => { - if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); - } - } - }, - ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { - implicit_clone::check(cx, name, expr, recv); - }, - ("unwrap", []) => match method_call(recv) { - Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false), - Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true), - _ => unwrap_used::check(cx, expr, recv), - }, - ("unwrap_or", [u_arg]) => match method_call(recv) { - Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => { - manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]); + unwrap_used::check(cx, expr, recv, self.allow_unwrap_in_tests); }, - Some(("map", [m_recv, m_arg], span)) => { - option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span); + ("unwrap_or", [u_arg]) => match method_call(recv) { + Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => { + manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]); + }, + Some(("map", [m_recv, m_arg], span)) => { + option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span); + }, + _ => {}, + }, + ("unwrap_or_else", [u_arg]) => match method_call(recv) { + Some(("map", [recv, map_arg], _)) + if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, + _ => { + unwrap_or_else_default::check(cx, expr, recv, u_arg); + unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); + }, + }, + ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => { + no_effect_replace::check(cx, expr, arg1, arg2); }, _ => {}, - }, - ("unwrap_or_else", [u_arg]) => match method_call(recv) { - Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {}, - _ => { - unwrap_or_else_default::check(cx, expr, recv, u_arg); - unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); - }, - }, - _ => {}, + } } } } @@ -2550,7 +2891,7 @@ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ]; -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] enum SelfKind { Value, Ref, diff --git a/clippy_lints/src/methods/needless_option_as_deref.rs b/clippy_lints/src/methods/needless_option_as_deref.rs new file mode 100644 index 00000000000..7030baf19ff --- /dev/null +++ b/clippy_lints/src/methods/needless_option_as_deref.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::path_res; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::usage::local_used_after_expr; +use rustc_errors::Applicability; +use rustc_hir::def::Res; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::NEEDLESS_OPTION_AS_DEREF; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: &str) { + let typeck = cx.typeck_results(); + let outer_ty = typeck.expr_ty(expr); + + if is_type_diagnostic_item(cx, outer_ty, sym::Option) && outer_ty == typeck.expr_ty(recv) { + if name == "as_deref_mut" && recv.is_syntactic_place_expr() { + let Res::Local(binding_id) = path_res(cx, recv) else { return }; + + if local_used_after_expr(cx, binding_id, recv) { + return; + } + } + + span_lint_and_sugg( + cx, + NEEDLESS_OPTION_AS_DEREF, + expr.span, + "derefed type is same as origin", + "try this", + snippet_opt(cx, recv.span).unwrap(), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs new file mode 100644 index 00000000000..829c118d291 --- /dev/null +++ b/clippy_lints/src/methods/needless_option_take.rs @@ -0,0 +1,41 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::match_def_path; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::NEEDLESS_OPTION_TAKE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place + if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_OPTION_TAKE, + expr.span, + "called `Option::take()` on a temporary value", + "try", + format!( + "{}", + snippet_with_applicability(cx, recv.span, "..", &mut applicability) + ), + applicability, + ); + } +} + +fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let expr_type = cx.typeck_results().expr_ty(expr); + is_type_diagnostic_item(cx, expr_type, sym::Option) +} + +fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]); + } + false +} diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints/src/methods/no_effect_replace.rs new file mode 100644 index 00000000000..a76341855b6 --- /dev/null +++ b/clippy_lints/src/methods/no_effect_replace.rs @@ -0,0 +1,47 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::SpanlessEq; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_hir::ExprKind; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::NO_EFFECT_REPLACE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx rustc_hir::Expr<'_>, + arg1: &'tcx rustc_hir::Expr<'_>, + arg2: &'tcx rustc_hir::Expr<'_>, +) { + let ty = cx.typeck_results().expr_ty(expr).peel_refs(); + if !(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)) { + return; + } + + if_chain! { + if let ExprKind::Lit(spanned) = &arg1.kind; + if let Some(param1) = lit_string_value(&spanned.node); + + if let ExprKind::Lit(spanned) = &arg2.kind; + if let LitKind::Str(param2, _) = &spanned.node; + if param1 == param2.as_str(); + + then { + span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself"); + } + } + + if SpanlessEq::new(cx).eq_expr(arg1, arg2) { + span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself"); + } +} + +fn lit_string_value(node: &LitKind) -> Option { + match node { + LitKind::Char(value) => Some(value.to_string()), + LitKind::Str(value, _) => Some(value.as_str().to_owned()), + _ => None, + } +} diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index ba2d2914315..b50a173d835 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -19,9 +19,9 @@ pub(super) fn check<'tcx>( as_ref_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>, is_mut: bool, - msrv: Option<&RustcVersion>, + msrv: Option, ) { - if !meets_msrv(msrv, &msrvs::OPTION_AS_DEREF) { + if !meets_msrv(msrv, msrvs::OPTION_AS_DEREF) { return; } diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 5e5c1038e82..76bc9466ed8 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_lang_ctor, single_segment_path}; +use clippy_utils::{is_lang_ctor, path_def_id}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; +use rustc_middle::ty::DefIdTree; use rustc_span::symbol::sym; use super::OPTION_MAP_OR_NONE; @@ -13,10 +14,7 @@ use super::RESULT_MAP_OR_INTO_OPTION; // The expression inside a closure may or may not have surrounding braces // which causes problems when generating a suggestion. -fn reduce_unit_expression<'a>( - cx: &LateContext<'_>, - expr: &'a hir::Expr<'_>, -) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { +fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { match expr.kind { hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)), hir::ExprKind::Block(block, _) => { @@ -24,7 +22,7 @@ fn reduce_unit_expression<'a>( (&[], Some(inner_expr)) => { // If block only contains an expression, // reduce `|x| { x + 1 }` to `|x| x + 1` - reduce_unit_expression(cx, inner_expr) + reduce_unit_expression(inner_expr) }, _ => None, } @@ -76,13 +74,11 @@ pub(super) fn check<'tcx>( if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind; let arg_snippet = snippet(cx, span, ".."); let body = cx.tcx.hir().body(id); - if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value); - if arg_char.len() == 1; - if let hir::ExprKind::Path(ref qpath) = func.kind; - if let Some(segment) = single_segment_path(qpath); - if segment.ident.name == sym::Some; + if let Some((func, [arg_char])) = reduce_unit_expression(&body.value); + if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id)); + if Some(id) == cx.tcx.lang_items().option_some_variant(); then { - let func_snippet = snippet(cx, arg_char[0].span, ".."); + let func_snippet = snippet(cx, arg_char.span, ".."); let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ `map(..)` instead"; return span_lint_and_sugg( diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 9c6f4211031..6c641af59f9 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::differing_macro_contexts; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_copy; use clippy_utils::ty::is_type_diagnostic_item; @@ -48,7 +47,7 @@ pub(super) fn check<'tcx>( } } - if differing_macro_contexts(unwrap_arg.span, map_span) { + if unwrap_arg.span.ctxt() != map_span.ctxt() { return; } diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs new file mode 100644 index 00000000000..be5768c3545 --- /dev/null +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -0,0 +1,68 @@ +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor}; +use rustc_errors::Applicability; +use rustc_hir::{lang_items::LangItem, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::{sym, Span}; + +use super::OR_THEN_UNWRAP; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + unwrap_expr: &Expr<'_>, + recv: &'tcx Expr<'tcx>, + or_arg: &'tcx Expr<'_>, + or_span: Span, +) { + let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result) + let title; + let or_arg_content: Span; + + if is_type_diagnostic_item(cx, ty, sym::Option) { + title = "found `.or(Some(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) { + or_arg_content = content; + } else { + return; + } + } else if is_type_diagnostic_item(cx, ty, sym::Result) { + title = "found `.or(Ok(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) { + or_arg_content = content; + } else { + return; + } + } else { + // Someone has implemented a struct with .or(...).unwrap() chaining, + // but it's not an Option or a Result, so bail + return; + } + + let mut applicability = Applicability::MachineApplicable; + let suggestion = format!( + "unwrap_or({})", + snippet_with_applicability(cx, or_arg_content, "..", &mut applicability) + ); + + span_lint_and_sugg( + cx, + OR_THEN_UNWRAP, + unwrap_expr.span.with_lo(or_span.lo()), + title, + "try this", + suggestion, + applicability, + ); +} + +fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option { + if let ExprKind::Call(some_expr, [arg]) = expr.kind + && let ExprKind::Path(qpath) = &some_expr.kind + && is_lang_ctor(cx, qpath, item) + { + Some(arg.span) + } else { + None + } +} diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 926c25b4b40..90651a6ba04 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -1,36 +1,83 @@ use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{is_diag_item_method, match_def_path, paths}; +use clippy_utils::usage::local_used_after_expr; +use clippy_utils::visitors::expr_visitor; +use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath}; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{ + BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind, +}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, adjustment::Adjust}; -use rustc_span::{symbol::sym, Span, SyntaxContext}; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_span::{sym, Span, Symbol, SyntaxContext}; -use super::MANUAL_SPLIT_ONCE; +use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; -pub(super) fn check_manual_split_once( +pub(super) fn check( cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>, + count: u128, + msrv: Option, ) { - if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { + if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { return; } - let ctxt = expr.span.ctxt(); - let (method_name, msg, reverse) = if method_name == "splitn" { - ("split_once", "manual implementation of `split_once`", false) - } else { - ("rsplit_once", "manual implementation of `rsplit_once`", true) + let needless = |usage_kind| match usage_kind { + IterUsageKind::Nth(n) => count > n + 1, + IterUsageKind::NextTuple => count > 2, }; - let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) { - Some(x) => x, - None => return, + let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE); + + match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) { + Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg), + Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage), + None if manual => { + check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg); + }, + _ => {}, + } +} + +fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) { + let mut app = Applicability::MachineApplicable; + let r = if method_name == "splitn" { "" } else { "r" }; + + span_lint_and_sugg( + cx, + NEEDLESS_SPLITN, + expr.span, + &format!("unnecessary use of `{r}splitn`"), + "try this", + format!( + "{}.{r}split({})", + snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0, + snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0, + ), + app, + ); +} + +fn check_manual_split_once( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, + usage: &IterUsage, +) { + let ctxt = expr.span.ctxt(); + let (msg, reverse) = if method_name == "splitn" { + ("manual implementation of `split_once`", false) + } else { + ("manual implementation of `rsplit_once`", true) }; let mut app = Applicability::MachineApplicable; @@ -39,84 +86,198 @@ pub(super) fn check_manual_split_once( let sugg = match usage.kind { IterUsageKind::NextTuple => { - format!("{}.{}({})", self_snip, method_name, pat_snip) - }, - IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip), - IterUsageKind::Next | IterUsageKind::Second => { - let self_deref = { - let adjust = cx.typeck_results().expr_adjustments(self_arg); - if adjust.len() < 2 { - String::new() - } else if cx.typeck_results().expr_ty(self_arg).is_box() - || adjust - .iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box()) - { - format!("&{}", "*".repeat(adjust.len().saturating_sub(1))) - } else { - "*".repeat(adjust.len().saturating_sub(2)) - } - }; - if matches!(usage.kind, IterUsageKind::Next) { - match usage.unwrap_kind { - Some(UnwrapKind::Unwrap) => { - if reverse { - format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip) - } else { - format!( - "{}.{}({}).map_or({}{}, |x| x.0)", - self_snip, method_name, pat_snip, self_deref, &self_snip - ) - } - }, - Some(UnwrapKind::QuestionMark) => { - format!( - "{}.{}({}).map_or({}{}, |x| x.0)", - self_snip, method_name, pat_snip, self_deref, &self_snip - ) - }, - None => { - format!( - "Some({}.{}({}).map_or({}{}, |x| x.0))", - &self_snip, method_name, pat_snip, self_deref, &self_snip - ) - }, - } + if reverse { + format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))") } else { - match usage.unwrap_kind { - Some(UnwrapKind::Unwrap) => { - if reverse { - // In this case, no better suggestion is offered. - return; - } - format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip) - }, - Some(UnwrapKind::QuestionMark) => { - format!("{}.{}({})?.1", self_snip, method_name, pat_snip) - }, - None => { - format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip) - }, - } + format!("{self_snip}.split_once({pat_snip})") } }, + IterUsageKind::Nth(1) => { + let (r, field) = if reverse { ("r", 0) } else { ("", 1) }; + + match usage.unwrap_kind { + Some(UnwrapKind::Unwrap) => { + format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}") + }, + Some(UnwrapKind::QuestionMark) => { + format!("{self_snip}.{r}split_once({pat_snip})?.{field}") + }, + None => { + format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})") + }, + } + }, + IterUsageKind::Nth(_) => return, }; span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app); } -enum IterUsageKind { - Next, - Second, - NextTuple, - RNextTuple, +/// checks for +/// +/// ``` +/// let mut iter = "a.b.c".splitn(2, '.'); +/// let a = iter.next(); +/// let b = iter.next(); +/// ``` +fn check_manual_split_once_indirect( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, +) -> Option<()> { + let ctxt = expr.span.ctxt(); + let mut parents = cx.tcx.hir().parent_iter(expr.hir_id); + if let (_, Node::Local(local)) = parents.next()? + && let PatKind::Binding(BindingAnnotation::Mutable, iter_binding_id, iter_ident, None) = local.pat.kind + && let (iter_stmt_id, Node::Stmt(_)) = parents.next()? + && let (_, Node::Block(enclosing_block)) = parents.next()? + + && let mut stmts = enclosing_block + .stmts + .iter() + .skip_while(|stmt| stmt.hir_id != iter_stmt_id) + .skip(1) + + && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? + && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? + && first.unwrap_kind == second.unwrap_kind + && first.name != second.name + && !local_used_after_expr(cx, iter_binding_id, second.init_expr) + { + let (r, lhs, rhs) = if method_name == "splitn" { + ("", first.name, second.name) + } else { + ("r", second.name, first.name) + }; + let msg = format!("manual implementation of `{r}split_once`"); + + let mut app = Applicability::MachineApplicable; + let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; + let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0; + + span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| { + diag.span_label(first.span, "first usage here"); + diag.span_label(second.span, "second usage here"); + + let unwrap = match first.unwrap_kind { + UnwrapKind::Unwrap => ".unwrap()", + UnwrapKind::QuestionMark => "?", + }; + diag.span_suggestion_verbose( + local.span, + &format!("try `{r}split_once`"), + format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"), + app, + ); + + let remove_msg = format!("remove the `{iter_ident}` usages"); + diag.span_suggestion( + first.span, + &remove_msg, + String::new(), + app, + ); + diag.span_suggestion( + second.span, + &remove_msg, + String::new(), + app, + ); + }); + } + + Some(()) } +#[derive(Debug)] +struct IndirectUsage<'a> { + name: Symbol, + span: Span, + init_expr: &'a Expr<'a>, + unwrap_kind: UnwrapKind, +} + +/// returns `Some(IndirectUsage)` for e.g. +/// +/// ```ignore +/// let name = binding.next()?; +/// let name = binding.next().unwrap(); +/// ``` +fn indirect_usage<'tcx>( + cx: &LateContext<'tcx>, + stmt: &Stmt<'tcx>, + binding: HirId, + ctxt: SyntaxContext, +) -> Option> { + if let StmtKind::Local(Local { + pat: + Pat { + kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None), + .. + }, + init: Some(init_expr), + hir_id: local_hir_id, + .. + }) = stmt.kind + { + let mut path_to_binding = None; + expr_visitor(cx, |expr| { + if path_to_local_id(expr, binding) { + path_to_binding = Some(expr); + } + + path_to_binding.is_none() + }) + .visit_expr(init_expr); + + let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id); + let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?; + + let (parent_id, _) = parents.find(|(_, node)| { + !matches!( + node, + Node::Expr(Expr { + kind: ExprKind::Match(.., MatchSource::TryDesugar), + .. + }) + ) + })?; + + if let IterUsage { + kind: IterUsageKind::Nth(0), + unwrap_kind: Some(unwrap_kind), + .. + } = iter_usage + { + if parent_id == *local_hir_id { + return Some(IndirectUsage { + name: ident.name, + span: stmt.span, + init_expr, + unwrap_kind, + }); + } + } + } + + None +} + +#[derive(Debug, Clone, Copy)] +enum IterUsageKind { + Nth(u128), + NextTuple, +} + +#[derive(Debug, PartialEq, Eq)] enum UnwrapKind { Unwrap, QuestionMark, } +#[derive(Debug)] struct IterUsage { kind: IterUsageKind, unwrap_kind: Option, @@ -128,7 +289,6 @@ fn parse_iter_usage<'tcx>( cx: &LateContext<'tcx>, ctxt: SyntaxContext, mut iter: impl Iterator)>, - reverse: bool, ) -> Option { let (kind, span) = match iter.next() { Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { @@ -141,23 +301,17 @@ fn parse_iter_usage<'tcx>( let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - if reverse { - (IterUsageKind::Second, e.span) - } else { - (IterUsageKind::Next, e.span) - } - }, + ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), ("next_tuple", []) => { return if_chain! { if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE); if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind(); - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did); + if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()); if let ty::Tuple(subs) = subs.type_at(0).kind(); if subs.len() == 2; then { Some(IterUsage { - kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple }, + kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None }) @@ -185,11 +339,7 @@ fn parse_iter_usage<'tcx>( } } }; - match if reverse { idx ^ 1 } else { idx } { - 0 => (IterUsageKind::Next, span), - 1 => (IterUsageKind::Second, span), - _ => return None, - } + (IterUsageKind::Nth(idx), span) } else { return None; } @@ -238,86 +388,3 @@ fn parse_iter_usage<'tcx>( span, }) } - -use super::NEEDLESS_SPLITN; - -pub(super) fn check_needless_splitn( - cx: &LateContext<'_>, - method_name: &str, - expr: &Expr<'_>, - self_arg: &Expr<'_>, - pat_arg: &Expr<'_>, - count: u128, -) { - if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { - return; - } - let ctxt = expr.span.ctxt(); - let mut app = Applicability::MachineApplicable; - let (reverse, message) = if method_name == "splitn" { - (false, "unnecessary use of `splitn`") - } else { - (true, "unnecessary use of `rsplitn`") - }; - if_chain! { - if count >= 2; - if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count); - then { - span_lint_and_sugg( - cx, - NEEDLESS_SPLITN, - expr.span, - message, - "try this", - format!( - "{}.{}({})", - snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0, - if reverse {"rsplit"} else {"split"}, - snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0 - ), - app, - ); - } - } -} - -fn check_iter<'tcx>( - cx: &LateContext<'tcx>, - ctxt: SyntaxContext, - mut iter: impl Iterator)>, - count: u128, -) -> bool { - match iter.next() { - Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { - let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind { - (name, args) - } else { - return false; - }; - if_chain! { - if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id); - if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator); - then { - match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - return true; - }, - ("next_tuple", []) if count > 2 => { - return true; - }, - ("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) { - if count > idx + 1 { - return true; - } - } - }, - _ => return false, - } - } - } - }, - _ => return false, - }; - false -} diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs index 1c546a15bf6..55567d8625e 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints/src/methods/suspicious_splitn.rs @@ -12,13 +12,13 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se if count <= 1; if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(call_id); - let lang_items = cx.tcx.lang_items(); - if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id); + if cx.tcx.impl_trait_ref(impl_id).is_none(); + let self_ty = cx.tcx.type_of(impl_id); + if self_ty.is_slice() || self_ty.is_str(); then { // Ignore empty slice and string literals when used with a literal count. if matches!(self_arg.kind, ExprKind::Array([])) || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty()) - { return; } @@ -28,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se "the resulting iterator will always return `None`") } else { (format!("`{}` called with `1` split", method_name), - if lang_items.slice_impl() == Some(impl_id) { + if self_ty.is_slice() { "the resulting iterator will always return the entire slice followed by `None`" } else { "the resulting iterator will always return the entire string followed by `None`" diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index ce89189bce9..77d21f1d373 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_expr_path_def_path, paths, ty::is_uninit_value_valid_for_ty}; +use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_span::sym; use super::UNINIT_ASSUMED_INIT; @@ -11,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if_chain! { if let hir::ExprKind::Call(callee, args) = recv.kind; if args.is_empty(); - if is_expr_path_def_path(cx, callee, &paths::MEM_MAYBEUNINIT_UNINIT); + if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)); then { span_lint( diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 784014f0d87..2fda254ca98 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,16 +1,19 @@ +use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::is_copy; use clippy_utils::usage::mutated_variables; use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id}; use rustc_hir as hir; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty; use rustc_span::sym; use super::UNNECESSARY_FILTER_MAP; +use super::UNNECESSARY_FIND_MAP; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) { if !is_trait_method(cx, expr, sym::Iterator) { return; } @@ -20,6 +23,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< let arg_id = body.params[0].pat.hir_id; let mutates_arg = mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id)); + let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value); let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value); @@ -28,15 +32,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< found_mapping |= return_visitor.found_mapping; found_filtering |= return_visitor.found_filtering; + let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { - "map" - } else if !found_mapping && !mutates_arg { - let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); + if name == "filter_map" { "map" } else { "map(..).next()" } + } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(&body.value).kind() { ty::Adt(adt, subst) - if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && TyS::same_type(in_ty, subst.type_at(0)) => + if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => { - "filter" + if name == "filter_map" { "filter" } else { "find" } }, _ => return, } @@ -45,9 +49,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< }; span_lint( cx, - UNNECESSARY_FILTER_MAP, + if name == "filter_map" { + UNNECESSARY_FILTER_MAP + } else { + UNNECESSARY_FIND_MAP + }, expr.span, - &format!("this `.filter_map` can be written more simply using `.{}`", sugg), + &format!("this `.{}` can be written more simply using `.{}`", name, sugg), ); } } diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 65e94c5f44a..7a39557ad57 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -1,14 +1,12 @@ +use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait}; -use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage}; +use clippy_utils::{fn_def_id, get_parent_expr}; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat}; +use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem}; use rustc_lint::LateContext; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty; use rustc_span::{sym, Symbol}; use super::UNNECESSARY_TO_OWNED; @@ -100,89 +98,6 @@ pub fn check_for_loop_iter( false } -/// The core logic of `check_for_loop_iter` above, this function wraps a use of -/// `CloneOrCopyVisitor`. -fn clone_or_copy_needed<'tcx>( - cx: &LateContext<'tcx>, - pat: &Pat<'tcx>, - body: &'tcx Expr<'tcx>, -) -> (bool, Vec<&'tcx Expr<'tcx>>) { - let mut visitor = CloneOrCopyVisitor { - cx, - binding_hir_ids: pat_bindings(pat), - clone_or_copy_needed: false, - addr_of_exprs: Vec::new(), - }; - visitor.visit_expr(body); - (visitor.clone_or_copy_needed, visitor.addr_of_exprs) -} - -/// Returns a vector of all `HirId`s bound by the pattern. -fn pat_bindings(pat: &Pat<'_>) -> Vec { - let mut collector = usage::ParamBindingIdCollector { - binding_hir_ids: Vec::new(), - }; - collector.visit_pat(pat); - collector.binding_hir_ids -} - -/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only -/// operations performed on `binding_hir_ids` are: -/// * to take non-mutable references to them -/// * to use them as non-mutable `&self` in method calls -/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true -/// when `CloneOrCopyVisitor` is done visiting. -struct CloneOrCopyVisitor<'cx, 'tcx> { - cx: &'cx LateContext<'tcx>, - binding_hir_ids: Vec, - clone_or_copy_needed: bool, - addr_of_exprs: Vec<&'tcx Expr<'tcx>>, -} - -impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - - fn nested_visit_map(&mut self) -> Self::Map { - self.cx.tcx.hir() - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - walk_expr(self, expr); - if self.is_binding(expr) { - if let Some(parent) = get_parent_expr(self.cx, expr) { - match parent.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { - self.addr_of_exprs.push(parent); - return; - }, - ExprKind::MethodCall(_, args, _) => { - if_chain! { - if args.iter().skip(1).all(|arg| !self.is_binding(arg)); - if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); - let method_ty = self.cx.tcx.type_of(method_def_id); - let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); - if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); - then { - return; - } - } - }, - _ => {}, - } - } - self.clone_or_copy_needed = true; - } - } -} - -impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { - fn is_binding(&self, expr: &Expr<'tcx>) -> bool { - self.binding_hir_ids - .iter() - .any(|hir_id| path_to_local_id(expr, *hir_id)) - } -} - /// Returns true if the named method is `IntoIterator::into_iter`. pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id) diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs new file mode 100644 index 00000000000..973b8a7e6bf --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_join.rs @@ -0,0 +1,41 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{Ref, Slice}; +use rustc_span::{sym, Span}; + +use super::UNNECESSARY_JOIN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + join_self_arg: &'tcx Expr<'tcx>, + join_arg: &'tcx Expr<'tcx>, + span: Span, +) { + let applicability = Applicability::MachineApplicable; + let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); + if_chain! { + // the turbofish for collect is ::> + if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind(); + if let Slice(slice) = ref_type.kind(); + if is_type_diagnostic_item(cx, *slice, sym::String); + // the argument for join is "" + if let ExprKind::Lit(spanned) = &join_arg.kind; + if let LitKind::Str(symbol, _) = spanned.node; + if symbol.is_empty(); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_JOIN, + span.with_hi(expr.span.hi()), + r#"called `.collect>().join("")` on an iterator"#, + "try using", + "collect::()".to_owned(), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 1e2765263c8..2369be70812 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{eager_or_lazy, usage}; @@ -48,20 +48,19 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect }; - span_lint_and_sugg( - cx, - UNNECESSARY_LAZY_EVALUATIONS, - expr.span, - msg, - &format!("use `{}` instead", simplify_using), - format!( - "{0}.{1}({2})", - snippet(cx, recv.span, ".."), - simplify_using, - snippet(cx, body_expr.span, ".."), - ), - applicability, - ); + // This is a duplicate of what's happening in clippy_lints::methods::method_call, + // which isn't ideal, We want to get the method call span, + // but prefer to avoid changing the signature of the function itself. + if let hir::ExprKind::MethodCall(_, _, span) = expr.kind { + span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { + diag.span_suggestion( + span, + &format!("use `{}(..)` instead", simplify_using), + format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")), + applicability, + ); + }); + } } } } diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index b67bfb6597b..97c4feb3122 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -2,7 +2,11 @@ use super::implicit_clone::is_clone_like; use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::ty::{ + contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, +}; +use clippy_utils::{meets_msrv, msrvs}; + use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; @@ -11,12 +15,19 @@ use rustc_middle::mir::Mutability; use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty}; +use rustc_semver::RustcVersion; use rustc_span::{sym, Symbol}; use std::cmp::max; use super::UNNECESSARY_TO_OWNED; -pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) { +pub fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + args: &'tcx [Expr<'tcx>], + msrv: Option, +) { if_chain! { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let [receiver] = args; @@ -31,7 +42,7 @@ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) { return; } - if check_into_iter_call_arg(cx, expr, method_name, receiver) { + if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) { return; } check_other_call_arg(cx, expr, method_name, receiver); @@ -54,13 +65,12 @@ fn check_addr_of_expr( if let Some(parent) = get_parent_expr(cx, expr); if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind; let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::>(); - if let Some(target_ty) = match adjustments[..] - { + if let // For matching uses of `Cow::from` [ Adjustment { kind: Adjust::Deref(None), - .. + target: referent_ty, }, Adjustment { kind: Adjust::Borrow(_), @@ -71,7 +81,7 @@ fn check_addr_of_expr( | [ Adjustment { kind: Adjust::Deref(None), - .. + target: referent_ty, }, Adjustment { kind: Adjust::Borrow(_), @@ -86,7 +96,7 @@ fn check_addr_of_expr( | [ Adjustment { kind: Adjust::Deref(None), - .. + target: referent_ty, }, Adjustment { kind: Adjust::Deref(Some(OverloadedDeref { .. })), @@ -96,17 +106,24 @@ fn check_addr_of_expr( kind: Adjust::Borrow(_), target: target_ty, }, - ] => Some(target_ty), - _ => None, - }; + ] = adjustments[..]; let receiver_ty = cx.typeck_results().expr_ty(receiver); - // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This - // restriction is to ensure there is not overlap between `redundant_clone` and this lint. - if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id); + let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty); + let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty); + // Only flag cases satisfying at least one of the following three conditions: + // * the referent and receiver types are distinct + // * the referent/receiver type is a copyable array + // * the method is `Cow::into_owned` + // This restriction is to ensure there is no overlap between `redundant_clone` and this + // lint. It also avoids the following false positive: + // https://github.com/rust-lang/rust-clippy/issues/8759 + // Arrays are a bit of a corner case. Non-copyable arrays are handled by + // `redundant_clone`, but copyable arrays are not. + if *referent_ty != receiver_ty + || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty)) + || is_cow_into_owned(cx, method_name, method_def_id); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty); - let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty); if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { span_lint_and_sugg( cx, @@ -114,7 +131,12 @@ fn check_addr_of_expr( parent.span, &format!("unnecessary use of `{}`", method_name), "use", - format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs), + format!( + "{:&>width$}{}", + "", + receiver_snippet, + width = n_target_refs - n_receiver_refs + ), Applicability::MachineApplicable, ); return true; @@ -171,7 +193,13 @@ fn check_addr_of_expr( /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its /// call of a `to_owned`-like function is unnecessary. -fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { +fn check_into_iter_call_arg( + cx: &LateContext<'_>, + expr: &Expr<'_>, + method_name: Symbol, + receiver: &Expr<'_>, + msrv: Option, +) -> bool { if_chain! { if let Some(parent) = get_parent_expr(cx, expr); if let Some(callee_def_id) = fn_def_id(cx, parent); @@ -182,16 +210,10 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - if unnecessary_iter_cloned::check_for_loop_iter( - cx, - parent, - method_name, - receiver, - true, - ) { + if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } - let cloned_or_copied = if is_copy(cx, item_ty) { + let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) { "copied" } else { "cloned" @@ -228,7 +250,7 @@ fn check_other_call_arg<'tcx>( let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id); if let Some(input) = fn_sig.inputs().get(i); - let (input, n_refs) = peel_mid_ty_refs(input); + let (input, n_refs) = peel_mid_ty_refs(*input); if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input); if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait(); if let [trait_predicate] = trait_predicates @@ -243,10 +265,11 @@ fn check_other_call_arg<'tcx>( if if trait_predicate.def_id() == deref_trait_id { if let [projection_predicate] = projection_predicates[..] { let normalized_ty = - cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); + cx.tcx + .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); implements_trait(cx, receiver_ty, deref_trait_id, &[]) - && get_associated_type(cx, receiver_ty, deref_trait_id, - "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) + && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") + .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) } else { false } @@ -254,7 +277,7 @@ fn check_other_call_arg<'tcx>( let composed_substs = compose_substs( cx, &trait_predicate.trait_ref.substs.iter().skip(1).collect::>()[..], - call_substs + call_substs, ); implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) } else { @@ -264,6 +287,12 @@ fn check_other_call_arg<'tcx>( // `Target = T`. if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id; let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 }); + // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then + // `T` must not be instantiated with a reference + // (https://github.com/rust-lang/rust-clippy/issues/8507). + if (n_refs == 0 && !receiver_ty.is_ref()) + || trait_predicate.def_id() != as_ref_trait_id + || !contains_ty(fn_sig.output(), input); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { span_lint_and_sugg( @@ -339,11 +368,7 @@ fn get_input_traits_and_projections<'tcx>( if let Some(arg) = substs.iter().next(); if let GenericArgKind::Type(arg_ty) = arg.unpack(); if arg_ty == input; - then { - true - } else { - false - } + then { true } else { false } } }; match predicate.kind().skip_binder() { diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs index 44676d78c60..5c761014927 100644 --- a/clippy_lints/src/methods/unwrap_used.rs +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_in_test_function; use clippy_utils::ty::is_type_diagnostic_item; use rustc_hir as hir; use rustc_lint::LateContext; @@ -7,7 +8,7 @@ use rustc_span::sym; use super::UNWRAP_USED; /// lint use of `unwrap()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { @@ -18,6 +19,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr None }; + if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { + return; + } + if let Some((lint, kind, none_value)) = mess { span_lint_and_help( cx, diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index c4cf994aaca..3015531e843 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -1,10 +1,14 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{get_parent_expr, path_to_local_id, usage}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat}; use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; @@ -19,7 +23,7 @@ pub(super) fn derefs_to_slice<'tcx>( ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec), ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(), - ty::Ref(_, inner, _) => may_slice(cx, inner), + ty::Ref(_, inner, _) => may_slice(cx, *inner), _ => false, } } @@ -35,7 +39,7 @@ pub(super) fn derefs_to_slice<'tcx>( ty::Slice(_) => Some(expr), ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr), ty::Ref(_, inner, _) => { - if may_slice(cx, inner) { + if may_slice(cx, *inner) { Some(expr) } else { None @@ -79,3 +83,86 @@ pub(super) fn get_hint_if_single_char_arg( } } } + +/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a +/// use of `CloneOrCopyVisitor`. +pub(super) fn clone_or_copy_needed<'tcx>( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + body: &'tcx Expr<'tcx>, +) -> (bool, Vec<&'tcx Expr<'tcx>>) { + let mut visitor = CloneOrCopyVisitor { + cx, + binding_hir_ids: pat_bindings(pat), + clone_or_copy_needed: false, + addr_of_exprs: Vec::new(), + }; + visitor.visit_expr(body); + (visitor.clone_or_copy_needed, visitor.addr_of_exprs) +} + +/// Returns a vector of all `HirId`s bound by the pattern. +fn pat_bindings(pat: &Pat<'_>) -> Vec { + let mut collector = usage::ParamBindingIdCollector { + binding_hir_ids: Vec::new(), + }; + collector.visit_pat(pat); + collector.binding_hir_ids +} + +/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only +/// operations performed on `binding_hir_ids` are: +/// * to take non-mutable references to them +/// * to use them as non-mutable `&self` in method calls +/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true +/// when `CloneOrCopyVisitor` is done visiting. +struct CloneOrCopyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + binding_hir_ids: Vec, + clone_or_copy_needed: bool, + addr_of_exprs: Vec<&'tcx Expr<'tcx>>, +} + +impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + walk_expr(self, expr); + if self.is_binding(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { + self.addr_of_exprs.push(parent); + return; + }, + ExprKind::MethodCall(_, args, _) => { + if_chain! { + if args.iter().skip(1).all(|arg| !self.is_binding(arg)); + if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); + let method_ty = self.cx.tcx.type_of(method_def_id); + let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); + if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); + then { + return; + } + } + }, + _ => {}, + } + } + self.clone_or_copy_needed = true; + } + } +} + +impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { + fn is_binding(&self, expr: &Expr<'tcx>) -> bool { + self.binding_hir_ids + .iter() + .any(|hir_id| path_to_local_id(expr, *hir_id)) + } +} diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index a2e09e5ecec..4b368d3ffae 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -2,7 +2,7 @@ use crate::methods::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; +use rustc_middle::ty::Ty; use rustc_span::source_map::Span; use std::fmt; @@ -14,7 +14,7 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]), (&[Convention::StartsWith("from_")], &[SelfKind::No]), (&[Convention::StartsWith("into_")], &[SelfKind::Value]), - (&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]), + (&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]), (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]), (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]), @@ -41,7 +41,7 @@ impl Convention { fn check<'tcx>( &self, cx: &LateContext<'tcx>, - self_ty: &'tcx TyS<'tcx>, + self_ty: Ty<'tcx>, other: &str, implements_trait: bool, is_trait_item: bool, @@ -84,8 +84,8 @@ impl fmt::Display for Convention { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, item_name: &str, - self_ty: &'tcx TyS<'tcx>, - first_arg_ty: &'tcx TyS<'tcx>, + self_ty: Ty<'tcx>, + first_arg_ty: Ty<'tcx>, first_arg_span: Span, implements_trait: bool, is_trait_item: bool, diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs index 866cf616679..e9f268da691 100644 --- a/clippy_lints/src/methods/zst_offset.rs +++ b/clippy_lints/src/methods/zst_offset.rs @@ -9,7 +9,7 @@ use super::ZST_OFFSET; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if_chain! { if let ty::RawPtr(ty::TypeAndMut { ty, .. }) = cx.typeck_results().expr_ty(recv).kind(); - if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(*ty)); if layout.is_zst(); then { span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index cf9770f5c1f..65d1f440b76 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -1,10 +1,11 @@ use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{match_def_path, match_trait_method, paths}; +use clippy_utils::{match_trait_method, paths}; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; use std::cmp::Ordering; declare_clippy_lint! { @@ -73,14 +74,10 @@ fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Cons cx.typeck_results() .qpath_res(qpath, path.hir_id) .opt_def_id() - .and_then(|def_id| { - if match_def_path(cx, def_id, &paths::CMP_MIN) { - fetch_const(cx, args, MinMax::Min) - } else if match_def_path(cx, def_id, &paths::CMP_MAX) { - fetch_const(cx, args, MinMax::Max) - } else { - None - } + .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::cmp_min) => fetch_const(cx, args, MinMax::Min), + Some(sym::cmp_max) => fetch_const(cx, args, MinMax::Max), + _ => None, }) } else { None diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 3918bdbdf43..7fdc28c5a06 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_opt}; -use clippy_utils::ty::implements_trait; +use clippy_utils::ty::{implements_trait, is_copy}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -20,8 +20,8 @@ use rustc_span::symbol::sym; use clippy_utils::consts::{constant, Constant}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats, - last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq, + get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment, + match_any_def_paths, path_def_id, paths, unsext, SpanlessEq, }; declare_clippy_lint! { @@ -548,7 +548,7 @@ fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) } -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { #[derive(Default)] struct EqImpl { @@ -569,34 +569,34 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: }) } - let (arg_ty, snip) = match expr.kind { - ExprKind::MethodCall(.., args, _) if args.len() == 1 => { - if_chain!( - 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_diag_trait_item(cx, expr_def_id, sym::ToOwned); - then { - (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, "..")) - } else { - return; - } - ) + let typeck = cx.typeck_results(); + let (arg, arg_span) = match expr.kind { + ExprKind::MethodCall(.., [arg], _) + if typeck + .type_dependent_def_id(expr.hir_id) + .and_then(|id| cx.tcx.trait_of_item(id)) + .map_or(false, |id| { + matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned)) + }) => + { + (arg, arg.span) }, - ExprKind::Call(path, [arg]) => { - if expr_path_res(cx, path) - .opt_def_id() + ExprKind::Call(path, [arg]) + if path_def_id(cx, path) .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) - .is_some() - { - (cx.typeck_results().expr_ty(arg), snippet(cx, arg.span, "..")) - } else { - return; - } + .map_or(false, |idx| match idx { + 0 => true, + 1 => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, + }) => + { + (arg, arg.span) }, _ => return, }; - let other_ty = cx.typeck_results().expr_ty(other); + let arg_ty = typeck.expr_ty(arg); + let other_ty = typeck.expr_ty(other); let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); let with_deref = arg_ty @@ -628,13 +628,14 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: return; } + let arg_snip = snippet(cx, arg_span, ".."); let expr_snip; let eq_impl; if with_deref.is_implemented() { - expr_snip = format!("*{}", snip); + expr_snip = format!("*{}", arg_snip); eq_impl = with_deref; } else { - expr_snip = snip.to_string(); + expr_snip = arg_snip.to_string(); eq_impl = without_deref; }; diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index d955fad7d41..6860b60acbd 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -361,7 +361,7 @@ impl MiscEarlyLints { // See for a regression. // FIXME: Find a better way to detect those cases. let lit_snip = match snippet_opt(cx, lit.span) { - Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip, + Some(snip) if snip.chars().next().map_or(false, |c| c.is_ascii_digit()) => snip, _ => return, }; diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index bad9e0be82e..16d65966c10 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -3,6 +3,7 @@ use clippy_utils::qualify_min_const_fn::is_min_const_fn; use clippy_utils::ty::has_drop; use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method}; use rustc_hir as hir; +use rustc_hir::def_id::CRATE_DEF_ID; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; @@ -92,7 +93,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { span: Span, hir_id: HirId, ) { - if !meets_msrv(self.msrv.as_ref(), &msrvs::CONST_IF_MATCH) { + if !meets_msrv(self.msrv, msrvs::CONST_IF_MATCH) { return; } @@ -131,11 +132,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { FnKind::Closure => return, } + // Const fns are not allowed as methods in a trait. + { + let parent = cx.tcx.hir().get_parent_item(hir_id); + if parent != CRATE_DEF_ID { + if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent) { + if let hir::ItemKind::Trait(..) = &item.kind { + return; + } + } + } + } + let mir = cx.tcx.optimized_mir(def_id); - if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) { + if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) { if cx.tcx.is_const_fn_raw(def_id.to_def_id()) { - cx.tcx.sess.span_err(span, &err); + cx.tcx.sess.span_err(span, err.as_ref()); } } else { span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index db6aab0671b..b99052e66ba 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -11,7 +11,7 @@ use if_chain::if_chain; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty; +use rustc_middle::ty::{self, DefIdTree}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::source_map::Span; @@ -131,8 +131,8 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { hir::ItemKind::Fn(..) => { // ignore main() if it.ident.name == sym::main { - let def_key = cx.tcx.hir().def_key(it.def_id); - if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) { + let at_root = cx.tcx.local_parent(it.def_id) == CRATE_DEF_ID; + if at_root { return; } } diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index 566e15ab2a6..3d0a2382283 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -58,7 +58,7 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]); impl LateLintPass<'_> for ImportRename { fn check_crate(&mut self, cx: &LateContext<'_>) { for Rename { path, rename } in &self.conf_renames { - if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &path.split("::").collect::>()) { + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::>()) { self.renames.insert(id, Symbol::intern(rename)); } } diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index ac2f16b49e3..0d953299189 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -44,7 +44,7 @@ declare_clippy_lint! { /// pub struct PubBaz; /// impl PubBaz { /// fn private() {} // ok - /// pub fn not_ptrivate() {} // missing #[inline] + /// pub fn not_private() {} // missing #[inline] /// } /// /// impl Bar for PubBaz { @@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let attrs = cx.tcx.hir().attrs(it.hir_id()); check_missing_inline_attrs(cx, attrs, it.span, desc); }, - hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, _bounds, trait_items) => { + hir::ItemKind::Trait(ref _is_auto, ref _unsafe, _generics, _bounds, trait_items) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for tit in trait_items { diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/mixed_read_write_in_expression.rs similarity index 98% rename from clippy_lints/src/eval_order_dependence.rs rename to clippy_lints/src/mixed_read_write_in_expression.rs index 65599a0587d..405fc23e8de 100644 --- a/clippy_lints/src/eval_order_dependence.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -40,8 +40,8 @@ declare_clippy_lint! { /// let a = tmp + x; /// ``` #[clippy::version = "pre 1.29.0"] - pub EVAL_ORDER_DEPENDENCE, - suspicious, + pub MIXED_READ_WRITE_IN_EXPRESSION, + restriction, "whether a variable read occurs before a write depends on sub-expression evaluation order" } @@ -73,7 +73,7 @@ declare_clippy_lint! { "whether an expression contains a diverging sub expression" } -declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_EXPRESSION]); +declare_lint_pass!(EvalOrderDependence => [MIXED_READ_WRITE_IN_EXPRESSION, DIVERGING_SUB_EXPRESSION]); impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -303,7 +303,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> { if !is_in_assignment_position(self.cx, expr) { span_lint_and_note( self.cx, - EVAL_ORDER_DEPENDENCE, + MIXED_READ_WRITE_IN_EXPRESSION, expr.span, &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)), Some(self.write_expr.span), diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index b8dfe996880..0a393657267 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -1,17 +1,14 @@ -use std::{ - ffi::OsString, - path::{Component, Path}, -}; - use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext}; +use std::ffi::OsStr; +use std::path::{Component, Path}; declare_clippy_lint! { /// ### What it does - /// Checks that module layout uses only self named module files, bans mod.rs files. + /// Checks that module layout uses only self named module files, bans `mod.rs` files. /// /// ### Why is this bad? /// Having multiple module layout styles in a project can be confusing. @@ -40,7 +37,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks that module layout uses only mod.rs files. + /// Checks that module layout uses only `mod.rs` files. /// /// ### Why is this bad? /// Having multiple module layout styles in a project can be confusing. @@ -82,11 +79,7 @@ impl EarlyLintPass for ModStyle { let files = cx.sess().source_map().files(); - let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess().opts.working_dir { - p.to_string_lossy() - } else { - return; - }; + let RealFileName::LocalPath(trim_to_src) = &cx.sess().opts.working_dir else { return }; // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives // `[path, to]` but not foo @@ -97,26 +90,27 @@ impl EarlyLintPass for ModStyle { // `{ foo => path/to/foo.rs, .. } let mut file_map = FxHashMap::default(); for file in files.iter() { - match &file.name { - FileName::Real(RealFileName::LocalPath(lp)) - if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) => - { - let p = lp.to_string_lossy(); - let path = Path::new(p.trim_start_matches(trim_to_src.as_ref())); - if let Some(stem) = path.file_stem() { - file_map.insert(stem.to_os_string(), (file, path.to_owned())); - } - process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders); - check_self_named_mod_exists(cx, path, file); - }, - _ => {}, + if let FileName::Real(RealFileName::LocalPath(lp)) = &file.name { + let path = if lp.is_relative() { + lp + } else if let Ok(relative) = lp.strip_prefix(trim_to_src) { + relative + } else { + continue; + }; + + if let Some(stem) = path.file_stem() { + file_map.insert(stem, (file, path)); + } + process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders); + check_self_named_mod_exists(cx, path, file); } } for folder in &folder_segments { if !mod_folders.contains(folder) { if let Some((file, path)) = file_map.get(folder) { - let mut correct = path.clone(); + let mut correct = path.to_path_buf(); correct.pop(); correct.push(folder); correct.push("mod.rs"); @@ -138,25 +132,17 @@ impl EarlyLintPass for ModStyle { /// For each `path` we add each folder component to `folder_segments` and if the file name /// is `mod.rs` we add it's parent folder to `mod_folders`. -fn process_paths_for_mod_files( - path: &Path, - folder_segments: &mut FxHashSet, - mod_folders: &mut FxHashSet, +fn process_paths_for_mod_files<'a>( + path: &'a Path, + folder_segments: &mut FxHashSet<&'a OsStr>, + mod_folders: &mut FxHashSet<&'a OsStr>, ) { let mut comp = path.components().rev().peekable(); let _ = comp.next(); if path.ends_with("mod.rs") { - mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default()); + mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); } - let folders = comp - .filter_map(|c| { - if let Component::Normal(s) = c { - Some(s.to_os_string()) - } else { - None - } - }) - .collect::>(); + let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None }); folder_segments.extend(folders); } diff --git a/clippy_lints/src/modulo_arithmetic.rs b/clippy_lints/src/modulo_arithmetic.rs index d182a7d5249..195b2e5c2ee 100644 --- a/clippy_lints/src/modulo_arithmetic.rs +++ b/clippy_lints/src/modulo_arithmetic.rs @@ -4,7 +4,7 @@ use clippy_utils::sext; use if_chain::if_chain; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use std::fmt::Display; @@ -77,7 +77,7 @@ fn floating_point_operand_info>(f: &T) -> Op } } -fn might_have_negative_value(t: &ty::TyS<'_>) -> bool { +fn might_have_negative_value(t: Ty<'_>) -> bool { t.is_signed() || t.is_floating_point() } diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs deleted file mode 100644 index 1f9db39cf8c..00000000000 --- a/clippy_lints/src/multiple_crate_versions.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! lint on multiple versions of a crate being used - -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_lint_allowed; -use rustc_hir::def_id::LOCAL_CRATE; -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; - -use cargo_metadata::{DependencyKind, Node, Package, PackageId}; -use if_chain::if_chain; -use itertools::Itertools; - -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_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]); - -impl LateLintPass<'_> for MultipleCrateVersions { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, MULTIPLE_CRATE_VERSIONS, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true); - let local_name = cx.tcx.crate_name(LOCAL_CRATE); - let mut packages = metadata.packages; - 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)) -} diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 1bdd805f658..cba54e14212 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -113,7 +113,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir:: let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); let fn_sig = cx.tcx.fn_sig(fn_def_id); for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) { - check_ty(cx, hir_ty.span, ty); + check_ty(cx, hir_ty.span, *ty); } check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output())); } @@ -125,7 +125,7 @@ fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) { if let Adt(def, substs) = ty.kind() { let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet] .iter() - .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did)); + .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did())); if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) { span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); } @@ -142,7 +142,7 @@ fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Sp size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_interior_mutable_type(cx, inner_ty, span) }, - Tuple(..) => ty.tuple_fields().any(|ty| is_interior_mutable_type(cx, ty, span)), + Tuple(fields) => fields.iter().any(|ty| is_interior_mutable_type(cx, ty, span)), Adt(def, substs) => { // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to // that of their type parameters. Note: we don't include `HashSet` and `HashMap` @@ -159,8 +159,8 @@ fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Sp sym::Arc, ] .iter() - .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did)); - let is_box = Some(def.did) == cx.tcx.lang_items().owned_box(); + .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did())); + let is_box = Some(def.did()) == cx.tcx.lang_items().owned_box(); if is_std_collection || is_box { // The type is mutable if any of its type parameters are substs.types().any(|ty| is_interior_mutable_type(cx, ty, span)) diff --git a/clippy_lints/src/mut_mutex_lock.rs b/clippy_lints/src/mut_mutex_lock.rs index 7871be41d62..b7f981faa2d 100644 --- a/clippy_lints/src/mut_mutex_lock.rs +++ b/clippy_lints/src/mut_mutex_lock.rs @@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for MutMutexLock { if path.ident.name == sym!(lock); let ty = cx.typeck_results().expr_ty(self_arg); if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); - if is_type_diagnostic_item(cx, inner_ty, sym::Mutex); + if is_type_diagnostic_item(cx, *inner_ty, sym::Mutex); then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 5c3e505c06c..9d8f8999ce4 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { ExprKind::MethodCall(path, arguments, _) => { let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap(); let substs = cx.typeck_results().node_substs(e.hir_id); - let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs); + let method_type = cx.tcx.bound_type_of(def_id).subst(cx.tcx, substs); check_arguments(cx, arguments, method_type, path.ident.as_str(), "method"); }, _ => (), diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 4ba68c8eacd..8dba60f3a58 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -16,7 +16,7 @@ declare_clippy_lint! { /// ### Why is this bad? /// In release builds `debug_assert!` macros are optimized out by the /// compiler. - /// Therefore mutating something in a `debug_assert!` macro results in different behaviour + /// Therefore mutating something in a `debug_assert!` macro results in different behavior /// between a release and debug build. /// /// ### Example diff --git a/clippy_lints/src/needless_bitwise_bool.rs b/clippy_lints/src/needless_bitwise_bool.rs index a8a8d174a82..623d22bc9bd 100644 --- a/clippy_lints/src/needless_bitwise_bool.rs +++ b/clippy_lints/src/needless_bitwise_bool.rs @@ -53,7 +53,7 @@ fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { false } -fn suggession_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { +fn suggestion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { if let ExprKind::Binary(ref op, left, right) = expr.kind { if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) { let op_snippet = match op.node { @@ -75,7 +75,7 @@ impl LateLintPass<'_> for NeedlessBitwiseBool { expr.span, "use of bitwise operator instead of lazy operator between booleans", |diag| { - if let Some(sugg) = suggession_snippet(cx, expr) { + if let Some(sugg) = suggestion_snippet(cx, expr) { diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable); } }, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 9957afcbf04..b70871b38be 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -1,10 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::path_to_local; use clippy_utils::source::snippet_opt; -use clippy_utils::visitors::{expr_visitor, is_local_used}; -use rustc_errors::Applicability; +use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::visitors::{expr_visitor, expr_visitor_no_bodies, is_local_used}; +use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::intravisit::Visitor; -use rustc_hir::{Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind}; +use rustc_hir::{ + BindingAnnotation, Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, + StmtKind, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; @@ -73,6 +77,31 @@ fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> seen } +fn contains_let(cond: &Expr<'_>) -> bool { + let mut seen = false; + expr_visitor_no_bodies(|expr| { + if let ExprKind::Let(_) = expr.kind { + seen = true; + } + + !seen + }) + .visit_expr(cond); + + seen +} + +fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { + let StmtKind::Local(local) = stmt.kind else { return false }; + !local.pat.walk_short(|pat| { + if let PatKind::Binding(.., None) = pat.kind { + !needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat)) + } else { + true + } + }) +} + #[derive(Debug)] struct LocalAssign { lhs_id: HirId, @@ -187,11 +216,14 @@ fn first_usage<'tcx>( local_stmt_id: HirId, block: &'tcx Block<'tcx>, ) -> Option> { + let significant_drop = needs_ordered_drop(cx, cx.typeck_results().node_type(binding_id)); + block .stmts .iter() .skip_while(|stmt| stmt.hir_id != local_stmt_id) .skip(1) + .take_while(|stmt| !significant_drop || !stmt_needs_ordered_drop(cx, stmt)) .find(|&stmt| is_local_used(cx, stmt, binding_id)) .and_then(|stmt| match stmt.kind { StmtKind::Expr(expr) => Some(Usage { @@ -235,12 +267,15 @@ fn check<'tcx>( match usage.expr.kind { ExprKind::Assign(..) => { let assign = LocalAssign::new(cx, usage.expr, binding_id)?; + let mut msg_span = MultiSpan::from_spans(vec![local_stmt.span, assign.span]); + msg_span.push_span_label(local_stmt.span, "created here"); + msg_span.push_span_label(assign.span, "initialised here"); span_lint_and_then( cx, NEEDLESS_LATE_INIT, - local_stmt.span, - "unneeded late initalization", + msg_span, + "unneeded late initialization", |diag| { diag.tool_only_span_suggestion( local_stmt.span, @@ -258,14 +293,14 @@ fn check<'tcx>( }, ); }, - ExprKind::If(_, then_expr, Some(else_expr)) => { + ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => { let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?; span_lint_and_then( cx, NEEDLESS_LATE_INIT, local_stmt.span, - "unneeded late initalization", + "unneeded late initialization", |diag| { diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability); @@ -296,7 +331,7 @@ fn check<'tcx>( cx, NEEDLESS_LATE_INIT, local_stmt.span, - "unneeded late initalization", + "unneeded late initialization", |diag| { diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability); @@ -333,12 +368,11 @@ fn check<'tcx>( impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { let mut parents = cx.tcx.hir().parent_iter(local.hir_id); - if_chain! { if let Local { init: None, pat: &Pat { - kind: PatKind::Binding(_, binding_id, _, None), + kind: PatKind::Binding(BindingAnnotation::Unannotated, binding_id, _, None), .. }, source: LocalSource::Normal, diff --git a/clippy_lints/src/needless_option_as_deref.rs b/clippy_lints/src/needless_option_as_deref.rs deleted file mode 100644 index 21d8263390a..00000000000 --- a/clippy_lints/src/needless_option_as_deref.rs +++ /dev/null @@ -1,66 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_opt; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyS; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for no-op uses of Option::{as_deref,as_deref_mut}, - /// for example, `Option<&T>::as_deref()` returns the same type. - /// - /// ### Why is this bad? - /// Redundant code and improving readability. - /// - /// ### Example - /// ```rust - /// let a = Some(&1); - /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> - /// ``` - /// Could be written as: - /// ```rust - /// let a = Some(&1); - /// let b = a; - /// ``` - #[clippy::version = "1.57.0"] - pub NEEDLESS_OPTION_AS_DEREF, - complexity, - "no-op use of `deref` or `deref_mut` method to `Option`." -} - -declare_lint_pass!(OptionNeedlessDeref=> [ - NEEDLESS_OPTION_AS_DEREF, -]); - -impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - let typeck = cx.typeck_results(); - let outer_ty = typeck.expr_ty(expr); - - if_chain! { - if is_type_diagnostic_item(cx,outer_ty,sym::Option); - if let ExprKind::MethodCall(path, [sub_expr], _) = expr.kind; - let symbol = path.ident.as_str(); - if symbol == "as_deref" || symbol == "as_deref_mut"; - if TyS::same_type( outer_ty, typeck.expr_ty(sub_expr) ); - then{ - span_lint_and_sugg( - cx, - NEEDLESS_OPTION_AS_DEREF, - expr.span, - "derefed type is same as origin", - "try this", - snippet_opt(cx,sub_expr.span).unwrap(), - Applicability::MachineApplicable - ); - } - } - } -} diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index ebd4fb0bf51..38960103d5e 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -6,7 +6,7 @@ use clippy_utils::{get_trait_def_id, is_self, paths}; use if_chain::if_chain; use rustc_ast::ast::Attribute; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_errors::{Applicability, Diagnostic}; use rustc_hir::intravisit::FnKind; use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind}; use rustc_hir::{HirIdMap, HirIdSet}; @@ -70,7 +70,7 @@ macro_rules! need { } impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn check_fn( &mut self, cx: &LateContext<'tcx>, @@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { } match kind { - FnKind::ItemFn(.., header, _) => { + FnKind::ItemFn(.., header) => { let attrs = cx.tcx.hir().attrs(hir_id); if header.abi != Abi::Rust || requires_exact_signature(attrs) { return; @@ -196,10 +196,15 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { } // Dereference suggestion - let sugg = |diag: &mut DiagnosticBuilder<'_>| { + let sugg = |diag: &mut Diagnostic| { if let ty::Adt(def, ..) = ty.kind() { - if let Some(span) = cx.tcx.hir().span_if_local(def.did) { - if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() { + if let Some(span) = cx.tcx.hir().span_if_local(def.did()) { + if can_type_implement_copy( + cx.tcx, + cx.param_env, + ty, + traits::ObligationCause::dummy_with_span(span), + ).is_ok() { diag.span_help(span, "consider marking this type as `Copy`"); } } @@ -230,12 +235,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - &snippet_opt(cx, span) + snippet_opt(cx, span) .map_or( "change the call to".into(), |x| Cow::from(format!("change `{}` to", x)), - ), - suggestion.into(), + ) + .as_ref(), + suggestion, Applicability::Unspecified, ); } @@ -259,12 +265,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - &snippet_opt(cx, span) + snippet_opt(cx, span) .map_or( "change the call to".into(), |x| Cow::from(format!("change `{}` to", x)) - ), - suggestion.into(), + ) + .as_ref(), + suggestion, Applicability::Unspecified, ); } diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index d4c823d1c1a..8f85b00596c 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -6,7 +6,6 @@ use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyS; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -128,7 +127,7 @@ fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { if expr.span.ctxt() == inner_expr.span.ctxt(); let expr_ty = cx.typeck_results().expr_ty(expr); let inner_ty = cx.typeck_results().expr_ty(inner_expr); - if TyS::same_type(expr_ty, inner_ty); + if expr_ty == inner_ty; then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index ed315efaa2f..c87c174ef73 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate { let ty = cx.typeck_results().expr_ty(expr); if let ty::Adt(def, _) = ty.kind() { if fields.len() == def.non_enum_variant().fields.len() - && !def.variants[0_usize.into()].is_field_list_non_exhaustive() + && !def.variant(0_usize.into()).is_field_list_non_exhaustive() { span_lint( cx, diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 0d05c83ffe4..707f3b2181a 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -34,7 +34,6 @@ declare_clippy_lint! { declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]); -#[allow(clippy::match_same_arms)] impl<'tcx> LateLintPass<'tcx> for NegMultiply { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Binary(ref op, left, right) = e.kind { @@ -53,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for NegMultiply { fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { if_chain! { if let ExprKind::Lit(ref l) = lit.kind; - if consts::lit_to_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1); + if consts::lit_to_mir_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1); if cx.typeck_results().expr_ty(exp).is_integral(); then { diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index aec95530bba..093ec389335 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,20 +1,19 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::return_ty; use clippy_utils::source::snippet; -use clippy_utils::sugg::DiagnosticBuilderExt; +use clippy_utils::sugg::DiagnosticExt; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::HirIdSet; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::TyS; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Checks for types with a `fn new() -> Self` method and no + /// Checks for public types with a `pub fn new() -> Self` method and no /// implementation of /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). /// @@ -25,10 +24,10 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// struct Foo(Bar); + /// pub struct Foo(Bar); /// /// impl Foo { - /// fn new() -> Self { + /// pub fn new() -> Self { /// Foo(Bar::new()) /// } /// } @@ -37,7 +36,7 @@ declare_clippy_lint! { /// To fix the lint, add a `Default` implementation that delegates to `new`: /// /// ```ignore - /// struct Foo(Bar); + /// pub struct Foo(Bar); /// /// impl Default for Foo { /// fn default() -> Self { @@ -48,7 +47,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub NEW_WITHOUT_DEFAULT, style, - "`fn new() -> Self` method without `Default` implementation" + "`pub fn new() -> Self` method without `Default` implementation" } #[derive(Clone, Default)] @@ -59,17 +58,16 @@ pub struct NewWithoutDefault { impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { - #[allow(clippy::too_many_lines)] fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { if let hir::ItemKind::Impl(hir::Impl { of_trait: None, - ref generics, + generics, self_ty: impl_self_ty, items, .. }) = item.kind { - for assoc_item in items { + for assoc_item in *items { if assoc_item.kind == (hir::AssocItemKind::Fn { has_self: false }) { let impl_item = cx.tcx.hir().impl_item(assoc_item.id); if in_external_macro(cx.sess(), impl_item.span) { @@ -86,6 +84,10 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // can't be implemented for unsafe new return; } + if cx.tcx.is_doc_hidden(impl_item.def_id) { + // shouldn't be implemented when it is hidden in docs + return; + } if impl_item .generics .params @@ -103,14 +105,14 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { if cx.access_levels.is_reachable(impl_item.def_id); let self_def_id = cx.tcx.hir().get_parent_item(id); let self_ty = cx.tcx.type_of(self_def_id); - if TyS::same_type(self_ty, return_ty(cx, id)); + if self_ty == return_ty(cx, id); if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); then { if self.impling_types.is_none() { let mut impls = HirIdSet::default(); cx.tcx.for_each_impl(default_trait_id, |d| { if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { - if let Some(local_def_id) = ty_def.did.as_local() { + if let Some(local_def_id) = ty_def.did().as_local() { impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id)); } } @@ -123,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { if_chain! { if let Some(ref impling_types) = self.impling_types; if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def(); - if let Some(self_local_did) = self_def.did.as_local(); + if let Some(self_local_did) = self_def.did().as_local(); let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did); if impling_types.contains(&self_id); then { diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 21ac6548b01..8db41ba6ee2 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -136,14 +136,14 @@ fn is_value_unfrozen_raw<'tcx>( result: Result, ErrorHandled>, ty: Ty<'tcx>, ) -> bool { - fn inner<'tcx>(cx: &LateContext<'tcx>, val: &'tcx Const<'tcx>) -> bool { - match val.ty.kind() { + fn inner<'tcx>(cx: &LateContext<'tcx>, val: Const<'tcx>) -> bool { + match val.ty().kind() { // the fact that we have to dig into every structs to search enums // leads us to the point checking `UnsafeCell` directly is the only option. - ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true, + ty::Adt(ty_def, ..) if Some(ty_def.did()) == cx.tcx.lang_items().unsafe_cell_type() => true, ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => { let val = cx.tcx.destructure_const(cx.param_env.and(val)); - val.fields.iter().any(|field| inner(cx, field)) + val.fields.iter().any(|field| inner(cx, *field)) }, _ => false, } diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 0d0c88b02c7..7f6b535c7b1 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -191,13 +191,13 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { } } - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn check_ident(&mut self, ident: Ident) { let interned_name = ident.name.as_str(); if interned_name.chars().any(char::is_uppercase) { return; } - if interned_name.chars().all(|c| c.is_digit(10) || c == '_') { + if interned_name.chars().all(|c| c.is_ascii_digit() || c == '_') { span_lint( self.0.cx, JUST_UNDERSCORES_AND_DIGITS, diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index ab1559c85d8..ddef7352de8 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { let mut non_send_fields = Vec::new(); let hir_map = cx.tcx.hir(); - for variant in &adt_def.variants { + for variant in adt_def.variants() { for field in &variant.fields { if_chain! { if let Some(field_hir_id) = field @@ -202,10 +202,10 @@ fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'t // The type is known to be `!Send` and `!Copy` match ty.kind() { - ty::Tuple(_) => ty - .tuple_fields() + ty::Tuple(fields) => fields + .iter() .all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)), - ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait), + ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, *ty, send_trait), ty::Adt(_, substs) => { if contains_pointer_like(cx, ty) { // descends only if ADT contains any raw pointers @@ -233,7 +233,7 @@ fn contains_pointer_like<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> b return true; }, ty::Adt(adt_def, _) => { - if match_def_path(cx, adt_def.did, &paths::PTR_NON_NULL) { + if match_def_path(cx, adt_def.did(), &paths::PTR_NON_NULL) { return true; } }, diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index c19cea66104..e8532db4f71 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -25,7 +25,7 @@ declare_clippy_lint! { /// /// ### Known problems /// The actual meaning can be the intended one. `\x00` can be used in these - /// cases to be unambigious. + /// cases to be unambiguous. /// /// The lint does not trigger for format strings in `print!()`, `write!()` /// and friends since the string is already preprocessed when Clippy lints diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs new file mode 100644 index 00000000000..d66698f8adc --- /dev/null +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -0,0 +1,660 @@ +use std::collections::VecDeque; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_lint_allowed; +use itertools::{izip, Itertools}; +use rustc_ast::{walk_list, Label, Mutability}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; +use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor}; +use rustc_hir::{ + Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment, + QPath, Stmt, StmtKind, TyKind, UnOp, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::{Ty, TyCtxt, TypeckResults}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for arguments that are only used in recursion with no side-effects. + /// + /// ### Why is this bad? + /// It could contain a useless calculation and can make function simpler. + /// + /// The arguments can be involved in calculations and assignments but as long as + /// the calculations have no side-effects (function calls or mutating dereference) + /// and the assigned variables are also only in recursion, it is useless. + /// + /// ### Known problems + /// Too many code paths in the linting code are currently untested and prone to produce false + /// positives or are prone to have performance implications. + /// + /// In some cases, this would not catch all useless arguments. + /// + /// ```rust + /// fn foo(a: usize, b: usize) -> usize { + /// let f = |x| x + 1; + /// + /// if a == 0 { + /// 1 + /// } else { + /// foo(a - 1, f(b)) + /// } + /// } + /// ``` + /// + /// For example, the argument `b` is only used in recursion, but the lint would not catch it. + /// + /// List of some examples that can not be caught: + /// - binary operation of non-primitive types + /// - closure usage + /// - some `break` relative operations + /// - struct pattern binding + /// + /// Also, when you recurse the function name with path segments, it is not possible to detect. + /// + /// ### Example + /// ```rust + /// fn f(a: usize, b: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1, b + 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1, 1)); + /// # } + /// ``` + /// Use instead: + /// ```rust + /// fn f(a: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1)); + /// # } + /// ``` + #[clippy::version = "1.60.0"] + pub ONLY_USED_IN_RECURSION, + nursery, + "arguments that is only used in recursion can be removed" +} +declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); + +impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx rustc_hir::FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + id: HirId, + ) { + if is_lint_allowed(cx, ONLY_USED_IN_RECURSION, id) { + return; + } + if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind { + let def_id = id.owner.to_def_id(); + let data = cx.tcx.def_path(def_id).data; + + if data.len() > 1 { + match data.get(data.len() - 2) { + Some(DisambiguatedDefPathData { + data: DefPathData::Impl, + disambiguator, + }) if *disambiguator != 0 => return, + _ => {}, + } + } + + let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None); + + let ty_res = cx.typeck_results(); + let param_span = body + .params + .iter() + .flat_map(|param| { + let mut v = Vec::new(); + param.pat.each_binding(|_, hir_id, span, ident| { + v.push((hir_id, span, ident)); + }); + v + }) + .skip(if has_self { 1 } else { 0 }) + .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_')) + .collect_vec(); + + let params = body.params.iter().map(|param| param.pat).collect(); + + let mut visitor = SideEffectVisit { + graph: FxHashMap::default(), + has_side_effect: FxHashSet::default(), + ret_vars: Vec::new(), + contains_side_effect: false, + break_vars: FxHashMap::default(), + params, + fn_ident: ident, + fn_def_id: def_id, + is_method: matches!(kind, FnKind::Method(..)), + has_self, + ty_res, + tcx: cx.tcx, + visited_exprs: FxHashSet::default(), + }; + + visitor.visit_expr(&body.value); + let vars = std::mem::take(&mut visitor.ret_vars); + // this would set the return variables to side effect + visitor.add_side_effect(vars); + + let mut queue = visitor.has_side_effect.iter().copied().collect::>(); + + // a simple BFS to check all the variables that have side effect + while let Some(id) = queue.pop_front() { + if let Some(next) = visitor.graph.get(&id) { + for i in next { + if !visitor.has_side_effect.contains(i) { + visitor.has_side_effect.insert(*i); + queue.push_back(*i); + } + } + } + } + + for (id, span, ident) in param_span { + // if the variable is not used in recursion, it would be marked as unused + if !visitor.has_side_effect.contains(&id) { + let mut queue = VecDeque::new(); + let mut visited = FxHashSet::default(); + + queue.push_back(id); + + // a simple BFS to check the graph can reach to itself + // if it can't, it means the variable is never used in recursion + while let Some(id) = queue.pop_front() { + if let Some(next) = visitor.graph.get(&id) { + for i in next { + if !visited.contains(i) { + visited.insert(id); + queue.push_back(*i); + } + } + } + } + + if visited.contains(&id) { + span_lint_and_sugg( + cx, + ONLY_USED_IN_RECURSION, + span, + "parameter is only used in recursion", + "if this is intentional, prefix with an underscore", + format!("_{}", ident.name.as_str()), + Applicability::MaybeIncorrect, + ); + } + } + } + } + } +} + +pub fn is_primitive(ty: Ty<'_>) -> bool { + let ty = ty.peel_refs(); + ty.is_primitive() || ty.is_str() +} + +pub fn is_array(ty: Ty<'_>) -> bool { + let ty = ty.peel_refs(); + ty.is_array() || ty.is_array_slice() +} + +/// This builds the graph of side effect. +/// The edge `a -> b` means if `a` has side effect, `b` will have side effect. +/// +/// There are some example in following code: +/// ```rust, ignore +/// let b = 1; +/// let a = b; // a -> b +/// let (c, d) = (a, b); // c -> b, d -> b +/// +/// let e = if a == 0 { // e -> a +/// c // e -> c +/// } else { +/// d // e -> d +/// }; +/// ``` +pub struct SideEffectVisit<'tcx> { + graph: FxHashMap>, + has_side_effect: FxHashSet, + // bool for if the variable was dereferenced from mutable reference + ret_vars: Vec<(HirId, bool)>, + contains_side_effect: bool, + // break label + break_vars: FxHashMap>, + params: Vec<&'tcx Pat<'tcx>>, + fn_ident: Ident, + fn_def_id: DefId, + is_method: bool, + has_self: bool, + ty_res: &'tcx TypeckResults<'tcx>, + tcx: TyCtxt<'tcx>, + visited_exprs: FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> { + fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { + match s.kind { + StmtKind::Local(Local { + pat, init: Some(init), .. + }) => { + self.visit_pat_expr(pat, init, false); + }, + StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => { + walk_stmt(self, s); + }, + StmtKind::Local(_) => {}, + } + self.ret_vars.clear(); + } + + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + if !self.visited_exprs.insert(ex.hir_id) { + return; + } + match ex.kind { + ExprKind::Array(exprs) | ExprKind::Tup(exprs) => { + self.ret_vars = exprs + .iter() + .flat_map(|expr| { + self.visit_expr(expr); + std::mem::take(&mut self.ret_vars) + }) + .collect(); + }, + ExprKind::Call(callee, args) => self.visit_fn(callee, args), + ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args), + ExprKind::Binary(_, lhs, rhs) => { + self.visit_bin_op(lhs, rhs); + }, + ExprKind::Unary(op, expr) => self.visit_un_op(op, expr), + ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false), + ExprKind::If(bind, then_expr, else_expr) => { + self.visit_if(bind, then_expr, else_expr); + }, + ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms), + // since analysing the closure is not easy, just set all variables in it to side-effect + ExprKind::Closure(_, _, body_id, _, _) => { + let body = self.tcx.hir().body(body_id); + self.visit_body(body); + let vars = std::mem::take(&mut self.ret_vars); + self.add_side_effect(vars); + }, + ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => { + self.visit_block_label(block, label); + }, + ExprKind::Assign(bind, expr, _) => { + self.visit_assign(bind, expr); + }, + ExprKind::AssignOp(_, bind, expr) => { + self.visit_assign(bind, expr); + self.visit_bin_op(bind, expr); + }, + ExprKind::Field(expr, _) => { + self.visit_expr(expr); + if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { + self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + } + }, + ExprKind::Index(expr, index) => { + self.visit_expr(expr); + let mut vars = std::mem::take(&mut self.ret_vars); + self.visit_expr(index); + self.ret_vars.append(&mut vars); + + if !is_array(self.ty_res.expr_ty(expr)) { + self.add_side_effect(self.ret_vars.clone()); + } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { + self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + } + }, + ExprKind::Break(dest, Some(expr)) => { + self.visit_expr(expr); + if let Some(label) = dest.label { + self.break_vars + .entry(label.ident) + .or_insert(Vec::new()) + .append(&mut self.ret_vars); + } + self.contains_side_effect = true; + }, + ExprKind::Ret(Some(expr)) => { + self.visit_expr(expr); + let vars = std::mem::take(&mut self.ret_vars); + self.add_side_effect(vars); + self.contains_side_effect = true; + }, + ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => { + self.contains_side_effect = true; + }, + ExprKind::Struct(_, exprs, expr) => { + let mut ret_vars = exprs + .iter() + .flat_map(|field| { + self.visit_expr(field.expr); + std::mem::take(&mut self.ret_vars) + }) + .collect(); + + walk_list!(self, visit_expr, expr); + self.ret_vars.append(&mut ret_vars); + }, + _ => walk_expr(self, ex), + } + } + + fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) { + if let Res::Local(id) = path.res { + self.ret_vars.push((id, false)); + } + } +} + +impl<'tcx> SideEffectVisit<'tcx> { + fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) { + // Just support array and tuple unwrapping for now. + // + // ex) `(a, b) = (c, d);` + // The graph would look like this: + // a -> c + // b -> d + // + // This would minimize the connection of the side-effect graph. + match (&lhs.kind, &rhs.kind) { + (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => { + // if not, it is a compile error + debug_assert!(lhs.len() == rhs.len()); + izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs)); + }, + // in other assigns, we have to connect all each other + // because they can be connected somehow + _ => { + self.visit_expr(lhs); + let lhs_vars = std::mem::take(&mut self.ret_vars); + self.visit_expr(rhs); + let rhs_vars = std::mem::take(&mut self.ret_vars); + self.connect_assign(&lhs_vars, &rhs_vars, false); + }, + } + } + + fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option