diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index a8202f6378fd..6ad16aead601 100644 --- a/.github/ISSUE_TEMPLATE/new_lint.yml +++ b/.github/ISSUE_TEMPLATE/new_lint.yml @@ -1,7 +1,5 @@ name: New lint suggestion -description: | - Suggest a new Clippy lint (currently not accepting new lints) - Check out the Clippy book for more information about the feature freeze. +description: Suggest a new Clippy lint. labels: ["A-lint"] body: - type: markdown diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 83bfd8e9c686..9e49f60892d2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,10 +32,6 @@ order to get feedback. Delete this line and everything above before opening your PR. -Note that we are currently not taking in new PRs that add new lints. We are in a -feature freeze. Check out the book for more information. If you open a -feature-adding pull request, its review will be delayed. - --- *Please write a short comment explaining your change (or "none" for internal only changes)* diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 0bcb71359359..9d099137449e 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -181,7 +181,7 @@ jobs: # Download - name: Download target dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: binaries path: target/debug diff --git a/.github/workflows/feature_freeze.yml b/.github/workflows/feature_freeze.yml deleted file mode 100644 index 5b139e767007..000000000000 --- a/.github/workflows/feature_freeze.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Feature freeze check - -on: - pull_request_target: - types: - - opened - branches: - - master - paths: - - 'clippy_lints/src/declared_lints.rs' - -jobs: - auto-comment: - runs-on: ubuntu-latest - - permissions: - pull-requests: write - - # Do not in any case add code that runs anything coming from the content - # of the pull request, as malicious code would be able to access the private - # GitHub token. - steps: - - name: Add freeze warning comment - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - COMMENT=$(echo "**Seems that you are trying to add a new lint!**\n\ - \n\ - We are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and [focusing on bugfixes](https://github.com/rust-lang/rust-clippy/issues/15086).\n\ - \n\ - Thanks a lot for your contribution, and sorry for the inconvenience.\n\ - \n\ - With ❤ from the Clippy team.\n\ - \n\ - @rustbot note Feature-freeze\n\ - @rustbot blocked\n\ - @rustbot label +A-lint" - ) - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Content-Type: application/vnd.github.raw+json" \ - -X POST \ - --data "{\"body\":\"${COMMENT}\"}" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 390d6a0f7475..45fd10ae7614 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -126,7 +126,7 @@ jobs: fail-on-cache-miss: true - name: Download JSON - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 - name: Store PR number run: echo ${{ github.event.pull_request.number }} > pr.txt diff --git a/.github/workflows/lintcheck_summary.yml b/.github/workflows/lintcheck_summary.yml index 52f52e155a07..6768cd65701a 100644 --- a/.github/workflows/lintcheck_summary.yml +++ b/.github/workflows/lintcheck_summary.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: summary path: untrusted @@ -35,7 +35,7 @@ jobs: github-token: ${{ github.token }} - name: Format comment - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const fs = require("fs"); diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index c9d350ee0b30..c2cc48ab9511 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -17,9 +17,9 @@ jobs: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' - name: Install remark run: npm install remark-cli remark-lint remark-lint-maximum-line-length@^3.1.3 remark-preset-lint-recommended remark-gfm diff --git a/.gitignore b/.gitignore index 36a4cdc1c352..666c4ceac4db 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ helper.txt # mdbook generated output /book/book + +# Remove jujutsu directory from search tools +.jj diff --git a/CHANGELOG.md b/CHANGELOG.md index eb2a76a81836..6cb2755be0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,235 @@ document. ## Unreleased / Beta / In Rust Nightly -[4ef75291...master](https://github.com/rust-lang/rust-clippy/compare/4ef75291...master) +[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master) + +## Rust 1.91 + +Current stable, released 2025-10-30 + +[View all 146 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-07-25T21%3A05%3A11Z..2025-09-04T22%3A34%3A27Z+base%3Amaster) + +### New Lints + +* Added [`possible_missing_else`] to `suspicious` + [#15317](https://github.com/rust-lang/rust-clippy/pull/15317) + +### Moves and Deprecations + +* Moved [`cognitive_complexity`] from `nursery` to `restriction` + [#15415](https://github.com/rust-lang/rust-clippy/pull/15415) +* Moved [`declare_interior_mutable_const`] from `style` to `suspicious` + [#15454](https://github.com/rust-lang/rust-clippy/pull/15454) +* Moved [`crosspointer_transmute`] from `complexity` to `suspicious` + [#15403](https://github.com/rust-lang/rust-clippy/pull/15403) + +### Enhancements + +* [`excessive_precision`] added `const_literal_digits_threshold` option to suppress overly precise constants. + [#15193](https://github.com/rust-lang/rust-clippy/pull/15193) +* [`unwrap_in_result`] rewritten for better accuracy; now lints on `.unwrap()` and `.expect()` + directly and no longer mixes `Result` and `Option`. + [#15445](https://github.com/rust-lang/rust-clippy/pull/15445) +* [`panic`] now works in `const` contexts. + [#15565](https://github.com/rust-lang/rust-clippy/pull/15565) +* [`implicit_clone`] now also lints `to_string` calls (merging [`string_to_string`] behavior). + [#14177](https://github.com/rust-lang/rust-clippy/pull/14177) +* [`collapsible_match`] improved suggestions to handle necessary ref/dereferencing. + [#14221](https://github.com/rust-lang/rust-clippy/pull/14221) +* [`map_identity`] now suggests making variables mutable when required; recognizes tuple struct restructuring. + [#15261](https://github.com/rust-lang/rust-clippy/pull/15261) +* [`option_map_unit_fn`] preserves `unsafe` blocks in suggestions. + [#15570](https://github.com/rust-lang/rust-clippy/pull/15570) +* [`unnecessary_mut_passed`] provides structured, clearer fix suggestions. + [#15438](https://github.com/rust-lang/rust-clippy/pull/15438) +* [`float_equality_without_abs`] now checks `f16` and `f128` types. + [#15054](https://github.com/rust-lang/rust-clippy/pull/15054) +* [`doc_markdown`] expanded whitelist (`InfiniBand`, `RoCE`, `PowerPC`) and improved handling of + identifiers like NixOS. + [#15558](https://github.com/rust-lang/rust-clippy/pull/15558) +* [`clone_on_ref_ptr`] now suggests fully qualified paths to avoid resolution errors. + [#15561](https://github.com/rust-lang/rust-clippy/pull/15561) +* [`manual_assert`] simplifies boolean expressions in suggested fixes. + [#15368](https://github.com/rust-lang/rust-clippy/pull/15368) +* [`four_forward_slashes`] warns about bare CR in comments and avoids invalid autofixes. + [#15175](https://github.com/rust-lang/rust-clippy/pull/15175) + +### False Positive Fixes + +* [`alloc_instead_of_core`] fixed FP when `alloc` is an alias + [#15581](https://github.com/rust-lang/rust-clippy/pull/15581) +* [`needless_range_loop`] fixed FP and FN when meeting multidimensional array + [#15486](https://github.com/rust-lang/rust-clippy/pull/15486) +* [`semicolon_inside_block`] fixed FP when attribute over expr is not enabled + [#15476](https://github.com/rust-lang/rust-clippy/pull/15476) +* [`unnested_or_patterns`] fixed FP on structs with only shorthand field patterns + [#15343](https://github.com/rust-lang/rust-clippy/pull/15343) +* [`match_ref_pats`] fixed FP on match scrutinee of never type + [#15474](https://github.com/rust-lang/rust-clippy/pull/15474) +* [`infinite_loop`] fixed FP in async blocks that are not awaited + [#15157](https://github.com/rust-lang/rust-clippy/pull/15157) +* [`iter_on_single_items`] fixed FP on function pointers and let statements + [#15013](https://github.com/rust-lang/rust-clippy/pull/15013) + +### ICE Fixes + +* [`len_zero`] fix ICE when fn len has a return type without generic type params + [#15660](https://github.com/rust-lang/rust-clippy/pull/15660) + +### Documentation Improvements + +* [`cognitive_complexity`] corrected documentation to state lint is in `restriction`, not `nursery` + [#15563](https://github.com/rust-lang/rust-clippy/pull/15563) + +### Performance Improvements + +* [`doc_broken_link`] optimized by 99.77% (12M → 27k instructions) + [#15385](https://github.com/rust-lang/rust-clippy/pull/15385) + +## Rust 1.90 + +Current stable, released 2025-09-18 + +[View all 118 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-06-13T15%3A55%3A04Z..2025-07-25T13%3A24%3A00Z+base%3Amaster) + +Note: This Clippy release does not introduce many new lints and is focused entirely on bug fixes — see +[#15086](https://github.com/rust-lang/rust-clippy/issues/15086) for more details. + +### New Lints + +* Added [`manual_is_multiple_of`] to `complexity` [#14292](https://github.com/rust-lang/rust-clippy/pull/14292) +* Added [`doc_broken_link`] to `pedantic` [#13696](https://github.com/rust-lang/rust-clippy/pull/13696) + +### Moves and Deprecations + +* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default) [#15287](https://github.com/rust-lang/rust-clippy/pull/15287) + +### Enhancements + +* [`or_fun_call`] now lints `Option::get_or_insert`, `Result::map_or`, `Option/Result::and` methods + [#15071](https://github.com/rust-lang/rust-clippy/pull/15071) + [#15073](https://github.com/rust-lang/rust-clippy/pull/15073) + [#15074](https://github.com/rust-lang/rust-clippy/pull/15074) +* [`incompatible_msrv`] now recognizes types exceeding MSRV + [#15296](https://github.com/rust-lang/rust-clippy/pull/15296) +* [`incompatible_msrv`] now checks the right MSRV when in a `const` context + [#15297](https://github.com/rust-lang/rust-clippy/pull/15297) +* [`zero_ptr`] now lints in `const` context as well + [#15152](https://github.com/rust-lang/rust-clippy/pull/15152) +* [`map_identity`],[`flat_map_identity`] now recognizes `|[x, y]| [x, y]` as an identity function + [#15229](https://github.com/rust-lang/rust-clippy/pull/15229) +* [`exit`] no longer fails on the main function when using `--test` or `--all-targets` flag + [#15222](https://github.com/rust-lang/rust-clippy/pull/15222) + +### False Positive Fixes + +* [`if_then_some_else_none`] fixed FP when require type coercion + [#15267](https://github.com/rust-lang/rust-clippy/pull/15267) +* [`module_name_repetitions`] fixed FP on exported macros + [#15319](https://github.com/rust-lang/rust-clippy/pull/15319) +* [`unused_async`] fixed FP on function with `todo!` + [#15308](https://github.com/rust-lang/rust-clippy/pull/15308) +* [`useless_attribute`] fixed FP when using `#[expect(redundant_imports)]` and similar lint attributes + on `use` statements + [#15318](https://github.com/rust-lang/rust-clippy/pull/15318) +* [`pattern_type_mismatch`] fixed FP in external macro + [#15306](https://github.com/rust-lang/rust-clippy/pull/15306) +* [`large_enum_variant`] fixed FP by not suggesting `Box` in `no_std` mode + [#15241](https://github.com/rust-lang/rust-clippy/pull/15241) +* [`missing_inline_in_public_items`] fixed FP on functions with `extern` + [#15313](https://github.com/rust-lang/rust-clippy/pull/15313) +* [`needless_range_loop`] fixed FP on array literals + [#15314](https://github.com/rust-lang/rust-clippy/pull/15314) +* [`ptr_as_ptr`] fixed wrong suggestions with turbo fish + [#15289](https://github.com/rust-lang/rust-clippy/pull/15289) +* [`range_plus_one`], [`range_minus_one`] fixed FP by restricting lint to cases where it is safe + to switch the range type + [#14432](https://github.com/rust-lang/rust-clippy/pull/14432) +* [`ptr_arg`] fixed FP with underscore binding to `&T` or `&mut T` argument + [#15105](https://github.com/rust-lang/rust-clippy/pull/15105) +* [`unsafe_derive_deserialize`] fixed FP caused by the standard library's `pin!()` macro + [#15137](https://github.com/rust-lang/rust-clippy/pull/15137) +* [`unused_trait_names`] fixed FP in macros + [#14947](https://github.com/rust-lang/rust-clippy/pull/14947) +* [`expect_fun_call`] fixed invalid suggestions + [#15122](https://github.com/rust-lang/rust-clippy/pull/15122) +* [`manual_is_multiple_of`] fixed various false positives + [#15205](https://github.com/rust-lang/rust-clippy/pull/15205) +* [`manual_assert`] fixed wrong suggestions for macros + [#15264](https://github.com/rust-lang/rust-clippy/pull/15264) +* [`expect_used`] fixed false negative when meeting `Option::ok().expect` + [#15253](https://github.com/rust-lang/rust-clippy/pull/15253) +* [`manual_abs_diff`] fixed wrong suggestions behind refs + [#15265](https://github.com/rust-lang/rust-clippy/pull/15265) +* [`approx_constant`] fixed FP with overly precise literals and non trivially formatted numerals + [#15236](https://github.com/rust-lang/rust-clippy/pull/15236) +* [`arithmetic_side_effects`] fixed FP on `NonZeroU*.get() - 1` + [#15238](https://github.com/rust-lang/rust-clippy/pull/15238) +* [`legacy_numeric_constants`] fixed suggestion when call is inside parentheses + [#15191](https://github.com/rust-lang/rust-clippy/pull/15191) +* [`manual_is_variant_and`] fixed inverted suggestions that could lead to code with different semantics + [#15206](https://github.com/rust-lang/rust-clippy/pull/15206) +* [`unnecessary_map_or`] fixed FP by not proposing to replace `map_or` call when types wouldn't be correct + [#15181](https://github.com/rust-lang/rust-clippy/pull/15181) +* [`return_and_then`] fixed FP in case of a partially used expression + [#15115](https://github.com/rust-lang/rust-clippy/pull/15115) +* [`manual_let_else`] fixed FP with binding subpattern with unused name + [#15118](https://github.com/rust-lang/rust-clippy/pull/15118) +* [`suboptimal_flops`] fixed FP with ambiguous float types in mul_add suggestions + [#15133](https://github.com/rust-lang/rust-clippy/pull/15133) +* [`borrow_as_ptr`] fixed FP by not removing cast to trait object pointer + [#15145](https://github.com/rust-lang/rust-clippy/pull/15145) +* [`unnecessary_operation`] fixed FP by not removing casts if they are useful to type the expression + [#15182](https://github.com/rust-lang/rust-clippy/pull/15182) +* [`empty_loop`] fixed FP on intrinsic function declaration + [#15201](https://github.com/rust-lang/rust-clippy/pull/15201) +* [`doc_nested_refdefs`] fixed FP where task lists are reported as refdefs + [#15146](https://github.com/rust-lang/rust-clippy/pull/15146) +* [`std_instead_of_core`] fixed FP when not all items come from the new crate + [#15165](https://github.com/rust-lang/rust-clippy/pull/15165) +* [`swap_with_temporary`] fixed FP leading to different semantics being suggested + [#15172](https://github.com/rust-lang/rust-clippy/pull/15172) +* [`cast_possible_truncation`] fixed FP by not giving suggestions inside const context + [#15164](https://github.com/rust-lang/rust-clippy/pull/15164) +* [`missing_panics_doc`] fixed FP by allowing unwrap() and expect()s in const-only contexts + [#15170](https://github.com/rust-lang/rust-clippy/pull/15170) +* [`disallowed_script_idents`] fixed FP on identifiers with `_` + [#15123](https://github.com/rust-lang/rust-clippy/pull/15123) +* [`wildcard_enum_match_arm`] fixed wrong suggestions with raw identifiers + [#15093](https://github.com/rust-lang/rust-clippy/pull/15093) +* [`borrow_deref_ref`] fixed FP by not proposing replacing a reborrow when the reborrow is itself mutably borrowed + [#14967](https://github.com/rust-lang/rust-clippy/pull/14967) +* [`manual_ok_err`] fixed wrong suggestions with references + [#15053](https://github.com/rust-lang/rust-clippy/pull/15053) +* [`identity_op`] fixed FP when encountering `Default::default()` + [#15028](https://github.com/rust-lang/rust-clippy/pull/15028) +* [`exhaustive_structs`] fixed FP on structs with default valued field + [#15022](https://github.com/rust-lang/rust-clippy/pull/15022) +* [`collapsible_else_if`] fixed FP on conditionally compiled stmt + [#14906](https://github.com/rust-lang/rust-clippy/pull/14906) +* [`missing_const_for_fn`] fixed FP by checking MSRV before emitting lint on function containing + non-`Sized` trait bounds + [#15080](https://github.com/rust-lang/rust-clippy/pull/15080) +* [`question_mark`] fixed FP when else branch of let-else contains `#[cfg]` + [#15082](https://github.com/rust-lang/rust-clippy/pull/15082) + +### ICE Fixes + +* [`single_match`] fixed ICE with deref patterns and string literals + [#15124](https://github.com/rust-lang/rust-clippy/pull/15124) +* [`needless_doctest_main`] fixed panic when doctest is invalid + [#15052](https://github.com/rust-lang/rust-clippy/pull/15052) +* [`zero_sized_map_values`] do not attempt to compute size of a type with escaping lifetimes + [#15434](https://github.com/rust-lang/rust-clippy/pull/15434) + +### Documentation Improvements + +* [`manual_is_variant_and`] improved documentation to include equality comparison patterns + [#15239](https://github.com/rust-lang/rust-clippy/pull/15239) +* [`uninlined_format_args`] improved documentation with example of how to fix a `{:?}` parameter + [#15228](https://github.com/rust-lang/rust-clippy/pull/15228) +* [`undocumented_unsafe_blocks`] improved documentation wording + [#15213](https://github.com/rust-lang/rust-clippy/pull/15213) ## Rust 1.89 @@ -147,7 +375,7 @@ Current stable, released 2025-06-26 [#14408](https://github.com/rust-lang/rust-clippy/pull/14408) * [`iter_kv_map`] now recognizes references on maps [#14596](https://github.com/rust-lang/rust-clippy/pull/14596) -* [`empty_enum_variants_with_brackets`] no longer lints reachable enums or enums used +* [`empty_enum_variants_with_brackets`] no longer lints reachable enums or enums used as functions within same crate [#12971](https://github.com/rust-lang/rust-clippy/pull/12971) * [`needless_lifetimes`] now checks for lifetime uses in closures [#14608](https://github.com/rust-lang/rust-clippy/pull/14608) @@ -783,50 +1011,50 @@ Released 2024-02-08 ### New Lints -- [`infinite_loop`] +* [`infinite_loop`] [#11829](https://github.com/rust-lang/rust-clippy/pull/11829) -- [`ineffective_open_options`] +* [`ineffective_open_options`] [#11902](https://github.com/rust-lang/rust-clippy/pull/11902) -- [`uninhabited_references`] +* [`uninhabited_references`] [#11878](https://github.com/rust-lang/rust-clippy/pull/11878) -- [`repeat_vec_with_capacity`] +* [`repeat_vec_with_capacity`] [#11597](https://github.com/rust-lang/rust-clippy/pull/11597) -- [`test_attr_in_doctest`] +* [`test_attr_in_doctest`] [#11872](https://github.com/rust-lang/rust-clippy/pull/11872) -- [`option_map_or_err_ok`] +* [`option_map_or_err_ok`] [#11864](https://github.com/rust-lang/rust-clippy/pull/11864) -- [`join_absolute_paths`] +* [`join_absolute_paths`] [#11453](https://github.com/rust-lang/rust-clippy/pull/11453) -- [`impl_hash_borrow_with_str_and_bytes`] +* [`impl_hash_borrow_with_str_and_bytes`] [#11781](https://github.com/rust-lang/rust-clippy/pull/11781) -- [`iter_over_hash_type`] +* [`iter_over_hash_type`] [#11791](https://github.com/rust-lang/rust-clippy/pull/11791) ### Moves and Deprecations -- Renamed `blocks_in_if_conditions` to [`blocks_in_conditions`] +* Renamed `blocks_in_if_conditions` to [`blocks_in_conditions`] [#11853](https://github.com/rust-lang/rust-clippy/pull/11853) -- Moved [`implied_bounds_in_impls`] to `complexity` (Now warn-by-default) +* Moved [`implied_bounds_in_impls`] to `complexity` (Now warn-by-default) [#11867](https://github.com/rust-lang/rust-clippy/pull/11867) -- Moved [`if_same_then_else`] to `style` (Now warn-by-default) +* Moved [`if_same_then_else`] to `style` (Now warn-by-default) [#11809](https://github.com/rust-lang/rust-clippy/pull/11809) ### Enhancements -- [`missing_safety_doc`], [`unnecessary_safety_doc`], [`missing_panics_doc`], [`missing_errors_doc`]: +* [`missing_safety_doc`], [`unnecessary_safety_doc`], [`missing_panics_doc`], [`missing_errors_doc`]: Added the [`check-private-items`] configuration to enable lints on private items [#11842](https://github.com/rust-lang/rust-clippy/pull/11842) ### ICE Fixes -- [`impl_trait_in_params`]: No longer crashes when a function has generics but no function parameters +* [`impl_trait_in_params`]: No longer crashes when a function has generics but no function parameters [#11804](https://github.com/rust-lang/rust-clippy/pull/11804) -- [`unused_enumerate_index`]: No longer crashes on empty tuples +* [`unused_enumerate_index`]: No longer crashes on empty tuples [#11756](https://github.com/rust-lang/rust-clippy/pull/11756) ### Others -- Clippy now respects the `CARGO` environment value +* Clippy now respects the `CARGO` environment value [#11944](https://github.com/rust-lang/rust-clippy/pull/11944) ## Rust 1.75 @@ -852,7 +1080,6 @@ Released 2023-12-28 * [`manual_hash_one`] [#11556](https://github.com/rust-lang/rust-clippy/pull/11556) - ### Moves and Deprecations * Moved [`read_zero_byte_vec`] to `nursery` (Now allow-by-default) @@ -928,7 +1155,7 @@ Released 2023-11-16 ### Enhancements -* [`undocumented_unsafe_blocks`]: The config values [`accept-comment-above-statement`] and +* [`undocumented_unsafe_blocks`]: The config values [`accept-comment-above-statement`] and [`accept-comment-above-attributes`] are now `true` by default [#11170](https://github.com/rust-lang/rust-clippy/pull/11170) * [`explicit_iter_loop`]: Added [`enforce-iter-loop-reborrow`] to disable reborrow linting by default @@ -2109,7 +2336,6 @@ Released 2022-09-22 * [`explicit_auto_deref`] [#8355](https://github.com/rust-lang/rust-clippy/pull/8355) - ### Moves and Deprecations * Moved [`format_push_string`] to `restriction` (now allow-by-default) @@ -2310,10 +2536,10 @@ Released 2022-08-11 * [`redundant_allocation`]: No longer lints on fat pointers that would become thin pointers [#8813](https://github.com/rust-lang/rust-clippy/pull/8813) * [`derive_partial_eq_without_eq`]: - * Handle differing predicates applied by `#[derive(PartialEq)]` and + * Handle differing predicates applied by `#[derive(PartialEq)]` and `#[derive(Eq)]` [#8869](https://github.com/rust-lang/rust-clippy/pull/8869) - * No longer lints on non-public types and better handles generics + * No longer lints on non-public types and better handles generics [#8950](https://github.com/rust-lang/rust-clippy/pull/8950) * [`empty_line_after_outer_attr`]: No longer lints empty lines in inner string values [#8892](https://github.com/rust-lang/rust-clippy/pull/8892) @@ -2807,12 +3033,12 @@ Released 2022-02-24 [#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 + * 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 + * 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`, @@ -2923,7 +3149,7 @@ Released 2022-02-24 [#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 +* *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 @@ -3133,15 +3359,15 @@ Released 2021-12-02 [#7566](https://github.com/rust-lang/rust-clippy/pull/7566) * [`option_if_let_else`]: Multiple fixes [#7573](https://github.com/rust-lang/rust-clippy/pull/7573) - * `break` and `continue` statements local to the would-be closure are + * `break` and `continue` statements local to the would-be closure are allowed - * Don't lint in const contexts - * Don't lint when yield expressions are used - * Don't lint when the captures made by the would-be closure conflict with + * Don't lint in const contexts + * Don't lint when yield expressions are used + * Don't lint when the captures made by the would-be closure conflict with the other branch - * Don't lint when a field of a local is used when the type could be + * Don't lint when a field of a local is used when the type could be potentially moved from - * In some cases, don't lint when scrutinee expression conflicts with the + * In some cases, don't lint when scrutinee expression conflicts with the captures of the would-be closure * [`redundant_allocation`]: No longer lints on `Box>` which replaces wide pointers with thin pointers @@ -3390,124 +3616,124 @@ Released 2021-07-29 ### New Lints -- [`ref_binding_to_reference`] +* [`ref_binding_to_reference`] [#7105](https://github.com/rust-lang/rust-clippy/pull/7105) -- [`needless_bitwise_bool`] +* [`needless_bitwise_bool`] [#7133](https://github.com/rust-lang/rust-clippy/pull/7133) -- [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225) -- [`manual_str_repeat`] +* [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225) +* [`manual_str_repeat`] [#7265](https://github.com/rust-lang/rust-clippy/pull/7265) -- [`suspicious_splitn`] +* [`suspicious_splitn`] [#7292](https://github.com/rust-lang/rust-clippy/pull/7292) ### Moves and Deprecations -- Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of +* Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of the new `avoid-breaking-exported-api` config option (see [Enhancements](#1-54-enhancements)) [#7187](https://github.com/rust-lang/rust-clippy/pull/7187) -- Move [`inconsistent_struct_constructor`] to `pedantic` +* Move [`inconsistent_struct_constructor`] to `pedantic` [#7193](https://github.com/rust-lang/rust-clippy/pull/7193) -- Move [`needless_borrow`] to `style` (now warn-by-default) +* Move [`needless_borrow`] to `style` (now warn-by-default) [#7254](https://github.com/rust-lang/rust-clippy/pull/7254) -- Move [`suspicious_operation_groupings`] to `nursery` +* Move [`suspicious_operation_groupings`] to `nursery` [#7266](https://github.com/rust-lang/rust-clippy/pull/7266) -- Move [`semicolon_if_nothing_returned`] to `pedantic` +* Move [`semicolon_if_nothing_returned`] to `pedantic` [#7268](https://github.com/rust-lang/rust-clippy/pull/7268) ### Enhancements -- [`while_let_on_iterator`]: Now also lints in nested loops +* [`while_let_on_iterator`]: Now also lints in nested loops [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) -- [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix` +* [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix` [#7156](https://github.com/rust-lang/rust-clippy/pull/7156) -- [`needless_collect`]: Now also lints on assignments with type annotations +* [`needless_collect`]: Now also lints on assignments with type annotations [#7163](https://github.com/rust-lang/rust-clippy/pull/7163) -- [`if_then_some_else_none`]: Now works with the MSRV config +* [`if_then_some_else_none`]: Now works with the MSRV config [#7177](https://github.com/rust-lang/rust-clippy/pull/7177) -- Add `avoid-breaking-exported-api` config option for the lints +* Add `avoid-breaking-exported-api` config option for the lints [`enum_variant_names`], [`large_types_passed_by_value`], [`trivially_copy_pass_by_ref`], [`unnecessary_wraps`], [`upper_case_acronyms`], and [`wrong_self_convention`]. We recommend to set this configuration option to `false` before a major release (1.0/2.0/...) to clean up the API [#7187](https://github.com/rust-lang/rust-clippy/pull/7187) -- [`needless_collect`]: Now lints on even more data structures +* [`needless_collect`]: Now lints on even more data structures [#7188](https://github.com/rust-lang/rust-clippy/pull/7188) -- [`missing_docs_in_private_items`]: No longer sees `#[ = ""]` like +* [`missing_docs_in_private_items`]: No longer sees `#[ = ""]` like attributes as sufficient documentation [#7281](https://github.com/rust-lang/rust-clippy/pull/7281) -- [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]: +* [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]: Now work as expected when used with `allow` [#7282](https://github.com/rust-lang/rust-clippy/pull/7282) ### False Positive Fixes -- [`implicit_return`]: Now takes all diverging functions in account to avoid +* [`implicit_return`]: Now takes all diverging functions in account to avoid false positives [#6951](https://github.com/rust-lang/rust-clippy/pull/6951) -- [`while_let_on_iterator`]: No longer lints when the iterator is a struct field +* [`while_let_on_iterator`]: No longer lints when the iterator is a struct field and the struct is used in the loop [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) -- [`multiple_inherent_impl`]: No longer lints with generic arguments +* [`multiple_inherent_impl`]: No longer lints with generic arguments [#7089](https://github.com/rust-lang/rust-clippy/pull/7089) -- [`comparison_chain`]: No longer lints in a `const` context +* [`comparison_chain`]: No longer lints in a `const` context [#7118](https://github.com/rust-lang/rust-clippy/pull/7118) -- [`while_immutable_condition`]: Fix false positive where mutation in the loop +* [`while_immutable_condition`]: Fix false positive where mutation in the loop variable wasn't picked up [#7144](https://github.com/rust-lang/rust-clippy/pull/7144) -- [`default_trait_access`]: No longer lints in macros +* [`default_trait_access`]: No longer lints in macros [#7150](https://github.com/rust-lang/rust-clippy/pull/7150) -- [`needless_question_mark`]: No longer lints when the inner value is implicitly +* [`needless_question_mark`]: No longer lints when the inner value is implicitly dereferenced [#7165](https://github.com/rust-lang/rust-clippy/pull/7165) -- [`unused_unit`]: No longer lints when multiple macro contexts are involved +* [`unused_unit`]: No longer lints when multiple macro contexts are involved [#7167](https://github.com/rust-lang/rust-clippy/pull/7167) -- [`eval_order_dependence`]: Fix false positive in async context +* [`eval_order_dependence`]: Fix false positive in async context [#7174](https://github.com/rust-lang/rust-clippy/pull/7174) -- [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the +* [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the type [#7175](https://github.com/rust-lang/rust-clippy/pull/7175) -- [`wrong_self_convention`]: No longer lints in trait implementations of +* [`wrong_self_convention`]: No longer lints in trait implementations of non-`Copy` types [#7182](https://github.com/rust-lang/rust-clippy/pull/7182) -- [`suboptimal_flops`]: No longer lints on `powi(2)` +* [`suboptimal_flops`]: No longer lints on `powi(2)` [#7201](https://github.com/rust-lang/rust-clippy/pull/7201) -- [`wrong_self_convention`]: No longer lints if there is no implicit `self` +* [`wrong_self_convention`]: No longer lints if there is no implicit `self` [#7215](https://github.com/rust-lang/rust-clippy/pull/7215) -- [`option_if_let_else`]: No longer lints on `else if let` pattern +* [`option_if_let_else`]: No longer lints on `else if let` pattern [#7216](https://github.com/rust-lang/rust-clippy/pull/7216) -- [`use_self`], [`useless_conversion`]: Fix false positives when generic +* [`use_self`], [`useless_conversion`]: Fix false positives when generic arguments are involved [#7223](https://github.com/rust-lang/rust-clippy/pull/7223) -- [`manual_unwrap_or`]: Fix false positive with deref coercion +* [`manual_unwrap_or`]: Fix false positive with deref coercion [#7233](https://github.com/rust-lang/rust-clippy/pull/7233) -- [`similar_names`]: No longer lints on `wparam`/`lparam` +* [`similar_names`]: No longer lints on `wparam`/`lparam` [#7255](https://github.com/rust-lang/rust-clippy/pull/7255) -- [`redundant_closure`]: No longer lints on using the `vec![]` macro in a +* [`redundant_closure`]: No longer lints on using the `vec![]` macro in a closure [#7263](https://github.com/rust-lang/rust-clippy/pull/7263) ### Suggestion Fixes/Improvements -- [`implicit_return`] +* [`implicit_return`] [#6951](https://github.com/rust-lang/rust-clippy/pull/6951) - - Fix suggestion for async functions - - Improve suggestion with macros - - Suggest to change `break` to `return` when appropriate -- [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary + * Fix suggestion for async functions + * Improve suggestion with macros + * Suggest to change `break` to `return` when appropriate +* [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) -- [`match_single_binding`]: Improve suggestion when match scrutinee has side +* [`match_single_binding`]: Improve suggestion when match scrutinee has side effects [#7095](https://github.com/rust-lang/rust-clippy/pull/7095) -- [`needless_borrow`]: Now suggests to also change usage sites as needed +* [`needless_borrow`]: Now suggests to also change usage sites as needed [#7105](https://github.com/rust-lang/rust-clippy/pull/7105) -- [`write_with_newline`]: Improve suggestion when only `\n` is written to the +* [`write_with_newline`]: Improve suggestion when only `\n` is written to the buffer [#7183](https://github.com/rust-lang/rust-clippy/pull/7183) -- [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also +* [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also when a `<_ as Trait>::_` is involved [#7264](https://github.com/rust-lang/rust-clippy/pull/7264) -- [`not_unsafe_ptr_arg_deref`]: Improved error message +* [`not_unsafe_ptr_arg_deref`]: Improved error message [#7294](https://github.com/rust-lang/rust-clippy/pull/7294) ### ICE Fixes -- Fix ICE when running Clippy on `libstd` +* Fix ICE when running Clippy on `libstd` [#7140](https://github.com/rust-lang/rust-clippy/pull/7140) -- [`implicit_return`] +* [`implicit_return`] [#7242](https://github.com/rust-lang/rust-clippy/pull/7242) ## Rust 1.53 @@ -3552,9 +3778,9 @@ Released 2021-06-17 [#6828](https://github.com/rust-lang/rust-clippy/pull/6828) * [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]: [#6863](https://github.com/rust-lang/rust-clippy/pull/6863) - * Attempt to find a common path prefix in suggestion - * Don't lint on `Option` and `Result` - * Consider `Self` prefix + * Attempt to find a common path prefix in suggestion + * Don't lint on `Option` and `Result` + * Consider `Self` prefix * [`explicit_deref_methods`]: Also lint on chained `deref` calls [#6865](https://github.com/rust-lang/rust-clippy/pull/6865) * [`or_fun_call`]: Also lint on `unsafe` blocks @@ -3814,6 +4040,7 @@ Released 2021-05-06 [#6782](https://github.com/rust-lang/rust-clippy/pull/6782) ### Others + * Running `cargo clippy` after `cargo check` now works as expected (`cargo clippy` and `cargo check` no longer shares the same build cache) [#6687](https://github.com/rust-lang/rust-clippy/pull/6687) @@ -4029,7 +4256,6 @@ Released 2021-02-11 * [`field_reassign_with_default`] No longer lint for private fields [#6537](https://github.com/rust-lang/rust-clippy/pull/6537) - ### Suggestion Fixes/Improvements * [`vec_box`]: Provide correct type scope suggestion @@ -4174,8 +4400,8 @@ Released 2020-12-31 ### Documentation Improvements * Some doc improvements: - * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090) - * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162) + * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090) + * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162) * [`doc_markdown`]: Document problematic link text style [#6107](https://github.com/rust-lang/rust-clippy/pull/6107) @@ -4558,7 +4784,6 @@ Released 2020-06-04 * [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) * [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) - ### Moves and Deprecations * Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380) @@ -4573,7 +4798,7 @@ Released 2020-06-04 ### Enhancements -* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to +* On *nightly* you can now use `cargo clippy --fix -Z unstable-options` to auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363) * Make [`redundant_clone`] also trigger on cases where the cloned value is not consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304) @@ -4600,7 +4825,6 @@ Released 2020-06-04 * [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287) * [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451) - ### Suggestion Improvements * Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481) @@ -4678,7 +4902,6 @@ Released 2020-04-23 * Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190) - ## Rust 1.42 Released 2020-03-12 @@ -4745,7 +4968,6 @@ Released 2020-03-12 * Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`] - ## Rust 1.41 Released 2020-01-30 @@ -4961,7 +5183,6 @@ Released 2019-07-04 * Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960) * Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931) - ## Rust 1.35 Released 2019-05-20 @@ -4984,7 +5205,7 @@ Released 2019-05-20 * Fix false positive in [`needless_continue`] pertaining to loop labels * Fix false positive for [`boxed_local`] pertaining to arguments moved into closures * Fix false positive for [`use_self`] in nested functions -* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846) +* Fix suggestion for [`expect_fun_call`] () * Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables * Fix suggestion for [`single_char_pattern`] to correctly escape single quotes * Avoid triggering [`redundant_closure`] in macros @@ -5128,6 +5349,7 @@ Released 2018-12-06 Released 2018-10-25 [View all 88 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2018-08-02T16%3A54%3A12Z..2018-09-17T09%3A44%3A06Z+base%3Amaster) + * Deprecate `assign_ops` lint * New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`], [`needless_collect`], [`copy_iterator`] @@ -5177,255 +5399,326 @@ Released 2018-09-13 * Improve website header ## 0.0.212 (2018-07-10) + * Rustup to *rustc 1.29.0-nightly (e06c87544 2018-07-06)* ## 0.0.211 + * Rustup to *rustc 1.28.0-nightly (e3bf634e0 2018-06-28)* ## 0.0.210 + * Rustup to *rustc 1.28.0-nightly (01cc982e9 2018-06-24)* ## 0.0.209 + * Rustup to *rustc 1.28.0-nightly (523097979 2018-06-18)* ## 0.0.208 + * Rustup to *rustc 1.28.0-nightly (86a8f1a63 2018-06-17)* ## 0.0.207 + * Rustup to *rustc 1.28.0-nightly (2a0062974 2018-06-09)* ## 0.0.206 + * Rustup to *rustc 1.28.0-nightly (5bf68db6e 2018-05-28)* ## 0.0.205 + * Rustup to *rustc 1.28.0-nightly (990d8aa74 2018-05-25)* * Rename `unused_lifetimes` to `extra_unused_lifetimes` because of naming conflict with new rustc lint ## 0.0.204 + * Rustup to *rustc 1.28.0-nightly (71e87be38 2018-05-22)* ## 0.0.203 + * Rustup to *rustc 1.28.0-nightly (a3085756e 2018-05-19)* * Clippy attributes are now of the form `clippy::cyclomatic_complexity` instead of `clippy(cyclomatic_complexity)` ## 0.0.202 + * Rustup to *rustc 1.28.0-nightly (952f344cd 2018-05-18)* ## 0.0.201 + * Rustup to *rustc 1.27.0-nightly (2f2a11dfc 2018-05-16)* ## 0.0.200 + * Rustup to *rustc 1.27.0-nightly (9fae15374 2018-05-13)* ## 0.0.199 + * Rustup to *rustc 1.27.0-nightly (ff2ac35db 2018-05-12)* ## 0.0.198 + * Rustup to *rustc 1.27.0-nightly (acd3871ba 2018-05-10)* ## 0.0.197 + * Rustup to *rustc 1.27.0-nightly (428ea5f6b 2018-05-06)* ## 0.0.196 + * Rustup to *rustc 1.27.0-nightly (e82261dfb 2018-05-03)* ## 0.0.195 + * Rustup to *rustc 1.27.0-nightly (ac3c2288f 2018-04-18)* ## 0.0.194 + * Rustup to *rustc 1.27.0-nightly (bd40cbbe1 2018-04-14)* * New lints: [`cast_ptr_alignment`], [`transmute_ptr_to_ptr`], [`write_literal`], [`write_with_newline`], [`writeln_empty_string`] ## 0.0.193 + * Rustup to *rustc 1.27.0-nightly (eeea94c11 2018-04-06)* ## 0.0.192 + * Rustup to *rustc 1.27.0-nightly (fb44b4c0e 2018-04-04)* * New lint: [`print_literal`] ## 0.0.191 + * Rustup to *rustc 1.26.0-nightly (ae544ee1c 2018-03-29)* * Lint audit; categorize lints as style, correctness, complexity, pedantic, nursery, restriction. ## 0.0.190 + * Fix a bunch of intermittent cargo bugs ## 0.0.189 + * Rustup to *rustc 1.26.0-nightly (5508b2714 2018-03-18)* ## 0.0.188 + * Rustup to *rustc 1.26.0-nightly (392645394 2018-03-15)* * New lint: [`while_immutable_condition`] ## 0.0.187 + * Rustup to *rustc 1.26.0-nightly (322d7f7b9 2018-02-25)* * New lints: [`redundant_field_names`], [`suspicious_arithmetic_impl`], [`suspicious_op_assign_impl`] ## 0.0.186 + * Rustup to *rustc 1.25.0-nightly (0c6091fbd 2018-02-04)* * Various false positive fixes ## 0.0.185 + * Rustup to *rustc 1.25.0-nightly (56733bc9f 2018-02-01)* * New lint: [`question_mark`] ## 0.0.184 + * Rustup to *rustc 1.25.0-nightly (90eb44a58 2018-01-29)* * New lints: [`double_comparisons`], [`empty_line_after_outer_attr`] ## 0.0.183 + * Rustup to *rustc 1.25.0-nightly (21882aad7 2018-01-28)* * New lint: [`misaligned_transmute`] ## 0.0.182 + * Rustup to *rustc 1.25.0-nightly (a0dcecff9 2018-01-24)* * New lint: [`decimal_literal_representation`] ## 0.0.181 + * Rustup to *rustc 1.25.0-nightly (97520ccb1 2018-01-21)* * New lints: [`else_if_without_else`], [`option_option`], [`unit_arg`], [`unnecessary_fold`] * Removed `unit_expr` * Various false positive fixes for [`needless_pass_by_value`] ## 0.0.180 + * Rustup to *rustc 1.25.0-nightly (3f92e8d89 2018-01-14)* ## 0.0.179 + * Rustup to *rustc 1.25.0-nightly (61452e506 2018-01-09)* ## 0.0.178 + * Rustup to *rustc 1.25.0-nightly (ee220daca 2018-01-07)* ## 0.0.177 + * Rustup to *rustc 1.24.0-nightly (250b49205 2017-12-21)* * New lint: [`match_as_ref`] ## 0.0.176 + * Rustup to *rustc 1.24.0-nightly (0077d128d 2017-12-14)* ## 0.0.175 + * Rustup to *rustc 1.24.0-nightly (bb42071f6 2017-12-01)* ## 0.0.174 + * Rustup to *rustc 1.23.0-nightly (63739ab7b 2017-11-21)* ## 0.0.173 + * Rustup to *rustc 1.23.0-nightly (33374fa9d 2017-11-20)* ## 0.0.172 + * Rustup to *rustc 1.23.0-nightly (d0f8e2913 2017-11-16)* ## 0.0.171 + * Rustup to *rustc 1.23.0-nightly (ff0f5de3b 2017-11-14)* ## 0.0.170 + * Rustup to *rustc 1.23.0-nightly (d6b06c63a 2017-11-09)* ## 0.0.169 + * Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)* * New lints: [`just_underscores_and_digits`], `result_map_unwrap_or_else`, [`transmute_bytes_to_str`] ## 0.0.168 + * Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)* ## 0.0.167 + * Rustup to *rustc 1.23.0-nightly (90ef3372e 2017-10-29)* * New lints: `const_static_lifetime`, [`erasing_op`], [`fallible_impl_from`], [`println_empty_string`], [`useless_asref`] ## 0.0.166 + * Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)* * New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], `invalid_ref`, [`option_map_or_none`], [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`], [`transmute_int_to_float`] ## 0.0.165 + * Rust upgrade to rustc 1.22.0-nightly (0e6f4cf51 2017-09-27) * New lint: [`mut_range_bound`] ## 0.0.164 + * Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)* * New lint: [`int_plus_one`] ## 0.0.163 + * Update to *rustc 1.22.0-nightly (14039a42a 2017-09-22)* ## 0.0.162 + * Update to *rustc 1.22.0-nightly (0701b37d9 2017-09-18)* * New lint: [`chars_last_cmp`] * Improved suggestions for [`needless_borrow`], [`ptr_arg`], ## 0.0.161 + * Update to *rustc 1.22.0-nightly (539f2083d 2017-09-13)* ## 0.0.160 + * Update to *rustc 1.22.0-nightly (dd08c3070 2017-09-12)* ## 0.0.159 + * Update to *rustc 1.22.0-nightly (eba374fb2 2017-09-11)* * New lint: [`clone_on_ref_ptr`] ## 0.0.158 + * New lint: [`manual_memcpy`] * [`cast_lossless`] no longer has redundant parentheses in its suggestions * Update to *rustc 1.22.0-nightly (dead08cb3 2017-09-08)* ## 0.0.157 - 2017-09-04 + * Update to *rustc 1.22.0-nightly (981ce7d8d 2017-09-03)* * New lint: `unit_expr` ## 0.0.156 - 2017-09-03 + * Update to *rustc 1.22.0-nightly (744dd6c1d 2017-09-02)* ## 0.0.155 + * Update to *rustc 1.21.0-nightly (c11f689d2 2017-08-29)* * New lint: [`infinite_iter`], [`maybe_infinite_iter`], [`cast_lossless`] ## 0.0.154 + * Update to *rustc 1.21.0-nightly (2c0558f63 2017-08-24)* * Fix [`use_self`] triggering inside derives * Add support for linting an entire workspace with `cargo clippy --all` * New lint: [`naive_bytecount`] ## 0.0.153 + * Update to *rustc 1.21.0-nightly (8c303ed87 2017-08-20)* * New lint: [`use_self`] ## 0.0.152 + * Update to *rustc 1.21.0-nightly (df511d554 2017-08-14)* ## 0.0.151 + * Update to *rustc 1.21.0-nightly (13d94d5fa 2017-08-10)* ## 0.0.150 + * Update to *rustc 1.21.0-nightly (215e0b10e 2017-08-08)* ## 0.0.148 + * Update to *rustc 1.21.0-nightly (37c7d0ebb 2017-07-31)* * New lints: [`unreadable_literal`], [`inconsistent_digit_grouping`], [`large_digit_groups`] ## 0.0.147 + * Update to *rustc 1.21.0-nightly (aac223f4f 2017-07-30)* ## 0.0.146 + * Update to *rustc 1.21.0-nightly (52a330969 2017-07-27)* * Fixes false positives in `inline_always` * Fixes false negatives in `panic_params` ## 0.0.145 + * Update to *rustc 1.20.0-nightly (afe145d22 2017-07-23)* ## 0.0.144 + * Update to *rustc 1.20.0-nightly (086eaa78e 2017-07-15)* ## 0.0.143 + * Update to *rustc 1.20.0-nightly (d84693b93 2017-07-09)* * Fix `cargo clippy` crashing on `dylib` projects * Fix false positives around `nested_while_let` and `never_loop` ## 0.0.142 + * Update to *rustc 1.20.0-nightly (067971139 2017-07-02)* ## 0.0.141 + * Rewrite of the `doc_markdown` lint. * Deprecated [`range_step_by_zero`] * New lint: [`iterator_step_by_zero`] @@ -5433,151 +5726,195 @@ Released 2018-09-13 * Update to *rustc 1.20.0-nightly (69c65d296 2017-06-28)* ## 0.0.140 - 2017-06-16 + * Update to *rustc 1.19.0-nightly (258ae6dd9 2017-06-15)* ## 0.0.139 — 2017-06-10 + * Update to *rustc 1.19.0-nightly (4bf5c99af 2017-06-10)* * Fix bugs with for loop desugaring * Check for [`AsRef`]/[`AsMut`] arguments in [`wrong_self_convention`] ## 0.0.138 — 2017-06-05 + * Update to *rustc 1.19.0-nightly (0418fa9d3 2017-06-04)* ## 0.0.137 — 2017-06-05 + * Update to *rustc 1.19.0-nightly (6684d176c 2017-06-03)* ## 0.0.136 — 2017—05—26 + * Update to *rustc 1.19.0-nightly (557967766 2017-05-26)* ## 0.0.135 — 2017—05—24 + * Update to *rustc 1.19.0-nightly (5b13bff52 2017-05-23)* ## 0.0.134 — 2017—05—19 + * Update to *rustc 1.19.0-nightly (0ed1ec9f9 2017-05-18)* ## 0.0.133 — 2017—05—14 + * Update to *rustc 1.19.0-nightly (826d8f385 2017-05-13)* ## 0.0.132 — 2017—05—05 + * Fix various bugs and some ices ## 0.0.131 — 2017—05—04 + * Update to *rustc 1.19.0-nightly (2d4ed8e0c 2017-05-03)* ## 0.0.130 — 2017—05—03 + * Update to *rustc 1.19.0-nightly (6a5fc9eec 2017-05-02)* ## 0.0.129 — 2017-05-01 + * Update to *rustc 1.19.0-nightly (06fb4d256 2017-04-30)* ## 0.0.128 — 2017-04-28 + * Update to *rustc 1.18.0-nightly (94e884b63 2017-04-27)* ## 0.0.127 — 2017-04-27 + * Update to *rustc 1.18.0-nightly (036983201 2017-04-26)* * New lint: [`needless_continue`] ## 0.0.126 — 2017-04-24 + * Update to *rustc 1.18.0-nightly (2bd4b5c6d 2017-04-23)* ## 0.0.125 — 2017-04-19 + * Update to *rustc 1.18.0-nightly (9f2abadca 2017-04-18)* ## 0.0.124 — 2017-04-16 + * Update to *rustc 1.18.0-nightly (d5cf1cb64 2017-04-15)* ## 0.0.123 — 2017-04-07 + * Fix various false positives ## 0.0.122 — 2017-04-07 + * Rustup to *rustc 1.18.0-nightly (91ae22a01 2017-04-05)* * New lint: [`op_ref`] ## 0.0.121 — 2017-03-21 + * Rustup to *rustc 1.17.0-nightly (134c4a0f0 2017-03-20)* ## 0.0.120 — 2017-03-17 + * Rustup to *rustc 1.17.0-nightly (0aeb9c129 2017-03-15)* ## 0.0.119 — 2017-03-13 + * Rustup to *rustc 1.17.0-nightly (824c9ebbd 2017-03-12)* ## 0.0.118 — 2017-03-05 + * Rustup to *rustc 1.17.0-nightly (b1e31766d 2017-03-03)* ## 0.0.117 — 2017-03-01 + * Rustup to *rustc 1.17.0-nightly (be760566c 2017-02-28)* ## 0.0.116 — 2017-02-28 + * Fix `cargo clippy` on 64 bit windows systems ## 0.0.115 — 2017-02-27 + * Rustup to *rustc 1.17.0-nightly (60a0edc6c 2017-02-26)* * New lints: [`zero_ptr`], [`never_loop`], [`mut_from_ref`] ## 0.0.114 — 2017-02-08 + * Rustup to *rustc 1.17.0-nightly (c49d10207 2017-02-07)* * Tests are now ui tests (testing the exact output of rustc) ## 0.0.113 — 2017-02-04 + * Rustup to *rustc 1.16.0-nightly (eedaa94e3 2017-02-02)* * New lint: [`large_enum_variant`] * `explicit_into_iter_loop` provides suggestions ## 0.0.112 — 2017-01-27 + * Rustup to *rustc 1.16.0-nightly (df8debf6d 2017-01-25)* ## 0.0.111 — 2017-01-21 + * Rustup to *rustc 1.16.0-nightly (a52da95ce 2017-01-20)* ## 0.0.110 — 2017-01-20 + * Add badges and categories to `Cargo.toml` ## 0.0.109 — 2017-01-19 + * Update to *rustc 1.16.0-nightly (c07a6ae77 2017-01-17)* ## 0.0.108 — 2017-01-12 + * Update to *rustc 1.16.0-nightly (2782e8f8f 2017-01-12)* ## 0.0.107 — 2017-01-11 + * Update regex dependency * Fix FP when matching `&&mut` by `&ref` * Reintroduce `for (_, x) in &mut hash_map` -> `for x in hash_map.values_mut()` * New lints: [`unused_io_amount`], [`forget_ref`], [`short_circuit_statement`] ## 0.0.106 — 2017-01-04 + * Fix FP introduced by rustup in [`wrong_self_convention`] ## 0.0.105 — 2017-01-04 + * Update to *rustc 1.16.0-nightly (468227129 2017-01-03)* * New lints: [`deref_addrof`], [`double_parens`], [`pub_enum_variant_names`] * Fix suggestion in [`new_without_default`] * FP fix in [`absurd_extreme_comparisons`] ## 0.0.104 — 2016-12-15 + * Update to *rustc 1.15.0-nightly (8f02c429a 2016-12-15)* ## 0.0.103 — 2016-11-25 + * Update to *rustc 1.15.0-nightly (d5814b03e 2016-11-23)* ## 0.0.102 — 2016-11-24 + * Update to *rustc 1.15.0-nightly (3bf2be9ce 2016-11-22)* ## 0.0.101 — 2016-11-23 + * Update to *rustc 1.15.0-nightly (7b3eeea22 2016-11-21)* * New lint: [`string_extend_chars`] ## 0.0.100 — 2016-11-20 + * Update to *rustc 1.15.0-nightly (ac635aa95 2016-11-18)* ## 0.0.99 — 2016-11-18 + * Update to rustc 1.15.0-nightly (0ed951993 2016-11-14) * New lint: [`get_unwrap`] ## 0.0.98 — 2016-11-08 + * Fixes an issue due to a change in how cargo handles `--sysroot`, which broke `cargo clippy` ## 0.0.97 — 2016-11-03 + * For convenience, `cargo clippy` defines a `cargo-clippy` feature. This was previously added for a short time under the name `clippy` but removed for compatibility. @@ -5586,34 +5923,43 @@ Released 2018-09-13 * New lints: [`if_let_redundant_pattern_matching`], [`partialeq_ne_impl`] ## 0.0.96 — 2016-10-22 + * Rustup to *rustc 1.14.0-nightly (f09420685 2016-10-20)* * New lint: [`iter_skip_next`] ## 0.0.95 — 2016-10-06 + * Rustup to *rustc 1.14.0-nightly (3210fd5c2 2016-10-05)* ## 0.0.94 — 2016-10-04 + * Fixes bustage on Windows due to forbidden directory name ## 0.0.93 — 2016-10-03 + * Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)* * `option_map_unwrap_or` and `option_map_unwrap_or_else` are now allowed by default. * New lint: [`explicit_into_iter_loop`] ## 0.0.92 — 2016-09-30 + * Rustup to *rustc 1.14.0-nightly (289f3a4ca 2016-09-29)* ## 0.0.91 — 2016-09-28 + * Rustup to *rustc 1.13.0-nightly (d0623cf7b 2016-09-26)* ## 0.0.90 — 2016-09-09 + * Rustup to *rustc 1.13.0-nightly (f1f40f850 2016-09-09)* ## 0.0.89 — 2016-09-06 + * Rustup to *rustc 1.13.0-nightly (cbe4de78e 2016-09-05)* ## 0.0.88 — 2016-09-04 + * Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)* * The following lints are not new but were only usable through the `clippy` lint groups: [`filter_next`], `for_loop_over_option`, @@ -5622,30 +5968,37 @@ Released 2018-09-13 through `cargo clippy`. ## 0.0.87 — 2016-08-31 + * Rustup to *rustc 1.13.0-nightly (eac41469d 2016-08-30)* * New lints: [`builtin_type_shadow`] * Fix FP in [`zero_prefixed_literal`] and `0b`/`0o` ## 0.0.86 — 2016-08-28 + * Rustup to *rustc 1.13.0-nightly (a23064af5 2016-08-27)* * New lints: [`missing_docs_in_private_items`], [`zero_prefixed_literal`] ## 0.0.85 — 2016-08-19 + * Fix ICE with [`useless_attribute`] * [`useless_attribute`] ignores `unused_imports` on `use` statements ## 0.0.84 — 2016-08-18 + * Rustup to *rustc 1.13.0-nightly (aef6971ca 2016-08-17)* ## 0.0.83 — 2016-08-17 + * Rustup to *rustc 1.12.0-nightly (1bf5fa326 2016-08-16)* * New lints: [`print_with_newline`], [`useless_attribute`] ## 0.0.82 — 2016-08-17 + * Rustup to *rustc 1.12.0-nightly (197be89f3 2016-08-15)* * New lint: [`module_inception`] ## 0.0.81 — 2016-08-14 + * Rustup to *rustc 1.12.0-nightly (1deb02ea6 2016-08-12)* * New lints: [`eval_order_dependence`], [`mixed_case_hex_literals`], [`unseparated_literal_suffix`] * False positive fix in [`too_many_arguments`] @@ -5655,14 +6008,17 @@ Released 2018-09-13 * Doc improvements ## 0.0.80 — 2016-07-31 + * Rustup to *rustc 1.12.0-nightly (1225e122f 2016-07-30)* * New lints: [`misrefactored_assign_op`], [`serde_api_misuse`] ## 0.0.79 — 2016-07-10 + * Rustup to *rustc 1.12.0-nightly (f93aaf84c 2016-07-09)* * Major suggestions refactoring ## 0.0.78 — 2016-07-02 + * Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)* * New lints: [`wrong_transmute`], [`double_neg`], [`filter_map`] * For compatibility, `cargo clippy` does not defines the `clippy` feature @@ -5670,118 +6026,148 @@ Released 2018-09-13 * [`collapsible_if`] now considers `if let` ## 0.0.77 — 2016-06-21 + * Rustup to *rustc 1.11.0-nightly (5522e678b 2016-06-20)* * New lints: `stutter` and [`iter_nth`] ## 0.0.76 — 2016-06-10 + * Rustup to *rustc 1.11.0-nightly (7d2f75a95 2016-06-09)* * `cargo clippy` now automatically defines the `clippy` feature * New lint: [`not_unsafe_ptr_arg_deref`] ## 0.0.75 — 2016-06-08 + * Rustup to *rustc 1.11.0-nightly (763f9234b 2016-06-06)* ## 0.0.74 — 2016-06-07 + * Fix bug with `cargo-clippy` JSON parsing * Add the `CLIPPY_DISABLE_DOCS_LINKS` environment variable to deactivate the “for further information visit *lint-link*” message. ## 0.0.73 — 2016-06-05 + * Fix false positives in [`useless_let_if_seq`] ## 0.0.72 — 2016-06-04 + * Fix false positives in [`useless_let_if_seq`] ## 0.0.71 — 2016-05-31 + * Rustup to *rustc 1.11.0-nightly (a967611d8 2016-05-30)* * New lint: [`useless_let_if_seq`] ## 0.0.70 — 2016-05-28 + * Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)* * [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`, `RegexBuilder::new` and byte regexes ## 0.0.69 — 2016-05-20 + * Rustup to *rustc 1.10.0-nightly (476fe6eef 2016-05-21)* * [`used_underscore_binding`] has been made `Allow` temporarily ## 0.0.68 — 2016-05-17 + * Rustup to *rustc 1.10.0-nightly (cd6a40017 2016-05-16)* * New lint: [`unnecessary_operation`] ## 0.0.67 — 2016-05-12 + * Rustup to *rustc 1.10.0-nightly (22ac88f1a 2016-05-11)* ## 0.0.66 — 2016-05-11 + * New `cargo clippy` subcommand * New lints: [`assign_op_pattern`], [`assign_ops`], [`needless_borrow`] ## 0.0.65 — 2016-05-08 + * Rustup to *rustc 1.10.0-nightly (62e2b2fb7 2016-05-06)* * New lints: [`float_arithmetic`], [`integer_arithmetic`] ## 0.0.64 — 2016-04-26 + * Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)* * New lints: `temporary_cstring_as_ptr`, [`unsafe_removed_from_name`], and [`mem_forget`] ## 0.0.63 — 2016-04-08 + * Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)* ## 0.0.62 — 2016-04-07 + * Rustup to *rustc 1.9.0-nightly (bf5da36f1 2016-04-06)* ## 0.0.61 — 2016-04-03 + * Rustup to *rustc 1.9.0-nightly (5ab11d72c 2016-04-02)* * New lint: [`invalid_upcast_comparisons`] ## 0.0.60 — 2016-04-01 + * Rustup to *rustc 1.9.0-nightly (e1195c24b 2016-03-31)* ## 0.0.59 — 2016-03-31 + * Rustup to *rustc 1.9.0-nightly (30a3849f2 2016-03-30)* * New lints: [`logic_bug`], [`nonminimal_bool`] * Fixed: [`match_same_arms`] now ignores arms with guards * Improved: [`useless_vec`] now warns on `for … in vec![…]` ## 0.0.58 — 2016-03-27 + * Rustup to *rustc 1.9.0-nightly (d5a91e695 2016-03-26)* * New lint: [`doc_markdown`] ## 0.0.57 — 2016-03-27 + * Update to *rustc 1.9.0-nightly (a1e29daf1 2016-03-25)* * Deprecated lints: [`str_to_string`], [`string_to_string`], [`unstable_as_slice`], [`unstable_as_mut_slice`] * New lint: [`crosspointer_transmute`] ## 0.0.56 — 2016-03-23 + * Update to *rustc 1.9.0-nightly (0dcc413e4 2016-03-22)* * New lints: [`many_single_char_names`] and [`similar_names`] ## 0.0.55 — 2016-03-21 + * Update to *rustc 1.9.0-nightly (02310fd31 2016-03-19)* ## 0.0.54 — 2016-03-16 + * Update to *rustc 1.9.0-nightly (c66d2380a 2016-03-15)* ## 0.0.53 — 2016-03-15 + * Add a [configuration file] ## ~~0.0.52~~ ## 0.0.51 — 2016-03-13 + * Add `str` to types considered by [`len_zero`] * New lints: [`indexing_slicing`] ## 0.0.50 — 2016-03-11 + * Update to *rustc 1.9.0-nightly (c9629d61c 2016-03-10)* ## 0.0.49 — 2016-03-09 + * Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)* * New lints: [`overflow_check_conditional`], `unused_label`, [`new_without_default`] ## 0.0.48 — 2016-03-07 + * Fixed: ICE in [`needless_range_loop`] with globals ## 0.0.47 — 2016-03-07 + * Update to *rustc 1.9.0-nightly (998a6720b 2016-03-07)* * New lint: [`redundant_closure_call`] @@ -5950,6 +6336,7 @@ Released 2018-09-13 [`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_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets +[`empty_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enums [`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments [`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 @@ -6280,6 +6667,7 @@ Released 2018-09-13 [`needless_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_else [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each [`needless_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_if +[`needless_ifs`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_ifs [`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 @@ -6408,6 +6796,7 @@ Released 2018-09-13 [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names [`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned [`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching @@ -6427,6 +6816,7 @@ Released 2018-09-13 [`renamed_function_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params [`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once [`repeat_vec_with_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity +[`replace_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_box [`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts [`repr_packed_without_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#repr_packed_without_abi [`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization @@ -6452,6 +6842,7 @@ Released 2018-09-13 [`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment [`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors [`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files +[`self_only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_only_used_in_recursion [`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned [`semicolon_inside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block [`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block @@ -6557,6 +6948,7 @@ Released 2018-09-13 [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds [`unbuffered_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#unbuffered_bytes [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction +[`unchecked_time_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction [`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks [`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops @@ -6590,6 +6982,7 @@ Released 2018-09-13 [`unnecessary_min_or_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_min_or_max [`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_option_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_option_map_or_else [`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings [`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else [`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment @@ -6650,6 +7043,7 @@ Released 2018-09-13 [`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero [`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask [`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads +[`volatile_composites`]: https://rust-lang.github.io/rust-clippy/master/index.html#volatile_composites [`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons [`waker_clone_wake`]: https://rust-lang.github.io/rust-clippy/master/index.html#waker_clone_wake [`while_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_float @@ -6726,6 +7120,7 @@ Released 2018-09-13 [`excessive-nesting-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#excessive-nesting-threshold [`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold [`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability +[`inherent-impl-lint-scope`]: https://doc.rust-lang.org/clippy/lint_configuration.html#inherent-impl-lint-scope [`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold [`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code [`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold @@ -6743,6 +7138,7 @@ Released 2018-09-13 [`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv [`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit [`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior +[`recursive-self-in-type-definitions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#recursive-self-in-type-definitions [`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline [`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline [`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e3708bc48539..133072e0586b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,3 @@ # The Rust Code of Conduct -The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html). +The Code of Conduct for this repository [can be found online](https://rust-lang.org/policies/code-of-conduct/). diff --git a/Cargo.toml b/Cargo.toml index b3618932ded7..fee885d8fa7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.91" +version = "0.1.93" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -38,13 +38,18 @@ ui_test = "0.30.2" regex = "1.5.5" serde = { version = "1.0.145", features = ["derive"] } serde_json = "1.0.122" -toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } +[dev-dependencies.toml] +version = "0.9.7" +default-features = false +# preserve_order keeps diagnostic output in file order +features = ["parse", "preserve_order"] + [build-dependencies] rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } diff --git a/book/book.toml b/book/book.toml index c918aadf83c4..ae5fd07487ce 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,8 +1,6 @@ [book] authors = ["The Rust Clippy Developers"] language = "en" -multilingual = false -src = "src" title = "Clippy Documentation" [rust] diff --git a/book/src/README.md b/book/src/README.md index db73b49ecc24..5d2c3972b060 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,9 +1,5 @@ # Clippy -[### IMPORTANT NOTE FOR CONTRIBUTORS ================](development/feature_freeze.md) - ----- - [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) A collection of lints to catch common mistakes and improve your diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b66c3481e493..39fe7358ed87 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -13,7 +13,6 @@ - [GitLab CI](continuous_integration/gitlab.md) - [Travis CI](continuous_integration/travis.md) - [Development](development/README.md) - - [IMPORTANT: FEATURE FREEZE](development/feature_freeze.md) - [Basics](development/basics.md) - [Adding Lints](development/adding_lints.md) - [Defining Lints](development/defining_lints.md) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index a42a29837446..d9a5f04c3e1c 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -1,8 +1,5 @@ # Adding a new lint -[### IMPORTANT NOTE FOR CONTRIBUTORS ================](feature_freeze.md) - - You are probably here because you want to add a new lint to Clippy. If this is the first time you're contributing to Clippy, this document guides you through creating an example lint from scratch. @@ -762,8 +759,7 @@ for some users. Adding a configuration is done in the following steps: Here are some pointers to things you are likely going to need for every lint: * [Clippy utils][utils] - Various helper functions. Maybe the function you need - is already in here ([`is_type_diagnostic_item`], [`implements_trait`], - [`snippet`], etc) + is already in here ([`implements_trait`], [`snippet`], etc) * [Clippy diagnostics][diagnostics] * [Let chains][let-chains] * [`from_expansion`][from_expansion] and @@ -793,7 +789,6 @@ get away with copying things from existing similar lints. If you are stuck, don't hesitate to ask on [Zulip] or in the issue/PR. [utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html -[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html [`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html [`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html [let-chains]: https://github.com/rust-lang/rust/pull/94927 diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index 3bec3ce33af3..b5958f802e38 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint { // Check our expr is calling a method if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind // Check the name of this method is `some_method` - && path.ident.name.as_str() == "some_method" + && path.ident.name == sym::some_method // Optionally, check the type of the self argument. // - See "Checking for a specific type" { @@ -85,9 +85,8 @@ to check for. All of these methods only check for the base type, generic arguments have to be checked separately. ```rust -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::paths; -use rustc_span::symbol::sym; +use clippy_utils::{paths, sym}; +use clippy_utils::res::MaybeDef; use rustc_hir::LangItem; impl LateLintPass<'_> for MyStructLint { @@ -97,12 +96,12 @@ impl LateLintPass<'_> for MyStructLint { // 1. Using diagnostic items // The last argument is the diagnostic item to check for - if is_type_diagnostic_item(cx, ty, sym::Option) { + if ty.is_diag_item(cx, sym::Option) { // The type is an `Option` } // 2. Using lang items - if is_type_lang_item(cx, ty, LangItem::RangeFull) { + if ty.is_lang_item(cx, LangItem::RangeFull) { // The type is a full range like `.drain(..)` } @@ -123,27 +122,29 @@ There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither. ```rust +use clippy_utils::sym; use clippy_utils::ty::implements_trait; -use clippy_utils::is_trait_method; -use rustc_span::symbol::sym; impl LateLintPass<'_> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - // 1. Using diagnostic items with the expression - // we use `is_trait_method` function from Clippy's utils - if is_trait_method(cx, expr, sym::Iterator) { - // method call in `expr` belongs to `Iterator` trait - } - // 2. Using lang items with the expression type + // 1. Get the `DefId` of the trait. + // via lang items + let trait_id = cx.tcx.lang_items().drop_trait(); + // via diagnostic items + let trait_id = cx.tcx.get_diagnostic_item(sym::Eq); + + // 2. Check for the trait implementation via the `implements_trait` util. let ty = cx.typeck_results().expr_ty(expr); - if cx.tcx.lang_items() - // we are looking for the `DefId` of `Drop` trait in lang items - .drop_trait() - // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils - .is_some_and(|id| implements_trait(cx, ty, id, &[])) { - // `expr` implements `Drop` trait - } + if trait_id.is_some_and(|id| implements_trait(cx, ty, id, &[])) { + // `ty` implements the trait. + } + + // 3. If the trait requires additional generic arguments + let trait_id = cx.tcx.lang_items().eq_trait(); + if trait_id.is_some_and(|id| implements_trait(cx, ty, id, &[ty])) { + // `ty` implements `PartialEq` + } } } ``` @@ -173,7 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go further and even check if its return type is `String` - && is_type_lang_item(cx, return_ty(cx, impl_item.hir_id), LangItem::String) + && return_ty(cx, impl_item.hir_id).is_lang_item(cx, LangItem::String) { // ... } diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md deleted file mode 100644 index 260cb136cc07..000000000000 --- a/book/src/development/feature_freeze.md +++ /dev/null @@ -1,55 +0,0 @@ -# IMPORTANT: FEATURE FREEZE - -This is a temporary notice. - -From the 26th of June until the 18th of September we will perform a feature freeze. Only bugfix PRs will be reviewed -except already open ones. Every feature-adding PR opened in between those dates will be moved into a -milestone to be reviewed separately at another time. - -We do this because of the long backlog of bugs that need to be addressed -in order to continue being the state-of-the-art linter that Clippy has become known for being. - -## For contributors - -If you are a contributor or are planning to become one, **please do not open a lint-adding PR**, we have lots of open -bugs of all levels of difficulty that you can address instead! - -We currently have about 800 lints, each one posing a maintainability challenge that needs to account to every possible -use case of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a -refinement period. - -If you open a PR at this time, we will not review it but push it into a milestone until the refinement period ends, -adding additional load into our reviewing schedules. - -## I want to help, what can I do - -Thanks a lot to everyone who wants to help Clippy become better software in this feature freeze period! -If you'd like to help, making a bugfix, making sure that it works, and opening a PR is a great step! - -To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like, go there and claim that -issue with `@rustbot claim`. - -As a general metric and always taking into account your skill and knowledge level, you can use this guide: - -- 🟥 [ICEs][search_ice], these are compiler errors that causes Clippy to panic and crash. Usually involves high-level -debugging, sometimes interacting directly with the upstream compiler. Difficult to fix but a great challenge that -improves a lot developer workflows! - -- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way. -Unacceptable, as this may have disastrous consequences. Easier to fix than ICEs - -- 🟨 [Suggestion causes error][sugg_causes_error], Clippy suggested code snippet that caused a compiler error -when applied. We need to make sure that Clippy doesn't suggest using a variable twice at the same time or similar -easy-to-happen occurrences. - -- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just" -identifying the root of a false positive and making an exception for those cases. - -Note that false negatives do not have priority unless the case is very clear, as they are a feature-request in a -trench coat. - -[search_ice]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22I-ICE%22 -[sugg_causes_bug]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-bug -[sugg_causes_error]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-error%20 -[false_positive]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-false-positive -[tracking_issue]: https://github.com/rust-lang/rust-clippy/issues/15086 diff --git a/book/src/development/macro_expansions.md b/book/src/development/macro_expansions.md index ed547130b358..63a96dc373f3 100644 --- a/book/src/development/macro_expansions.md +++ b/book/src/development/macro_expansions.md @@ -37,7 +37,7 @@ before emitting suggestions to the end user to avoid false positives. Several functions are available for working with macros. -### The `Span.from_expansion` method +### The `Span::from_expansion` method We could utilize a `span`'s [`from_expansion`] method, which detects if the `span` is from a macro expansion / desugaring. @@ -50,7 +50,7 @@ if expr.span.from_expansion() { } ``` -### `Span.ctxt` method +### `Span::ctxt` method The `span`'s context, given by the method [`ctxt`] and returning [SyntaxContext], represents if the span is from a macro expansion and, if it is, which diff --git a/book/src/development/method_checking.md b/book/src/development/method_checking.md index b3126024b990..f3912f81d859 100644 --- a/book/src/development/method_checking.md +++ b/book/src/development/method_checking.md @@ -15,20 +15,19 @@ the [`ExprKind`] that we can access from `expr.kind`: ```rust use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; -use rustc_span::sym; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::sym; impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { // Check our expr is calling a method with pattern matching - if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind + if let hir::ExprKind::MethodCall(path, _, _, _) = &expr.kind // Check if the name of this method is `our_fancy_method` - && path.ident.name.as_str() == "our_fancy_method" - // We can check the type of the self argument whenever necessary. - // (It's necessary if we want to check that method is specifically belonging to a specific trait, - // for example, a `map` method could belong to user-defined trait instead of to `Iterator`) + && path.ident.name == sym::our_fancy_method + // Check if the method belongs to the `sym::OurFancyTrait` trait. + // (for example, a `map` method could belong to user-defined trait instead of to `Iterator`) // See the next section for more information. - && is_trait_method(cx, self_arg, sym::OurFancyTrait) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait) { println!("`expr` is a method call for `our_fancy_method`"); } @@ -41,6 +40,16 @@ information on the pattern matching. As mentioned in [Define Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern matching with `MethodCall` in case the reader wishes to explore more. +New symbols such as `our_fancy_method` need to be added to the `clippy_utils::sym` module. +This module extends the list of symbols already provided by the compiler crates +in `rustc_span::sym`. + +If a trait defines only one method (such as the `std::ops::Deref` trait, which only has the `deref()` method), +one might be tempted to omit the method name check. This would work, but is not always advisable because: +- If a new method (possibly with a default implementation) were to be added to the trait, there would be a risk of + matching the wrong method. +- Comparing symbols is very cheap and might prevent a more expensive lookup. + ## Checking if a `impl` block implements a method While sometimes we want to check whether a method is being called or not, other @@ -56,11 +65,10 @@ Let us take a look at how we might check for the implementation of `our_fancy_method` on a type: ```rust -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::return_ty; +use clippy_utils::{return_ty, sym}; +use clippy_utils::res::MaybeDef; use rustc_hir::{ImplItem, ImplItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_span::symbol::sym; impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { @@ -71,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go even further and even check if its return type is `String` - && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym::String) + && return_ty(cx, impl_item.hir_id).is_diag_item(cx, sym::String) { println!("`our_fancy_method` is implemented!"); } diff --git a/book/src/development/trait_checking.md b/book/src/development/trait_checking.md index 6d01496eebe0..714607ef25e5 100644 --- a/book/src/development/trait_checking.md +++ b/book/src/development/trait_checking.md @@ -17,19 +17,18 @@ providing the `LateContext` (`cx`), our expression at hand, and the symbol of the trait in question: ```rust +use clippy_utils::sym; use clippy_utils::ty::implements_trait; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; -use rustc_span::symbol::sym; impl LateLintPass<'_> for CheckIteratorTraitLint { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let implements_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[]) - }); - if implements_iterator { - // [...] - } + let implements_iterator = (cx.tcx.get_diagnostic_item(sym::Iterator)) + .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[])); + if implements_iterator { + // [...] + } } } @@ -54,7 +53,7 @@ For instance, if we want to examine whether an expression `expr` implements we can check that the `Ty` of the `expr` implements the trait: ```rust -use clippy_utils::implements_trait; +use clippy_utils::ty::implements_trait; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; @@ -80,7 +79,8 @@ If neither diagnostic item nor a language item is available, we can use Below, we check if the given `expr` implements [`core::iter::Step`](https://doc.rust-lang.org/std/iter/trait.Step.html): ```rust -use clippy_utils::{implements_trait, paths}; +use clippy_utils::paths; +use clippy_utils::ty::implements_trait; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; @@ -125,8 +125,8 @@ The following code demonstrates how to do this: ```rust use rustc_middle::ty::Ty; +use clippy_utils::sym; use clippy_utils::ty::implements_trait; -use rustc_span::symbol::sym; let ty = todo!("Get the `Foo` type to check for a trait implementation"); let borrow_id = cx.tcx.get_diagnostic_item(sym::Borrow).unwrap(); // avoid unwrap in real code diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c2d080cd96a1..6569bdabf115 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -671,6 +671,16 @@ A list of paths to types that should be treated as if they do not contain interi * [`mutable_key_type`](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type) +## `inherent-impl-lint-scope` +Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted. + +**Default Value:** `"crate"` + +--- +**Affected lints:** +* [`multiple_inherent_impl`](https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl) + + ## `large-error-threshold` The maximum size of the `Err`-variant in a `Result` returned from a function @@ -845,9 +855,11 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`from_over_into`](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into) * [`if_then_some_else_none`](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none) * [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice) +* [`inefficient_to_string`](https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string) * [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error) * [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map) * [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants) +* [`len_zero`](https://rust-lang.github.io/rust-clippy/master/index.html#len_zero) * [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok) * [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff) * [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits) @@ -883,6 +895,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow) * [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics) * [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref) +* [`or_fun_call`](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call) * [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr) * [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark) * [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names) @@ -894,9 +907,10 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref) * [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions) * [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds) -* [`unchecked_duration_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction) +* [`unchecked_time_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction) * [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args) * [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations) +* [`unnecessary_unwrap`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap) * [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns) * [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names) * [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self) @@ -924,6 +938,16 @@ exported visibility, or whether they are marked as "pub". * [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields) +## `recursive-self-in-type-definitions` +Whether the type itself in a struct or enum should be replaced with `Self` when encountering recursive types. + +**Default Value:** `true` + +--- +**Affected lints:** +* [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self) + + ## `semicolon-inside-block-ignore-singleline` Whether to lint only if it's multiline. diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 6ad2cf0d0b10..3f6b26d3334e 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.91" +version = "0.1.93" edition = "2024" publish = false diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2f28f6175ad8..2a042e6c3d85 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -1,9 +1,9 @@ use crate::ClippyConfiguration; use crate::types::{ - DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, - Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, - SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, - SourceItemOrderingWithinModuleItemGroupings, + DisallowedPath, DisallowedPathWithoutReplacement, InherentImplLintScope, MacroMatcher, MatchLintBehaviour, + PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingCategory, + SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, + SourceItemOrderingTraitAssocItemKinds, SourceItemOrderingWithinModuleItemGroupings, }; use clippy_utils::msrvs::Msrv; use itertools::Itertools; @@ -248,7 +248,7 @@ macro_rules! define_Conf { #[derive(Deserialize)] #[serde(field_identifier, rename_all = "kebab-case")] - #[allow(non_camel_case_types)] + #[expect(non_camel_case_types)] enum Field { $($name,)* third_party, } struct ConfVisitor<'a>(&'a SourceFile); @@ -663,6 +663,9 @@ define_Conf! { /// A list of paths to types that should be treated as if they do not contain interior mutability #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)] ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()]), + /// Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted. + #[lints(multiple_inherent_impl)] + inherent_impl_lint_scope: InherentImplLintScope = InherentImplLintScope::Crate, /// The maximum size of the `Err`-variant in a `Result` returned from a function #[lints(result_large_err)] large_error_threshold: u64 = 128, @@ -741,9 +744,11 @@ define_Conf! { from_over_into, if_then_some_else_none, index_refutable_slice, + inefficient_to_string, io_other_error, iter_kv_map, legacy_numeric_constants, + len_zero, lines_filter_map_ok, manual_abs_diff, manual_bits, @@ -779,6 +784,7 @@ define_Conf! { needless_borrow, non_std_lazy_statics, option_as_ref_deref, + or_fun_call, ptr_as_ptr, question_mark, redundant_field_names, @@ -790,9 +796,10 @@ define_Conf! { transmute_ptr_to_ref, tuple_array_conversions, type_repetition_in_bounds, - unchecked_duration_subtraction, + unchecked_time_subtraction, uninlined_format_args, unnecessary_lazy_evaluations, + unnecessary_unwrap, unnested_or_patterns, unused_trait_names, use_self, @@ -806,6 +813,9 @@ define_Conf! { /// exported visibility, or whether they are marked as "pub". #[lints(pub_underscore_fields)] pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported, + /// Whether the type itself in a struct or enum should be replaced with `Self` when encountering recursive types. + #[lints(use_self)] + recursive_self_in_type_definitions: bool = true, /// Whether to lint only if it's multiline. #[lints(semicolon_inside_block)] semicolon_inside_block_ignore_singleline: bool = false, @@ -1210,7 +1220,7 @@ mod tests { for entry in toml_files { let file = fs::read_to_string(entry.path()).unwrap(); - #[allow(clippy::zero_sized_map_values)] + #[expect(clippy::zero_sized_map_values)] if let Ok(map) = toml::from_str::>(&file) { for name in map.keys() { names.remove(name.as_str()); diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index f64eefa0c232..8d9326a904b1 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -131,7 +131,7 @@ impl DisallowedPathEnum { } /// Creates a map of disallowed items to the reason they were disallowed. -#[allow(clippy::type_complexity)] +#[expect(clippy::type_complexity)] pub fn create_disallowed_map( tcx: TyCtxt<'_>, disallowed_paths: &'static [DisallowedPath], @@ -698,3 +698,11 @@ pub enum PubUnderscoreFieldsBehaviour { PubliclyExported, AllPubFields, } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum InherentImplLintScope { + Crate, + File, + Module, +} diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 3bdc5b277232..0401cfda7080 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,4 +1,5 @@ -use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{DeprecatedLint, Lint, ParseCx}; +use crate::update_lints::generate_lint_files; use crate::utils::{UpdateMode, Version}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; @@ -13,21 +14,20 @@ use std::{fs, io}; /// # Panics /// /// If a file path could not read from or written to -pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { - if let Some((prefix, _)) = name.split_once("::") { - panic!("`{name}` should not contain the `{prefix}` prefix"); - } - - let mut lints = find_lint_decls(); - let (mut deprecated_lints, renamed_lints) = read_deprecated_lints(); +pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str, reason: &'cx str) { + let mut lints = cx.find_lint_decls(); + let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints(); let Some(lint) = lints.iter().find(|l| l.name == name) else { eprintln!("error: failed to find lint `{name}`"); return; }; - let prefixed_name = String::from_iter(["clippy::", name]); - match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) { + let prefixed_name = cx.str_buf.with(|buf| { + buf.extend(["clippy::", name]); + cx.arena.alloc_str(buf) + }); + match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) { Ok(_) => { println!("`{name}` is already deprecated"); return; @@ -36,8 +36,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { idx, DeprecatedLint { name: prefixed_name, - reason: reason.into(), - version: clippy_version.rust_display().to_string(), + reason, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), }, ), } @@ -61,8 +61,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { } } -fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { - fn remove_lint(name: &str, lints: &mut Vec) { +fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { + fn remove_lint(name: &str, lints: &mut Vec>) { lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); } @@ -135,14 +135,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io ); assert!( - content[lint.declaration_range.clone()].contains(&name.to_uppercase()), + content[lint.declaration_range].contains(&name.to_uppercase()), "error: `{}` does not contain lint `{}`'s declaration", path.display(), lint.name ); // Remove lint declaration (declare_clippy_lint!) - content.replace_range(lint.declaration_range.clone(), ""); + content.replace_range(lint.declaration_range, ""); // Remove the module declaration (mod xyz;) let mod_decl = format!("\nmod {name};"); diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs index d0fca952b932..9eb323eaef5a 100644 --- a/clippy_dev/src/dogfood.rs +++ b/clippy_dev/src/dogfood.rs @@ -4,7 +4,7 @@ use itertools::Itertools; /// # Panics /// /// Panics if unable to run the dogfood test -#[allow(clippy::fn_params_excessive_bools)] +#[expect(clippy::fn_params_excessive_bools)] pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) { run_exit_on_err( "cargo test", diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 2b2138d3108d..781e37e6144e 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) { .expect("invalid rustfmt path"); rustfmt_path.truncate(rustfmt_path.trim_end().len()); - let args: Vec<_> = walk_dir_no_dot_or_target() + let args: Vec<_> = walk_dir_no_dot_or_target(".") .filter_map(|e| { let e = expect_action(e, ErrAction::Read, "."); e.path() diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 16f413e0c862..dcca08aee7e6 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,9 +1,12 @@ #![feature( - rustc_private, exit_status_error, if_let_guard, + new_range, + new_range_api, os_str_slice, os_string_truncate, + pattern, + rustc_private, slice_split_once )] #![warn( @@ -15,6 +18,7 @@ )] #![allow(clippy::missing_panics_doc)] +extern crate rustc_arena; #[expect(unused_extern_crates, reason = "required to link to rustc crates")] extern crate rustc_driver; extern crate rustc_lexer; @@ -32,5 +36,8 @@ pub mod setup; pub mod sync; pub mod update_lints; +mod parse; mod utils; -pub use utils::{ClippyInfo, UpdateMode}; + +pub use self::parse::{ParseCx, new_parse_cx}; +pub use self::utils::{ClippyInfo, UpdateMode}; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 5fef231f6ca1..392c3aabf193 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -4,10 +4,9 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ - ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, - update_lints, + ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve, + setup, sync, update_lints, }; -use std::convert::Infallible; use std::env; fn main() { @@ -28,7 +27,7 @@ fn main() { allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)), + DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))), DevCommand::NewLint { pass, name, @@ -36,7 +35,7 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => update_lints::update(UpdateMode::Change), + Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { @@ -79,13 +78,18 @@ fn main() { old_name, new_name, uplift, - } => rename_lint::rename( - clippy.version, - &old_name, - new_name.as_ref().unwrap_or(&old_name), - uplift, - ), - DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason), + } => new_parse_cx(|cx| { + rename_lint::rename( + cx, + clippy.version, + &old_name, + new_name.as_ref().unwrap_or(&old_name), + uplift, + ); + }), + DevCommand::Deprecate { name, reason } => { + new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason)); + }, DevCommand::Sync(SyncCommand { subcommand }) => match subcommand { SyncSubcommand::UpdateNightly => sync::update_nightly(), }, @@ -95,6 +99,20 @@ fn main() { } } +fn lint_name(name: &str) -> Result { + let name = name.replace('-', "_"); + if let Some((pre, _)) = name.split_once("::") { + Err(format!("lint name should not contain the `{pre}` prefix")) + } else if name + .bytes() + .any(|x| !matches!(x, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')) + { + Err("lint name contains invalid characters".to_owned()) + } else { + Ok(name) + } +} + #[derive(Parser)] #[command(name = "dev", about)] struct Dev { @@ -150,7 +168,7 @@ enum DevCommand { #[arg( short, long, - value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")), + value_parser = lint_name, )] /// Name of the new lint in snake case, ex: `fn_too_long` name: String, @@ -192,7 +210,7 @@ enum DevCommand { /// Which lint's page to load initially (optional) lint: Option, }, - #[allow(clippy::doc_markdown)] + #[expect(clippy::doc_markdown)] /// Manually run clippy on a file or package /// /// ## Examples @@ -223,8 +241,12 @@ enum DevCommand { /// Rename a lint RenameLint { /// The name of the lint to rename + #[arg(value_parser = lint_name)] old_name: String, - #[arg(required_unless_present = "uplift")] + #[arg( + required_unless_present = "uplift", + value_parser = lint_name, + )] /// The new name of the lint new_name: Option, #[arg(long)] @@ -234,6 +256,7 @@ enum DevCommand { /// Deprecate the given lint Deprecate { /// The name of the lint to deprecate + #[arg(value_parser = lint_name)] name: String, #[arg(long, short)] /// The reason for deprecation diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 4121daa85e6d..a180db6ad062 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,5 @@ -use crate::utils::{RustSearcher, Token, Version}; +use crate::parse::cursor::{self, Capture, Cursor}; +use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; use std::fmt::{self, Write as _}; @@ -443,7 +444,6 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R Ok(()) } -#[allow(clippy::too_many_lines)] fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> { let lint_name_upper = lint.name.to_uppercase(); @@ -517,22 +517,22 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { #[allow(clippy::enum_glob_use)] - use Token::*; + use cursor::Pat::*; let mut context = None; let mut decl_end = None; - let mut searcher = RustSearcher::new(contents); - while let Some(name) = searcher.find_capture_token(CaptureIdent) { - match name { + let mut cursor = Cursor::new(contents); + let mut captures = [Capture::EMPTY]; + while let Some(name) = cursor.find_any_ident() { + match cursor.get_text(name) { "declare_clippy_lint" => { - if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) { - decl_end = Some(searcher.pos()); + if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) { + decl_end = Some(cursor.pos()); } }, "impl" => { - let mut capture = ""; - if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) { - match capture { + if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) { + match cursor.get_text(captures[0]) { "LateLintPass" => context = Some("LateContext"), "EarlyLintPass" => context = Some("EarlyContext"), _ => {}, diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs new file mode 100644 index 000000000000..de5caf4e1ef6 --- /dev/null +++ b/clippy_dev/src/parse.rs @@ -0,0 +1,285 @@ +pub mod cursor; + +use self::cursor::{Capture, Cursor}; +use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target}; +use core::fmt::{Display, Write as _}; +use core::range::Range; +use rustc_arena::DroplessArena; +use std::fs; +use std::path::{self, Path, PathBuf}; +use std::str::pattern::Pattern; + +pub struct ParseCxImpl<'cx> { + pub arena: &'cx DroplessArena, + pub str_buf: StrBuf, +} +pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>; + +/// Calls the given function inside a newly created parsing context. +pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T { + let arena = DroplessArena::default(); + f(&mut Scoped::new(ParseCxImpl { + arena: &arena, + str_buf: StrBuf::with_capacity(128), + })) +} + +/// A string used as a temporary buffer used to avoid allocating for short lived strings. +pub struct StrBuf(String); +impl StrBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(String::with_capacity(cap)) + } + + /// Allocates the result of formatting the given value onto the arena. + pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str { + self.0.clear(); + write!(self.0, "{value}").expect("`Display` impl returned an error"); + arena.alloc_str(&self.0) + } + + /// Allocates the string onto the arena with all ascii characters converted to + /// lowercase. + pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str { + self.0.clear(); + self.0.push_str(s); + self.0.make_ascii_lowercase(); + arena.alloc_str(&self.0) + } + + /// Allocates the result of replacing all instances the pattern with the given string + /// onto the arena. + pub fn alloc_replaced<'cx>( + &mut self, + arena: &'cx DroplessArena, + s: &str, + pat: impl Pattern, + replacement: &str, + ) -> &'cx str { + let mut parts = s.split(pat); + let Some(first) = parts.next() else { + return ""; + }; + self.0.clear(); + self.0.push_str(first); + for part in parts { + self.0.push_str(replacement); + self.0.push_str(part); + } + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { + self.0.clear(); + f(&mut self.0) + } +} + +pub struct Lint<'cx> { + pub name: &'cx str, + pub group: &'cx str, + pub module: &'cx str, + pub path: PathBuf, + pub declaration_range: Range, +} + +pub struct DeprecatedLint<'cx> { + pub name: &'cx str, + pub reason: &'cx str, + pub version: &'cx str, +} + +pub struct RenamedLint<'cx> { + pub old_name: &'cx str, + pub new_name: &'cx str, + pub version: &'cx str, +} + +impl<'cx> ParseCxImpl<'cx> { + /// Finds all lint declarations (`declare_clippy_lint!`) + #[must_use] + pub fn find_lint_decls(&mut self) -> Vec> { + let mut lints = Vec::with_capacity(1000); + let mut contents = String::new(); + for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { + let e = expect_action(e, ErrAction::Read, "."); + + // Skip if this isn't a lint crate's directory. + let mut crate_path = if expect_action(e.file_type(), ErrAction::Read, ".").is_dir() + && let Ok(crate_path) = e.file_name().into_string() + && crate_path.starts_with("clippy_lints") + && crate_path != "clippy_lints_internal" + { + crate_path + } else { + continue; + }; + + crate_path.push(path::MAIN_SEPARATOR); + crate_path.push_str("src"); + for e in walk_dir_no_dot_or_target(&crate_path) { + let e = expect_action(e, ErrAction::Read, &crate_path); + if let Some(path) = e.path().to_str() + && let Some(path) = path.strip_suffix(".rs") + && let Some(path) = path.get(crate_path.len() + 1..) + { + let module = if path == "lib" { + "" + } else { + let path = path + .strip_suffix("mod") + .and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR)) + .unwrap_or(path); + self.str_buf + .alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::") + }; + self.parse_clippy_lint_decls( + e.path(), + File::open_read_to_cleared_string(e.path(), &mut contents), + module, + &mut lints, + ); + } + } + } + lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); + lints + } + + /// Parse a source file looking for `declare_clippy_lint` macro invocations. + fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, lints: &mut Vec>) { + #[allow(clippy::enum_glob_use)] + use cursor::Pat::*; + #[rustfmt::skip] + static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ /// docs + Bang, OpenBrace, AnyComment, + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, + // pub NAME, GROUP, + Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, + ]; + + let mut cursor = Cursor::new(contents); + let mut captures = [Capture::EMPTY; 2]; + while let Some(start) = cursor.find_ident("declare_clippy_lint") { + if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) { + lints.push(Lint { + name: self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), + group: self.arena.alloc_str(cursor.get_text(captures[1])), + module, + path: path.into(), + declaration_range: start as usize..cursor.pos() as usize, + }); + } + } + } + + #[must_use] + pub fn read_deprecated_lints(&mut self) -> (Vec>, Vec>) { + #[allow(clippy::enum_glob_use)] + use cursor::Pat::*; + #[rustfmt::skip] + static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, + // ("first", "second"), + OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, + ]; + #[rustfmt::skip] + static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ DEPRECATED(DEPRECATED_VERSION) = [ + Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ RENAMED(RENAMED_VERSION) = [ + Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + + let path = "clippy_lints/src/deprecated_lints.rs"; + let mut deprecated = Vec::with_capacity(30); + let mut renamed = Vec::with_capacity(80); + let mut contents = String::new(); + File::open_read_to_cleared_string(path, &mut contents); + + let mut cursor = Cursor::new(&contents); + let mut captures = [Capture::EMPTY; 3]; + + // First instance is the macro definition. + assert!( + cursor.find_ident("declare_with_version").is_some(), + "error reading deprecated lints" + ); + + if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) { + while cursor.match_all(DECL_TOKENS, &mut captures) { + deprecated.push(DeprecatedLint { + name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])), + reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])), + version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), + }); + } + } else { + panic!("error reading deprecated lints"); + } + + if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) { + while cursor.match_all(DECL_TOKENS, &mut captures) { + renamed.push(RenamedLint { + old_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])), + new_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])), + version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), + }); + } + } else { + panic!("error reading renamed lints"); + } + + deprecated.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); + renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(rhs.old_name)); + (deprecated, renamed) + } + + /// Removes the line splices and surrounding quotes from a string literal + fn parse_str_lit(&mut self, s: &str) -> &'cx str { + let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { + (s.trim_matches('#'), true) + } else { + (s, false) + }; + let s = s + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); + + if is_raw { + if s.is_empty() { "" } else { self.arena.alloc_str(s) } + } else { + self.str_buf.with(|buf| { + rustc_literal_escaper::unescape_str(s, &mut |_, ch| { + if let Ok(ch) = ch { + buf.push(ch); + } + }); + if buf.is_empty() { "" } else { self.arena.alloc_str(buf) } + }) + } + } + + fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str { + let value = self.parse_str_lit(s); + assert!( + !value.contains('\n'), + "error parsing `{}`: `{s}` should be a single line string", + path.display(), + ); + value + } +} diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs new file mode 100644 index 000000000000..6dc003f326de --- /dev/null +++ b/clippy_dev/src/parse/cursor.rs @@ -0,0 +1,263 @@ +use core::slice; +use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind}; + +/// A token pattern used for searching and matching by the [`Cursor`]. +/// +/// In the event that a pattern is a multi-token sequence, earlier tokens will be consumed +/// even if the pattern ultimately isn't matched. e.g. With the sequence `:*` matching +/// `DoubleColon` will consume the first `:` and then fail to match, leaving the cursor at +/// the `*`. +#[derive(Clone, Copy)] +pub enum Pat<'a> { + /// Matches any number of comments and doc comments. + AnyComment, + Ident(&'a str), + CaptureIdent, + LitStr, + CaptureLitStr, + Bang, + CloseBrace, + CloseBracket, + CloseParen, + Comma, + DoubleColon, + Eq, + Lifetime, + Lt, + Gt, + OpenBrace, + OpenBracket, + OpenParen, + Pound, + Semi, +} + +#[derive(Clone, Copy)] +pub struct Capture { + pub pos: u32, + pub len: u32, +} +impl Capture { + pub const EMPTY: Self = Self { pos: 0, len: 0 }; +} + +/// A unidirectional cursor over a token stream that is lexed on demand. +pub struct Cursor<'txt> { + next_token: Token, + pos: u32, + inner: lex::Cursor<'txt>, + text: &'txt str, +} +impl<'txt> Cursor<'txt> { + #[must_use] + pub fn new(text: &'txt str) -> Self { + let mut inner = lex::Cursor::new(text, lex::FrontmatterAllowed::Yes); + Self { + next_token: inner.advance_token(), + pos: 0, + inner, + text, + } + } + + /// Gets the text of the captured token assuming it came from this cursor. + #[must_use] + pub fn get_text(&self, capture: Capture) -> &'txt str { + &self.text[capture.pos as usize..(capture.pos + capture.len) as usize] + } + + /// Gets the text that makes up the next token in the stream, or the empty string if + /// stream is exhausted. + #[must_use] + pub fn peek_text(&self) -> &'txt str { + &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] + } + + /// Gets the length of the next token in bytes, or zero if the stream is exhausted. + #[must_use] + pub fn peek_len(&self) -> u32 { + self.next_token.len + } + + /// Gets the next token in the stream, or [`TokenKind::Eof`] if the stream is + /// exhausted. + #[must_use] + pub fn peek(&self) -> TokenKind { + self.next_token.kind + } + + /// Gets the offset of the next token in the source string, or the string's length if + /// the stream is exhausted. + #[must_use] + pub fn pos(&self) -> u32 { + self.pos + } + + /// Gets whether the cursor has exhausted its input. + #[must_use] + pub fn at_end(&self) -> bool { + self.next_token.kind == TokenKind::Eof + } + + /// Advances the cursor to the next token. If the stream is exhausted this will set + /// the next token to [`TokenKind::Eof`]. + pub fn step(&mut self) { + // `next_token.len` is zero for the eof marker. + self.pos += self.next_token.len; + self.next_token = self.inner.advance_token(); + } + + /// Consumes tokens until the given pattern is either fully matched of fails to match. + /// Returns whether the pattern was fully matched. + /// + /// For each capture made by the pattern one item will be taken from the capture + /// sequence with the result placed inside. + fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { + loop { + match (pat, self.next_token.kind) { + #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/6697 + (_, TokenKind::Whitespace) + | ( + Pat::AnyComment, + TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. }, + ) => self.step(), + (Pat::AnyComment, _) => return true, + (Pat::Bang, TokenKind::Bang) + | (Pat::CloseBrace, TokenKind::CloseBrace) + | (Pat::CloseBracket, TokenKind::CloseBracket) + | (Pat::CloseParen, TokenKind::CloseParen) + | (Pat::Comma, TokenKind::Comma) + | (Pat::Eq, TokenKind::Eq) + | (Pat::Lifetime, TokenKind::Lifetime { .. }) + | (Pat::Lt, TokenKind::Lt) + | (Pat::Gt, TokenKind::Gt) + | (Pat::OpenBrace, TokenKind::OpenBrace) + | (Pat::OpenBracket, TokenKind::OpenBracket) + | (Pat::OpenParen, TokenKind::OpenParen) + | (Pat::Pound, TokenKind::Pound) + | (Pat::Semi, TokenKind::Semi) + | ( + Pat::LitStr, + TokenKind::Literal { + kind: LiteralKind::Str { terminated: true } | LiteralKind::RawStr { .. }, + .. + }, + ) => { + self.step(); + return true; + }, + (Pat::Ident(x), TokenKind::Ident) if x == self.peek_text() => { + self.step(); + return true; + }, + (Pat::DoubleColon, TokenKind::Colon) => { + self.step(); + if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) { + self.step(); + return true; + } + return false; + }, + #[rustfmt::skip] + ( + Pat::CaptureLitStr, + TokenKind::Literal { + kind: + LiteralKind::Str { terminated: true } + | LiteralKind::RawStr { n_hashes: Some(_) }, + .. + }, + ) + | (Pat::CaptureIdent, TokenKind::Ident) => { + *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; + self.step(); + return true; + }, + _ => return false, + } + } + } + + /// Consumes all tokens until the specified identifier is found and returns its + /// position. Returns `None` if the identifier could not be found. + /// + /// The cursor will be positioned immediately after the identifier, or at the end if + /// it is not. + pub fn find_ident(&mut self, ident: &str) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident if self.peek_text() == ident => { + let pos = self.pos; + self.step(); + return Some(pos); + }, + TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + /// Consumes all tokens until the next identifier is found and captures it. Returns + /// `None` if no identifier could be found. + /// + /// The cursor will be positioned immediately after the identifier, or at the end if + /// it is not. + pub fn find_any_ident(&mut self) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident => { + let res = Capture { + pos: self.pos, + len: self.next_token.len, + }; + self.step(); + return Some(res); + }, + TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + /// Continually attempt to match the pattern on subsequent tokens until a match is + /// found. Returns whether the pattern was successfully matched. + /// + /// Not generally suitable for multi-token patterns or patterns that can match + /// nothing. + #[must_use] + pub fn find_pat(&mut self, pat: Pat<'_>) -> bool { + let mut capture = [].iter_mut(); + while !self.match_impl(pat, &mut capture) { + self.step(); + if self.at_end() { + return false; + } + } + true + } + + /// Attempts to match a sequence of patterns at the current position. Returns whether + /// all patterns were successfully matched. + /// + /// Captures will be written to the given slice in the order they're matched. If a + /// capture is matched, but there are no more capture slots this will panic. If the + /// match is completed without filling all the capture slots they will be left + /// unmodified. + /// + /// If the match fails the cursor will be positioned at the first failing token. + #[must_use] + pub fn match_all(&mut self, pats: &[Pat<'_>], captures: &mut [Capture]) -> bool { + let mut captures = captures.iter_mut(); + pats.iter().all(|&p| self.match_impl(p, &mut captures)) + } + + /// Attempts to match a single pattern at the current position. Returns whether the + /// pattern was successfully matched. + /// + /// If the pattern attempts to capture anything this will panic. If the match fails + /// the cursor will be positioned at the first failing token. + #[must_use] + pub fn match_pat(&mut self, pat: Pat<'_>) -> bool { + self.match_impl(pat, &mut [].iter_mut()) + } +} diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 15392dd1d292..d11070bab85b 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -23,7 +23,7 @@ pub fn bump_version(mut version: Version) { dst.push_str(&src[..package.version_range.start]); write!(dst, "\"{}\"", version.toml_display()).unwrap(); dst.push_str(&src[package.version_range.end..]); - UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range)) + UpdateStatus::from_changed(src.get(package.version_range) != dst.get(package.version_range)) } }); } diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index d62597428e21..8e30eb7ce95b 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,7 +1,9 @@ -use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::cursor::{self, Capture, Cursor}; +use crate::parse::{ParseCx, RenamedLint}; +use crate::update_lints::generate_lint_files; use crate::utils::{ - ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, - delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, + ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, + expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, }; use rustc_lexer::TokenKind; use std::ffi::OsString; @@ -24,36 +26,35 @@ use std::path::Path; /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. #[expect(clippy::too_many_lines)] -pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) { - if let Some((prefix, _)) = old_name.split_once("::") { - panic!("`{old_name}` should not contain the `{prefix}` prefix"); - } - if let Some((prefix, _)) = new_name.split_once("::") { - panic!("`{new_name}` should not contain the `{prefix}` prefix"); - } - +pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) { let mut updater = FileUpdater::default(); - let mut lints = find_lint_decls(); - let (deprecated_lints, mut renamed_lints) = read_deprecated_lints(); + let mut lints = cx.find_lint_decls(); + let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); - let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else { + let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else { panic!("could not find lint `{old_name}`"); }; let lint = &lints[lint_idx]; - let old_name_prefixed = String::from_iter(["clippy::", old_name]); + let old_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", old_name]); + cx.arena.alloc_str(buf) + }); let new_name_prefixed = if uplift { - new_name.to_owned() + new_name } else { - String::from_iter(["clippy::", new_name]) + cx.str_buf.with(|buf| { + buf.extend(["clippy::", new_name]); + cx.arena.alloc_str(buf) + }) }; for lint in &mut renamed_lints { if lint.new_name == old_name_prefixed { - lint.new_name.clone_from(&new_name_prefixed); + lint.new_name = new_name_prefixed; } } - match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) { + match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { Ok(_) => { println!("`{old_name}` already has a rename registered"); return; @@ -63,12 +64,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b idx, RenamedLint { old_name: old_name_prefixed, - new_name: if uplift { - new_name.to_owned() - } else { - String::from_iter(["clippy::", new_name]) - }, - version: clippy_version.rust_display().to_string(), + new_name: new_name_prefixed, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), }, ); }, @@ -104,7 +101,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } delete_test_files(old_name, change_prefixed_tests); lints.remove(lint_idx); - } else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() { + } else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { let lint = &mut lints[lint_idx]; if lint.module.ends_with(old_name) && lint @@ -118,13 +115,15 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b mod_edit = ModEdit::Rename; } - let mod_len = lint.module.len(); - lint.module.truncate(mod_len - old_name.len()); - lint.module.push_str(new_name); + lint.module = cx.str_buf.with(|buf| { + buf.push_str(&lint.module[..lint.module.len() - old_name.len()]); + buf.push_str(new_name); + cx.arena.alloc_str(buf) + }); } rename_test_files(old_name, new_name, change_prefixed_tests); - new_name.clone_into(&mut lints[lint_idx].name); - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); + lints[lint_idx].name = new_name; + lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); } else { println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); println!("Since `{new_name}` already exists the existing code has not been changed"); @@ -132,7 +131,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut update_fn = file_update_fn(old_name, new_name, mod_edit); - for e in walk_dir_no_dot_or_target() { + for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { updater.update_file(e.path(), &mut update_fn); @@ -285,47 +284,46 @@ fn file_update_fn<'a, 'b>( move |_, src, dst| { let mut copy_pos = 0u32; let mut changed = false; - let mut searcher = RustSearcher::new(src); - let mut capture = ""; + let mut cursor = Cursor::new(src); + let mut captures = [Capture::EMPTY]; loop { - match searcher.peek() { + match cursor.peek() { TokenKind::Eof => break, TokenKind::Ident => { - let match_start = searcher.pos(); - let text = searcher.peek_text(); - searcher.step(); + let match_start = cursor.pos(); + let text = cursor.peek_text(); + cursor.step(); match text { // clippy::line_name or clippy::lint-name "clippy" => { - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; } }, // mod lint_name "mod" => { if !matches!(mod_edit, ModEdit::None) - && searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + && let Some(pos) = cursor.find_ident(old_name) { match mod_edit { ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, - ModEdit::Delete if searcher.match_tokens(&[Token::Semi], &mut []) => { + ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => { let mut start = &src[copy_pos as usize..match_start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } dst.push_str(start); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); if src[copy_pos as usize..].starts_with("\n\n") { copy_pos += 1; } @@ -337,8 +335,8 @@ fn file_update_fn<'a, 'b>( }, // lint_name:: name if matches!(mod_edit, ModEdit::Rename) && name == old_name => { - let name_end = searcher.pos(); - if searcher.match_tokens(&[Token::DoubleColon], &mut []) { + let name_end = cursor.pos(); + if cursor.match_pat(cursor::Pat::DoubleColon) { dst.push_str(&src[copy_pos as usize..match_start as usize]); dst.push_str(new_name); copy_pos = name_end; @@ -356,36 +354,36 @@ fn file_update_fn<'a, 'b>( }; dst.push_str(&src[copy_pos as usize..match_start as usize]); dst.push_str(replacement); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, } }, // //~ lint_name TokenKind::LineComment { doc_style: None } => { - let text = searcher.peek_text(); + let text = cursor.peek_text(); if text.starts_with("//~") && let Some(text) = text.strip_suffix(old_name) && !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize + text.len()]); + dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]); dst.push_str(new_name); - copy_pos = searcher.pos() + searcher.peek_len(); + copy_pos = cursor.pos() + cursor.peek_len(); changed = true; } - searcher.step(); + cursor.step(); }, // ::lint_name TokenKind::Colon - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name => + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, - _ => searcher.step(), + _ => cursor.step(), } } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5f6e874ffe25..3d0da6846114 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,10 @@ -use crate::utils::{ - ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn, -}; +use crate::parse::cursor::Cursor; +use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; use std::collections::HashSet; use std::fmt::Write; -use std::fs; -use std::ops::Range; -use std::path::{self, Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use std::path::{self, Path}; const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ @@ -24,18 +21,18 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht /// # Panics /// /// Panics if a file path could not read from or then written to -pub fn update(update_mode: UpdateMode) { - let lints = find_lint_decls(); - let (deprecated, renamed) = read_deprecated_lints(); +pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { + let lints = cx.find_lint_decls(); + let (deprecated, renamed) = cx.read_deprecated_lints(); generate_lint_files(update_mode, &lints, &deprecated, &renamed); } #[expect(clippy::too_many_lines)] pub fn generate_lint_files( update_mode: UpdateMode, - lints: &[Lint], - deprecated: &[DeprecatedLint], - renamed: &[RenamedLint], + lints: &[Lint<'_>], + deprecated: &[DeprecatedLint<'_>], + renamed: &[RenamedLint<'_>], ) { let mut updater = FileUpdater::default(); updater.update_file_checked( @@ -64,7 +61,7 @@ pub fn generate_lint_files( |dst| { for lint in lints .iter() - .map(|l| &*l.name) + .map(|l| l.name) .chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) .chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) .sorted() @@ -79,13 +76,13 @@ pub fn generate_lint_files( update_mode, "clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - let mut searcher = RustSearcher::new(src); + let mut cursor = Cursor::new(src); assert!( - searcher.find_token(Token::Ident("declare_with_version")) - && searcher.find_token(Token::Ident("declare_with_version")), + cursor.find_ident("declare_with_version").is_some() + && cursor.find_ident("declare_with_version").is_some(), "error reading deprecated lints" ); - dst.push_str(&src[..searcher.pos() as usize]); + dst.push_str(&src[..cursor.pos() as usize]); dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); for lint in deprecated { write!( @@ -135,13 +132,13 @@ pub fn generate_lint_files( dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); for lint in renamed { - if seen_lints.insert(&lint.new_name) { + if seen_lints.insert(lint.new_name) { writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); } } seen_lints.clear(); for lint in renamed { - if seen_lints.insert(&lint.old_name) { + if seen_lints.insert(lint.old_name) { writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); } } @@ -167,7 +164,7 @@ pub fn generate_lint_files( for lint_mod in lints .iter() .filter(|l| !l.module.is_empty()) - .map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0)) + .map(|l| l.module.split_once("::").map_or(l.module, |x| x.0)) .sorted() .dedup() { @@ -200,260 +197,3 @@ pub fn generate_lint_files( fn round_to_fifty(count: usize) -> usize { count / 50 * 50 } - -/// Lint data parsed from the Clippy source code. -#[derive(PartialEq, Eq, Debug)] -pub struct Lint { - pub name: String, - pub group: String, - pub module: String, - pub path: PathBuf, - pub declaration_range: Range, -} - -pub struct DeprecatedLint { - pub name: String, - pub reason: String, - pub version: String, -} - -pub struct RenamedLint { - pub old_name: String, - pub new_name: String, - pub version: String, -} - -/// Finds all lint declarations (`declare_clippy_lint!`) -#[must_use] -pub fn find_lint_decls() -> Vec { - let mut lints = Vec::with_capacity(1000); - let mut contents = String::new(); - for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { - let e = expect_action(e, ErrAction::Read, "."); - if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() { - continue; - } - let Ok(mut name) = e.file_name().into_string() else { - continue; - }; - if name.starts_with("clippy_lints") && name != "clippy_lints_internal" { - name.push_str("/src"); - for (file, module) in read_src_with_module(name.as_ref()) { - parse_clippy_lint_decls( - file.path(), - File::open_read_to_cleared_string(file.path(), &mut contents), - &module, - &mut lints, - ); - } - } - } - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - lints -} - -/// Reads the source files from the given root directory -fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator { - WalkDir::new(src_root).into_iter().filter_map(move |e| { - let e = expect_action(e, ErrAction::Read, src_root); - let path = e.path().as_os_str().as_encoded_bytes(); - if let Some(path) = path.strip_suffix(b".rs") - && let Some(path) = path.get(src_root.as_os_str().len() + 1..) - { - if path == b"lib" { - Some((e, String::new())) - } else { - let path = if let Some(path) = path.strip_suffix(b"mod") - && let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\")) - { - path - } else { - path - }; - if let Ok(path) = str::from_utf8(path) { - let path = path.replace(['/', '\\'], "::"); - Some((e, path)) - } else { - None - } - } - } else { - None - } - }) -} - -/// Parse a source file looking for `declare_clippy_lint` macro invocations. -fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // !{ /// docs - Bang, OpenBrace, AnyComment, - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, - // pub NAME, GROUP, - Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, - ]; - - let mut searcher = RustSearcher::new(contents); - while searcher.find_token(Ident("declare_clippy_lint")) { - let start = searcher.pos() as usize - "declare_clippy_lint".len(); - let (mut name, mut group) = ("", ""); - if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { - lints.push(Lint { - name: name.to_lowercase(), - group: group.into(), - module: module.into(), - path: path.into(), - declaration_range: start..searcher.pos() as usize, - }); - } - } -} - -#[must_use] -pub fn read_deprecated_lints() -> (Vec, Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, - // ("first", "second"), - OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, - ]; - #[rustfmt::skip] - static DEPRECATED_TOKENS: &[Token<'_>] = &[ - // !{ DEPRECATED(DEPRECATED_VERSION) = [ - Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - #[rustfmt::skip] - static RENAMED_TOKENS: &[Token<'_>] = &[ - // !{ RENAMED(RENAMED_VERSION) = [ - Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - - let path = "clippy_lints/src/deprecated_lints.rs"; - let mut deprecated = Vec::with_capacity(30); - let mut renamed = Vec::with_capacity(80); - let mut contents = String::new(); - File::open_read_to_cleared_string(path, &mut contents); - - let mut searcher = RustSearcher::new(&contents); - - // First instance is the macro definition. - assert!( - searcher.find_token(Ident("declare_with_version")), - "error reading deprecated lints" - ); - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { - let mut version = ""; - let mut name = ""; - let mut reason = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { - deprecated.push(DeprecatedLint { - name: parse_str_single_line(path.as_ref(), name), - reason: parse_str_single_line(path.as_ref(), reason), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading deprecated lints"); - } - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { - let mut version = ""; - let mut old_name = ""; - let mut new_name = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) { - renamed.push(RenamedLint { - old_name: parse_str_single_line(path.as_ref(), old_name), - new_name: parse_str_single_line(path.as_ref(), new_name), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading renamed lints"); - } - - deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name)); - (deprecated, renamed) -} - -/// Removes the line splices and surrounding quotes from a string literal -fn parse_str_lit(s: &str) -> String { - let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#'); - let s = s - .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()); - rustc_literal_escaper::unescape_str(s, &mut |_, ch| { - if let Ok(ch) = ch { - res.push(ch); - } - }); - res -} - -fn parse_str_single_line(path: &Path, s: &str) -> String { - let value = parse_str_lit(s); - assert!( - !value.contains('\n'), - "error parsing `{}`: `{s}` should be a single line string", - path.display(), - ); - value -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_clippy_lint_decls() { - 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_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![ - Lint { - name: "ptr_arg".into(), - group: "style".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - Lint { - name: "doc_markdown".into(), - group: "pedantic".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - ]; - assert_eq!(expected, result); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 057951d0e33b..52452dd86b49 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,9 +1,9 @@ use core::fmt::{self, Display}; +use core::marker::PhantomData; use core::num::NonZero; -use core::ops::Range; -use core::slice; +use core::ops::{Deref, DerefMut}; +use core::range::Range; use core::str::FromStr; -use rustc_lexer::{self as lexer, FrontmatterAllowed}; use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; @@ -12,6 +12,24 @@ use std::process::{self, Command, Stdio}; use std::{env, thread}; use walkdir::WalkDir; +pub struct Scoped<'inner, 'outer: 'inner, T>(T, PhantomData<&'inner mut T>, PhantomData<&'outer mut ()>); +impl Scoped<'_, '_, T> { + pub fn new(value: T) -> Self { + Self(value, PhantomData, PhantomData) + } +} +impl Deref for Scoped<'_, '_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Scoped<'_, '_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Clone, Copy)] pub enum ErrAction { Open, @@ -410,179 +428,6 @@ pub fn update_text_region_fn( move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert) } -#[derive(Clone, Copy)] -pub enum Token<'a> { - /// Matches any number of comments / doc comments. - AnyComment, - Ident(&'a str), - CaptureIdent, - LitStr, - CaptureLitStr, - Bang, - CloseBrace, - CloseBracket, - CloseParen, - /// This will consume the first colon even if the second doesn't exist. - DoubleColon, - Comma, - Eq, - Lifetime, - Lt, - Gt, - OpenBrace, - OpenBracket, - OpenParen, - Pound, - Semi, -} - -pub struct RustSearcher<'txt> { - text: &'txt str, - cursor: lexer::Cursor<'txt>, - pos: u32, - next_token: lexer::Token, -} -impl<'txt> RustSearcher<'txt> { - #[must_use] - #[expect(clippy::inconsistent_struct_constructor)] - pub fn new(text: &'txt str) -> Self { - let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); - Self { - text, - pos: 0, - next_token: cursor.advance_token(), - cursor, - } - } - - #[must_use] - pub fn peek_text(&self) -> &'txt str { - &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] - } - - #[must_use] - pub fn peek_len(&self) -> u32 { - self.next_token.len - } - - #[must_use] - pub fn peek(&self) -> lexer::TokenKind { - self.next_token.kind - } - - #[must_use] - pub fn pos(&self) -> u32 { - self.pos - } - - #[must_use] - pub fn at_end(&self) -> bool { - self.next_token.kind == lexer::TokenKind::Eof - } - - pub fn step(&mut self) { - // `next_len` is zero for the sentinel value and the eof marker. - self.pos += self.next_token.len; - self.next_token = self.cursor.advance_token(); - } - - /// Consumes the next token if it matches the requested value and captures the value if - /// requested. Returns true if a token was matched. - fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool { - loop { - match (token, self.next_token.kind) { - (_, lexer::TokenKind::Whitespace) - | ( - Token::AnyComment, - lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, - ) => self.step(), - (Token::AnyComment, _) => return true, - (Token::Bang, lexer::TokenKind::Bang) - | (Token::CloseBrace, lexer::TokenKind::CloseBrace) - | (Token::CloseBracket, lexer::TokenKind::CloseBracket) - | (Token::CloseParen, lexer::TokenKind::CloseParen) - | (Token::Comma, lexer::TokenKind::Comma) - | (Token::Eq, lexer::TokenKind::Eq) - | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) - | (Token::Lt, lexer::TokenKind::Lt) - | (Token::Gt, lexer::TokenKind::Gt) - | (Token::OpenBrace, lexer::TokenKind::OpenBrace) - | (Token::OpenBracket, lexer::TokenKind::OpenBracket) - | (Token::OpenParen, lexer::TokenKind::OpenParen) - | (Token::Pound, lexer::TokenKind::Pound) - | (Token::Semi, lexer::TokenKind::Semi) - | ( - Token::LitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) => { - self.step(); - return true; - }, - (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { - self.step(); - return true; - }, - (Token::DoubleColon, lexer::TokenKind::Colon) => { - self.step(); - if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) { - self.step(); - return true; - } - return false; - }, - ( - Token::CaptureLitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) - | (Token::CaptureIdent, lexer::TokenKind::Ident) => { - **captures.next().unwrap() = self.peek_text(); - self.step(); - return true; - }, - _ => return false, - } - } - } - - #[must_use] - pub fn find_token(&mut self, token: Token<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return false; - } - } - true - } - - #[must_use] - pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> { - let mut res = ""; - let mut capture = &mut res; - let mut capture = slice::from_mut(&mut capture).iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return None; - } - } - Some(res) - } - - #[must_use] - pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool { - let mut captures = captures.iter_mut(); - tokens.iter().all(|&t| self.read_token(t, &mut captures)) - } -} - #[track_caller] pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { match OpenOptions::new().create_new(true).write(true).open(new_name) { @@ -748,8 +593,8 @@ pub fn delete_dir_if_exists(path: &Path) { } /// Walks all items excluding top-level dot files/directories and any target directories. -pub fn walk_dir_no_dot_or_target() -> impl Iterator> { - WalkDir::new(".").into_iter().filter_entry(|e| { +pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator> { + WalkDir::new(p).into_iter().filter_entry(|e| { e.path() .file_name() .is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.')) diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 70184ee2ca59..bc97746a1cba 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.91" +version = "0.1.93" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -18,12 +18,17 @@ itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" serde = { version = "1.0", features = ["derive"] } -toml = "0.7.3" unicode-normalization = "0.1" unicode-script = { version = "0.5", default-features = false } semver = "1.0" url = "2.2" +[dependencies.toml] +version = "0.9.7" +default-features = false +# preserve_order keeps diagnostic output in file order +features = ["parse", "preserve_order"] + [dev-dependencies] walkdir = "2.3" diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 36498adff502..454026e80ab3 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -167,7 +167,7 @@ declare_clippy_lint! { impl_lint_pass!(ArbitrarySourceItemOrdering => [ARBITRARY_SOURCE_ITEM_ORDERING]); #[derive(Debug)] -#[allow(clippy::struct_excessive_bools)] // Bools are cached feature flags. +#[expect(clippy::struct_excessive_bools, reason = "Bools are cached feature flags")] pub struct ArbitrarySourceItemOrdering { assoc_types_order: SourceItemOrderingTraitAssocItemKinds, enable_ordering_for_enum: bool, @@ -550,7 +550,6 @@ fn get_item_name(item: &Item<'_>) -> Option { // This case doesn't exist in the clippy tests codebase. None }, - QPath::LangItem(_, _) => None, } } else { // Impls for anything that isn't a named type can be skipped. diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index 085029a744be..acfdfa65baed 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::implements_trait; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -46,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { && let ExprKind::Path(QPath::TypeRelative(func_ty, func_name)) = func.kind && func_name.ident.name == sym::new && !expr.span.from_expansion() - && is_type_diagnostic_item(cx, cx.typeck_results().node_type(func_ty.hir_id), sym::Arc) + && cx.typeck_results().node_type(func_ty.hir_id).is_diag_item(cx, sym::Arc) && let arg_ty = cx.typeck_results().expr_ty(arg) // make sure that the type is not and does not contain any type parameters && arg_ty.walk().all(|arg| { diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index b6684825835a..2586c89bc868 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,10 +1,13 @@ +use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_inside_always_const_context; -use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_node}; +use clippy_utils::macros::{find_assert_args, root_macro_call_first_node}; +use clippy_utils::msrvs::Msrv; +use clippy_utils::{is_inside_always_const_context, msrvs}; +use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::sym; declare_clippy_lint! { @@ -28,56 +31,60 @@ declare_clippy_lint! { "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`" } -declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); +impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); +pub struct AssertionsOnConstants { + msrv: Msrv, +} +impl AssertionsOnConstants { + pub fn new(conf: &Conf) -> Self { + Self { msrv: conf.msrv } + } +} impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - let Some(macro_call) = root_macro_call_first_node(cx, e) else { - return; - }; - let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) { - Some(sym::debug_assert_macro) => true, - Some(sym::assert_macro) => false, - _ => return, - }; - let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { - return; - }; - let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else { - return; - }; + if let Some(macro_call) = root_macro_call_first_node(cx, e) + && let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) { + Some(sym::debug_assert_macro) => true, + Some(sym::assert_macro) => false, + _ => return, + } + && let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn) + && let Some((Constant::Bool(assert_val), const_src)) = + ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt()) + && let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id) + && (const_src.is_local() || !in_const_context) + && !(is_debug && as_bool_lit(condition) == Some(false)) + { + let (msg, help) = if !const_src.is_local() { + let help = if self.msrv.meets(cx, msrvs::CONST_BLOCKS) { + "consider moving this into a const block: `const { assert!(..) }`" + } else if self.msrv.meets(cx, msrvs::CONST_PANIC) { + "consider moving this to an anonymous constant: `const _: () = { assert!(..); }`" + } else { + return; + }; + ("this assertion has a constant value", help) + } else if assert_val { + ("this assertion is always `true`", "remove the assertion") + } else { + ( + "this assertion is always `false`", + "replace this with `panic!()` or `unreachable!()`", + ) + }; - match condition.kind { - ExprKind::Path(..) | ExprKind::Lit(_) => {}, - _ if is_inside_always_const_context(cx.tcx, e.hir_id) => return, - _ => {}, + span_lint_and_help(cx, ASSERTIONS_ON_CONSTANTS, macro_call.span, msg, None, help); } + } +} - if val { - span_lint_and_help( - cx, - ASSERTIONS_ON_CONSTANTS, - macro_call.span, - format!( - "`{}!(true)` will be optimized out by the compiler", - cx.tcx.item_name(macro_call.def_id) - ), - None, - "remove it", - ); - } else if !is_debug { - let (assert_arg, panic_arg) = match panic_expn { - PanicExpn::Empty => ("", ""), - _ => (", ..", ".."), - }; - span_lint_and_help( - cx, - ASSERTIONS_ON_CONSTANTS, - macro_call.span, - format!("`assert!(false{assert_arg})` should probably be replaced"), - None, - format!("use `panic!({panic_arg})` or `unreachable!({panic_arg})`"), - ); - } +fn as_bool_lit(e: &Expr<'_>) -> Option { + if let ExprKind::Lit(l) = e.kind + && let LitKind::Bool(b) = l.node + { + Some(b) + } else { + None } } diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index 08253b0c4995..cc62306b33b5 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_node}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; +use clippy_utils::sym; +use clippy_utils::ty::{has_debug_impl, is_copy}; use clippy_utils::usage::local_used_after_expr; -use clippy_utils::{path_res, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{Expr, ExprKind, Node}; @@ -55,13 +56,13 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { && let ExprKind::MethodCall(method_segment, recv, [], _) = condition.kind && let result_type_with_refs = cx.typeck_results().expr_ty(recv) && let result_type = result_type_with_refs.peel_refs() - && is_type_diagnostic_item(cx, result_type, sym::Result) + && result_type.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = result_type.kind() { if !is_copy(cx, result_type) { if result_type_with_refs != result_type { return; - } else if let Res::Local(binding_id) = path_res(cx, recv) + } else if let Res::Local(binding_id) = *recv.basic_res() && local_used_after_expr(cx, binding_id, recv) { return; diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 52287be34c78..efce23d13a38 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -2,8 +2,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::mir::{PossibleBorrowerMap, enclosing_mir}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_diag_trait_item, is_in_test, last_path_segment, local_is_initialized, path_to_local, sym}; +use clippy_utils::{is_in_test, last_path_segment, local_is_initialized, sym}; use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -68,15 +69,15 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = e.kind && let typeck = cx.typeck_results() - && let (call_kind, fn_name, fn_id, fn_arg, fn_gen_args) = match rhs.kind { + && let (call_kind, fn_name, fn_def, fn_arg, fn_gen_args) = match rhs.kind { ExprKind::Call(f, [arg]) if let ExprKind::Path(fn_path) = &f.kind - && let Some(id) = typeck.qpath_res(fn_path, f.hir_id).opt_def_id() => + && let Some(def) = typeck.qpath_res(fn_path, f.hir_id).opt_def(cx) => { - (CallKind::Ufcs, last_path_segment(fn_path).ident.name, id, arg, typeck.node_args(f.hir_id)) + (CallKind::Ufcs, last_path_segment(fn_path).ident.name, def, arg, typeck.node_args(f.hir_id)) }, - ExprKind::MethodCall(name, recv, [], _) if let Some(id) = typeck.type_dependent_def_id(rhs.hir_id) => { - (CallKind::Method, name.ident.name, id, recv, typeck.node_args(rhs.hir_id)) + ExprKind::MethodCall(name, recv, [], _) if let Some(def) = typeck.type_dependent_def(rhs.hir_id) => { + (CallKind::Method, name.ident.name, def, recv, typeck.node_args(rhs.hir_id)) }, _ => return, } @@ -84,20 +85,20 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones { // Don't lint in macros. && ctxt.is_root() && let which_trait = match fn_name { - sym::clone if is_diag_trait_item(cx, fn_id, sym::Clone) => CloneTrait::Clone, + sym::clone if fn_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Clone) => CloneTrait::Clone, sym::to_owned - if is_diag_trait_item(cx, fn_id, sym::ToOwned) + if fn_def.assoc_fn_parent(cx).is_diag_item(cx, sym::ToOwned) && self.msrv.meets(cx, msrvs::CLONE_INTO) => { CloneTrait::ToOwned }, _ => return, } - && let Ok(Some(resolved_fn)) = Instance::try_resolve(cx.tcx, cx.typing_env(), fn_id, fn_gen_args) + && let Ok(Some(resolved_fn)) = Instance::try_resolve(cx.tcx, cx.typing_env(), fn_def.1, fn_gen_args) // TODO: This check currently bails if the local variable has no initializer. // That is overly conservative - the lint should fire even if there was no initializer, // but the variable has been initialized before `lhs` was evaluated. - && path_to_local(lhs).is_none_or(|lhs| local_is_initialized(cx, lhs)) + && lhs.res_local_id().is_none_or(|lhs| local_is_initialized(cx, lhs)) && let Some(resolved_impl) = cx.tcx.impl_of_assoc(resolved_fn.def_id()) // Derived forms don't implement `clone_from`/`clone_into`. // See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index b9b5cedb5aa7..1cebc18edc90 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -30,6 +30,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { sym::ambiguous_glob_reexports | sym::dead_code | sym::deprecated + | sym::deprecated_in_future | sym::hidden_glob_reexports | sym::unreachable_pub | sym::unused diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 64aeb27df693..f3985603c4d2 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -2,9 +2,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher::has_let_expr; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::implements_trait; use clippy_utils::{eq_expr_value, sym}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -431,9 +432,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio }, ExprKind::MethodCall(path, receiver, args, _) => { let type_of_receiver = cx.typeck_results().expr_ty(receiver); - if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option) - && !is_type_diagnostic_item(cx, type_of_receiver, sym::Result) - { + if !type_of_receiver.is_diag_item(cx, sym::Option) && !type_of_receiver.is_diag_item(cx, sym::Result) { return None; } METHODS_WITH_NEGATION diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index 3b861848f94a..6f51f2343ab5 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_default_equivalent; use clippy_utils::macros::macro_backtrace; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::ty::expr_sig; -use clippy_utils::{is_default_equivalent, path_def_id}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; -use rustc_hir::{AmbigArg, Block, Expr, ExprKind, HirId, LetStmt, Node, QPath, Ty, TyKind}; +use rustc_hir::{AmbigArg, Block, Expr, ExprKind, HirId, LangItem, LetStmt, Node, QPath, Ty, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; use rustc_span::{Span, sym}; @@ -44,7 +45,7 @@ impl LateLintPass<'_> for BoxDefault { // And that method is `new` && seg.ident.name == sym::new // And the call is that of a `Box` method - && path_def_id(cx, ty).is_some_and(|id| Some(id) == cx.tcx.lang_items().owned_box()) + && ty.basic_res().is_lang_item(cx, LangItem::OwnedBox) // And the single argument to the call is another function call // This is the `T::default()` (or default equivalent) of `Box::new(T::default())` && let ExprKind::Call(arg_path, _) = arg.kind diff --git a/clippy_lints/src/cargo/lint_groups_priority.rs b/clippy_lints/src/cargo/lint_groups_priority.rs index ffd6c520c9ae..14c5e22fb9cd 100644 --- a/clippy_lints/src/cargo/lint_groups_priority.rs +++ b/clippy_lints/src/cargo/lint_groups_priority.rs @@ -4,142 +4,103 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_lint::{LateContext, unerased_lint_store}; use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use std::ops::Range; use std::path::Path; use toml::Spanned; +use toml::de::{DeTable, DeValue}; -#[derive(Deserialize, Serialize, Debug)] -struct LintConfigTable { - level: String, - priority: Option, +fn toml_span(range: Range, file: &SourceFile) -> Span { + Span::new( + file.start_pos + BytePos::from_usize(range.start), + file.start_pos + BytePos::from_usize(range.end), + SyntaxContext::root(), + None, + ) } -#[derive(Deserialize, Debug)] -#[serde(untagged)] -enum LintConfig { - Level(String), - Table(LintConfigTable), +struct LintConfig<'a> { + sp: Range, + level: &'a str, + priority: Option, } - -impl LintConfig { - fn level(&self) -> &str { - match self { - LintConfig::Level(level) => level, - LintConfig::Table(table) => &table.level, - } - } - +impl<'a> LintConfig<'a> { fn priority(&self) -> i64 { - match self { - LintConfig::Level(_) => 0, - LintConfig::Table(table) => table.priority.unwrap_or(0), - } + self.priority.unwrap_or(0) } fn is_implicit(&self) -> bool { - if let LintConfig::Table(table) = self { - table.priority.is_none() - } else { - true - } + self.priority.is_none() } -} - -type LintTable = BTreeMap, Spanned>; - -#[derive(Deserialize, Debug, Default)] -struct Lints { - #[serde(default)] - rust: LintTable, - #[serde(default)] - clippy: LintTable, -} - -#[derive(Deserialize, Debug, Default)] -struct Workspace { - #[serde(default)] - lints: Lints, -} -#[derive(Deserialize, Debug)] -struct CargoToml { - #[serde(default)] - lints: Lints, - #[serde(default)] - workspace: Workspace, -} - -fn toml_span(range: Range, file: &SourceFile) -> Span { - Span::new( - file.start_pos + BytePos::from_usize(range.start), - file.start_pos + BytePos::from_usize(range.end), - SyntaxContext::root(), - None, - ) + fn parse(value: &'a Spanned>) -> Option { + let sp = value.span(); + let (level, priority) = match value.get_ref() { + DeValue::String(level) => (&**level, None), + DeValue::Table(tbl) => { + let level = tbl.get("level")?.get_ref().as_str()?; + let priority = if let Some(priority) = tbl.get("priority") { + let priority = priority.get_ref().as_integer()?; + Some(i64::from_str_radix(priority.as_str(), priority.radix()).ok()?) + } else { + None + }; + (level, priority) + }, + _ => return None, + }; + Some(Self { sp, level, priority }) + } } -fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet<&str>, file: &SourceFile) { +fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet<&str>, file: &SourceFile) { let mut lints = Vec::new(); let mut groups = Vec::new(); for (name, config) in table { - if name.get_ref() == "warnings" { - continue; - } - - if known_groups.contains(name.get_ref().as_str()) { - groups.push((name, config)); - } else { - lints.push((name, config.into_inner())); + if name.get_ref() != "warnings" + && let Some(config) = LintConfig::parse(config) + { + if known_groups.contains(&**name.get_ref()) { + groups.push((name, config)); + } else { + lints.push((name, config)); + } } } for (group, group_config) in groups { - let priority = group_config.get_ref().priority(); - let level = group_config.get_ref().level(); - if let Some((conflict, _)) = lints - .iter() - .rfind(|(_, lint_config)| lint_config.priority() == priority && lint_config.level() != level) - { + if let Some((conflict, _)) = lints.iter().rfind(|(_, lint_config)| { + lint_config.priority() == group_config.priority() && lint_config.level != group_config.level + }) { span_lint_and_then( cx, LINT_GROUPS_PRIORITY, toml_span(group.span(), file), format!( - "lint group `{}` has the same priority ({priority}) as a lint", - group.as_ref() + "lint group `{}` has the same priority ({}) as a lint", + group.as_ref(), + group_config.priority(), ), |diag| { - let config_span = toml_span(group_config.span(), file); + let config_span = toml_span(group_config.sp.clone(), file); - if group_config.as_ref().is_implicit() { + if group_config.is_implicit() { diag.span_label(config_span, "has an implicit priority of 0"); } diag.span_label(toml_span(conflict.span(), file), "has the same priority as this lint"); diag.note("the order of the lints in the table is ignored by Cargo"); - let mut suggestion = String::new(); let low_priority = lints .iter() - .map(|(_, config)| config.priority().saturating_sub(1)) + .map(|(_, lint_config)| lint_config.priority().saturating_sub(1)) .min() .unwrap_or(-1); - Serialize::serialize( - &LintConfigTable { - level: level.into(), - priority: Some(low_priority), - }, - toml::ser::ValueSerializer::new(&mut suggestion), - ) - .unwrap(); diag.span_suggestion_verbose( config_span, format!( "to have lints override the group set `{}` to a lower priority", group.as_ref() ), - suggestion, + format!("{{ level = {:?}, priority = {low_priority} }}", group_config.level,), Applicability::MaybeIncorrect, ); }, @@ -148,10 +109,29 @@ fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet< } } +struct LintTbls<'a> { + rust: Option<&'a DeTable<'a>>, + clippy: Option<&'a DeTable<'a>>, +} +fn get_lint_tbls<'a>(tbl: &'a DeTable<'a>) -> LintTbls<'a> { + if let Some(lints) = tbl.get("lints") + && let Some(lints) = lints.get_ref().as_table() + { + let rust = lints.get("rust").and_then(|x| x.get_ref().as_table()); + let clippy = lints.get("clippy").and_then(|x| x.get_ref().as_table()); + LintTbls { rust, clippy } + } else { + LintTbls { + rust: None, + clippy: None, + } + } +} + pub fn check(cx: &LateContext<'_>) { if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml")) && let Some(src) = file.src.as_deref() - && let Ok(cargo_toml) = toml::from_str::(src) + && let Ok(cargo_toml) = DeTable::parse(src) { let mut rustc_groups = FxHashSet::default(); let mut clippy_groups = FxHashSet::default(); @@ -167,9 +147,23 @@ pub fn check(cx: &LateContext<'_>) { } } - check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file); - check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file); - check_table(cx, cargo_toml.workspace.lints.rust, &rustc_groups, &file); - check_table(cx, cargo_toml.workspace.lints.clippy, &clippy_groups, &file); + let lints = get_lint_tbls(cargo_toml.get_ref()); + if let Some(lints) = lints.rust { + check_table(cx, lints, &rustc_groups, &file); + } + if let Some(lints) = lints.clippy { + check_table(cx, lints, &clippy_groups, &file); + } + if let Some(tbl) = cargo_toml.get_ref().get("workspace") + && let Some(tbl) = tbl.get_ref().as_table() + { + let lints = get_lint_tbls(tbl); + if let Some(lints) = lints.rust { + check_table(cx, lints, &rustc_groups, &file); + } + if let Some(lints) = lints.clippy { + check_table(cx, lints, &clippy_groups, &file); + } + } } } diff --git a/clippy_lints/src/casts/as_underscore.rs b/clippy_lints/src/casts/as_underscore.rs index 3ac486dd63fb..a73e48e5fd5d 100644 --- a/clippy_lints/src/casts/as_underscore.rs +++ b/clippy_lints/src/casts/as_underscore.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::{Expr, Ty, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::IsSuggestable; use super::AS_UNDERSCORE; @@ -10,15 +10,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ty: &'tc if matches!(ty.kind, TyKind::Infer(())) { span_lint_and_then(cx, AS_UNDERSCORE, expr.span, "using `as _` conversion", |diag| { let ty_resolved = cx.typeck_results().expr_ty(expr); - if let ty::Error(_) = ty_resolved.kind() { - diag.help("consider giving the type explicitly"); - } else { + if ty_resolved.is_suggestable(cx.tcx, true) { diag.span_suggestion( ty.span, "consider giving the type explicitly", ty_resolved, Applicability::MachineApplicable, ); + } else { + diag.help("consider giving the type explicitly"); } }); } diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index e26c03ccda93..9eaa6e4cf26e 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -1,4 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -16,7 +19,14 @@ enum EmitState { LintOnPtrSize(u64), } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_op: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + msrv: Msrv, +) { let (Some(from_nbits), Some(to_nbits)) = ( utils::int_ty_to_nbits(cx.tcx, cast_from), utils::int_ty_to_nbits(cx.tcx, cast_to), @@ -85,5 +95,23 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca .note("`usize` and `isize` may be as small as 16 bits on some platforms") .note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types"); } + + if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST) + && let Some(cast) = utils::is_signedness_cast(cast_from, cast_to) + { + let method = match cast { + utils::CastTo::Signed => "cast_signed()", + utils::CastTo::Unsigned => "cast_unsigned()", + }; + let mut app = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app); + + diag.span_suggestion( + expr.span, + format!("if this is intentional, use `{method}` instead"), + format!("{}.{method}", sugg.maybe_paren()), + app, + ); + } }); } diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index d78da9396faf..7d14ba7fcf13 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -43,8 +43,8 @@ fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_f expr.span, format!( "casting from `{cast_from}` to a more-strictly-aligned pointer (`{cast_to}`) ({} < {} bytes)", - from_layout.align.abi.bytes(), - to_layout.align.abi.bytes(), + from_layout.align.bytes(), + to_layout.align.bytes(), ), ); } diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index a70bd8861919..f870d27b796e 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -2,15 +2,18 @@ use std::convert::Infallible; use std::ops::ControlFlow; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use clippy_utils::{method_chain_args, sext, sym}; +use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::Symbol; -use super::CAST_SIGN_LOSS; +use super::{CAST_SIGN_LOSS, utils}; /// A list of methods that can never return a negative value. /// Includes methods that panic rather than returning a negative value. @@ -42,13 +45,33 @@ pub(super) fn check<'cx>( cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>, + msrv: Msrv, ) { if should_lint(cx, cast_op, cast_from, cast_to) { - span_lint( + span_lint_and_then( cx, CAST_SIGN_LOSS, expr.span, format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + |diag| { + if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST) + && let Some(cast) = utils::is_signedness_cast(cast_from, cast_to) + { + let method = match cast { + utils::CastTo::Signed => "cast_signed()", + utils::CastTo::Unsigned => "cast_unsigned()", + }; + let mut app = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app); + + diag.span_suggestion( + expr.span, + format!("if this is intentional, use `{method}` instead"), + format!("{}.{method}", sugg.maybe_paren()), + app, + ); + } + }, ); } } diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs index 92910cf8adf5..be1f406770ce 100644 --- a/clippy_lints/src/casts/manual_dangling_ptr.rs +++ b/clippy_lints/src/casts/manual_dangling_ptr.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{expr_or_init, is_path_diagnostic_item, std_or_core, sym}; +use clippy_utils::{expr_or_init, std_or_core, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind}; @@ -53,7 +54,7 @@ fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool { if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind - && is_path_diagnostic_item(cx, fun, sym::mem_align_of) + && fun.basic_res().is_diag_item(cx, sym::mem_align_of) && let Some(args) = path.segments.last().and_then(|seg| seg.args) && let [GenericArg::Type(generic_ty)] = args.args { @@ -72,7 +73,7 @@ fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned, to: &Ty<'_>) cx.tcx .layout_of(cx.typing_env().as_query_input(to_mid_ty)) .is_ok_and(|layout| { - let align = u128::from(layout.align.abi.bytes()); + let align = u128::from(layout.align.bytes()); u128::from(val) <= align }) } diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index d2e62ee56e43..47cc1da0a6e9 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -890,9 +890,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if cast_to.is_numeric() { cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { - cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_precision_loss::check(cx, expr, cast_from, cast_to); - cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to); + cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to); } diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index c88a0539d70e..7bfe9201d812 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::numeric_literal::NumericLiteral; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{SpanRangeExt, snippet_opt}; use clippy_utils::visitors::{Visitable, for_each_expr_without_closures}; -use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local}; +use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias}; use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -167,11 +168,11 @@ pub(super) fn check<'tcx>( sym::assert_ne_macro, sym::debug_assert_ne_macro, ]; - matches!(expr.span.ctxt().outer_expn_data().macro_def_id, Some(def_id) if + matches!(expr.span.ctxt().outer_expn_data().macro_def_id, Some(def_id) if cx.tcx.get_diagnostic_name(def_id).is_some_and(|sym| ALLOWED_MACROS.contains(&sym))) } - if let Some(id) = path_to_local(cast_expr) + if let Some(id) = cast_expr.res_local_id() && !cx.tcx.hir_span(id).eq_ctxt(cast_expr.span) { // Binding context is different than the identifiers context. diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index d846d78b9ee7..707fc2a8eed3 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -60,3 +60,18 @@ pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 { neg_bits.max(pos_bits).into() } } + +pub(super) enum CastTo { + Signed, + Unsigned, +} +/// Returns `Some` if the type cast is between 2 integral types that differ +/// only in signedness, otherwise `None`. The value of `Some` is which +/// signedness is casted to. +pub(super) fn is_signedness_cast(cast_from: Ty<'_>, cast_to: Ty<'_>) -> Option { + match (cast_from.kind(), cast_to.kind()) { + (ty::Int(from), ty::Uint(to)) if from.to_unsigned() == *to => Some(CastTo::Unsigned), + (ty::Uint(from), ty::Int(to)) if *from == to.to_unsigned() => Some(CastTo::Signed), + _ => None, + } +} diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 72ab292ee3c6..35b799aefb04 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -1,9 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::sugg::Sugg; use clippy_utils::visitors::is_const_evaluatable; -use clippy_utils::{is_in_const_context, is_mutable, is_trait_method}; +use clippy_utils::{is_in_const_context, is_mutable}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -73,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { // check for clones && let ExprKind::MethodCall(_, val, _, _) = item.kind - && is_trait_method(cx, item, sym::Clone) + && cx.ty_based_def(item).opt_parent(cx).is_diag_item(cx, sym::Clone) // check for immutability or purity && (!is_mutable(cx, val) || is_const_evaluatable(cx, val)) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 7646aa48b772..595625c08bef 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -1,9 +1,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{IntoSpan, SpanRangeExt}; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn, sym}; +use clippy_utils::{LimitStack, get_async_fn_body, sym}; use core::ops::ControlFlow; use rustc_hir::intravisit::FnKind; use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl}; @@ -93,7 +93,7 @@ impl CognitiveComplexity { }); let ret_ty = cx.typeck_results().node_type(expr.hir_id); - let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) { + let ret_adjust = if ret_ty.is_diag_item(cx, sym::Result) { returns } else { #[expect(clippy::integer_division)] @@ -147,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { def_id: LocalDefId, ) { if !cx.tcx.has_attr(def_id, sym::test) { - let expr = if is_async_fn(kind) { + let expr = if kind.asyncness().is_async() { match get_async_fn_body(cx.tcx, body) { Some(b) => b, None => { diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index ad610fbd8d2c..b13e307a3f9c 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,16 +1,16 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::msrvs::Msrv; use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; -use clippy_utils::{span_contains_non_whitespace, tokenize_with_text}; -use rustc_ast::BinOpKind; +use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text}; +use rustc_ast::{BinOpKind, MetaItemInner}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, StmtKind}; use rustc_lexer::TokenKind; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -95,14 +95,14 @@ impl CollapsibleIf { fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) { if let Some(else_) = expr_block(else_block) - && cx.tcx.hir_attrs(else_.hir_id).is_empty() && !else_.span.from_expansion() && let ExprKind::If(else_if_cond, ..) = else_.kind - && !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_code) + && self.check_significant_tokens_and_expect_attrs(cx, else_block, else_, sym::collapsible_else_if) { - span_lint_and_then( + span_lint_hir_and_then( cx, COLLAPSIBLE_ELSE_IF, + else_.hir_id, else_block.span, "this `else { if .. }` block can be collapsed", |diag| { @@ -166,15 +166,15 @@ impl CollapsibleIf { fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) { if let Some(inner) = expr_block(then) - && cx.tcx.hir_attrs(inner.hir_id).is_empty() && let ExprKind::If(check_inner, _, None) = &inner.kind && self.eligible_condition(cx, check_inner) && expr.span.eq_ctxt(inner.span) - && !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code) + && self.check_significant_tokens_and_expect_attrs(cx, then, inner, sym::collapsible_if) { - span_lint_and_then( + span_lint_hir_and_then( cx, COLLAPSIBLE_IF, + inner.hir_id, expr.span, "this `if` statement can be collapsed", |diag| { @@ -216,8 +216,46 @@ impl CollapsibleIf { } fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool { - !matches!(cond.kind, ExprKind::Let(..)) - || (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS)) + !matches!(cond.kind, ExprKind::Let(..)) || can_use_if_let_chains(cx, self.msrv) + } + + // Check that nothing significant can be found between the initial `{` of `inner_if` and + // the beginning of `inner_if_expr`... + // + // Unless it's only an `#[expect(clippy::collapsible{,_else}_if)]` attribute, in which case we + // _do_ need to lint, in order to actually fulfill its expectation (#13365) + fn check_significant_tokens_and_expect_attrs( + &self, + cx: &LateContext<'_>, + inner_if: &Block<'_>, + inner_if_expr: &Expr<'_>, + expected_lint_name: Symbol, + ) -> bool { + match cx.tcx.hir_attrs(inner_if_expr.hir_id) { + [] => { + // There aren't any attributes, so just check for significant tokens + let span = inner_if.span.split_at(1).1.until(inner_if_expr.span); + !span_contains_non_whitespace(cx, span, self.lint_commented_code) + }, + + [attr] + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let Some(metas) = attr.meta_item_list() + && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) => + { + // There is an `expect` attribute -- check that there is no _other_ significant text + let span_before_attr = inner_if.span.split_at(1).1.until(attr.span()); + let span_after_attr = attr.span().between(inner_if_expr.span); + !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) + && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) + }, + + // There are other attributes, which are significant tokens -- check failed + _ => false, + } } } @@ -242,18 +280,6 @@ impl LateLintPass<'_> for CollapsibleIf { } } -// Check that nothing significant can be found but whitespaces between the initial `{` of `block` -// and the beginning of `stop_at`. -fn block_starts_with_significant_tokens( - cx: &LateContext<'_>, - block: &Block<'_>, - stop_at: &Expr<'_>, - lint_commented_code: bool, -) -> bool { - let span = block.span.split_at(1).1.until(stop_at.span); - span_contains_non_whitespace(cx, span, lint_commented_code) -} - /// If `block` is a block with either one expression or a statement containing an expression, /// return the expression. We don't peel blocks recursively, as extra blocks might be intentional. fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index 1279be34ed8f..fd84ce70bd71 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::{get_type_diagnostic_name, is_type_lang_item}; +use clippy_utils::get_enclosing_block; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::visitors::{Visitable, for_each_expr}; -use clippy_utils::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_hir::{Body, ExprKind, HirId, LangItem, LetStmt, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { fn match_acceptable_type(cx: &LateContext<'_>, local: &LetStmt<'_>) -> bool { let ty = cx.typeck_results().pat_ty(local.pat); matches!( - get_type_diagnostic_name(cx, ty), + ty.opt_diag_name(cx), Some( sym::BTreeMap | sym::BTreeSet @@ -71,7 +71,7 @@ fn match_acceptable_type(cx: &LateContext<'_>, local: &LetStmt<'_>) -> bool { | sym::Vec | sym::VecDeque ) - ) || is_type_lang_item(cx, ty, LangItem::String) + ) || ty.is_lang_item(cx, LangItem::String) } fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirId, block: T) -> bool { @@ -81,7 +81,7 @@ fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirI // Inspect all expressions and sub-expressions in the block. for_each_expr(cx, block, |expr| { // Ignore expressions that are not simply `id`. - if !path_to_local_id(expr, id) { + if expr.res_local_id() != Some(id) { return ControlFlow::Continue(()); } @@ -93,7 +93,7 @@ fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirI // id = ...; // Not reading `id`. if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id) && let ExprKind::Assign(lhs, ..) = parent.kind - && path_to_local_id(lhs, id) + && lhs.res_local_id() == Some(id) { return ControlFlow::Continue(()); } @@ -107,7 +107,7 @@ fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirI // have side effects, so consider them a read. if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id) && let ExprKind::MethodCall(_, receiver, args, _) = parent.kind - && path_to_local_id(receiver, id) + && receiver.res_local_id() == Some(id) && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && !method_def_id.is_local() { diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index d0c7443a4a4b..a754eea31165 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -84,10 +84,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::collapsible_if::COLLAPSIBLE_IF_INFO, crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO, crate::comparison_chain::COMPARISON_CHAIN_INFO, - crate::copies::BRANCHES_SHARING_CODE_INFO, - crate::copies::IFS_SAME_COND_INFO, - crate::copies::IF_SAME_THEN_ELSE_INFO, - crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::copy_iterator::COPY_ITERATOR_INFO, crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO, crate::create_dir::CREATE_DIR_INFO, @@ -139,7 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::duplicate_mod::DUPLICATE_MOD_INFO, crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, - crate::empty_enum::EMPTY_ENUM_INFO, + crate::empty_enums::EMPTY_ENUMS_INFO, crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, @@ -204,6 +200,10 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::if_let_mutex::IF_LET_MUTEX_INFO, crate::if_not_else::IF_NOT_ELSE_INFO, crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO, + crate::ifs::BRANCHES_SHARING_CODE_INFO, + crate::ifs::IFS_SAME_COND_INFO, + crate::ifs::IF_SAME_THEN_ELSE_INFO, + crate::ifs::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO, crate::impl_hash_with_borrow_str_and_bytes::IMPL_HASH_BORROW_WITH_STR_AND_BYTES_INFO, crate::implicit_hasher::IMPLICIT_HASHER_INFO, @@ -226,11 +226,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO, crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO, crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, - crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO, - crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO, - crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, - crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO, crate::item_name_repetitions::MODULE_INCEPTION_INFO, crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO, @@ -260,7 +256,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::lifetimes::ELIDABLE_LIFETIME_NAMES_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, - crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO, crate::literal_representation::LARGE_DIGIT_GROUPS_INFO, @@ -300,7 +295,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, crate::manual_clamp::MANUAL_CLAMP_INFO, - crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO, crate::manual_float_methods::MANUAL_IS_FINITE_INFO, crate::manual_float_methods::MANUAL_IS_INFINITE_INFO, crate::manual_hash_one::MANUAL_HASH_ONE_INFO, @@ -406,6 +400,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::JOIN_ABSOLUTE_PATHS_INFO, + crate::methods::LINES_FILTER_MAP_OK_INFO, crate::methods::MANUAL_CONTAINS_INFO, crate::methods::MANUAL_C_STR_LITERALS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, @@ -448,10 +443,12 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::OR_THEN_UNWRAP_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::PATH_ENDS_WITH_EXT_INFO, + crate::methods::PTR_OFFSET_WITH_CAST_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::REDUNDANT_AS_STR_INFO, + crate::methods::REDUNDANT_ITER_CLONED_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, @@ -488,6 +485,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, crate::methods::UNNECESSARY_MAP_OR_INFO, crate::methods::UNNECESSARY_MIN_OR_MAX_INFO, + crate::methods::UNNECESSARY_OPTION_MAP_OR_ELSE_INFO, crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, crate::methods::UNNECESSARY_SORT_BY_INFO, crate::methods::UNNECESSARY_TO_OWNED_INFO, @@ -503,7 +501,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::min_ident_chars::MIN_IDENT_CHARS_INFO, crate::minmax::MIN_MAX_INFO, crate::misc::SHORT_CIRCUIT_STATEMENT_INFO, - crate::misc::TOPLEVEL_REF_ARG_INFO, crate::misc::USED_UNDERSCORE_BINDING_INFO, crate::misc::USED_UNDERSCORE_ITEMS_INFO, crate::misc_early::BUILTIN_TYPE_SHADOW_INFO, @@ -534,7 +531,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_mut::MUT_MUT_INFO, - crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO, crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO, crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, @@ -546,7 +542,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::needless_continue::NEEDLESS_CONTINUE_INFO, crate::needless_else::NEEDLESS_ELSE_INFO, crate::needless_for_each::NEEDLESS_FOR_EACH_INFO, - crate::needless_if::NEEDLESS_IF_INFO, + crate::needless_ifs::NEEDLESS_IFS_INFO, crate::needless_late_init::NEEDLESS_LATE_INIT_INFO, crate::needless_maybe_sized::NEEDLESS_MAYBE_SIZED_INFO, crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO, @@ -575,6 +571,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO, crate::octal_escapes::OCTAL_ESCAPES_INFO, crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO, + crate::only_used_in_recursion::SELF_ONLY_USED_IN_RECURSION_INFO, crate::operators::ABSURD_EXTREME_COMPARISONS_INFO, crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO, crate::operators::ASSIGN_OP_PATTERN_INFO, @@ -592,6 +589,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::operators::IMPOSSIBLE_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, crate::operators::INTEGER_DIVISION_INFO, + crate::operators::INTEGER_DIVISION_REMAINDER_USED_INFO, + crate::operators::INVALID_UPCAST_COMPARISONS_INFO, + crate::operators::MANUAL_DIV_CEIL_INFO, crate::operators::MANUAL_IS_MULTIPLE_OF_INFO, crate::operators::MANUAL_MIDPOINT_INFO, crate::operators::MISREFACTORED_ASSIGN_OP_INFO, @@ -625,7 +625,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::ptr::MUT_FROM_REF_INFO, crate::ptr::PTR_ARG_INFO, crate::ptr::PTR_EQ_INFO, - crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO, crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, @@ -657,6 +656,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::regex::REGEX_CREATION_IN_LOOPS_INFO, crate::regex::TRIVIAL_REGEX_INFO, crate::repeat_vec_with_capacity::REPEAT_VEC_WITH_CAPACITY_INFO, + crate::replace_box::REPLACE_BOX_INFO, crate::reserve_after_initialization::RESERVE_AFTER_INITIALIZATION_INFO, crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO, crate::returns::LET_AND_RETURN_INFO, @@ -704,8 +704,11 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, + crate::time_subtraction::MANUAL_INSTANT_ELAPSED_INFO, + crate::time_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO, + crate::toplevel_ref_arg::TOPLEVEL_REF_ARG_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO, @@ -750,6 +753,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO, crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO, crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO, + crate::unnecessary_mut_passed::UNNECESSARY_MUT_PASSED_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO, @@ -778,6 +782,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::visibility::NEEDLESS_PUB_SELF_INFO, crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, crate::visibility::PUB_WITH_SHORTHAND_INFO, + crate::volatile_composites::VOLATILE_COMPOSITES_INFO, crate::wildcard_imports::ENUM_GLOB_USE_INFO, crate::wildcard_imports::WILDCARD_IMPORTS_INFO, crate::write::PRINTLN_EMPTY_STRING_INFO, diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index f8a9037fc804..641f8ae03b72 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -75,7 +75,7 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs { && !base.is_suggestable_infer_ty() { let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())]; - if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) { + if expr.span.check_source_text(cx, |s| s.starts_with('<')) { // Remove `<`, '>` has already been removed by the existing removal expression. removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new())); } diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 88aebc3e6a16..f010d17917f9 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -7,7 +7,6 @@ macro_rules! declare_with_version { $e:expr, )*]) => { pub static $name: &[(&str, &str)] = &[$($e),*]; - #[allow(unused)] pub static $name_version: &[&str] = &[$($version),*]; }; } @@ -18,11 +17,11 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [ ("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"), #[clippy::version = "pre 1.29.0"] ("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"), - #[clippy::version = "1.86.0"] + #[clippy::version = "1.88.0"] ("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"), #[clippy::version = "pre 1.29.0"] ("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"), - #[clippy::version = "1.86.0"] + #[clippy::version = "1.87.0"] ("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"), #[clippy::version = "1.54.0"] ("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"), @@ -34,7 +33,7 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [ ("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"), #[clippy::version = "pre 1.29.0"] ("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"), - #[clippy::version = "1.90.0"] + #[clippy::version = "1.91.0"] ("clippy::string_to_string", "`clippy:implicit_clone` covers those cases"), #[clippy::version = "pre 1.29.0"] ("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"), @@ -86,6 +85,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::drop_copy", "dropping_copy_types"), #[clippy::version = ""] ("clippy::drop_ref", "dropping_references"), + #[clippy::version = "1.92.0"] + ("clippy::empty_enum", "clippy::empty_enums"), #[clippy::version = ""] ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"), #[clippy::version = "1.53.0"] @@ -138,6 +139,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"), #[clippy::version = "1.80.0"] ("clippy::mismatched_target_os", "unexpected_cfgs"), + #[clippy::version = "1.92.0"] + ("clippy::needless_if", "clippy::needless_ifs"), #[clippy::version = ""] ("clippy::new_without_default_derive", "clippy::new_without_default"), #[clippy::version = ""] @@ -184,6 +187,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::transmute_int_to_float", "unnecessary_transmutes"), #[clippy::version = "1.88.0"] ("clippy::transmute_num_to_bytes", "unnecessary_transmutes"), + #[clippy::version = "1.90.0"] + ("clippy::unchecked_duration_subtraction", "clippy::unchecked_time_subtraction"), #[clippy::version = ""] ("clippy::undropped_manually_drops", "undropped_manually_drops"), #[clippy::version = ""] diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 9aa2f3cf0a5b..de1362081323 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop}; +use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; use clippy_utils::{ - DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, - peel_middle_ty_refs, + DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; @@ -239,7 +239,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { return; } - if let Some(local) = path_to_local(expr) { + if let Some(local) = expr.res_local_id() { self.check_local_usage(cx, expr, local); } @@ -261,6 +261,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; self.skip_expr = skip_expr; + if is_from_proc_macro(cx, expr) { + if let Some((state, data)) = self.state.take() { + report(cx, expr, state, data, cx.typeck_results()); + } + return; + } + match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); @@ -364,7 +371,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // priority. if let Some(fn_id) = typeck.type_dependent_def_id(hir_id) && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) - && let arg_ty = cx.tcx.erase_regions(adjusted_ty) + && let arg_ty = cx.tcx.erase_and_anonymize_regions(adjusted_ty) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let args = typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() @@ -942,7 +949,7 @@ fn report<'tcx>( let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); let ty = typeck.expr_ty(expr); - let (_, ref_count) = peel_middle_ty_refs(ty); + let (_, ref_count, _) = peel_and_count_ty_refs(ty); let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { // a deref call changing &T -> &U requires two deref operators the first time // this occurs. One to remove the reference, a second to call the deref impl. @@ -1045,7 +1052,7 @@ fn report<'tcx>( if let ty::Ref(_, dst, _) = data.adjusted_ty.kind() && dst.is_slice() { - let (src, n_src_refs) = peel_middle_ty_refs(ty); + let (src, n_src_refs, _) = peel_and_count_ty_refs(ty); if n_src_refs >= 2 && src.is_array() { return; } diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs deleted file mode 100644 index c53a957f6a8b..000000000000 --- a/clippy_lints/src/derive.rs +++ /dev/null @@ -1,527 +0,0 @@ -use std::ops::ControlFlow; - -use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; -use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths}; -use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; -use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; -use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty::{ - self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast, -}; -use rustc_session::declare_lint_pass; -use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; - -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialEq` implementations for types with a derived `Hash` - /// implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `HashMap`) so it’s probably a bad idea to use a - /// default-generated `Hash` implementation with an explicitly defined - /// `PartialEq`. In particular, the following must hold for any type: - /// - /// ```text - /// k1 == k2 ⇒ hash(k1) == hash(k2) - /// ``` - /// - /// ### Example - /// ```ignore - /// #[derive(Hash)] - /// struct Foo; - /// - /// impl PartialEq for Foo { - /// ... - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DERIVED_HASH_WITH_MANUAL_EQ, - correctness, - "deriving `Hash` but implementing `PartialEq` explicitly" -} - -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` - /// or `PartialOrd` implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `sort`) so it’s probably a bad idea to use a - /// default-generated `Ord` implementation with an explicitly defined - /// `PartialOrd`. In particular, the following must hold for any type - /// implementing `Ord`: - /// - /// ```text - /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() - /// ``` - /// - /// ### Example - /// ```rust,ignore - /// #[derive(Ord, PartialEq, Eq)] - /// struct Foo; - /// - /// impl PartialOrd for Foo { - /// ... - /// } - /// ``` - /// Use instead: - /// ```rust,ignore - /// #[derive(PartialEq, Eq)] - /// struct Foo; - /// - /// impl PartialOrd for Foo { - /// fn partial_cmp(&self, other: &Foo) -> Option { - /// Some(self.cmp(other)) - /// } - /// } - /// - /// impl Ord for Foo { - /// ... - /// } - /// ``` - /// or, if you don't need a custom ordering: - /// ```rust,ignore - /// #[derive(Ord, PartialOrd, PartialEq, Eq)] - /// struct Foo; - /// ``` - #[clippy::version = "1.47.0"] - pub DERIVE_ORD_XOR_PARTIAL_ORD, - correctness, - "deriving `Ord` but implementing `PartialOrd` explicitly" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for explicit `Clone` implementations for `Copy` - /// types. - /// - /// ### Why is this bad? - /// 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. - /// - /// ### Example - /// ```rust,ignore - /// #[derive(Copy)] - /// struct Foo; - /// - /// impl Clone for Foo { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPL_IMPL_CLONE_ON_COPY, - pedantic, - "implementing `Clone` explicitly on `Copy` types" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for deriving `serde::Deserialize` on a type that - /// has methods using `unsafe`. - /// - /// ### Why is this bad? - /// Deriving `serde::Deserialize` will create a constructor - /// that may violate invariants held by another constructor. - /// - /// ### Example - /// ```rust,ignore - /// use serde::Deserialize; - /// - /// #[derive(Deserialize)] - /// pub struct Foo { - /// // .. - /// } - /// - /// impl Foo { - /// pub fn new() -> Self { - /// // setup here .. - /// } - /// - /// pub unsafe fn parts() -> (&str, &str) { - /// // assumes invariants hold - /// } - /// } - /// ``` - #[clippy::version = "1.45.0"] - pub UNSAFE_DERIVE_DESERIALIZE, - pedantic, - "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 - /// ```no_run - /// #[derive(PartialEq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[derive(PartialEq, Eq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - #[clippy::version = "1.63.0"] - pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, - nursery, - "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" -} - -declare_lint_pass!(Derive => [ - EXPL_IMPL_CLONE_ON_COPY, - DERIVED_HASH_WITH_MANUAL_EQ, - DERIVE_ORD_XOR_PARTIAL_ORD, - UNSAFE_DERIVE_DESERIALIZE, - DERIVE_PARTIAL_EQ_WITHOUT_EQ -]); - -impl<'tcx> LateLintPass<'tcx> for Derive { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Impl(Impl { - of_trait: Some(of_trait), - .. - }) = item.kind - { - let trait_ref = &of_trait.trait_ref; - let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); - - check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); - check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); - - 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); - } - } - } -} - -/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint. -fn check_hash_peq<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, - hash_is_automatically_derived: bool, -) { - if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() - && let Some(def_id) = trait_ref.trait_def_id() - && cx.tcx.is_diagnostic_item(sym::Hash, def_id) - { - // Look for the PartialEq implementations for `ty` - cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { - let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); - - if !hash_is_automatically_derived || peq_is_automatically_derived { - return; - } - - let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); - - // Only care about `impl PartialEq for Foo` - // For `impl PartialEq for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { - span_lint_and_then( - cx, - DERIVED_HASH_WITH_MANUAL_EQ, - span, - "you are deriving `Hash` but have implemented `PartialEq` explicitly", - |diag| { - if let Some(local_def_id) = impl_id.as_local() { - let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); - diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here"); - } - }, - ); - } - }); - } -} - -/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. -fn check_ord_partial_ord<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, - ord_is_automatically_derived: bool, -) { - if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) - && let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait() - && let Some(def_id) = &trait_ref.trait_def_id() - && *def_id == ord_trait_def_id - { - // Look for the PartialOrd implementations for `ty` - cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { - let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); - - if partial_ord_is_automatically_derived == ord_is_automatically_derived { - return; - } - - let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); - - // Only care about `impl PartialOrd for Foo` - // For `impl PartialOrd for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { - let mess = if partial_ord_is_automatically_derived { - "you are implementing `Ord` explicitly but have derived `PartialOrd`" - } else { - "you are deriving `Ord` but have implemented `PartialOrd` explicitly" - }; - - span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| { - if let Some(local_def_id) = impl_id.as_local() { - let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); - diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); - } - }); - } - }); - } -} - -/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { - let clone_id = match cx.tcx.lang_items().clone_trait() { - Some(id) if trait_ref.trait_def_id() == Some(id) => id, - _ => return, - }; - let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { - return; - }; - let (ty_adt, ty_subs) = match *ty.kind() { - // Unions can't derive clone. - ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), - _ => return, - }; - // If the current self type doesn't implement Copy (due to generic constraints), search to see if - // there's a Copy impl for any instance of the adt. - if !is_copy(cx, ty) { - if ty_subs.non_erasable_generics().next().is_some() { - let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| { - matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) - if ty_adt.did() == adt.did()) - }); - if !has_copy_impl { - return; - } - } else { - return; - } - } - // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for - // this impl. - if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { - return; - } - // `#[repr(packed)]` structs with type/const parameters can't derive `Clone`. - // https://github.com/rust-lang/rust-clippy/issues/10188 - if ty_adt.repr().packed() - && ty_subs - .iter() - .any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_))) - { - return; - } - // The presence of `unsafe` fields prevents deriving `Clone` automatically - if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) { - return; - } - - span_lint_and_note( - cx, - EXPL_IMPL_CLONE_ON_COPY, - item.span, - "you are implementing `Clone` explicitly on a `Copy` type", - Some(item.span), - "consider deriving `Clone` or removing `Copy`", - ); -} - -/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. -fn check_unsafe_derive_deserialize<'tcx>( - cx: &LateContext<'tcx>, - item: &Item<'_>, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, -) { - fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { - let mut visitor = UnsafeVisitor { cx }; - walk_item(&mut visitor, item).is_break() - } - - if let Some(trait_def_id) = trait_ref.trait_def_id() - && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) - && let ty::Adt(def, _) = ty.kind() - && let Some(local_def_id) = def.did().as_local() - && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) - && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) - && 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)) - { - span_lint_hir_and_then( - cx, - UNSAFE_DERIVE_DESERIALIZE, - adt_hir_id, - item.span, - "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", - |diag| { - diag.help( - "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", - ); - }, - ); - } -} - -struct UnsafeVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, -} - -impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { - type Result = ControlFlow<()>; - type NestedFilter = nested_filter::All; - - fn visit_fn( - &mut self, - kind: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, - body_id: BodyId, - _: Span, - id: LocalDefId, - ) -> Self::Result { - if let Some(header) = kind.header() - && header.is_unsafe() - { - ControlFlow::Break(()) - } else { - walk_fn(self, kind, decl, body_id, id) - } - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { - if let ExprKind::Block(block, _) = expr.kind - && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) - && block - .span - .source_callee() - .and_then(|expr| expr.macro_def_id) - .is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did)) - { - return ControlFlow::Break(()); - } - - walk_expr(self, expr) - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} - -/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { - if let ty::Adt(adt, args) = ty.kind() - && cx.tcx.visibility(adt.did()).is_public() - && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) - && let Some(def_id) = trait_ref.trait_def_id() - && cx.tcx.is_diagnostic_item(sym::PartialEq, def_id) - && !has_non_exhaustive_attr(cx.tcx, *adt) - && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) - && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) - && let Some(local_def_id) = adt.did().as_local() - // If all of our fields implement `Eq`, we can implement `Eq` too - && adt - .all_fields() - .map(|f| f.ty(cx.tcx, args)) - .all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[])) - { - span_lint_hir_and_then( - cx, - DERIVE_PARTIAL_EQ_WITHOUT_EQ, - cx.tcx.local_def_id_to_hir_id(local_def_id), - span.ctxt().outer_expn_data().call_site, - "you are deriving `PartialEq` and can implement `Eq`", - |diag| { - diag.span_suggestion( - span.ctxt().outer_expn_data().call_site, - "consider deriving `Eq` as well", - "PartialEq, Eq", - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool { - tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() -} - -/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. -fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { - // Initial map from generic index to param def. - // Vec<(param_def, needs_eq)> - let mut params = tcx - .generics_of(did) - .own_params - .iter() - .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) - .collect::>(); - - let ty_predicates = tcx.predicates_of(did).predicates; - for (p, _) in ty_predicates { - if let ClauseKind::Trait(p) = p.kind().skip_binder() - && p.trait_ref.def_id == eq_trait_id - && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() - { - // Flag types which already have an `Eq` bound. - params[self_ty.index as usize].1 = false; - } - } - - let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( - params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { - ClauseKind::Trait(TraitPredicate { - trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), - polarity: ty::PredicatePolarity::Positive, - }) - .upcast(tcx) - }), - ))); - ty::TypingEnv { - typing_mode: ty::TypingMode::non_body_analysis(), - param_env, - } -} diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs new file mode 100644 index 000000000000..2bd5e2cbfb1a --- /dev/null +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -0,0 +1,51 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{self as hir, HirId}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Span, sym}; + +use super::DERIVE_ORD_XOR_PARTIAL_ORD; + +/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, + ord_is_automatically_derived: bool, +) { + if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) + && let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait() + && let Some(def_id) = &trait_ref.trait_def_id() + && *def_id == ord_trait_def_id + { + // Look for the PartialOrd implementations for `ty` + cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { + let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); + + if partial_ord_is_automatically_derived == ord_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id); + + // Only care about `impl PartialOrd for Foo` + // For `impl PartialOrd for A, input_types is [A, B] + if trait_ref.instantiate_identity().args.type_at(1) == ty { + let mess = if partial_ord_is_automatically_derived { + "you are implementing `Ord` explicitly but have derived `PartialOrd`" + } else { + "you are deriving `Ord` but have implemented `PartialOrd` explicitly" + }; + + span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, adt_hir_id, span, mess, |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); + diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); + } + }); + } + }); + } +} diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs new file mode 100644 index 000000000000..fbace0bd73ac --- /dev/null +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -0,0 +1,92 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::has_non_exhaustive_attr; +use clippy_utils::ty::implements_trait_with_env; +use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, HirId}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast}; +use rustc_span::{Span, sym}; + +use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ; + +/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { + if let ty::Adt(adt, args) = ty.kind() + && cx.tcx.visibility(adt.did()).is_public() + && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) + && let Some(def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::PartialEq, def_id) + && !has_non_exhaustive_attr(cx.tcx, *adt) + && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) + && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) + // If all of our fields implement `Eq`, we can implement `Eq` too + && adt + .all_fields() + .map(|f| f.ty(cx.tcx, args)) + .all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[])) + { + span_lint_hir_and_then( + cx, + DERIVE_PARTIAL_EQ_WITHOUT_EQ, + adt_hir_id, + span.ctxt().outer_expn_data().call_site, + "you are deriving `PartialEq` and can implement `Eq`", + |diag| { + diag.span_suggestion( + span.ctxt().outer_expn_data().call_site, + "consider deriving `Eq` as well", + "PartialEq, Eq", + Applicability::MachineApplicable, + ); + }, + ); + } +} + +fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool { + tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() +} + +/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. +fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { + // Initial map from generic index to param def. + // Vec<(param_def, needs_eq)> + let mut params = tcx + .generics_of(did) + .own_params + .iter() + .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) + .collect::>(); + + let ty_predicates = tcx.predicates_of(did).predicates; + for (p, _) in ty_predicates { + if let ClauseKind::Trait(p) = p.kind().skip_binder() + && p.trait_ref.def_id == eq_trait_id + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + { + // Flag types which already have an `Eq` bound. + params[self_ty.index as usize].1 = false; + } + } + + let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( + params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { + ClauseKind::Trait(TraitPredicate { + trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), + polarity: ty::PredicatePolarity::Positive, + }) + .upcast(tcx) + }), + ))); + ty::TypingEnv { + typing_mode: ty::TypingMode::non_body_analysis(), + param_env, + } +} diff --git a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs new file mode 100644 index 000000000000..dc3fbe5d7012 --- /dev/null +++ b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs @@ -0,0 +1,51 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{HirId, TraitRef}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Span, sym}; + +use super::DERIVED_HASH_WITH_MANUAL_EQ; + +/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, + hash_is_automatically_derived: bool, +) { + if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() + && let Some(def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Hash, def_id) + { + // Look for the PartialEq implementations for `ty` + cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { + let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); + + if !hash_is_automatically_derived || peq_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id); + + // Only care about `impl PartialEq for Foo` + // For `impl PartialEq for A, input_types is [A, B] + if trait_ref.instantiate_identity().args.type_at(1) == ty { + span_lint_hir_and_then( + cx, + DERIVED_HASH_WITH_MANUAL_EQ, + adt_hir_id, + span, + "you are deriving `Hash` but have implemented `PartialEq` explicitly", + |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); + diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here"); + } + }, + ); + } + }); + } +} diff --git a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs new file mode 100644 index 000000000000..b2bc6402561f --- /dev/null +++ b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs @@ -0,0 +1,76 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::fulfill_or_allowed; +use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_hir::{self as hir, HirId, Item}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArgKind, Ty}; + +use super::EXPL_IMPL_CLONE_ON_COPY; + +/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { + let clone_id = match cx.tcx.lang_items().clone_trait() { + Some(id) if trait_ref.trait_def_id() == Some(id) => id, + _ => return, + }; + let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { + return; + }; + let (ty_adt, ty_subs) = match *ty.kind() { + // Unions can't derive clone. + ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), + _ => return, + }; + // If the current self type doesn't implement Copy (due to generic constraints), search to see if + // there's a Copy impl for any instance of the adt. + if !is_copy(cx, ty) { + if ty_subs.non_erasable_generics().next().is_some() { + let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| { + matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) + if ty_adt.did() == adt.did()) + }); + if !has_copy_impl { + return; + } + } else { + return; + } + } + // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for + // this impl. + if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { + return; + } + // `#[repr(packed)]` structs with type/const parameters can't derive `Clone`. + // https://github.com/rust-lang/rust-clippy/issues/10188 + if ty_adt.repr().packed() + && ty_subs + .iter() + .any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_))) + { + return; + } + // The presence of `unsafe` fields prevents deriving `Clone` automatically + if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) { + return; + } + + if fulfill_or_allowed(cx, EXPL_IMPL_CLONE_ON_COPY, [adt_hir_id]) { + return; + } + + span_lint_and_help( + cx, + EXPL_IMPL_CLONE_ON_COPY, + item.span, + "you are implementing `Clone` explicitly on a `Copy` type", + None, + "consider deriving `Clone` or removing `Copy`", + ); +} diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs new file mode 100644 index 000000000000..eafe7c4bb9f2 --- /dev/null +++ b/clippy_lints/src/derive/mod.rs @@ -0,0 +1,221 @@ +use clippy_utils::res::MaybeResPath; +use rustc_hir::def::Res; +use rustc_hir::{Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +mod derive_ord_xor_partial_ord; +mod derive_partial_eq_without_eq; +mod derived_hash_with_manual_eq; +mod expl_impl_clone_on_copy; +mod unsafe_derive_deserialize; + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// ### Example + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DERIVED_HASH_WITH_MANUAL_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` + /// or `PartialOrd` implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `sort`) so it’s probably a bad idea to use a + /// default-generated `Ord` implementation with an explicitly defined + /// `PartialOrd`. In particular, the following must hold for any type + /// implementing `Ord`: + /// + /// ```text + /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() + /// ``` + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Ord, PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// ... + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// #[derive(PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// fn partial_cmp(&self, other: &Foo) -> Option { + /// Some(self.cmp(other)) + /// } + /// } + /// + /// impl Ord for Foo { + /// ... + /// } + /// ``` + /// or, if you don't need a custom ordering: + /// ```rust,ignore + /// #[derive(Ord, PartialOrd, PartialEq, Eq)] + /// struct Foo; + /// ``` + #[clippy::version = "1.47.0"] + pub DERIVE_ORD_XOR_PARTIAL_ORD, + correctness, + "deriving `Ord` but implementing `PartialOrd` explicitly" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for explicit `Clone` implementations for `Copy` + /// types. + /// + /// ### Why is this bad? + /// 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. + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Copy)] + /// struct Foo; + /// + /// impl Clone for Foo { + /// // .. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EXPL_IMPL_CLONE_ON_COPY, + pedantic, + "implementing `Clone` explicitly on `Copy` types" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for deriving `serde::Deserialize` on a type that + /// has methods using `unsafe`. + /// + /// ### Why is this bad? + /// Deriving `serde::Deserialize` will create a constructor + /// that may violate invariants held by another constructor. + /// + /// ### Example + /// ```rust,ignore + /// use serde::Deserialize; + /// + /// #[derive(Deserialize)] + /// pub struct Foo { + /// // .. + /// } + /// + /// impl Foo { + /// pub fn new() -> Self { + /// // setup here .. + /// } + /// + /// pub unsafe fn parts() -> (&str, &str) { + /// // assumes invariants hold + /// } + /// } + /// ``` + #[clippy::version = "1.45.0"] + pub UNSAFE_DERIVE_DESERIALIZE, + pedantic, + "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 + /// ```no_run + /// #[derive(PartialEq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, + nursery, + "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" +} + +declare_lint_pass!(Derive => [ + EXPL_IMPL_CLONE_ON_COPY, + DERIVED_HASH_WITH_MANUAL_EQ, + DERIVE_ORD_XOR_PARTIAL_ORD, + UNSAFE_DERIVE_DESERIALIZE, + DERIVE_PARTIAL_EQ_WITHOUT_EQ +]); + +impl<'tcx> LateLintPass<'tcx> for Derive { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + self_ty, + .. + }) = item.kind + && let Res::Def(_, def_id) = *self_ty.basic_res() + && let Some(local_def_id) = def_id.as_local() + { + let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); + let trait_ref = &of_trait.trait_ref; + let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); + + derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); + derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); + + if is_automatically_derived { + unsafe_derive_deserialize::check(cx, item, trait_ref, ty, adt_hir_id); + derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty, adt_hir_id); + } else { + expl_impl_clone_on_copy::check(cx, item, trait_ref, ty, adt_hir_id); + } + } + } +} diff --git a/clippy_lints/src/derive/unsafe_derive_deserialize.rs b/clippy_lints/src/derive/unsafe_derive_deserialize.rs new file mode 100644 index 000000000000..38f3251fd389 --- /dev/null +++ b/clippy_lints/src/derive/unsafe_derive_deserialize.rs @@ -0,0 +1,97 @@ +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::{is_lint_allowed, paths}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; +use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, UnsafeSource}; +use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{Span, sym}; + +use super::UNSAFE_DERIVE_DESERIALIZE; + +/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { + fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { + let mut visitor = UnsafeVisitor { cx }; + walk_item(&mut visitor, item).is_break() + } + + if let Some(trait_def_id) = trait_ref.trait_def_id() + && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) + && let ty::Adt(def, _) = ty.kind() + && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) + && 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)) + { + span_lint_hir_and_then( + cx, + UNSAFE_DERIVE_DESERIALIZE, + adt_hir_id, + item.span, + "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", + |diag| { + diag.help( + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + ); + }, + ); + } +} + +struct UnsafeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; + type NestedFilter = nested_filter::All; + + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) -> Self::Result { + if let Some(header) = kind.header() + && header.is_unsafe() + { + ControlFlow::Break(()) + } else { + walk_fn(self, kind, decl, body_id, id) + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { + if let ExprKind::Block(block, _) = expr.kind + && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + && block + .span + .source_callee() + .and_then(|expr| expr.macro_def_id) + .is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did)) + { + return ControlFlow::Break(()); + } + + walk_expr(self, expr) + } + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } +} diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 044903ce5753..1c9c971730f6 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -7,7 +7,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefIdMap; use rustc_hir::{ - AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty, + AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, ImplItemImplKind, Item, ItemKind, OwnerId, Pat, Path, Stmt, + TraitItem, Ty, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; @@ -176,7 +177,9 @@ impl LateLintPass<'_> for DisallowedMacros { fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { self.check(cx, item.span, None); - self.check(cx, item.vis_span, None); + if let ImplItemImplKind::Inherent { vis_span, .. } = item.impl_kind { + self.check(cx, vis_span, None); + } } fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { diff --git a/clippy_lints/src/doc/broken_link.rs b/clippy_lints/src/doc/broken_link.rs index 8878fa9180fe..2fa41d83915a 100644 --- a/clippy_lints/src/doc/broken_link.rs +++ b/clippy_lints/src/doc/broken_link.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; -use pulldown_cmark::BrokenLink as PullDownBrokenLink; use rustc_lint::LateContext; +use rustc_resolve::rustdoc::pulldown_cmark::BrokenLink as PullDownBrokenLink; use rustc_resolve::rustdoc::{DocFragment, source_span_for_markdown_range}; use rustc_span::{BytePos, Pos, Span}; diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs index 3033ac0d0b0b..b164a9a99782 100644 --- a/clippy_lints/src/doc/missing_headers.rs +++ b/clippy_lints/src/doc/missing_headers.rs @@ -1,7 +1,8 @@ use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC}; use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::implements_trait_with_env; use clippy_utils::visitors::for_each_expr; use clippy_utils::{fulfill_or_allowed, is_doc_hidden, is_inside_always_const_context, method_chain_args, return_ty}; use rustc_hir::{BodyId, FnSig, OwnerId, Safety}; @@ -62,7 +63,7 @@ pub fn check( ); } if !headers.errors { - if is_type_diagnostic_item(cx, return_ty(cx, owner_id), sym::Result) { + if return_ty(cx, owner_id).is_diag_item(cx, sym::Result) { span_lint( cx, MISSING_ERRORS_DOC, @@ -83,7 +84,7 @@ pub fn check( &[], ) && let ty::Coroutine(_, subs) = ret_ty.kind() - && is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result) + && subs.as_coroutine().return_ty().is_diag_item(cx, sym::Result) { span_lint( cx, @@ -119,10 +120,7 @@ fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option { if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]).or_else(|| method_chain_args(expr, &[sym::expect])) && let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs() - && matches!( - get_type_diagnostic_name(cx, receiver_ty), - Some(sym::Option | sym::Result) - ) + && matches!(receiver_ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) && panic_span.is_none() { diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index eca3bc390d77..1e1d6e69cc91 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -4,23 +4,24 @@ use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then}; use clippy_utils::{is_entrypoint_fn, is_trait_impl_item}; -use pulldown_cmark::Event::{ - Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, - TaskListMarker, Text, -}; -use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph}; -use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_resolve::rustdoc::pulldown_cmark::Event::{ + Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, + TaskListMarker, Text, +}; +use rustc_resolve::rustdoc::pulldown_cmark::Tag::{ + BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph, +}; +use rustc_resolve::rustdoc::pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd}; use rustc_resolve::rustdoc::{ - DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, - span_of_fragments, + DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, pulldown_cmark, + source_span_for_markdown_range, span_of_fragments, }; use rustc_session::impl_lint_pass; use rustc_span::Span; -use rustc_span::edition::Edition; use std::ops::Range; use url::Url; @@ -34,6 +35,7 @@ mod markdown; mod missing_headers; mod needless_doctest_main; mod suspicious_doc_comments; +mod test_attr_in_doctest; mod too_long_first_doc_paragraph; declare_clippy_lint! { @@ -315,7 +317,7 @@ declare_clippy_lint! { /// /// [example of a good link](https://github.com/rust-lang/rust-clippy/) /// pub fn do_something() {} /// ``` - #[clippy::version = "1.84.0"] + #[clippy::version = "1.90.0"] pub DOC_BROKEN_LINK, pedantic, "broken document link" @@ -898,8 +900,6 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ )) } -const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"]; - enum Container { Blockquote, List(usize), @@ -964,11 +964,75 @@ fn check_for_code_clusters<'a, Events: Iterator Self { + Self { + no_run: false, + ignore: false, + compile_fail: false, + + rust: true, + } + } +} + +impl CodeTags { + /// Based on + fn parse(lang: &str) -> Self { + let mut tags = Self::default(); + + let mut seen_rust_tags = false; + let mut seen_other_tags = false; + for item in lang.split([',', ' ', '\t']) { + match item.trim() { + "" => {}, + "rust" => { + tags.rust = true; + seen_rust_tags = true; + }, + "ignore" => { + tags.ignore = true; + seen_rust_tags = !seen_other_tags; + }, + "no_run" => { + tags.no_run = true; + seen_rust_tags = !seen_other_tags; + }, + "should_panic" => seen_rust_tags = !seen_other_tags, + "compile_fail" => { + tags.compile_fail = true; + seen_rust_tags = !seen_other_tags || seen_rust_tags; + }, + "test_harness" | "standalone_crate" => { + seen_rust_tags = !seen_other_tags || seen_rust_tags; + }, + _ if item.starts_with("ignore-") => seen_rust_tags = true, + _ if item.starts_with("edition") => {}, + _ => seen_other_tags = true, + } + } + + tags.rust &= seen_rust_tags || !seen_other_tags; + + tags + } +} + /// Checks parsed documentation. /// This walks the "events" (think sections of markdown) produced by `pulldown_cmark`, /// so lints here will generally access that information. /// Returns documentation headers -- whether a "Safety", "Errors", "Panic" section was found -#[allow(clippy::too_many_lines)] // Only a big match statement +#[expect(clippy::too_many_lines, reason = "big match statement")] fn check_doc<'a, Events: Iterator, Range)>>( cx: &LateContext<'_>, valid_idents: &FxHashSet, @@ -979,14 +1043,10 @@ fn check_doc<'a, Events: Iterator, Range DocHeaders { // true if a safety header was found let mut headers = DocHeaders::default(); - let mut in_code = false; + let mut code = None; let mut in_link = None; let mut in_heading = false; let mut in_footnote_definition = false; - let mut is_rust = false; - let mut no_test = false; - let mut ignore = false; - let mut edition = None; let mut ticks_unbalanced = false; let mut text_to_check: Vec<(CowStr<'_>, Range, isize)> = Vec::new(); let mut paragraph_range = 0..0; @@ -1046,31 +1106,12 @@ fn check_doc<'a, Events: Iterator, Range { - in_code = true; - if let CodeBlockKind::Fenced(lang) = kind { - for item in lang.split(',') { - if item == "ignore" { - is_rust = false; - break; - } else if item == "no_test" { - no_test = true; - } else if item == "no_run" || item == "compile_fail" { - ignore = true; - } - if let Some(stripped) = item.strip_prefix("edition") { - is_rust = true; - edition = stripped.parse::().ok(); - } else if item.is_empty() || RUST_CODE.contains(&item) { - is_rust = true; - } - } - } - }, - End(TagEnd::CodeBlock) => { - in_code = false; - is_rust = false; - ignore = false; + code = Some(match kind { + CodeBlockKind::Indented => CodeTags::default(), + CodeBlockKind::Fenced(lang) => CodeTags::parse(lang), + }); }, + End(TagEnd::CodeBlock) => code = None, Start(Link { dest_url, .. }) => in_link = Some(dest_url), End(TagEnd::Link) => in_link = None, Start(Heading { .. } | Paragraph | Item) => { @@ -1180,7 +1221,7 @@ fn check_doc<'a, Events: Iterator, Range, Range>) { - test_attr_spans.extend( - item.attrs - .iter() - .find(|attr| attr.has_name(sym::test)) - .map(|attr| attr.span.lo().to_usize()..ident.span.hi().to_usize()), - ); -} - -pub fn check( - cx: &LateContext<'_>, - text: &str, - edition: Edition, - range: Range, - fragments: Fragments<'_>, - ignore: bool, -) { - // return whether the code contains a needless `fn main` plus a vector of byte position ranges - // of all `#[test]` attributes in not ignored code examples - fn check_code_sample(code: String, edition: Edition, ignore: bool) -> (bool, Vec>) { - rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_globals_then(edition, &[], None, || { - let mut test_attr_spans = vec![]; - let filename = FileName::anon_source_code(&code); - - let translator = rustc_driver::default_translator(); - let emitter = HumanEmitter::new(Box::new(io::sink()), translator); - let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); - #[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx - let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); - let psess = ParseSess::with_dcx(dcx, sm); - - let mut parser = match new_parser_from_source_str(&psess, filename, code) { - Ok(p) => p, - Err(errs) => { - errs.into_iter().for_each(Diag::cancel); - return (false, test_attr_spans); - }, - }; - - let mut relevant_main_found = false; - let mut eligible = true; - loop { - match parser.parse_item(ForceCollect::No) { - Ok(Some(item)) => match &item.kind { - ItemKind::Fn(box Fn { - ident, - sig, - body: Some(block), - .. - }) if ident.name == sym::main => { - if !ignore { - get_test_spans(&item, *ident, &mut test_attr_spans); - } - - let is_async = matches!(sig.header.coroutine_kind, Some(CoroutineKind::Async { .. })); - let returns_nothing = match &sig.decl.output { - FnRetTy::Default(..) => true, - FnRetTy::Ty(ty) if ty.kind.is_unit() => true, - FnRetTy::Ty(_) => false, - }; - - if returns_nothing && !is_async && !block.stmts.is_empty() { - // This main function should be linted, but only if there are no other functions - relevant_main_found = true; - } else { - // This main function should not be linted, we're done - eligible = false; - } - }, - // Another function was found; this case is ignored for needless_doctest_main - ItemKind::Fn(fn_) => { - eligible = false; - if ignore { - // If ignore is active invalidating one lint, - // and we already found another function thus - // invalidating the other one, we have no - // business continuing. - return (false, test_attr_spans); - } - get_test_spans(&item, fn_.ident, &mut test_attr_spans); - }, - // Tests with one of these items are ignored - ItemKind::Static(..) - | ItemKind::Const(..) - | ItemKind::ExternCrate(..) - | ItemKind::ForeignMod(..) => { - eligible = false; - }, - _ => {}, - }, - Ok(None) => break, - Err(e) => { - // See issue #15041. When calling `.cancel()` on the `Diag`, Clippy will unexpectedly panic - // when the `Diag` is unwinded. Meanwhile, we can just call `.emit()`, since the `DiagCtxt` - // is just a sink, nothing will be printed. - e.emit(); - return (false, test_attr_spans); - }, - } - } - - (relevant_main_found & eligible, test_attr_spans) - }) - }) - .ok() - .unwrap_or_default() +use rustc_span::InnerSpan; + +fn returns_unit<'a>(mut tokens: impl Iterator) -> bool { + let mut next = || tokens.next().map_or(TokenKind::Whitespace, |(kind, ..)| kind); + + match next() { + // { + TokenKind::OpenBrace => true, + // - > ( ) { + TokenKind::Minus => { + next() == TokenKind::Gt + && next() == TokenKind::OpenParen + && next() == TokenKind::CloseParen + && next() == TokenKind::OpenBrace + }, + _ => false, } +} - let trailing_whitespace = text.len() - text.trim_end().len(); - - // We currently only test for "fn main". Checking for the real - // entrypoint (with tcx.entry_fn(())) in each block would be unnecessarily - // expensive, as those are probably intended and relevant. Same goes for - // macros and other weird ways of declaring a main function. - // - // Also, as we only check for attribute names and don't do macro expansion, - // we can check only for #[test] - - if !((text.contains("main") && text.contains("fn")) || text.contains("#[test]")) { +pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) { + if !text.contains("main") { return; } - // Because of the global session, we need to create a new session in a different thread with - // the edition we need. - let text = text.to_owned(); - let (has_main, test_attr_spans) = thread::spawn(move || check_code_sample(text, edition, ignore)) - .join() - .expect("thread::spawn failed"); - if has_main && let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace) { - span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); - } - for span in test_attr_spans { - let span = (range.start + span.start)..(range.start + span.end); - if let Some(span) = fragments.span(cx, span) { - span_lint(cx, TEST_ATTR_IN_DOCTEST, span, "unit tests in doctest are not executed"); + let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| { + !matches!( + kind, + TokenKind::Whitespace | TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } + ) + }); + if let Some((TokenKind::Ident, "fn", fn_span)) = tokens.next() + && let Some((TokenKind::Ident, "main", main_span)) = tokens.next() + && let Some((TokenKind::OpenParen, ..)) = tokens.next() + && let Some((TokenKind::CloseParen, ..)) = tokens.next() + && returns_unit(&mut tokens) + { + let mut depth = 1; + for (kind, ..) in &mut tokens { + match kind { + TokenKind::OpenBrace => depth += 1, + TokenKind::CloseBrace => { + depth -= 1; + if depth == 0 { + break; + } + }, + _ => {}, + } + } + + if tokens.next().is_none() + && let Some(span) = fragments.span(cx, fn_span.start + offset..main_span.end + offset) + { + span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); } } } diff --git a/clippy_lints/src/doc/test_attr_in_doctest.rs b/clippy_lints/src/doc/test_attr_in_doctest.rs new file mode 100644 index 000000000000..65738434ac28 --- /dev/null +++ b/clippy_lints/src/doc/test_attr_in_doctest.rs @@ -0,0 +1,34 @@ +use super::Fragments; +use crate::doc::TEST_ATTR_IN_DOCTEST; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::tokenize_with_text; +use rustc_lexer::TokenKind; +use rustc_lint::LateContext; + +pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) { + if !text.contains("#[test]") { + return; + } + + let mut spans = Vec::new(); + let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| kind != TokenKind::Whitespace); + while let Some(token) = tokens.next() { + if let (TokenKind::Pound, _, pound_span) = token + && let Some((TokenKind::OpenBracket, ..)) = tokens.next() + && let Some((TokenKind::Ident, "test", _)) = tokens.next() + && let Some((TokenKind::CloseBracket, _, close_span)) = tokens.next() + && let Some(span) = fragments.span(cx, pound_span.start + offset..close_span.end + offset) + { + spans.push(span); + } + } + + if !spans.is_empty() { + span_lint( + cx, + TEST_ATTR_IN_DOCTEST, + spans, + "unit tests in doctest are not executed", + ); + } +} diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index 4dd8f01ee709..8defbeeaa5f2 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -1,5 +1,7 @@ -use clippy_utils::diagnostics::span_lint; -use rustc_ast::ast::{Expr, ExprKind}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{HasSession, SpanRangeExt, snippet_with_applicability, snippet_with_context}; +use rustc_ast::ast::{Expr, ExprKind, MethodCall}; +use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -24,7 +26,7 @@ declare_clippy_lint! { /// Use instead: /// ```no_run /// fn simple_no_parens() -> i32 { - /// 0 + /// (0) /// } /// /// # fn foo(bar: usize) {} @@ -40,29 +42,81 @@ declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]); impl EarlyLintPass for DoubleParens { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - let span = match &expr.kind { - ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span, - ExprKind::Call(_, params) - if let [param] = &**params - && let ExprKind::Paren(_) = param.kind => - { - param.span + match &expr.kind { + // ((..)) + // ^^^^^^ expr + // ^^^^ inner + ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => { + if expr.span.eq_ctxt(inner.span) + && !expr.span.in_external_macro(cx.sess().source_map()) + && check_source(cx, inner) + { + // suggest removing the outer parens + + let mut applicability = Applicability::MachineApplicable; + // We don't need to use `snippet_with_context` here, because: + // - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above) + // - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine + let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + expr.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); + } }, - ExprKind::MethodCall(call) - if let [arg] = &*call.args - && let ExprKind::Paren(_) = arg.kind => + + // func((n)) + // ^^^^^^^^^ expr + // ^^^ arg + // ^ inner + ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. }) + if let [arg] = &**args + && let ExprKind::Paren(inner) = &arg.kind => { - arg.span + if expr.span.eq_ctxt(arg.span) + && !arg.span.in_external_macro(cx.sess().source_map()) + && check_source(cx, arg) + { + // suggest removing the inner parens + + let mut applicability = Applicability::MachineApplicable; + let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0; + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + arg.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); + } }, - _ => return, - }; - if !expr.span.from_expansion() { - span_lint( - cx, - DOUBLE_PARENS, - span, - "consider removing unnecessary double parentheses", - ); + _ => {}, } } } + +/// Check that the span does indeed look like `( (..) )` +fn check_source(cx: &EarlyContext<'_>, inner: &Expr) -> bool { + if let Some(sfr) = inner.span.get_source_range(cx) + // this is the same as `SourceFileRange::as_str`, but doesn't apply the range right away, because + // we're interested in the source code outside it + && let Some(src) = sfr.sf.src.as_ref().map(|src| src.as_str()) + && let Some((start, outer_after_inner)) = src.split_at_checked(sfr.range.end) + && let Some((outer_before_inner, inner)) = start.split_at_checked(sfr.range.start) + && outer_before_inner.trim_end().ends_with('(') + && inner.starts_with('(') + && inner.ends_with(')') + && outer_after_inner.trim_start().starts_with(')') + { + true + } else { + false + } +} diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 5c360ce6a5f7..3bb8c484ceec 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_must_use_func_call; -use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::{is_copy, is_must_use_ty}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -97,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { sym::mem_forget if arg_ty.is_ref() => return, sym::mem_drop if is_copy && !drop_is_single_call_in_arm => return, sym::mem_forget if is_copy => return, - sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => return, + sym::mem_drop if arg_ty.is_lang_item(cx, LangItem::ManuallyDrop) => return, sym::mem_drop if !(arg_ty.needs_drop(cx.tcx, cx.typing_env()) || is_must_use_func_call(cx, arg) diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enums.rs similarity index 83% rename from clippy_lints/src/empty_enum.rs rename to clippy_lints/src/empty_enums.rs index b0389fd9a2f6..f96854411fe6 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enums.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::span_contains_cfg; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -25,10 +26,6 @@ declare_clippy_lint! { /// matched to mark code unreachable. If the field is not visible, then the struct /// acts like any other struct with private fields. /// - /// * If the enum has no variants only because all variants happen to be - /// [disabled by conditional compilation][cfg], then it would be appropriate - /// to allow the lint, with `#[allow(empty_enum)]`. - /// /// For further information, visit /// [the never type’s documentation][`!`]. /// @@ -53,24 +50,24 @@ declare_clippy_lint! { /// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction /// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html #[clippy::version = "pre 1.29.0"] - pub EMPTY_ENUM, + pub EMPTY_ENUMS, pedantic, "enum with no variants" } -declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]); +declare_lint_pass!(EmptyEnums => [EMPTY_ENUMS]); -impl LateLintPass<'_> for EmptyEnum { +impl LateLintPass<'_> for EmptyEnums { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if let ItemKind::Enum(..) = item.kind + if let ItemKind::Enum(.., def) = item.kind + && def.variants.is_empty() // Only suggest the `never_type` if the feature is enabled && cx.tcx.features().never_type() - && let Some(adt) = cx.tcx.type_of(item.owner_id).instantiate_identity().ty_adt_def() - && adt.variants().is_empty() + && !span_contains_cfg(cx, item.span) { span_lint_and_help( cx, - EMPTY_ENUM, + EMPTY_ENUMS, item.span, "enum with no variants", None, diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index c828fc57f760..1a56c8f810ee 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -43,12 +43,8 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { if let Some(anon_const) = &var.disr_expr { let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body); let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity(); - let constant = cx - .tcx - .const_eval_poly(def_id.to_def_id()) - .ok() - .map(|val| rustc_middle::mir::Const::from_value(val, ty)); - if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) { + let constant = cx.tcx.const_eval_poly(def_id.to_def_id()).ok(); + if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c, ty)) { if let ty::Adt(adt, _) = ty.kind() && adt.is_enum() { diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs index 3018e1f12734..3d8650f05168 100644 --- a/clippy_lints/src/error_impl_error.rs +++ b/clippy_lints/src/error_impl_error.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; -use clippy_utils::path_res; +use clippy_utils::res::MaybeResPath; use clippy_utils::ty::implements_trait; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{Item, ItemKind}; @@ -55,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for ErrorImplError { if let Some(trait_def_id) = imp.of_trait.and_then(|t| t.trait_ref.trait_def_id()) && let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) && error_def_id == trait_def_id - && let Some(def_id) = path_res(cx, imp.self_ty).opt_def_id().and_then(DefId::as_local) + && let Some(def_id) = imp.self_ty.basic_res().opt_def_id().and_then(DefId::as_local) && let Some(ident) = cx.tcx.opt_item_ident(def_id.to_def_id()) && ident.name == sym::Error && is_visible_outside_module(cx, def_id) => diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 2da1c2bad117..21385ee4fdc7 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,11 +1,9 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher::VecArgs; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; -use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; -use clippy_utils::{ - get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, -}; +use clippy_utils::{get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate}; use rustc_abi::ExternAbi; use rustc_errors::Applicability; use rustc_hir::attrs::AttributeKind; @@ -86,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { } } -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx>>, expr: &Expr<'tcx>) { let body = if let ExprKind::Closure(c) = expr.kind && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer(()))) @@ -144,7 +142,7 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx { let callee_ty_raw = typeck.expr_ty(callee); let callee_ty = callee_ty_raw.peel_refs(); - if matches!(get_type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) + if matches!(callee_ty.opt_diag_name(cx), Some(sym::Arc | sym::Rc)) || !check_inputs(typeck, body.params, None, args) { return; @@ -197,6 +195,18 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx // in a type which is `'static`. // For now ignore all callee types which reference a type parameter. && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + // Rule out `AsyncFn*`s, because while they can be called as `|x| f(x)`, + // they can't be passed directly into a place expecting an `Fn*` (#13892) + && let Ok((closure_kind, _)) = cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { span_lint_hir_and_then( cx, @@ -206,26 +216,17 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx "redundant closure", |diag| { if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if path_to_local(callee).is_some_and(|l| { + if callee.res_local_id().is_some_and(|l| { // FIXME: Do we really need this `local_used_in` check? // Isn't it checking something like... `callee(callee)`? // If somehow this check is needed, add some test for it, // 'cuz currently nothing changes after deleting this check. local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) }) { - match cx - .tcx - .infer_ctxt() - .build(cx.typing_mode()) - .err_ctxt() - .type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { + match closure_kind { // Mutable closure is used after current expr; we cannot consume it. - Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), - Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { + ClosureKind::FnMut => snippet = format!("&mut {snippet}"), + ClosureKind::Fn if !callee_ty_raw.is_ref() => { snippet = format!("&{snippet}"); }, _ => (), @@ -304,7 +305,7 @@ fn check_inputs( matches!( p.pat.kind, PatKind::Binding(BindingMode::NONE, id, _, None) - if path_to_local_id(arg, id) + if arg.res_local_id() == Some(id) ) // Only allow adjustments which change regions (i.e. re-borrowing). && typeck diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 085ee4448a4e..c59ffa14a5fe 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_expn_of, path_def_id, sym}; +use clippy_utils::{is_expn_of, is_in_test, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}; @@ -59,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind) && let ExprKind::Call(write_recv_path, []) = write_recv.kind && write_fun.ident.name == sym::write_fmt - && let Some(def_id) = path_def_id(cx, write_recv_path) + && let Some(def_id) = write_recv_path.basic_res().opt_def_id() { // match calls to std::io::stdout() / std::io::stderr () let (dest_name, prefix) = match cx.tcx.get_diagnostic_name(def_id) { @@ -71,6 +72,11 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { return; }; + // Performing an explicit write in a test circumvent's libtest's capture of stdio and stdout. + if is_in_test(cx.tcx, expr.hir_id) { + return; + } + // ordering is important here, since `writeln!` uses `write!` internally let calling_macro = if is_expn_of(write_call.span, sym::writeln).is_some() { Some("writeln") diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index c0b0fd88d9e1..fb98cb30ae90 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -157,6 +157,11 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { let spans = if explicit_params.len() == extra_params.len() { vec![self.generics.span] // Remove the entire list of generics } else { + // 1. Start from the last extra param + // 2. While the params preceding it are also extra, construct spans going from the current param to + // the comma before it + // 3. Once this chain of extra params stops, switch to constructing spans going from the current + // param to the comma _after_ it let mut end: Option = None; extra_params .iter() diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index fdfcbb540bce..c42998ffc3f5 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::method_chain_args; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -52,11 +52,9 @@ declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]); impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { // check for `impl From for ..` - if let hir::ItemKind::Impl(_) = &item.kind - && let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id) - && cx - .tcx - .is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id) + if let hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = &item.kind + && let impl_trait_id = cx.tcx.impl_trait_id(item.owner_id) + && cx.tcx.is_diagnostic_item(sym::From, impl_trait_id) { lint_impl_body(cx, item.owner_id, item.span); } @@ -84,9 +82,7 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp // check for `unwrap` if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]) { 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) - { + if receiver_ty.is_diag_item(self.lcx, sym::Option) || receiver_ty.is_diag_item(self.lcx, sym::Result) { self.result.push(expr.span); } } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 84d39dd81c91..5f022ba307ff 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -1,9 +1,10 @@ use clippy_utils::consts::Constant::{F32, F64, Int}; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::{ - eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, - is_inherent_method_call, is_no_std_crate, numeric_literal, peel_blocks, sugg, sym, + eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, is_no_std_crate, + numeric_literal, peel_blocks, sugg, sym, }; use rustc_ast::ast; use rustc_errors::Applicability; @@ -11,6 +12,7 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; +use rustc_span::SyntaxContext; use rustc_span::source_map::Spanned; use std::f32::consts as f32_consts; use std::f64::consts as f64_consts; @@ -110,8 +112,8 @@ declare_lint_pass!(FloatingPointArithmetic => [ // Returns the specialized log method for a given base if base is constant // and is one of 2, 10 and e -fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { - if let Some(value) = ConstEvalCtxt::new(cx).eval(base) { +fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { + if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { if F32(2.0) == value || F64(2.0) == value { return Some("log2"); } else if F32(10.0) == value || F64(10.0) == value { @@ -157,7 +159,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su } fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(method) = get_specialized_log_method(cx, &args[0]) { + if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { span_lint_and_sugg( cx, SUBOPTIMAL_FLOPS, @@ -205,7 +207,7 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { // ranges [-16777215, 16777216) for type f32 as whole number floats outside // this range are lossy and ambiguous. #[expect(clippy::cast_possible_truncation)] -fn get_integer_from_float_constant(value: &Constant<'_>) -> Option { +fn get_integer_from_float_constant(value: &Constant) -> Option { match value { F32(num) if num.fract() == 0.0 => { if (-16_777_215.0..16_777_216.0).contains(num) { @@ -517,8 +519,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test), + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), _ => false, } } else { @@ -530,8 +532,8 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test), + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), _ => false, } } else { @@ -540,8 +542,8 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - } /// Returns true iff expr is some zero literal -fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match ConstEvalCtxt::new(cx).eval_simple(expr) { +fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { Some(Int(i)) => i == 0, Some(F32(f)) => f == 0.0, Some(F64(f)) => f == 0.0, @@ -736,7 +738,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { if let ExprKind::MethodCall(path, receiver, args, _) = &expr.kind { let recv_ty = cx.typeck_results().expr_ty(receiver); - if recv_ty.is_floating_point() && !is_no_std_crate(cx) && is_inherent_method_call(cx, expr) { + if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) { match path.ident.name { sym::ln => check_ln1p(cx, expr, receiver), sym::log => check_log_base(cx, expr, receiver, args), diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 94e66769eb26..098bf4ba42f9 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -39,7 +39,6 @@ declare_clippy_lint! { "useless use of `format!`" } -#[allow(clippy::module_name_repetitions)] pub struct UselessFormat { format_args: FormatArgsStorage, } diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 3359aa603239..011cbf8c5d41 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -9,9 +9,10 @@ use clippy_utils::macros::{ root_macro_call_first_node, }; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::{implements_trait, is_type_lang_item}; -use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test, trait_ref_of_method}; +use clippy_utils::ty::implements_trait; +use clippy_utils::{is_from_proc_macro, is_in_test, trait_ref_of_method}; use itertools::Itertools; use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, @@ -237,7 +238,7 @@ impl_lint_pass!(FormatArgs<'_> => [ POINTER_FORMAT, ]); -#[allow(clippy::struct_field_names)] +#[expect(clippy::struct_field_names)] pub struct FormatArgs<'tcx> { format_args: FormatArgsStorage, msrv: Msrv, @@ -344,7 +345,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { if let Some(placeholder_span) = placeholder.span && *options != FormatOptions::default() && let ty = self.cx.typeck_results().expr_ty(arg).peel_refs() - && is_type_lang_item(self.cx, ty, LangItem::FormatArguments) + && ty.is_lang_item(self.cx, LangItem::FormatArguments) { span_lint_and_then( self.cx, @@ -497,8 +498,11 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { let cx = self.cx; if !value.span.from_expansion() && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind - && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) - && is_diag_trait_item(cx, method_def_id, sym::ToString) + && cx + .typeck_results() + .type_dependent_def_id(value.hir_id) + .opt_parent(cx) + .is_diag_item(cx, sym::ToString) && let receiver_ty = cx.typeck_results().expr_ty(receiver) && let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display) && let (n_needed_derefs, target) = diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 416aea51ea19..903d43e56c4b 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; -use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators, sym}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::{get_parent_as_impl, peel_ref_operators, sym}; use rustc_ast::{FormatArgsPiece, FormatTrait}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; @@ -157,8 +158,12 @@ impl FormatImplExpr<'_, '_> { && path.ident.name == sym::to_string // Is the method a part of the ToString trait? (i.e. not to_string() implemented // separately) - && let Some(expr_def_id) = self.cx.typeck_results().type_dependent_def_id(self.expr.hir_id) - && is_diag_trait_item(self.cx, expr_def_id, sym::ToString) + && self + .cx + .typeck_results() + .type_dependent_def_id(self.expr.hir_id) + .opt_parent(self.cx) + .is_diag_item(self.cx, sym::ToString) // Is the method is called on self && let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind && let [segment] = path.segments @@ -210,7 +215,7 @@ impl FormatImplExpr<'_, '_> { // Since the argument to fmt is itself a reference: &self let reference = peel_ref_operators(self.cx, arg); // Is the reference self? - if path_to_local(reference).map(|x| self.cx.tcx.hir_name(x)) == Some(kw::SelfLower) { + if reference.res_local_id().map(|x| self.cx.tcx.hir_name(x)) == Some(kw::SelfLower) { let FormatTraitNames { name, .. } = self.format_trait_impl; span_lint( self.cx, diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index b64d608c0c70..a23ba9ab837a 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::MaybeDef; use rustc_hir::{AssignOpKind, Expr, ExprKind, LangItem, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -41,7 +41,10 @@ declare_clippy_lint! { declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - is_type_lang_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), LangItem::String) + cx.typeck_results() + .expr_ty(e) + .peel_refs() + .is_lang_item(cx, LangItem::String) } fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { let e = e.peel_blocks().peel_borrows(); diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index 1c751643becb..3b9c70e23e20 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -110,7 +110,7 @@ declare_clippy_lint! { /// } if bar { // looks like an `else` is missing here /// } /// ``` - #[clippy::version = "1.90.0"] + #[clippy::version = "1.91.0"] pub POSSIBLE_MISSING_ELSE, suspicious, "possibly missing `else`" diff --git a/clippy_lints/src/four_forward_slashes.rs b/clippy_lints/src/four_forward_slashes.rs index a7b0edeb7991..5a0cee40a155 100644 --- a/clippy_lints/src/four_forward_slashes.rs +++ b/clippy_lints/src/four_forward_slashes.rs @@ -47,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { .tcx .hir_attrs(item.hir_id()) .iter() - .filter(|i| i.is_doc_comment()) + .filter(|i| i.is_doc_comment().is_some()) .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span())); let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else { return; diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index e3bb5ee10db7..ed55f90848f8 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -4,7 +4,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::span_is_local; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::path_def_id; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_path}; @@ -76,8 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { // `impl Into for self_ty` && let Some(GenericArgs { args: [GenericArg::Type(target_ty)], .. }) = into_trait_seg.args && span_is_local(item.span) - && let Some(middle_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id) - .map(ty::EarlyBinder::instantiate_identity) + && let middle_trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity() && cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id) && !matches!(middle_trait_ref.args.type_at(1).kind(), ty::Alias(ty::Opaque, _)) && self.msrv.meets(cx, msrvs::RE_REBALANCING_COHERENCE) @@ -90,7 +89,12 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { |diag| { // If the target type is likely foreign mention the orphan rules as it's a common source of // confusion - if path_def_id(cx, target_ty.peel_refs()).is_none_or(|id| !id.is_local()) { + if target_ty + .peel_refs() + .basic_res() + .opt_def_id() + .is_none_or(|id| !id.is_local()) + { diag.help( "`impl From for Foreign` is allowed by the orphan rules, for more information see\n\ https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence" diff --git a/clippy_lints/src/from_raw_with_void_ptr.rs b/clippy_lints/src/from_raw_with_void_ptr.rs index 5e2e2c9dbf72..0e6eeb75ec57 100644 --- a/clippy_lints/src/from_raw_with_void_ptr.rs +++ b/clippy_lints/src/from_raw_with_void_ptr.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::res::MaybeResPath; +use clippy_utils::sym; use clippy_utils::ty::is_c_void; -use clippy_utils::{path_def_id, sym}; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; @@ -41,7 +42,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr { if let ExprKind::Call(box_from_raw, [arg]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind && seg.ident.name == sym::from_raw - && let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id)) + && let Some(type_str) = ty.basic_res().opt_def_id().and_then(|id| def_id_matches_type(cx, id)) && let arg_kind = cx.typeck_results().expr_ty(arg).kind() && let ty::RawPtr(ty, _) = arg_kind && is_c_void(cx, *ty) diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index d5873b3f85aa..df8a35d9658b 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_lang_item; use clippy_utils::{is_in_const_context, is_integer_literal, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, PrimTy, QPath, TyKind, def}; @@ -89,5 +89,5 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { /// Checks if a Ty is `String` or `&str` fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - is_type_lang_item(cx, ty, LangItem::String) || ty.peel_refs().is_str() + ty.is_lang_item(cx, LangItem::String) || ty.peel_refs().is_str() } diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 8de68bfcb511..68532de0368f 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -132,7 +132,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr } // FIXME: needs to be an EARLY LINT. all attribute lints should be -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_needless_must_use( cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, 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 906bbd006d46..c6b0e7c54c06 100644 --- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -1,12 +1,13 @@ +use clippy_utils::res::MaybeResPath; use rustc_hir::{self as hir, HirId, HirIdSet, intravisit}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::def_id::LocalDefId; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::type_is_unsafe_function; +use clippy_utils::iter_input_pats; +use clippy_utils::ty::is_unsafe_fn; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{iter_input_pats, path_to_local}; use core::ops::ControlFlow; @@ -51,7 +52,7 @@ fn check_raw_ptr<'tcx>( let typeck = cx.tcx.typeck_body(body.id()); let _: Option = for_each_expr(cx, body.value, |e| { match e.kind { - hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => { + hir::ExprKind::Call(f, args) if is_unsafe_fn(cx, typeck.expr_ty(f)) => { for arg in args { check_arg(cx, &raw_ptrs, arg); } @@ -87,7 +88,7 @@ fn raw_ptr_arg(cx: &LateContext<'_>, arg: &hir::Param<'_>) -> Option { } fn check_arg(cx: &LateContext<'_>, raw_ptrs: &HirIdSet, arg: &hir::Expr<'_>) { - if path_to_local(arg).is_some_and(|id| raw_ptrs.contains(&id)) { + if arg.res_local_id().is_some_and(|id| raw_ptrs.contains(&id)) { span_lint( cx, NOT_UNSAFE_PTR_ARG_DEREF, diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index 106202d00d40..cc9dc47e15f2 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -1,23 +1,20 @@ use crate::functions::REF_OPTION; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_trait_impl_item; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::option_arg_ty; +use clippy_utils::{is_from_proc_macro, is_trait_impl_item}; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_hir::intravisit::FnKind; -use rustc_hir::{FnDecl, HirId}; -use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgKind, Mutability, Ty}; +use rustc_hir::{self as hir, FnDecl, HirId}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::{self, Mutability, Ty}; +use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; -fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { - if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() - && is_type_diagnostic_item(cx, *opt_ty, sym::Option) - && let ty::Adt(_, opt_gen_args) = opt_ty.kind() - && let [gen_arg] = opt_gen_args.as_slice() - && let GenericArgKind::Type(gen_ty) = gen_arg.kind() +fn check_ty<'a>(cx: &LateContext<'a>, param: &hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { + if !param.span.in_external_macro(cx.sess().source_map()) + && let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() + && let Some(gen_ty) = option_arg_ty(cx, *opt_ty) && !gen_ty.is_ref() // Need to gen the original spans, so first parsing mid, and hir parsing afterward && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind @@ -27,6 +24,7 @@ fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a args: [hir::GenericArg::Type(opt_ty)], .. }) = last.args + && !is_from_proc_macro(cx, param) { let lifetime = snippet(cx, lifetime.ident.span, ".."); fixes.push(( @@ -64,24 +62,27 @@ fn check_fn_sig<'a>(cx: &LateContext<'a>, decl: &FnDecl<'a>, span: Span, sig: ty } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] pub(crate) fn check_fn<'a>( cx: &LateContext<'a>, - kind: FnKind<'_>, + kind: FnKind<'a>, decl: &FnDecl<'a>, span: Span, hir_id: HirId, def_id: LocalDefId, - body: &hir::Body<'_>, + body: &hir::Body<'a>, avoid_breaking_exported_api: bool, ) { if avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } + if span.in_external_macro(cx.sess().source_map()) { + return; + } if let FnKind::Closure = kind { // Compute the span of the closure parameters + return type if set - let span = if let hir::FnRetTy::Return(out_ty) = &decl.output { + let inputs_output_span = if let hir::FnRetTy::Return(out_ty) = &decl.output { if decl.inputs.is_empty() { out_ty.span } else { @@ -100,9 +101,18 @@ pub(crate) fn check_fn<'a>( }; let sig = args.as_closure().sig().skip_binder(); - check_fn_sig(cx, decl, span, sig); + if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { + return; + } + + check_fn_sig(cx, decl, inputs_output_span, sig); } else if !is_trait_impl_item(cx, hir_id) { let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + + if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { + return; + } + check_fn_sig(cx, decl, span, sig); } } @@ -112,8 +122,10 @@ pub(super) fn check_trait_item<'a>( trait_item: &hir::TraitItem<'a>, avoid_breaking_exported_api: bool, ) { - if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind + if !trait_item.span.in_external_macro(cx.sess().source_map()) + && let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind && !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id)) + && !is_from_proc_macro(cx, trait_item) { let def_id = trait_item.owner_id.def_id; let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs index f8e8f5544b99..e25611d48817 100644 --- a/clippy_lints/src/functions/renamed_function_params.rs +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::{Applicability, MultiSpan}; -use rustc_hir::def_id::{DefId, DefIdSet}; -use rustc_hir::hir_id::OwnerId; +use rustc_hir::def_id::DefIdSet; use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node, TraitRef}; use rustc_lint::LateContext; use rustc_span::Span; @@ -19,7 +18,7 @@ pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>, ignored of_trait: Some(of_trait), .. }) = &parent_item.kind - && let Some(did) = trait_item_def_id_of_impl(cx, item.owner_id) + && let Some(did) = cx.tcx.trait_item_of(item.owner_id) && !is_from_ignored_trait(&of_trait.trait_ref, ignored_traits) { let mut param_idents_iter = cx.tcx.hir_body_param_idents(body_id); @@ -87,11 +86,6 @@ impl RenamedFnArgs { } } -/// Get the [`trait_item_def_id`](ImplItemRef::trait_item_def_id) of a relevant impl item. -fn trait_item_def_id_of_impl(cx: &LateContext<'_>, target: OwnerId) -> Option { - cx.tcx.associated_item(target).trait_item_def_id -} - fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool { of_trait .trait_def_id() diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs index 1f2fce687ed1..fb80cc1a63a3 100644 --- a/clippy_lints/src/functions/result.rs +++ b/clippy_lints/src/functions/result.rs @@ -1,4 +1,5 @@ use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use rustc_errors::Diag; use rustc_hir as hir; use rustc_lint::{LateContext, LintContext}; @@ -6,7 +7,7 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::{Span, sym}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_type_diagnostic_item}; +use clippy_utils::ty::{AdtVariantInfo, approx_ty_size}; use clippy_utils::{is_no_std_crate, trait_ref_of_method}; use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR}; @@ -24,7 +25,7 @@ fn result_err_ty<'tcx>( && let ty = cx .tcx .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(id).instantiate_identity().output()) - && is_type_diagnostic_item(cx, ty, sym::Result) + && ty.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = ty.kind() { let err_ty = args.type_at(1); diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 3ccfa51ab70b..221107ba4b93 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -78,66 +78,65 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind() && let Some(future_trait) = cx.tcx.lang_items().future_trait() && let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send) + && let preds = cx.tcx.explicit_item_self_bounds(def_id) + // If is a Future + && preds + .iter_instantiated_copied(cx.tcx, args) + .filter_map(|(p, _)| p.as_trait_clause()) + .any(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) { - let preds = cx.tcx.explicit_item_self_bounds(def_id); - let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| { - p.as_trait_clause() - .is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) - }); - if is_future { - let span = decl.output.span(); - let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); - let cause = traits::ObligationCause::misc(span, fn_def_id); - ocx.register_bound(cause, cx.param_env, ret_ty, send_trait); - let send_errors = ocx.select_all_or_error(); + let span = decl.output.span(); + let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let cause = traits::ObligationCause::misc(span, fn_def_id); + ocx.register_bound(cause, cx.param_env, ret_ty, send_trait); + let send_errors = ocx.evaluate_obligations_error_on_ambiguity(); - // Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top - // level". - // For example, allow errors that `T: Send` can't be proven, but reject `Rc: Send` errors, - // which is always unconditionally `!Send` for any possible type `T`. - // - // We also allow associated type projections if the self type is either itself a projection or a - // type parameter. - // This is to prevent emitting warnings for e.g. holding a `::Output` across await - // points, where `Fut` is a type parameter. + // Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top + // level". + // For example, allow errors that `T: Send` can't be proven, but reject `Rc: Send` errors, + // which is always unconditionally `!Send` for any possible type `T`. + // + // We also allow associated type projections if the self type is either itself a projection or a + // type parameter. + // This is to prevent emitting warnings for e.g. holding a `::Output` across await + // points, where `Fut` is a type parameter. - let is_send = send_errors.iter().all(|err| { - err.obligation - .predicate - .as_trait_clause() - .map(Binder::skip_binder) - .is_some_and(|pred| { - pred.def_id() == send_trait - && pred.self_ty().has_param() - && TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true) - }) - }); + let is_send = send_errors.iter().all(|err| { + err.obligation + .predicate + .as_trait_clause() + .map(Binder::skip_binder) + .is_some_and(|pred| { + pred.def_id() == send_trait + && pred.self_ty().has_param() + && TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true) + }) + }); - if !is_send { - span_lint_and_then( - cx, - FUTURE_NOT_SEND, - span, - "future cannot be sent between threads safely", - |db| { - for FulfillmentError { obligation, .. } in send_errors { - infcx - .err_ctxt() - .maybe_note_obligation_cause_for_async_await(db, &obligation); - if let PredicateKind::Clause(ClauseKind::Trait(trait_pred)) = - obligation.predicate.kind().skip_binder() - { - db.note(format!( - "`{}` doesn't implement `{}`", - trait_pred.self_ty(), - trait_pred.trait_ref.print_only_trait_path(), - )); - } + if !is_send { + span_lint_and_then( + cx, + FUTURE_NOT_SEND, + span, + "future cannot be sent between threads safely", + |db| { + for FulfillmentError { obligation, .. } in send_errors { + infcx + .err_ctxt() + .maybe_note_obligation_cause_for_async_await(db, &obligation); + if let PredicateKind::Clause(ClauseKind::Trait(trait_pred)) = + obligation.predicate.kind().skip_binder() + { + db.note(format!( + "`{}` doesn't implement `{}`", + trait_pred.self_ty(), + trait_pred.trait_ref.print_only_trait_path(), + )); } - }, - ); - } + } + }, + ); } } } diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index a99118f90f88..eed2d5d88535 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{eq_expr_value, higher, sym}; use core::ops::ControlFlow; @@ -95,7 +95,7 @@ fn mutex_lock_call<'tcx>( if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind && path.ident.name == sym::lock && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() - && is_type_diagnostic_item(cx, ty, sym::Mutex) + && ty.is_diag_item(cx, sym::Mutex) && op_mutex.is_none_or(|op| eq_expr_value(cx, self_arg, op)) { ControlFlow::Break(self_arg) diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index e8afa69b537e..54e9538fcb99 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -60,10 +60,14 @@ impl LateLintPass<'_> for IfNotElse { ), // Don't lint on `… != 0`, as these are likely to be bit tests. // For example, `if foo & 0x0F00 != 0 { … } else { … }` is already in the "proper" order. - ExprKind::Binary(op, _, rhs) if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs) => ( - "unnecessary `!=` operation", - "change to `==` and swap the blocks of the `if`/`else`", - ), + ExprKind::Binary(op, _, rhs) + if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs, e.span.ctxt()) => + { + ( + "unnecessary `!=` operation", + "change to `==` and swap the blocks of the `if`/`else`", + ) + }, _ => return, }; diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index b50d91f10146..7f3ef58c93d1 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -5,11 +5,10 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, - path_res, peel_blocks, sym, + as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, + is_none_expr, peel_blocks, sym, }; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -70,15 +69,15 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { }) = higher::If::hir(expr) && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr - && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let Some(then_arg) = as_some_expr(cx, then_expr) && !expr.span.from_expansion() && !then_expr.span.from_expansion() - && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) - && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) + && is_none_expr(cx, peel_blocks(els)) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) && !contains_return(then_block.stmts) + && then_block.expr.is_none_or(|expr| !contains_return(expr)) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { sym::then_some diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/ifs/branches_sharing_code.rs similarity index 62% rename from clippy_lints/src/copies.rs rename to clippy_lints/src/ifs/branches_sharing_code.rs index 4fdb497950f8..b3f597cc8736 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/ifs/branches_sharing_code.rs @@ -1,218 +1,23 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then}; -use clippy_utils::higher::has_let_expr; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet}; -use clippy_utils::ty::{InteriorMut, needs_ordered_drop}; +use clippy_utils::ty::needs_ordered_drop; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{ - ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, eq_expr_value, find_binding_init, - get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, path_to_local, - search_same, + ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, get_enclosing_block, hash_expr, hash_stmt, }; use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyCtxt; -use rustc_session::impl_lint_pass; +use rustc_lint::LateContext; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; use rustc_span::{Span, Symbol}; -declare_clippy_lint! { - /// ### What it does - /// Checks for consecutive `if`s with the same condition. - /// - /// ### Why is this bad? - /// This is probably a copy & paste error. - /// - /// ### Example - /// ```ignore - /// if a == b { - /// … - /// } else if a == b { - /// … - /// } - /// ``` - /// - /// Note that this lint ignores all conditions with a function call as it could - /// have side effects: - /// - /// ```ignore - /// if foo() { - /// … - /// } else if foo() { // not linted - /// … - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub IFS_SAME_COND, - correctness, - "consecutive `if`s with the same condition" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for consecutive `if`s with the same function call. - /// - /// ### Why is this bad? - /// This is probably a copy & paste error. - /// Despite the fact that function can have side effects and `if` works as - /// intended, such an approach is implicit and can be considered a "code smell". - /// - /// ### Example - /// ```ignore - /// if foo() == bar { - /// … - /// } else if foo() == bar { - /// … - /// } - /// ``` - /// - /// This probably should be: - /// ```ignore - /// if foo() == bar { - /// … - /// } else if foo() == baz { - /// … - /// } - /// ``` - /// - /// or if the original code was not a typo and called function mutates a state, - /// consider move the mutation out of the `if` condition to avoid similarity to - /// a copy & paste error: - /// - /// ```ignore - /// let first = foo(); - /// if first == bar { - /// … - /// } else { - /// let second = foo(); - /// if second == bar { - /// … - /// } - /// } - /// ``` - #[clippy::version = "1.41.0"] - pub SAME_FUNCTIONS_IN_IF_CONDITION, - pedantic, - "consecutive `if`s with the same function call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `if/else` with the same body as the *then* part - /// and the *else* part. - /// - /// ### Why is this bad? - /// This is probably a copy & paste error. - /// - /// ### Example - /// ```ignore - /// let foo = if … { - /// 42 - /// } else { - /// 42 - /// }; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub IF_SAME_THEN_ELSE, - style, - "`if` with the same `then` and `else` blocks" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks if the `if` and `else` block contain shared code that can be - /// moved out of the blocks. - /// - /// ### Why is this bad? - /// Duplicate code is less maintainable. - /// - /// ### Example - /// ```ignore - /// let foo = if … { - /// println!("Hello World"); - /// 13 - /// } else { - /// println!("Hello World"); - /// 42 - /// }; - /// ``` - /// - /// Use instead: - /// ```ignore - /// println!("Hello World"); - /// let foo = if … { - /// 13 - /// } else { - /// 42 - /// }; - /// ``` - #[clippy::version = "1.53.0"] - pub BRANCHES_SHARING_CODE, - nursery, - "`if` statement with shared code in all blocks" -} - -pub struct CopyAndPaste<'tcx> { - interior_mut: InteriorMut<'tcx>, -} - -impl<'tcx> CopyAndPaste<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self { - Self { - interior_mut: InteriorMut::new(tcx, &conf.ignore_interior_mutability), - } - } -} - -impl_lint_pass!(CopyAndPaste<'_> => [ - IFS_SAME_COND, - SAME_FUNCTIONS_IN_IF_CONDITION, - IF_SAME_THEN_ELSE, - BRANCHES_SHARING_CODE -]); - -impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { - let (conds, blocks) = if_sequence(expr); - lint_same_cond(cx, &conds, &mut self.interior_mut); - lint_same_fns_in_if_cond(cx, &conds); - let all_same = - !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks); - if !all_same && conds.len() != blocks.len() { - lint_branches_sharing_code(cx, &conds, &blocks, expr); - } - } - } -} - -fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { - let mut eq = SpanlessEq::new(cx); - blocks - .array_windows::<2>() - .enumerate() - .fold(true, |all_eq, (i, &[lhs, rhs])| { - if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) { - span_lint_and_note( - cx, - IF_SAME_THEN_ELSE, - lhs.span, - "this `if` has identical blocks", - Some(rhs.span), - "same as this", - ); - all_eq - } else { - false - } - }) -} +use super::BRANCHES_SHARING_CODE; -fn lint_branches_sharing_code<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, conds: &[&'tcx Expr<'_>], blocks: &[&'tcx Block<'_>], @@ -344,7 +149,7 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { /// Checks if the statement modifies or moves any of the given locals. fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool { for_each_expr_without_closures(s, |e| { - if let Some(id) = path_to_local(e) + if let Some(id) = e.res_local_id() && locals.contains(&id) && !capture_local_usage(cx, e).is_imm_ref() { @@ -356,8 +161,8 @@ fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: & .is_some() } -/// Checks if the given statement should be considered equal to the statement in the same position -/// for each block. +/// Checks if the given statement should be considered equal to the statement in the same +/// position for each block. fn eq_stmts( stmt: &Stmt<'_>, blocks: &[&Block<'_>], @@ -393,7 +198,7 @@ fn scan_block_for_eq<'tcx>( let mut cond_locals = HirIdSet::default(); for &cond in conds { let _: Option = for_each_expr_without_closures(cond, |e| { - if let Some(id) = path_to_local(e) { + if let Some(id) = e.res_local_id() { cond_locals.insert(id); } ControlFlow::Continue(()) @@ -516,9 +321,9 @@ fn scan_block_for_eq<'tcx>( } } -/// Adjusts the index for which the statements begin to differ to the closest macro callsite. This -/// avoids giving suggestions that requires splitting a macro call in half, when only a part of the -/// macro expansion is equal. +/// Adjusts the index for which the statements begin to differ to the closest macro callsite. +/// This avoids giving suggestions that requires splitting a macro call in half, when only a +/// part of the macro expansion is equal. /// /// For example, for the following macro: /// ```rust,ignore @@ -587,70 +392,6 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo }) } -fn method_caller_is_mutable<'tcx>( - cx: &LateContext<'tcx>, - caller_expr: &Expr<'_>, - interior_mut: &mut InteriorMut<'tcx>, -) -> bool { - let caller_ty = cx.typeck_results().expr_ty(caller_expr); - - interior_mut.is_interior_mut_ty(cx, caller_ty) - || caller_ty.is_mutable_ptr() - // `find_binding_init` will return the binding iff its not mutable - || path_to_local(caller_expr) - .and_then(|hid| find_binding_init(cx, hid)) - .is_none() -} - -/// Implementation of `IFS_SAME_COND`. -fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) { - for group in search_same( - conds, - |e| hash_expr(cx, e), - |lhs, rhs| { - // Ignore eq_expr side effects iff one of the expression kind is a method call - // and the caller is not a mutable, including inner mutable type. - if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind { - if method_caller_is_mutable(cx, caller, interior_mut) { - false - } else { - SpanlessEq::new(cx).eq_expr(lhs, rhs) - } - } else { - eq_expr_value(cx, lhs, rhs) - } - }, - ) { - let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); - span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition"); - } -} - -/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. -fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { - // Do not lint if any expr originates from a macro - if lhs.span.from_expansion() || rhs.span.from_expansion() { - return false; - } - // Do not spawn warning if `IFS_SAME_COND` already produced it. - if eq_expr_value(cx, lhs, rhs) { - return false; - } - SpanlessEq::new(cx).eq_expr(lhs, rhs) - }; - - for group in search_same(conds, |e| hash_expr(cx, e), eq) { - let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); - span_lint( - cx, - SAME_FUNCTIONS_IN_IF_CONDITION, - spans, - "these `if` branches have the same function call", - ); - } -} - fn is_expr_parent_assignment(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let parent = cx.tcx.parent_hir_node(expr.hir_id); if let Node::LetStmt(LetStmt { init: Some(e), .. }) diff --git a/clippy_lints/src/ifs/if_same_then_else.rs b/clippy_lints/src/ifs/if_same_then_else.rs new file mode 100644 index 000000000000..69402ec89076 --- /dev/null +++ b/clippy_lints/src/ifs/if_same_then_else.rs @@ -0,0 +1,29 @@ +use clippy_utils::SpanlessEq; +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::higher::has_let_expr; +use rustc_hir::{Block, Expr}; +use rustc_lint::LateContext; + +use super::IF_SAME_THEN_ELSE; + +pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { + let mut eq = SpanlessEq::new(cx); + blocks + .array_windows::<2>() + .enumerate() + .fold(true, |all_eq, (i, &[lhs, rhs])| { + if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) { + span_lint_and_note( + cx, + IF_SAME_THEN_ELSE, + lhs.span, + "this `if` has identical blocks", + Some(rhs.span), + "same as this", + ); + all_eq + } else { + false + } + }) +} diff --git a/clippy_lints/src/ifs/ifs_same_cond.rs b/clippy_lints/src/ifs/ifs_same_cond.rs new file mode 100644 index 000000000000..3ea941320a08 --- /dev/null +++ b/clippy_lints/src/ifs/ifs_same_cond.rs @@ -0,0 +1,47 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::res::MaybeResPath; +use clippy_utils::ty::InteriorMut; +use clippy_utils::{SpanlessEq, eq_expr_value, find_binding_init, hash_expr, search_same}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::IFS_SAME_COND; + +fn method_caller_is_mutable<'tcx>( + cx: &LateContext<'tcx>, + caller_expr: &Expr<'_>, + interior_mut: &mut InteriorMut<'tcx>, +) -> bool { + let caller_ty = cx.typeck_results().expr_ty(caller_expr); + + interior_mut.is_interior_mut_ty(cx, caller_ty) + || caller_ty.is_mutable_ptr() + // `find_binding_init` will return the binding iff its not mutable + || caller_expr.res_local_id() + .and_then(|hid| find_binding_init(cx, hid)) + .is_none() +} + +/// Implementation of `IFS_SAME_COND`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) { + for group in search_same( + conds, + |e| hash_expr(cx, e), + |lhs, rhs| { + // Ignore eq_expr side effects iff one of the expression kind is a method call + // and the caller is not a mutable, including inner mutable type. + if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind { + if method_caller_is_mutable(cx, caller, interior_mut) { + false + } else { + SpanlessEq::new(cx).eq_expr(lhs, rhs) + } + } else { + eq_expr_value(cx, lhs, rhs) + } + }, + ) { + let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); + span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition"); + } +} diff --git a/clippy_lints/src/ifs/mod.rs b/clippy_lints/src/ifs/mod.rs new file mode 100644 index 000000000000..739f2fc91729 --- /dev/null +++ b/clippy_lints/src/ifs/mod.rs @@ -0,0 +1,182 @@ +use clippy_config::Conf; +use clippy_utils::ty::InteriorMut; +use clippy_utils::{if_sequence, is_else_clause, is_lint_allowed}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyCtxt; +use rustc_session::impl_lint_pass; + +mod branches_sharing_code; +mod if_same_then_else; +mod ifs_same_cond; +mod same_functions_in_if_cond; + +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive `if`s with the same condition. + /// + /// ### Why is this bad? + /// This is probably a copy & paste error. + /// + /// ### Example + /// ```ignore + /// if a == b { + /// … + /// } else if a == b { + /// … + /// } + /// ``` + /// + /// Note that this lint ignores all conditions with a function call as it could + /// have side effects: + /// + /// ```ignore + /// if foo() { + /// … + /// } else if foo() { // not linted + /// … + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub IFS_SAME_COND, + correctness, + "consecutive `if`s with the same condition" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive `if`s with the same function call. + /// + /// ### Why is this bad? + /// This is probably a copy & paste error. + /// Despite the fact that function can have side effects and `if` works as + /// intended, such an approach is implicit and can be considered a "code smell". + /// + /// ### Example + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == bar { + /// … + /// } + /// ``` + /// + /// This probably should be: + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == baz { + /// … + /// } + /// ``` + /// + /// or if the original code was not a typo and called function mutates a state, + /// consider move the mutation out of the `if` condition to avoid similarity to + /// a copy & paste error: + /// + /// ```ignore + /// let first = foo(); + /// if first == bar { + /// … + /// } else { + /// let second = foo(); + /// if second == bar { + /// … + /// } + /// } + /// ``` + #[clippy::version = "1.41.0"] + pub SAME_FUNCTIONS_IN_IF_CONDITION, + pedantic, + "consecutive `if`s with the same function call" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `if/else` with the same body as the *then* part + /// and the *else* part. + /// + /// ### Why is this bad? + /// This is probably a copy & paste error. + /// + /// ### Example + /// ```ignore + /// let foo = if … { + /// 42 + /// } else { + /// 42 + /// }; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub IF_SAME_THEN_ELSE, + style, + "`if` with the same `then` and `else` blocks" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks if the `if` and `else` block contain shared code that can be + /// moved out of the blocks. + /// + /// ### Why is this bad? + /// Duplicate code is less maintainable. + /// + /// ### Example + /// ```ignore + /// let foo = if … { + /// println!("Hello World"); + /// 13 + /// } else { + /// println!("Hello World"); + /// 42 + /// }; + /// ``` + /// + /// Use instead: + /// ```ignore + /// println!("Hello World"); + /// let foo = if … { + /// 13 + /// } else { + /// 42 + /// }; + /// ``` + #[clippy::version = "1.53.0"] + pub BRANCHES_SHARING_CODE, + nursery, + "`if` statement with shared code in all blocks" +} + +pub struct CopyAndPaste<'tcx> { + interior_mut: InteriorMut<'tcx>, +} + +impl<'tcx> CopyAndPaste<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self { + Self { + interior_mut: InteriorMut::new(tcx, &conf.ignore_interior_mutability), + } + } +} + +impl_lint_pass!(CopyAndPaste<'_> => [ + IFS_SAME_COND, + SAME_FUNCTIONS_IN_IF_CONDITION, + IF_SAME_THEN_ELSE, + BRANCHES_SHARING_CODE +]); + +impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { + let (conds, blocks) = if_sequence(expr); + ifs_same_cond::check(cx, &conds, &mut self.interior_mut); + same_functions_in_if_cond::check(cx, &conds); + let all_same = + !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && if_same_then_else::check(cx, &conds, &blocks); + if !all_same && conds.len() != blocks.len() { + branches_sharing_code::check(cx, &conds, &blocks, expr); + } + } + } +} diff --git a/clippy_lints/src/ifs/same_functions_in_if_cond.rs b/clippy_lints/src/ifs/same_functions_in_if_cond.rs new file mode 100644 index 000000000000..1f6bf04e22e7 --- /dev/null +++ b/clippy_lints/src/ifs/same_functions_in_if_cond.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::{SpanlessEq, eq_expr_value, hash_expr, search_same}; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +use super::SAME_FUNCTIONS_IN_IF_CONDITION; + +/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. +pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { + // Do not lint if any expr originates from a macro + if lhs.span.from_expansion() || rhs.span.from_expansion() { + return false; + } + // Do not spawn warning if `IFS_SAME_COND` already produced it. + if eq_expr_value(cx, lhs, rhs) { + return false; + } + SpanlessEq::new(cx).eq_expr(lhs, rhs) + }; + + for group in search_same(conds, |e| hash_expr(cx, e), eq) { + let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); + span_lint( + cx, + SAME_FUNCTIONS_IN_IF_CONDITION, + spans, + "these `if` branches have the same function call", + ); + } +} diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index b3c90f364e83..d2bc0b6d9935 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; +use clippy_utils::res::MaybeDef; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{Visitor, VisitorExt, walk_body, walk_expr, walk_ty}; use rustc_hir::{self as hir, AmbigArg, Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind}; @@ -14,7 +15,6 @@ use rustc_span::Span; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{IntoSpan, SpanRangeExt, snippet}; use clippy_utils::sym; -use clippy_utils::ty::is_type_diagnostic_item; declare_clippy_lint! { /// ### What it does @@ -227,14 +227,14 @@ impl<'tcx> ImplicitHasherType<'tcx> { let ty = lower_ty(cx.tcx, hir_ty); - if is_type_diagnostic_item(cx, ty, sym::HashMap) && params_len == 2 { + if ty.is_diag_item(cx, sym::HashMap) && params_len == 2 { Some(ImplicitHasherType::HashMap( hir_ty.span, ty, snippet(cx, params[0].span, "K"), snippet(cx, params[1].span, "V"), )) - } else if is_type_diagnostic_item(cx, ty, sym::HashSet) && params_len == 1 { + } else if ty.is_diag_item(cx, sym::HashSet) && params_len == 1 { Some(ImplicitHasherType::HashSet( hir_ty.span, ty, diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 076017a247b4..6ed478b2708a 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro}; +use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_from_proc_macro}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -240,7 +240,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { return; } - let expr = if is_async_fn(kind) { + let expr = if kind.asyncness().is_async() { match get_async_fn_body(cx.tcx, body) { Some(e) => e, None => return, diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index 0fdbf6797381..4bf3a390b050 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -117,10 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option { fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> { if let ExprKind::Binary(op, l, r) = expr.kind { let ecx = ConstEvalCtxt::new(cx); - if let Some(Constant::Int(c)) = ecx.eval(r) { + let ctxt = expr.span.ctxt(); + if let Some(Constant::Int(c)) = ecx.eval_local(r, ctxt) { return Some((c, op.node, l)); } - if let Some(Constant::Int(c)) = ecx.eval(l) { + if let Some(Constant::Int(c)) = ecx.eval_local(l, ctxt) { return Some((c, invert_op(op.node)?, r)); } } diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index c634c12e1877..7b6f8729cb75 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -112,7 +112,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_manual_check<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'tcx>, @@ -165,7 +165,7 @@ fn check_manual_check<'tcx>( } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_gt( cx: &LateContext<'_>, condition_span: Span, @@ -196,7 +196,7 @@ fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { eq_expr_value(cx, expr, expr) } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_subtraction( cx: &LateContext<'_>, condition_span: Span, @@ -339,8 +339,7 @@ fn check_with_condition<'tcx>( ExprKind::Path(QPath::TypeRelative(_, name)) => { if name.ident.name == sym::MIN && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_assoc(const_id) - && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl + && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(const_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() { print_lint_and_sugg(cx, var_name, expr); @@ -350,8 +349,7 @@ fn check_with_condition<'tcx>( if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind && name.ident.name == sym::min_value && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_assoc(func_id) - && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl + && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(func_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() { print_lint_and_sugg(cx, var_name, expr); diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 89988be58758..716334656926 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::{is_in_const_context, is_in_test}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def::DefKind; -use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath, RustcVersion, StabilityLevel, StableSince}; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::{ExpnKind, Span, sym}; @@ -83,6 +82,10 @@ pub struct IncompatibleMsrv { availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, core_crate: Option, + + // The most recently called path. Used to skip checking the path after it's + // been checked when visiting the call expression. + called_path: Option, } impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]); @@ -98,6 +101,7 @@ impl IncompatibleMsrv { .iter() .find(|krate| tcx.crate_name(**krate) == sym::core) .copied(), + called_path: None, } } @@ -140,7 +144,14 @@ impl IncompatibleMsrv { } /// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV. - fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) { + fn emit_lint_if_under_msrv( + &mut self, + cx: &LateContext<'_>, + needs_const: bool, + def_id: DefId, + node: HirId, + span: Span, + ) { if def_id.is_local() { // We don't check local items since their MSRV is supposed to always be valid. return; @@ -158,10 +169,6 @@ impl IncompatibleMsrv { return; } - let needs_const = cx.enclosing_body.is_some() - && is_in_const_context(cx) - && matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn); - if (self.check_in_tests || !is_in_test(cx.tcx, node)) && let Some(current) = self.msrv.current(cx) && let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const) @@ -190,28 +197,45 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { match expr.kind { ExprKind::MethodCall(_, _, _, span) => { if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); + self.emit_lint_if_under_msrv(cx, is_in_const_context(cx), method_did, expr.hir_id, span); } }, + ExprKind::Call(callee, _) if let ExprKind::Path(qpath) = callee.kind => { + self.called_path = Some(callee.hir_id); + let needs_const = is_in_const_context(cx); + let def_id = if let Some(def_id) = cx.qpath_res(&qpath, callee.hir_id).opt_def_id() { + def_id + } else if needs_const && let ty::FnDef(def_id, _) = *cx.typeck_results().expr_ty(callee).kind() { + // Edge case where a function is first assigned then called. + // We previously would have warned for the non-const MSRV, when + // checking the path, but now that it's called the const MSRV + // must also be met. + def_id + } else { + return; + }; + self.emit_lint_if_under_msrv(cx, needs_const, def_id, expr.hir_id, callee.span); + }, // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should // not be linted as they will not be generated in older compilers if the function is not available, // and the compiler is allowed to call unstable functions. - ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) => { - if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() { - self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span); - } + ExprKind::Path(qpath) + if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() + && self.called_path != Some(expr.hir_id) => + { + self.emit_lint_if_under_msrv(cx, false, path_def_id, expr.hir_id, expr.span); }, _ => {}, } } fn check_ty(&mut self, cx: &LateContext<'tcx>, hir_ty: &'tcx hir::Ty<'tcx, AmbigArg>) { - if let hir::TyKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = hir_ty.kind + if let hir::TyKind::Path(qpath) = hir_ty.kind && let Some(ty_def_id) = cx.qpath_res(&qpath, hir_ty.hir_id).opt_def_id() // `CStr` and `CString` have been moved around but have been available since Rust 1.0.0 && !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type)) { - self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span); + self.emit_lint_if_under_msrv(cx, false, ty_def_id, hir_ty.hir_id, hir_ty.span); } } } diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 8f9f71a14769..919702c5714a 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -2,9 +2,10 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLet; +use clippy_utils::is_lint_allowed; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::ty::is_copy; -use clippy_utils::{is_lint_allowed, path_to_local}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -225,7 +226,7 @@ impl<'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'_, 'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - if let Some(local_id) = path_to_local(expr) { + if let Some(local_id) = expr.res_local_id() { let Self { cx, ref mut slice_lint_info, diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 99a393b4d53a..a2fcdb4a54b4 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { let note = "the suggestion might not be applicable in constant blocks"; let ty = cx.typeck_results().expr_ty(array).peel_refs(); let allowed_in_tests = self.allow_indexing_slicing_in_tests && is_in_test(cx.tcx, expr.hir_id); - if let Some(range) = higher::Range::hir(index) { + if let Some(range) = higher::Range::hir(cx, index) { // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..] if let ty::Array(_, s) = ty.kind() { let size: u128 = if let Some(size) = s.try_to_target_usize(cx.tcx) { diff --git a/clippy_lints/src/ineffective_open_options.rs b/clippy_lints/src/ineffective_open_options.rs index a159f6157183..bc57d9e85478 100644 --- a/clippy_lints/src/ineffective_open_options.rs +++ b/clippy_lints/src/ineffective_open_options.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{peel_blocks, peel_hir_expr_while, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -47,7 +47,11 @@ impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions { if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind && name.ident.name == sym::open && !expr.span.from_expansion() - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions) + && cx + .typeck_results() + .expr_ty(recv) + .peel_refs() + .is_diag_item(cx, sym::FsOpenOptions) { let mut append = false; let mut write = None; diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index bf3eafe09b3d..8f6de9fc60bd 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::{get_type_diagnostic_name, implements_trait}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::implements_trait; use clippy_utils::{higher, sym}; use rustc_hir::{BorrowKind, Closure, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -177,7 +178,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { Finite } }, - ExprKind::Struct(..) => higher::Range::hir(expr).is_some_and(|r| r.end.is_none()).into(), + ExprKind::Struct(..) => higher::Range::hir(cx, expr).is_some_and(|r| r.end.is_none()).into(), _ => Finite, } } @@ -235,7 +236,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { } else if method.ident.name == sym::collect { let ty = cx.typeck_results().expr_ty(expr); if matches!( - get_type_diagnostic_name(cx, ty), + ty.opt_diag_name(cx), Some( sym::BinaryHeap | sym::BTreeMap diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 309d2dfb28b8..a08efbc52d45 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,17 +1,23 @@ +use clippy_config::Conf; +use clippy_config::types::InherentImplLintScope; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_lint_allowed; +use clippy_utils::fulfill_or_allowed; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::{Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; +use rustc_session::impl_lint_pass; +use rustc_span::{FileName, Span}; use std::collections::hash_map::Entry; declare_clippy_lint! { /// ### What it does /// Checks for multiple inherent implementations of a struct /// + /// The config option controls the scope in which multiple inherent `impl` blocks for the same + /// struct are linted, allowing values of `module` (only within the same module), `file` + /// (within the same file), or `crate` (anywhere in the crate, default). + /// /// ### Why restrict this? /// Splitting the implementation of a type makes the code harder to navigate. /// @@ -41,7 +47,26 @@ declare_clippy_lint! { "Multiple inherent impl that could be grouped" } -declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); +impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); + +pub struct MultipleInherentImpl { + scope: InherentImplLintScope, +} + +impl MultipleInherentImpl { + pub fn new(conf: &'static Conf) -> Self { + Self { + scope: conf.inherent_impl_lint_scope, + } + } +} + +#[derive(Hash, Eq, PartialEq, Clone)] +enum Criterion { + Module(LocalModDefId), + File(FileName), + Crate, +} impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { @@ -55,18 +80,27 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { for (&id, impl_ids) in &impls.inherent_impls { if impl_ids.len() < 2 - // Check for `#[allow]` on the type definition - || is_lint_allowed( + // Check for `#[expect]` or `#[allow]` on the type definition + || fulfill_or_allowed( cx, MULTIPLE_INHERENT_IMPL, - cx.tcx.local_def_id_to_hir_id(id), + [cx.tcx.local_def_id_to_hir_id(id)], ) { continue; } for impl_id in impl_ids.iter().map(|id| id.expect_local()) { let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity(); - match type_map.entry(impl_ty) { + let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id); + let criterion = match self.scope { + InherentImplLintScope::Module => Criterion::Module(cx.tcx.parent_module(hir_id)), + InherentImplLintScope::File => { + let span = cx.tcx.hir_span(hir_id); + Criterion::File(cx.tcx.sess.source_map().lookup_source_file(span.lo()).name.clone()) + }, + InherentImplLintScope::Crate => Criterion::Crate, + }; + match type_map.entry((impl_ty, criterion)) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); @@ -97,7 +131,6 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { // Switching to the next type definition, no need to keep the current entries around. type_map.clear(); } - // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. lint_spans.sort_by_key(|x| x.0.lo()); for (span, first_span) in lint_spans { @@ -125,7 +158,7 @@ fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { { (!span.from_expansion() && impl_item.generics.params.is_empty() - && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) + && !fulfill_or_allowed(cx, MULTIPLE_INHERENT_IMPL, [id])) .then_some(span) } else { None diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 7f2e25367a6a..e569a5c7b612 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::{implements_trait, is_type_lang_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::implements_trait; use clippy_utils::{return_ty, trait_ref_of_method}; use rustc_abi::ExternAbi; use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem}; @@ -104,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) && !impl_item.span.from_expansion() // Check if return type is String - && is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String) + && return_ty(cx, impl_item.owner_id).is_lang_item(cx, LangItem::String) // Filters instances of to_string which are required by a trait && trait_ref_of_method(cx, impl_item.owner_id).is_none() { diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs deleted file mode 100644 index 13117f60abd5..000000000000 --- a/clippy_lints/src/instant_subtraction.rs +++ /dev/null @@ -1,151 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_context; -use clippy_utils::sugg::Sugg; -use clippy_utils::{is_path_diagnostic_item, ty}; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; -use rustc_span::source_map::Spanned; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Lints subtraction between `Instant::now()` and another `Instant`. - /// - /// ### Why is this bad? - /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns - /// as `Instant` subtraction saturates. - /// - /// `prev_instant.elapsed()` also more clearly signals intention. - /// - /// ### Example - /// ```no_run - /// use std::time::Instant; - /// let prev_instant = Instant::now(); - /// let duration = Instant::now() - prev_instant; - /// ``` - /// Use instead: - /// ```no_run - /// use std::time::Instant; - /// let prev_instant = Instant::now(); - /// let duration = prev_instant.elapsed(); - /// ``` - #[clippy::version = "1.65.0"] - pub MANUAL_INSTANT_ELAPSED, - pedantic, - "subtraction between `Instant::now()` and previous `Instant`" -} - -declare_clippy_lint! { - /// ### What it does - /// Lints subtraction between an `Instant` and a `Duration`. - /// - /// ### Why is this bad? - /// Unchecked subtraction could cause underflow on certain platforms, leading to - /// unintentional panics. - /// - /// ### Example - /// ```no_run - /// # use std::time::{Instant, Duration}; - /// let time_passed = Instant::now() - Duration::from_secs(5); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::time::{Instant, Duration}; - /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); - /// ``` - #[clippy::version = "1.67.0"] - pub UNCHECKED_DURATION_SUBTRACTION, - pedantic, - "finds unchecked subtraction of a 'Duration' from an 'Instant'" -} - -pub struct InstantSubtraction { - msrv: Msrv, -} - -impl InstantSubtraction { - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]); - -impl LateLintPass<'_> for InstantSubtraction { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Sub, .. - }, - lhs, - rhs, - ) = expr.kind - && let typeck = cx.typeck_results() - && ty::is_type_diagnostic_item(cx, typeck.expr_ty(lhs), sym::Instant) - { - let rhs_ty = typeck.expr_ty(rhs); - - if is_instant_now_call(cx, lhs) - && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant) - && let Some(sugg) = Sugg::hir_opt(cx, rhs) - { - print_manual_instant_elapsed_sugg(cx, expr, sugg); - } else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration) - && !expr.span.from_expansion() - && self.msrv.meets(cx, msrvs::TRY_FROM) - { - print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); - } - } - } -} - -fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { - if let ExprKind::Call(fn_expr, []) = expr_block.kind - && is_path_diagnostic_item(cx, fn_expr, sym::instant_now) - { - true - } else { - false - } -} - -fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { - span_lint_and_sugg( - cx, - MANUAL_INSTANT_ELAPSED, - expr.span, - "manual implementation of `Instant::elapsed`", - "try", - format!("{}.elapsed()", sugg.maybe_paren()), - Applicability::MachineApplicable, - ); -} - -fn print_unchecked_duration_subtraction_sugg( - cx: &LateContext<'_>, - left_expr: &Expr<'_>, - right_expr: &Expr<'_>, - expr: &Expr<'_>, -) { - let mut applicability = Applicability::MachineApplicable; - - let ctxt = expr.span.ctxt(); - let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "", &mut applicability).0; - let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "", &mut applicability).0; - - span_lint_and_sugg( - cx, - UNCHECKED_DURATION_SUBTRACTION, - expr.span, - "unchecked subtraction of a 'Duration' from an 'Instant'", - "try", - format!("{left_expr}.checked_sub({right_expr}).unwrap()"), - applicability, - ); -} diff --git a/clippy_lints/src/integer_division_remainder_used.rs b/clippy_lints/src/integer_division_remainder_used.rs deleted file mode 100644 index a1215491b48c..000000000000 --- a/clippy_lints/src/integer_division_remainder_used.rs +++ /dev/null @@ -1,50 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use rustc_ast::BinOpKind; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self}; -use rustc_session::declare_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of division (`/`) and remainder (`%`) operations - /// when performed on any integer types using the default `Div` and `Rem` trait implementations. - /// - /// ### Why restrict this? - /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, - /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). - /// - /// ### Example - /// ```no_run - /// let my_div = 10 / 2; - /// ``` - /// Use instead: - /// ```no_run - /// let my_div = 10 >> 1; - /// ``` - #[clippy::version = "1.79.0"] - pub INTEGER_DIVISION_REMAINDER_USED, - restriction, - "use of disallowed default division and remainder operations" -} - -declare_lint_pass!(IntegerDivisionRemainderUsed => [INTEGER_DIVISION_REMAINDER_USED]); - -impl LateLintPass<'_> for IntegerDivisionRemainderUsed { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary(op, lhs, rhs) = &expr.kind - && let BinOpKind::Div | BinOpKind::Rem = op.node - && let lhs_ty = cx.typeck_results().expr_ty(lhs) - && let rhs_ty = cx.typeck_results().expr_ty(rhs) - && let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind() - && let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind() - { - span_lint( - cx, - INTEGER_DIVISION_REMAINDER_USED, - expr.span.source_callsite(), - format!("use of {} has been disallowed in this context", op.node.as_str()), - ); - } - } -} diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 945bb84708f8..76f5fdfaa8dc 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -1,11 +1,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir}; -use clippy_utils::is_bool; -use clippy_utils::macros::span_is_local; -use clippy_utils::source::is_present_in_source; use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case}; +use clippy_utils::{is_bool, is_from_proc_macro}; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData}; +use rustc_hir::{Body, EnumDef, FieldDef, Item, ItemKind, QPath, TyKind, UseKind, Variant, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::symbol::Symbol; @@ -158,7 +156,8 @@ declare_clippy_lint! { } pub struct ItemNameRepetitions { - modules: Vec<(Symbol, String, OwnerId)>, + /// The module path the lint pass is in. + modules: Vec, enum_threshold: u64, struct_threshold: u64, avoid_breaking_exported_api: bool, @@ -167,6 +166,17 @@ pub struct ItemNameRepetitions { allowed_prefixes: FxHashSet, } +struct ModInfo { + name: Symbol, + name_camel: String, + /// Does this module have the `pub` visibility modifier. + is_public: bool, + /// How many bodies are between this module and the current lint pass position. + /// + /// Only the most recently seen module is updated when entering/exiting a body. + in_body_count: u32, +} + impl ItemNameRepetitions { pub fn new(conf: &'static Conf) -> Self { Self { @@ -458,71 +468,109 @@ fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_> } impl LateLintPass<'_> for ItemNameRepetitions { - fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { - let Some(_ident) = item.kind.ident() else { return }; - - let last = self.modules.pop(); - assert!(last.is_some()); + fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { + if matches!(item.kind, ItemKind::Mod(..)) { + let prev = self.modules.pop(); + debug_assert!(prev.is_some()); + } } - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - let Some(ident) = item.kind.ident() else { return }; - - let item_name = ident.name.as_str(); - let item_camel = to_camel_case(item_name); - if !item.span.from_expansion() && is_present_in_source(cx, item.span) - && let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules - // constants don't have surrounding modules - && !mod_camel.is_empty() - { - if mod_name == &ident.name - && let ItemKind::Mod(..) = item.kind - && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) - { - span_lint( - cx, - MODULE_INCEPTION, - item.span, - "module has the same name as its containing module", - ); - } + fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) { + if let [.., last] = &mut *self.modules { + last.in_body_count += 1; + } + } - // The `module_name_repetitions` lint should only trigger if the item has the module in its - // name. Having the same name is only accepted if `allow_exact_repetition` is set to `true`. + fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) { + if let [.., last] = &mut *self.modules { + last.in_body_count -= 1; + } + } - let both_are_public = - cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public(); + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + let ident = match item.kind { + ItemKind::Mod(ident, _) => { + if let [.., prev] = &*self.modules + && prev.name == ident.name + && prev.in_body_count == 0 + && (!self.allow_private_module_inception || prev.is_public) + && !item.span.from_expansion() + && !is_from_proc_macro(cx, item) + { + span_lint( + cx, + MODULE_INCEPTION, + item.span, + "module has the same name as its containing module", + ); + } + ident + }, - if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel { - span_lint( - cx, - MODULE_NAME_REPETITIONS, - ident.span, - "item name is the same as its containing module's name", - ); - } + ItemKind::Enum(ident, _, def) => { + if !ident.span.in_external_macro(cx.tcx.sess.source_map()) { + self.check_variants(cx, item, &def); + } + ident + }, + ItemKind::Struct(ident, _, data) => { + if let VariantData::Struct { fields, .. } = data + && !ident.span.in_external_macro(cx.tcx.sess.source_map()) + { + self.check_fields(cx, item, fields); + } + ident + }, - let is_macro = matches!(item.kind, ItemKind::Macro(_, _, _)); - if both_are_public && item_camel.len() > mod_camel.len() && !is_macro { - let matching = count_match_start(mod_camel, &item_camel); - let rmatching = count_match_end(mod_camel, &item_camel); - let nchars = mod_camel.chars().count(); + ItemKind::Const(ident, ..) + | ItemKind::ExternCrate(_, ident) + | ItemKind::Fn { ident, .. } + | ItemKind::Macro(ident, ..) + | ItemKind::Static(_, ident, ..) + | ItemKind::Trait(_, _, _, ident, ..) + | ItemKind::TraitAlias(ident, ..) + | ItemKind::TyAlias(ident, ..) + | ItemKind::Union(ident, ..) + | ItemKind::Use(_, UseKind::Single(ident)) => ident, + + ItemKind::ForeignMod { .. } | ItemKind::GlobalAsm { .. } | ItemKind::Impl(_) | ItemKind::Use(..) => return, + }; - let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); + let item_name = ident.name.as_str(); + let item_camel = to_camel_case(item_name); - if matching.char_count == nchars { - match item_camel.chars().nth(nchars) { - Some(c) if is_word_beginning(c) => span_lint( + if let [.., prev] = &*self.modules + && prev.is_public + && prev.in_body_count == 0 + && !item.span.from_expansion() + && !matches!(item.kind, ItemKind::Macro(..)) + && cx.tcx.visibility(item.owner_id).is_public() + { + if !self.allow_exact_repetitions && item_camel == prev.name_camel { + if !is_from_proc_macro(cx, item) { + span_lint( + cx, + MODULE_NAME_REPETITIONS, + ident.span, + "item name is the same as its containing module's name", + ); + } + } else if item_camel.len() > prev.name_camel.len() { + if let Some(s) = item_camel.strip_prefix(&prev.name_camel) + && let Some(c) = s.chars().next() + && (c == '_' || c.is_uppercase() || c.is_numeric()) + { + if !is_from_proc_macro(cx, item) { + span_lint( cx, MODULE_NAME_REPETITIONS, ident.span, "item name starts with its containing module's name", - ), - _ => (), + ); } - } - if rmatching.char_count == nchars - && !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count]) + } else if let Some(s) = item_camel.strip_suffix(&prev.name_camel) + && !self.is_allowed_prefix(s) + && !is_from_proc_macro(cx, item) { span_lint( cx, @@ -534,17 +582,13 @@ impl LateLintPass<'_> for ItemNameRepetitions { } } - if span_is_local(item.span) { - match item.kind { - ItemKind::Enum(_, _, def) => { - self.check_variants(cx, item, &def); - }, - ItemKind::Struct(_, _, VariantData::Struct { fields, .. }) => { - self.check_fields(cx, item, fields); - }, - _ => (), - } + if matches!(item.kind, ItemKind::Mod(..)) { + self.modules.push(ModInfo { + name: ident.name, + name_camel: item_camel, + is_public: cx.tcx.visibility(item.owner_id).is_public(), + in_body_count: 0, + }); } - self.modules.push((ident.name, item_camel, item.owner_id)); } } diff --git a/clippy_lints/src/iter_over_hash_type.rs b/clippy_lints/src/iter_over_hash_type.rs index b1cb6da9475b..6bb46ac3b554 100644 --- a/clippy_lints/src/iter_over_hash_type.rs +++ b/clippy_lints/src/iter_over_hash_type.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::higher::ForLoop; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -55,9 +55,7 @@ impl LateLintPass<'_> for IterOverHashType { if let Some(for_loop) = ForLoop::hir(expr) && !for_loop.body.span.from_expansion() && let ty = cx.typeck_results().expr_ty(for_loop.arg).peel_refs() - && hash_iter_tys - .into_iter() - .any(|sym| is_type_diagnostic_item(cx, ty, sym)) + && hash_iter_tys.into_iter().any(|sym| ty.is_diag_item(cx, sym)) { span_lint( cx, diff --git a/clippy_lints/src/large_futures.rs b/clippy_lints/src/large_futures.rs index fd7965d564d5..b11e89a9b566 100644 --- a/clippy_lints/src/large_futures.rs +++ b/clippy_lints/src/large_futures.rs @@ -4,7 +4,7 @@ use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; use rustc_abi::Size; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_hir::{Expr, ExprKind, LangItem, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -58,7 +58,8 @@ impl<'tcx> LateLintPass<'tcx> for LargeFuture { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind && let ExprKind::Call(func, [arg]) = scrutinee.kind - && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind + && let ExprKind::Path(qpath) = func.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoFutureIntoFuture) && !expr.span.from_expansion() && let ty = cx.typeck_results().expr_ty(arg) && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() diff --git a/clippy_lints/src/legacy_numeric_constants.rs b/clippy_lints/src/legacy_numeric_constants.rs index 42c636505c01..8a5d97294d2b 100644 --- a/clippy_lints/src/legacy_numeric_constants.rs +++ b/clippy_lints/src/legacy_numeric_constants.rs @@ -113,35 +113,18 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { // since this would only require removing a `use` import (which is already linted). && !is_numeric_const_path_canonical(path, [*mod_name, *name]) { - ( - vec![(expr.span, format!("{mod_name}::{name}"))], - "usage of a legacy numeric constant", - ) + (format!("{mod_name}::{name}"), "usage of a legacy numeric constant") // `::xxx_value` check } else if let ExprKind::Call(func, []) = &expr.kind && let ExprKind::Path(qpath) = &func.kind && let QPath::TypeRelative(ty, last_segment) = qpath && let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() && is_integer_method(cx, def_id) + && let Some(mod_name) = ty.span.get_source_text(cx) + && ty.span.eq_ctxt(last_segment.ident.span) { - let mut sugg = vec![ - // Replace the function name up to the end by the constant name - ( - last_segment.ident.span.to(expr.span.shrink_to_hi()), - last_segment.ident.name.as_str()[..=2].to_ascii_uppercase(), - ), - ]; - let before_span = expr.span.shrink_to_lo().until(ty.span); - if !before_span.is_empty() { - // Remove everything before the type name - sugg.push((before_span, String::new())); - } - // Use `::` between the type name and the constant - let between_span = ty.span.shrink_to_hi().until(last_segment.ident.span); - if !between_span.check_source_text(cx, |s| s == "::") { - sugg.push((between_span, String::from("::"))); - } - (sugg, "usage of a legacy numeric method") + let name = last_segment.ident.name.as_str()[..=2].to_ascii_uppercase(); + (format!("{mod_name}::{name}"), "usage of a legacy numeric method") } else { return; }; @@ -151,7 +134,8 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { && !is_from_proc_macro(cx, expr) { span_lint_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.span, msg, |diag| { - diag.multipart_suggestion_verbose( + diag.span_suggestion_verbose( + expr.span, "use the associated constant instead", sugg, Applicability::MaybeIncorrect, diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index f44a5fdf715e..877bd34a732b 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,22 +1,23 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::msrvs::Msrv; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; use clippy_utils::ty::implements_trait; -use clippy_utils::{ - fulfill_or_allowed, get_parent_as_impl, is_trait_method, parent_item_name, peel_ref_operators, sym, -}; +use clippy_utils::{fulfill_or_allowed, get_parent_as_impl, parent_item_name, peel_ref_operators, sym}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, - Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemId, - TyKind, + Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, RustcVersion, + StabilityLevel, StableSince, TraitItemId, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FnSig, Ty}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; use rustc_span::symbol::kw; use rustc_span::{Ident, Span, Symbol}; @@ -121,7 +122,17 @@ declare_clippy_lint! { "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead" } -declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]); +pub struct LenZero { + msrv: Msrv, +} + +impl_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]); + +impl LenZero { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} impl<'tcx> LateLintPass<'tcx> for LenZero { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { @@ -185,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { _ => false, } && !expr.span.from_expansion() - && has_is_empty(cx, lt.init) + && has_is_empty(cx, lt.init, self.msrv) { let mut applicability = Applicability::MachineApplicable; @@ -204,10 +215,10 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { } if let ExprKind::MethodCall(method, lhs_expr, [rhs_expr], _) = expr.kind - && is_trait_method(cx, expr, sym::PartialEq) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::PartialEq) && !expr.span.from_expansion() { - check_empty_expr( + self.check_empty_expr( cx, expr.span, lhs_expr, @@ -227,29 +238,110 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { let actual_span = span_without_enclosing_paren(cx, expr.span); match cmp { BinOpKind::Eq => { - check_cmp(cx, actual_span, left, right, "", 0); // len == 0 - check_cmp(cx, actual_span, right, left, "", 0); // 0 == len + self.check_cmp(cx, actual_span, left, right, "", 0); // len == 0 + self.check_cmp(cx, actual_span, right, left, "", 0); // 0 == len }, BinOpKind::Ne => { - check_cmp(cx, actual_span, left, right, "!", 0); // len != 0 - check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len + self.check_cmp(cx, actual_span, left, right, "!", 0); // len != 0 + self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len }, BinOpKind::Gt => { - check_cmp(cx, actual_span, left, right, "!", 0); // len > 0 - check_cmp(cx, actual_span, right, left, "", 1); // 1 > len + self.check_cmp(cx, actual_span, left, right, "!", 0); // len > 0 + self.check_cmp(cx, actual_span, right, left, "", 1); // 1 > len }, BinOpKind::Lt => { - check_cmp(cx, actual_span, left, right, "", 1); // len < 1 - check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len + self.check_cmp(cx, actual_span, left, right, "", 1); // len < 1 + self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len }, - BinOpKind::Ge => check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1 - BinOpKind::Le => check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len + BinOpKind::Ge => self.check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1 + BinOpKind::Le => self.check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len _ => (), } } } } +impl LenZero { + fn check_cmp( + &self, + cx: &LateContext<'_>, + span: Span, + method: &Expr<'_>, + lit: &Expr<'_>, + op: &str, + compare_to: u32, + ) { + if method.span.from_expansion() { + return; + } + + if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) { + // check if we are in an is_empty() method + if parent_item_name(cx, method) == Some(sym::is_empty) { + return; + } + + self.check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to); + } else { + self.check_empty_expr(cx, span, method, lit, op); + } + } + + #[expect(clippy::too_many_arguments)] + fn check_len( + &self, + cx: &LateContext<'_>, + span: Span, + method_name: Symbol, + receiver: &Expr<'_>, + lit: &LitKind, + op: &str, + compare_to: u32, + ) { + if let LitKind::Int(lit, _) = *lit { + // check if length is compared to the specified number + if lit != u128::from(compare_to) { + return; + } + + if method_name == sym::len && has_is_empty(cx, receiver, self.msrv) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + LEN_ZERO, + span, + format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), + format!("using `{op}is_empty` is clearer and more explicit"), + format!( + "{op}{}.is_empty()", + snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0, + ), + applicability, + ); + } + } + } + + fn check_empty_expr(&self, cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { + if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1, self.msrv) { + let mut applicability = Applicability::MachineApplicable; + + let lit1 = peel_ref_operators(cx, lit1); + let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren(); + + span_lint_and_sugg( + cx, + COMPARISON_TO_EMPTY, + span, + "comparison to empty slice", + format!("using `{op}is_empty` is clearer and more explicit"), + format!("{op}{lit_str}.is_empty()"), + applicability, + ); + } + } +} + fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span { let Some(snippet) = span.get_source_text(cx) else { return span; @@ -371,9 +463,9 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Option) => Some(LenOutput::Option(adt.did())), - Some(sym::Result) => Some(LenOutput::Result(adt.did())), + ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), + Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), _ => None, }, _ => None, @@ -514,75 +606,6 @@ fn check_for_is_empty( } } -fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { - if method.span.from_expansion() { - return; - } - - if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) { - // check if we are in an is_empty() method - if parent_item_name(cx, method) == Some(sym::is_empty) { - return; - } - - check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to); - } else { - check_empty_expr(cx, span, method, lit, op); - } -} - -fn check_len( - cx: &LateContext<'_>, - span: Span, - method_name: Symbol, - receiver: &Expr<'_>, - lit: &LitKind, - op: &str, - compare_to: u32, -) { - if let LitKind::Int(lit, _) = *lit { - // check if length is compared to the specified number - if lit != u128::from(compare_to) { - return; - } - - if method_name == sym::len && has_is_empty(cx, receiver) { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - LEN_ZERO, - span, - format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), - format!("using `{op}is_empty` is clearer and more explicit"), - format!( - "{op}{}.is_empty()", - snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0, - ), - applicability, - ); - } - } -} - -fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { - if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) { - let mut applicability = Applicability::MachineApplicable; - - let lit1 = peel_ref_operators(cx, lit1); - let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren(); - - span_lint_and_sugg( - cx, - COMPARISON_TO_EMPTY, - span, - "comparison to empty slice", - format!("using `{op}is_empty` is clearer and more explicit"), - format!("{op}{lit_str}.is_empty()"), - applicability, - ); - } -} - fn is_empty_string(expr: &Expr<'_>) -> bool { if let ExprKind::Lit(lit) = expr.kind && let LitKind::Str(lit, _) = lit.node @@ -601,45 +624,59 @@ fn is_empty_array(expr: &Expr<'_>) -> bool { } /// Checks if this type has an `is_empty` method. -fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { +fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) -> bool { /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. - fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool { + fn is_is_empty_and_stable(cx: &LateContext<'_>, item: &ty::AssocItem, msrv: Msrv) -> bool { if item.is_fn() { let sig = cx.tcx.fn_sig(item.def_id).skip_binder(); let ty = sig.skip_binder(); ty.inputs().len() == 1 + && cx.tcx.lookup_stability(item.def_id).is_none_or(|stability| { + if let StabilityLevel::Stable { since, .. } = stability.level { + let version = match since { + StableSince::Version(version) => version, + StableSince::Current => RustcVersion::CURRENT, + StableSince::Err(_) => return false, + }; + + msrv.meets(cx, version) + } else { + // Unstable fn, check if the feature is enabled. + cx.tcx.features().enabled(stability.feature) && msrv.current(cx).is_none() + } + }) } else { false } } /// Checks the inherent impl's items for an `is_empty(self)` method. - fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool { + fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId, msrv: Msrv) -> bool { cx.tcx.inherent_impls(id).iter().any(|imp| { cx.tcx .associated_items(*imp) .filter_by_name_unhygienic(sym::is_empty) - .any(|item| is_is_empty(cx, item)) + .any(|item| is_is_empty_and_stable(cx, item, msrv)) }) } - fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool { + fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize, msrv: Msrv) -> bool { match ty.kind() { ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| { cx.tcx .associated_items(principal.def_id()) .filter_by_name_unhygienic(sym::is_empty) - .any(|item| is_is_empty(cx, item)) + .any(|item| is_is_empty_and_stable(cx, item, msrv)) }), - ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), + ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id, msrv), ty::Adt(id, _) => { - has_is_empty_impl(cx, id.did()) + has_is_empty_impl(cx, id.did(), msrv) || (cx.tcx.recursion_limit().value_within_limit(depth) && cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| { implements_trait(cx, ty, deref_id, &[]) && cx .get_associated_type(ty, deref_id, sym::Target) - .is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1)) + .is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1, msrv)) })) }, ty::Array(..) | ty::Slice(..) | ty::Str => true, @@ -647,5 +684,5 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } } - ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0) + ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0, msrv) } diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index e480c8fbed53..2dbf55a8540b 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::path_to_local_id; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::visitors::is_local_used; use rustc_errors::Applicability; @@ -145,7 +145,7 @@ fn check_assign<'tcx>( && let Some(expr) = block.stmts.iter().last() && let hir::StmtKind::Semi(expr) = expr.kind && let hir::ExprKind::Assign(var, value, _) = expr.kind - && path_to_local_id(var, decl) + && var.res_local_id() == Some(decl) { if block .stmts diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index 5b0f95ffc377..24a4c321bdab 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use clippy_utils::source::{IntoSpan, SpanRangeExt}; +use rustc_ast::{Local, TyKind}; use rustc_errors::Applicability; -use rustc_hir::{LetStmt, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -26,14 +26,14 @@ declare_clippy_lint! { } declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]); -impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { - if let Some(ty) = local.ty // Ensure that it has a type defined - && let TyKind::Infer(()) = &ty.kind // that type is '_' +impl EarlyLintPass for UnderscoreTyped { + fn check_local(&mut self, cx: &EarlyContext<'_>, local: &Local) { + if let Some(ty) = &local.ty // Ensure that it has a type defined + && let TyKind::Infer = ty.kind // that type is '_' && local.span.eq_ctxt(ty.span) - && let sm = cx.tcx.sess.source_map() + && let sm = cx.sess().source_map() && !local.span.in_external_macro(sm) - && !is_from_proc_macro(cx, ty) + && !is_from_proc_macro(cx, &**ty) { let span_to_remove = sm .span_extend_to_prev_char_before(ty.span, ':', true) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a89cf3fdc1ee..033f85e70ef0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -7,7 +7,6 @@ #![feature(iter_intersperse)] #![feature(iter_partition_in_place)] #![feature(never_type)] -#![cfg_attr(bootstrap, feature(round_char_boundary))] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![feature(unwrap_infallible)] @@ -28,15 +27,11 @@ rustc::internal )] -// FIXME: switch to something more ergonomic here, once available. -// (Currently there is no way to opt into sysroot crates without `extern crate`.) -extern crate pulldown_cmark; extern crate rustc_abi; extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_data_structures; -extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_hir; extern crate rustc_hir_analysis; @@ -47,15 +42,12 @@ extern crate rustc_infer; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_middle; -extern crate rustc_parse; extern crate rustc_parse_format; extern crate rustc_resolve; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; -extern crate smallvec; -extern crate thin_vec; #[macro_use] extern crate clippy_utils; @@ -100,7 +92,6 @@ mod cognitive_complexity; mod collapsible_if; mod collection_is_never_read; mod comparison_chain; -mod copies; mod copy_iterator; mod crate_in_macro_def; mod create_dir; @@ -124,7 +115,7 @@ mod drop_forget_ref; mod duplicate_mod; mod else_if_without_else; mod empty_drop; -mod empty_enum; +mod empty_enums; mod empty_line_after; mod empty_with_brackets; mod endian_bytes; @@ -158,6 +149,7 @@ mod future_not_send; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; +mod ifs; mod ignored_unit_patterns; mod impl_hash_with_borrow_str_and_bytes; mod implicit_hasher; @@ -176,10 +168,7 @@ mod inherent_impl; mod inherent_to_string; mod init_numbered_fields; mod inline_fn_without_body; -mod instant_subtraction; mod int_plus_one; -mod integer_division_remainder_used; -mod invalid_upcast_comparisons; mod item_name_repetitions; mod items_after_statements; mod items_after_test_module; @@ -198,7 +187,6 @@ mod let_if_seq; mod let_underscore; mod let_with_type_underscore; mod lifetimes; -mod lines_filter_map_ok; mod literal_representation; mod literal_string_with_formatting_args; mod loops; @@ -210,7 +198,6 @@ mod manual_assert; mod manual_async_fn; mod manual_bits; mod manual_clamp; -mod manual_div_ceil; mod manual_float_methods; mod manual_hash_one; mod manual_ignore_case_cmp; @@ -253,7 +240,6 @@ mod multiple_bound_locations; mod multiple_unsafe_ops_per_block; mod mut_key; mod mut_mut; -mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; mod needless_arbitrary_self_type; @@ -263,7 +249,7 @@ mod needless_borrows_for_generic_args; mod needless_continue; mod needless_else; mod needless_for_each; -mod needless_if; +mod needless_ifs; mod needless_late_init; mod needless_maybe_sized; mod needless_parens_on_range_literals; @@ -302,7 +288,6 @@ mod permissions_set_readonly_false; mod pointers_in_nomem_asm_block; mod precedence; mod ptr; -mod ptr_offset_with_cast; mod pub_underscore_fields; mod pub_use; mod question_mark; @@ -327,6 +312,7 @@ mod ref_patterns; mod reference; mod regex; mod repeat_vec_with_capacity; +mod replace_box; mod reserve_after_initialization; mod return_self_not_must_use; mod returns; @@ -358,8 +344,10 @@ mod swap_ptr_to_ref; mod tabs_in_doc_comments; mod temporary_assignment; mod tests_outside_test_module; +mod time_subtraction; mod to_digit_is_some; mod to_string_trait_impl; +mod toplevel_ref_arg; mod trailing_empty_array; mod trait_bounds; mod transmute; @@ -375,6 +363,7 @@ mod unit_types; mod unnecessary_box_returns; mod unnecessary_literal_bound; mod unnecessary_map_on_constructor; +mod unnecessary_mut_passed; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; mod unnecessary_semicolon; @@ -400,6 +389,7 @@ mod useless_conversion; mod vec; mod vec_init_then_push; mod visibility; +mod volatile_composites; mod wildcard_imports; mod write; mod zero_div_zero; @@ -482,10 +472,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|_| Box::new(misc::LintPass)); store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); - store.register_late_pass(|_| Box::new(mut_mut::MutMut)); - store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed)); + store.register_late_pass(|_| Box::new(mut_mut::MutMut::default())); + store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(len_zero::LenZero)); + store.register_late_pass(move |_| Box::new(len_zero::LenZero::new(conf))); store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf))); store.register_late_pass(|_| Box::new(blocks_in_conditions::BlocksInConditions)); store.register_late_pass(|_| Box::new(unicode::Unicode)); @@ -546,10 +536,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(derive::Derive)); store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))); 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(invalid_upcast_comparisons::InvalidUpcastComparisons)); + store.register_late_pass(|_| Box::new(empty_enums::EmptyEnums)); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(copies::CopyAndPaste::new(tcx, conf))); + store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); @@ -587,16 +576,15 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)); store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); - store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl)); + store.register_late_pass(move |_| Box::new(inherent_impl::MultipleInherentImpl::new(conf))); store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); - store.register_late_pass(|_| Box::new(unwrap::Unwrap)); + store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf))); store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))); store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))); - store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants)); + store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))); store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)); store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString)); store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf))); @@ -619,7 +607,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); store.register_early_pass(|| Box::new(precedence::Precedence)); store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); - store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue)); + store.register_late_pass(|_| Box::new(needless_continue::NeedlessContinue)); 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)); @@ -672,7 +660,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))); store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); - store.register_early_pass(move || Box::new(module_style::ModStyle)); + store.register_early_pass(move || Box::new(module_style::ModStyle::default())); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))); store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))); @@ -719,7 +707,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); - store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); + store.register_late_pass(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); @@ -744,11 +732,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)); store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock)); - store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped)); + store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))); - store.register_late_pass(move |_| Box::new(lines_filter_map_ok::LinesFilterMapOk::new(conf))); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))); store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))); @@ -760,12 +747,12 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes)); store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)); store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)); - store.register_late_pass(|_| Box::new(needless_if::NeedlessIf)); + store.register_late_pass(|_| Box::new(needless_ifs::NeedlessIfs)); store.register_late_pass(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))); store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))); store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)); store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))); - store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls)); + store.register_late_pass(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx))); store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))); store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf))); store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))); @@ -803,7 +790,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); - store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); @@ -812,7 +798,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); - store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))); store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)); @@ -831,5 +816,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); + store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); + store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); + store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default())); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 149ae5e710c1..727e9b172a87 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -184,7 +184,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_fn_inner<'tcx>( cx: &LateContext<'tcx>, sig: &'tcx FnSig<'_>, @@ -540,7 +540,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ false } -#[allow(clippy::struct_excessive_bools)] +#[expect(clippy::struct_excessive_bools)] struct Usage { lifetime: Lifetime, in_where_predicate: bool, @@ -745,7 +745,7 @@ fn report_elidable_impl_lifetimes<'tcx>( impl_: &'tcx Impl<'_>, map: &FxIndexMap>, ) { - let single_usages = map + let (elidable_lts, usages): (Vec<_>, Vec<_>) = map .iter() .filter_map(|(def_id, usages)| { if let [ @@ -762,14 +762,12 @@ fn report_elidable_impl_lifetimes<'tcx>( None } }) - .collect::>(); + .unzip(); - if single_usages.is_empty() { + if elidable_lts.is_empty() { return; } - let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip(); - report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true); } @@ -795,9 +793,7 @@ fn report_elidable_lifetimes( // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a // `Node::GenericParam`. .filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident()) - .map(|ident| ident.to_string()) - .collect::>() - .join(", "); + .format(", "); let elidable_usages: Vec = usages .iter() @@ -867,20 +863,33 @@ fn elision_suggestions( // ^^^^ vec![(generics.span, String::new())] } else { + // 1. Start from the last elidable lifetime + // 2. While the lifetimes preceding it are also elidable, construct spans going from the current + // lifetime to the comma before it + // 3. Once this chain of elidable lifetimes stops, switch to constructing spans going from the + // current lifetime to the comma _after_ it + let mut end: Option = None; elidable_lts .iter() + .rev() .map(|&id| { - let pos = explicit_params.iter().position(|param| param.def_id == id)?; - let param = explicit_params.get(pos)?; + let (idx, param) = explicit_params.iter().find_position(|param| param.def_id == id)?; - let span = if let Some(next) = explicit_params.get(pos + 1) { + let span = if let Some(next) = explicit_params.get(idx + 1) + && end != Some(next.def_id) + { + // Extend the current span forward, up until the next param in the list. // fn x<'prev, 'a, 'next>() {} // ^^^^ param.span.until(next.span) } else { - // `pos` should be at least 1 here, because the param in position 0 would either have a `next` - // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch. - let prev = explicit_params.get(pos - 1)?; + // Extend the current span back to include the comma following the previous + // param. If the span of the next param in the list has already been + // extended, we continue the chain. This is why we're iterating in reverse. + end = Some(param.def_id); + + // `idx` will never be 0, else we'd be removing the entire list of generics + let prev = explicit_params.get(idx - 1)?; // fn x<'prev, 'a>() {} // ^^^^ diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs deleted file mode 100644 index 14ccb6fce223..000000000000 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ /dev/null @@ -1,133 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_diag_item_method, is_trait_method, path_to_local_id, sym}; -use rustc_errors::Applicability; -use rustc_hir::{Body, Closure, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; -use rustc_span::Symbol; - -pub struct LinesFilterMapOk { - msrv: Msrv, -} - -impl LinesFilterMapOk { - pub fn new(conf: &Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` - /// when `lines` has type `std::io::Lines`. - /// - /// ### Why is this bad? - /// `Lines` instances might produce a never-ending stream of `Err`, in which case - /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an - /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, - /// even in the absence of explicit loops in the user code. - /// - /// This situation can arise when working with user-provided paths. On some platforms, - /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, - /// but any later attempt to read from `fs` will return an error. - /// - /// ### Known problems - /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` - /// instance in all cases. There are two cases where the suggestion might not be - /// appropriate or necessary: - /// - /// - If the `Lines` instance can never produce any error, or if an error is produced - /// only once just before terminating the iterator, using `map_while()` is not - /// necessary but will not do any harm. - /// - If the `Lines` instance can produce intermittent errors then recover and produce - /// successful results, using `map_while()` would stop at the first error. - /// - /// ### Example - /// ```no_run - /// # use std::{fs::File, io::{self, BufRead, BufReader}}; - /// # let _ = || -> io::Result<()> { - /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); - /// // If "some-path" points to a directory, the next statement never terminates: - /// let first_line: Option = lines.next(); - /// # Ok(()) }; - /// ``` - /// Use instead: - /// ```no_run - /// # use std::{fs::File, io::{self, BufRead, BufReader}}; - /// # let _ = || -> io::Result<()> { - /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); - /// let first_line: Option = lines.next(); - /// # Ok(()) }; - /// ``` - #[clippy::version = "1.70.0"] - pub LINES_FILTER_MAP_OK, - suspicious, - "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop" -} - -impl_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]); - -impl LateLintPass<'_> for LinesFilterMapOk { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind - && is_trait_method(cx, expr, sym::Iterator) - && let fm_method_name = fm_method.ident.name - && matches!(fm_method_name, sym::filter_map | sym::flat_map | sym::flatten) - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines) - && should_lint(cx, fm_args, fm_method_name) - && self.msrv.meets(cx, msrvs::MAP_WHILE) - { - span_lint_and_then( - cx, - LINES_FILTER_MAP_OK, - fm_span, - format!("`{fm_method_name}()` will run forever if the iterator repeatedly produces an `Err`",), - |diag| { - diag.span_note( - fm_receiver.span, - "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error"); - diag.span_suggestion( - fm_span, - "replace with", - "map_while(Result::ok)", - Applicability::MaybeIncorrect, - ); - }, - ); - } - } -} - -fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_name: Symbol) -> bool { - match args { - [] => method_name == sym::flatten, - [fm_arg] => { - match &fm_arg.kind { - // Detect `Result::ok` - ExprKind::Path(qpath) => cx - .qpath_res(qpath, fm_arg.hir_id) - .opt_def_id() - .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::result_ok_method, did)), - // Detect `|x| x.ok()` - ExprKind::Closure(Closure { body, .. }) => { - if let Body { - params: [param], value, .. - } = cx.tcx.hir_body(*body) - && let ExprKind::MethodCall(method, receiver, [], _) = value.kind - && path_to_local_id(receiver, param.pat.hir_id) - && let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id) - { - is_diag_item_method(cx, method_did, sym::Result) && method.ident.name == sym::ok - } else { - false - } - }, - _ => false, - } - }, - _ => false, - } -} diff --git a/clippy_lints/src/loops/char_indices_as_byte_indices.rs b/clippy_lints/src/loops/char_indices_as_byte_indices.rs index a702e60f1c27..4aae602ea051 100644 --- a/clippy_lints/src/loops/char_indices_as_byte_indices.rs +++ b/clippy_lints/src/loops/char_indices_as_byte_indices.rs @@ -1,9 +1,9 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{eq_expr_value, higher, path_to_local_id, sym}; +use clippy_utils::{eq_expr_value, higher, sym}; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::{Expr, ExprKind, LangItem, Node, Pat, PatKind}; use rustc_lint::LateContext; @@ -49,7 +49,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, iterable: &Expr { // Destructured iterator element `(idx, _)`, look for uses of the binding for_each_expr(cx, body, |expr| { - if path_to_local_id(expr, binding_id) { + if expr.res_local_id() == Some(binding_id) { check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv); } CONTINUE @@ -58,7 +58,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, iterable: &Expr // Bound as a tuple, look for `tup.0` for_each_expr(cx, body, |expr| { if let ExprKind::Field(e, field) = expr.kind - && path_to_local_id(e, binding_id) + && e.res_local_id() == Some(binding_id) && field.name == sym::integer(0) { check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv); @@ -81,7 +81,7 @@ fn check_index_usage<'tcx>( return; }; - let is_string_like = |ty: Ty<'_>| ty.is_str() || is_type_lang_item(cx, ty, LangItem::String); + let is_string_like = |ty: Ty<'_>| ty.is_str() || ty.is_lang_item(cx, LangItem::String); let message = match parent_expr.kind { ExprKind::MethodCall(segment, recv, ..) // We currently only lint `str` methods (which `String` can deref to), so a `.is_str()` check is sufficient here @@ -131,7 +131,7 @@ fn check_index_usage<'tcx>( fn index_consumed_at<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) { match node { - Node::Expr(expr) if higher::Range::hir(expr).is_some() => {}, + Node::Expr(expr) if higher::Range::hir(cx, expr).is_some() => {}, Node::ExprField(_) => {}, Node::Expr(expr) => return Some(expr), _ => break, diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs index 4aa1c2e211d3..daca78e34474 100644 --- a/clippy_lints/src/loops/explicit_into_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -1,6 +1,6 @@ use super::EXPLICIT_INTO_ITER_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir::Expr; @@ -43,7 +43,11 @@ impl AdjustKind { } pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) { - if !is_trait_method(cx, call_expr, sym::IntoIterator) { + if !cx + .ty_based_def(call_expr) + .opt_parent(cx) + .is_diag_item(cx, sym::IntoIterator) + { return; } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 010652e1cb90..40d1d36bd162 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -1,10 +1,11 @@ use super::EXPLICIT_ITER_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::sym; use clippy_utils::ty::{ - implements_trait, implements_trait_with_env, is_copy, is_type_lang_item, make_normalized_projection, + implements_trait, implements_trait_with_env, is_copy, make_normalized_projection, make_normalized_projection_with_regions, normalize_with_regions, }; use rustc_errors::Applicability; @@ -127,8 +128,7 @@ fn is_ref_iterable<'tcx>( let self_ty = typeck.expr_ty(self_arg); let self_is_copy = is_copy(cx, self_ty); - if is_type_lang_item(cx, self_ty.peel_refs(), rustc_hir::LangItem::OwnedBox) - && !msrv.meets(cx, msrvs::BOX_INTO_ITER) + if self_ty.peel_refs().is_lang_item(cx, rustc_hir::LangItem::OwnedBox) && !msrv.meets(cx, msrvs::BOX_INTO_ITER) { return None; } @@ -138,9 +138,9 @@ fn is_ref_iterable<'tcx>( return Some((AdjustKind::None, self_ty)); } - let res_ty = cx - .tcx - .erase_regions(EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id))); + let res_ty = cx.tcx.erase_and_anonymize_regions( + EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)), + ); let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() { Some(mutbl) } else { diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index e314bc2068b3..c6b650a1a88b 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -1,7 +1,7 @@ use super::FOR_KV_MAP; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; @@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx _ => arg, }; - if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) { + if ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap) { span_lint_and_then( cx, FOR_KV_MAP, diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs index b8a263817d29..8a4644cdf5ef 100644 --- a/clippy_lints/src/loops/iter_next_loop.rs +++ b/clippy_lints/src/loops/iter_next_loop.rs @@ -1,12 +1,12 @@ use super::ITER_NEXT_LOOP; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>) { - if is_trait_method(cx, arg, sym::Iterator) { + if cx.ty_based_def(arg).opt_parent(cx).is_diag_item(cx, sym::Iterator) { span_lint( cx, ITER_NEXT_LOOP, diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index f99989ec6ba4..34917252d049 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -1,12 +1,12 @@ use super::MANUAL_FIND; use super::utils::make_iterator_snippet; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt}; +use clippy_utils::{as_some_expr, higher, peel_blocks_with_stmt}; use rustc_errors::Applicability; -use rustc_hir::def::Res; use rustc_hir::lang_items::LangItem; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind}; use rustc_lint::LateContext; @@ -33,9 +33,8 @@ pub(super) fn check<'tcx>( && let [stmt] = block.stmts && let StmtKind::Semi(semi) = stmt.kind && let ExprKind::Ret(Some(ret_value)) = semi.kind - && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind - && is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome) - && path_res(cx, inner_ret) == Res::Local(binding_id) + && let Some(inner_ret) = as_some_expr(cx, ret_value) + && inner_ret.res_local_id() == Some(binding_id) && !contains_return_break_continue_macro(cond) && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr) { @@ -150,7 +149,7 @@ fn last_stmt_and_ret<'tcx>( && let Some((_, Node::Block(block))) = parent_iter.next() && let Some((last_stmt, last_ret)) = extract(block) && last_stmt.hir_id == node_hir - && is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone) + && last_ret.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionNone) && let Some((_, Node::Expr(_block))) = parent_iter.next() // This includes the function header && let Some((_, func)) = parent_iter.next() diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index ddb8bb536c04..96de118b5233 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -2,9 +2,10 @@ use super::MANUAL_FLATTEN; use super::utils::make_iterator_snippet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet_with_applicability}; use clippy_utils::visitors::is_local_used; -use clippy_utils::{higher, is_refutable, path_to_local_id, peel_blocks_with_stmt, span_contains_comment}; +use clippy_utils::{higher, is_refutable, peel_blocks_with_stmt, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, Pat, PatKind}; @@ -27,7 +28,7 @@ pub(super) fn check<'tcx>( = higher::IfLet::hir(cx, inner_expr) // Ensure match_expr in `if let` statement is the same as the pat from the for-loop && let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind - && path_to_local_id(let_expr, pat_hir_id) + && let_expr.res_local_id() == Some(pat_hir_id) // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result` && let PatKind::TupleStruct(ref qpath, [inner_pat], _) = let_pat.kind && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id) diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index d9c4b526da99..e5c37377e857 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -1,10 +1,11 @@ use super::{IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_copy; use clippy_utils::usage::local_used_in; -use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg}; +use clippy_utils::{get_enclosing_block, higher, sugg}; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir::intravisit::walk_block; @@ -27,7 +28,8 @@ pub(super) fn check<'tcx>( start: Some(start), end: Some(end), limits, - }) = higher::Range::hir(arg) + span: _, + }) = higher::Range::hir(cx, arg) // the var must be a single name && let PatKind::Binding(_, canonical_id, _, _) = pat.kind { @@ -67,7 +69,7 @@ pub(super) fn check<'tcx>( && !local_used_in(cx, canonical_id, base_left) && !local_used_in(cx, canonical_id, base_right) // Source and destination must be different - && path_to_local(base_left) != path_to_local(base_right) + && base_left.res_local_id() != base_right.res_local_id() { Some(( ty, @@ -128,7 +130,7 @@ fn build_manual_memcpy_suggestion<'tcx>( let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { if let ExprKind::MethodCall(method, recv, [], _) = end.kind && method.ident.name == sym::len - && path_to_local(recv) == path_to_local(base) + && recv.res_local_id() == base.res_local_id() { if sugg.to_string() == end_str { sugg::EMPTY.into() @@ -364,7 +366,7 @@ fn get_details_from_idx<'tcx>( starts: &[Start<'tcx>], ) -> Option<(StartKind<'tcx>, Offset)> { fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { - let id = path_to_local(e)?; + let id = e.res_local_id()?; starts.iter().find(|start| start.id == id).map(|start| start.kind) } @@ -425,7 +427,7 @@ fn get_assignments<'a, 'tcx>( .chain(*expr) .filter(move |e| { if let ExprKind::AssignOp(_, place, _) = e.kind { - path_to_local(place).is_some_and(|id| { + place.res_local_id().is_some_and(|id| { !loop_counters .iter() // skip the first item which should be `StartKind::Range` diff --git a/clippy_lints/src/loops/manual_slice_fill.rs b/clippy_lints/src/loops/manual_slice_fill.rs index 15c656cc7bc7..a2ff60a3d8ac 100644 --- a/clippy_lints/src/loops/manual_slice_fill.rs +++ b/clippy_lints/src/loops/manual_slice_fill.rs @@ -31,7 +31,8 @@ pub(super) fn check<'tcx>( start: Some(start), end: Some(end), limits: RangeLimits::HalfOpen, - }) = higher::Range::hir(arg) + span: _, + }) = higher::Range::hir(cx, arg) && let ExprKind::Lit(Spanned { node: LitKind::Int(Pu128(0), _), .. diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs index 8a2d0036203a..d5dbcbe9dc70 100644 --- a/clippy_lints/src/loops/missing_spin_loop.rs +++ b/clippy_lints/src/loops/missing_spin_loop.rs @@ -1,7 +1,7 @@ use super::MISSING_SPIN_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::std_or_core; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind}; use rustc_lint::LateContext; @@ -40,7 +40,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' && let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name) && let callee_ty = cx.typeck_results().expr_ty(callee) - && is_type_diagnostic_item(cx, callee_ty, sym::AtomicBool) + && callee_ty.is_diag_item(cx, sym::AtomicBool) && let Some(std_or_core) = std_or_core(cx) { span_lint_and_sugg( diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 01c36b8cb12f..21198c3c8bc2 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -861,6 +861,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { // check for `loop { if let {} else break }` that could be `while let` // (also matches an explicit "match" instead of "if let") // (even if the "match" or "if let" is used for declaration) + // (also matches on `let {} else break`) if let ExprKind::Loop(block, label, LoopSource::Loop, _) = expr.kind { // also check for empty `loop {}` statements, skipping those in #[panic_handler] empty_loop::check(cx, expr, block); @@ -870,17 +871,29 @@ impl<'tcx> LateLintPass<'tcx> for Loops { while_let_on_iterator::check(cx, expr); - if let Some(higher::While { condition, body, span }) = higher::While::hir(expr) { + if let Some(higher::While { + condition, body, span, .. + }) = higher::While::hir(expr) + { while_immutable_condition::check(cx, condition, body); while_float::check(cx, condition); missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } impl Loops { - #[allow(clippy::too_many_arguments)] + #[expect(clippy::too_many_arguments)] fn check_for_loop<'tcx>( &self, cx: &LateContext<'tcx>, @@ -904,7 +917,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index 70ca452013f9..6ab7bb628eca 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -1,6 +1,7 @@ use super::MUT_RANGE_BOUND; use clippy_utils::diagnostics::span_lint_and_note; -use clippy_utils::{get_enclosing_block, higher, path_to_local}; +use clippy_utils::res::MaybeResPath; +use clippy_utils::{get_enclosing_block, higher}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Node, PatKind}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; @@ -15,7 +16,7 @@ pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { start: Some(start), end: Some(end), .. - }) = higher::Range::hir(arg) + }) = higher::Range::hir(cx, arg) && let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end)) && (mut_id_start.is_some() || mut_id_end.is_some()) { @@ -39,7 +40,7 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { } fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option { - if let Some(hir_id) = path_to_local(bound) + if let Some(hir_id) = bound.res_local_id() && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) && let PatKind::Binding(BindingMode::MUT, ..) = pat.kind { diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 11edb929d70b..a0de464ff7f8 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -30,7 +30,8 @@ pub(super) fn check<'tcx>( start: Some(start), ref end, limits, - }) = higher::Range::hir(arg) + span, + }) = higher::Range::hir(cx, arg) // the var must be a single name && let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { @@ -149,17 +150,14 @@ pub(super) fn check<'tcx>( span_lint_and_then( cx, NEEDLESS_RANGE_LOOP, - arg.span, + span, format!("the loop variable `{}` is used to index `{indexed}`", ident.name), |diag| { diag.multipart_suggestion( "consider using an iterator and enumerate()", vec![ (pat.span, format!("({}, )", ident.name)), - ( - arg.span, - format!("{indexed}.{method}().enumerate(){method_1}{method_2}"), - ), + (span, format!("{indexed}.{method}().enumerate(){method_1}{method_2}")), ], Applicability::HasPlaceholders, ); @@ -175,12 +173,12 @@ pub(super) fn check<'tcx>( span_lint_and_then( cx, NEEDLESS_RANGE_LOOP, - arg.span, + span, format!("the loop variable `{}` is only used to index `{indexed}`", ident.name), |diag| { diag.multipart_suggestion( "consider using an iterator", - vec![(pat.span, "".to_string()), (arg.span, repl)], + vec![(pat.span, "".to_string()), (span, repl)], Applicability::HasPlaceholders, ); }, diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 2ccff7680976..0d37be17689a 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -7,7 +7,7 @@ use clippy_utils::source::snippet; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use rustc_errors::Applicability; use rustc_hir::{ - Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, + Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, }; use rustc_lint::LateContext; use rustc_span::{BytePos, Span, sym}; @@ -22,7 +22,10 @@ pub(super) fn check<'tcx>( for_loop: Option<&ForLoop<'_>>, ) { match never_loop_block(cx, block, &mut Vec::new(), loop_id) { - NeverLoopResult::Diverging { ref break_spans } => { + NeverLoopResult::Diverging { + ref break_spans, + ref never_spans, + } => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { arg: iterator, @@ -34,12 +37,16 @@ pub(super) fn check<'tcx>( { // If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not // appropriate. - let app = if !contains_any_break_or_continue(block) && label.is_none() { + let mut app = if !contains_any_break_or_continue(block) && label.is_none() { Applicability::MachineApplicable } else { Applicability::Unspecified }; + if !never_spans.is_empty() { + app = Applicability::HasPlaceholders; + } + let mut suggestions = vec![( for_span.with_hi(iterator.span.hi()), for_to_if_let_sugg(cx, iterator, pat), @@ -51,6 +58,13 @@ pub(super) fn check<'tcx>( suggestions, app, ); + + for span in never_spans { + diag.span_help( + *span, + "this code is unreachable. Consider moving the reachable parts out", + ); + } } }); }, @@ -61,12 +75,19 @@ pub(super) fn check<'tcx>( fn contains_any_break_or_continue(block: &Block<'_>) -> bool { for_each_expr_without_closures(block, |e| match e.kind { ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()), + ExprKind::InlineAsm(asm) if contains_label(asm) => ControlFlow::Break(()), ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), _ => ControlFlow::Continue(Descend::Yes), }) .is_some() } +fn contains_label(asm: &InlineAsm<'_>) -> bool { + asm.operands + .iter() + .any(|(op, _span)| matches!(op, InlineAsmOperand::Label { .. })) +} + /// The `never_loop` analysis keeps track of three things: /// /// * Has any (reachable) code path hit a `continue` of the main loop? @@ -77,13 +98,16 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool { /// The first two bits of information are in this enum, and the last part is in the /// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by /// scope. -#[derive(Clone)] +#[derive(Clone, Debug)] enum NeverLoopResult { /// A continue may occur for the main loop. MayContinueMainLoop, /// We have not encountered any main loop continue, /// but we are diverging (subsequent control flow is not reachable) - Diverging { break_spans: Vec }, + Diverging { + break_spans: Vec, + never_spans: Vec, + }, /// We have not encountered any main loop continue, /// and subsequent control flow is (possibly) reachable Normal, @@ -128,14 +152,18 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult ( NeverLoopResult::Diverging { break_spans: mut break_spans1, + never_spans: mut never_spans1, }, NeverLoopResult::Diverging { break_spans: mut break_spans2, + never_spans: mut never_spans2, }, ) => { break_spans1.append(&mut break_spans2); + never_spans1.append(&mut never_spans2); NeverLoopResult::Diverging { break_spans: break_spans1, + never_spans: never_spans1, } }, } @@ -207,6 +235,8 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { } return vec![stmt.span]; + } else if let Node::Block(_) = cx.tcx.parent_hir_node(expr.hir_id) { + return vec![expr.span]; } vec![] @@ -217,7 +247,7 @@ fn is_label_for_block(cx: &LateContext<'_>, dest: &Destination) -> bool { .is_ok_and(|hir_id| matches!(cx.tcx.hir_node(hir_id), Node::Block(_))) } -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn never_loop_expr<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'tcx>, @@ -270,10 +300,13 @@ fn never_loop_expr<'tcx>( ExprKind::Match(e, arms, _) => { let e = never_loop_expr(cx, e, local_labels, main_loop_id); combine_seq(e, || { - arms.iter() - .fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| { - combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) - }) + arms.iter().fold( + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + }, + |a, b| combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)), + ) }) }, ExprKind::Block(b, _) => { @@ -296,6 +329,7 @@ fn never_loop_expr<'tcx>( } else { NeverLoopResult::Diverging { break_spans: all_spans_after_expr(cx, expr), + never_spans: vec![], } } }, @@ -306,7 +340,10 @@ fn never_loop_expr<'tcx>( combine_seq(first, || { // checks if break targets a block instead of a loop mark_block_as_reachable(expr, local_labels); - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }) }, ExprKind::Break(dest, e) => { @@ -322,11 +359,15 @@ fn never_loop_expr<'tcx>( } else { all_spans_after_expr(cx, expr) }, + never_spans: vec![], } }) }, ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }), ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { @@ -344,7 +385,15 @@ fn never_loop_expr<'tcx>( InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => { NeverLoopResult::Normal }, - InlineAsmOperand::Label { block } => never_loop_block(cx, block, local_labels, main_loop_id), + InlineAsmOperand::Label { block } => + // We do not know whether the label will be executed or not, so `Diverging` must be + // downgraded to `Normal`. + { + match never_loop_block(cx, block, local_labels, main_loop_id) { + NeverLoopResult::Diverging { .. } => NeverLoopResult::Normal, + result => result, + } + }, })), ExprKind::OffsetOf(_, _) | ExprKind::Yield(_, _) @@ -356,7 +405,10 @@ fn never_loop_expr<'tcx>( }; let result = combine_seq(result, || { if cx.typeck_results().expr_ty(expr).is_never() { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: all_spans_after_expr(cx, expr), + } } else { NeverLoopResult::Normal } diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index e792edbe23e0..4135c63d4d7f 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -1,9 +1,10 @@ use super::SAME_ITEM_PUSH; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::{msrvs, path_to_local, std_or_core, sym}; +use clippy_utils::ty::implements_trait; +use clippy_utils::{msrvs, std_or_core, sym}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -125,7 +126,7 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> { if !self.non_deterministic_expr && !self.multiple_pushes && let Some((vec, _, _)) = self.vec_push - && let Some(hir_id) = path_to_local(vec) + && let Some(hir_id) = vec.res_local_id() { !self.used_locals.contains(&hir_id) } else { @@ -141,7 +142,7 @@ impl<'tcx> Visitor<'tcx> for SameItemPushVisitor<'_, 'tcx> { ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) => self.non_deterministic_expr = true, ExprKind::Block(block, _) => self.visit_block(block), _ => { - if let Some(hir_id) = path_to_local(expr) { + if let Some(hir_id) = expr.res_local_id() { self.used_locals.insert(hir_id); } walk_expr(self, expr); @@ -186,7 +187,7 @@ fn get_vec_push<'tcx>( && let ExprKind::MethodCall(path, self_expr, [pushed_item], _) = &semi_stmt.kind // Check that the method being called is push() on a Vec && path.ident.name == sym::push - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec) + && cx.typeck_results().expr_ty(self_expr).is_diag_item(cx, sym::Vec) { return Some((self_expr, pushed_item, semi_stmt.span.ctxt())); } diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index d66771a8b5be..edbf6e63e659 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -90,7 +90,7 @@ pub(super) fn check<'tcx>( arg_snip = format!("({arg_snip})").into(); } - if clippy_utils::higher::Range::hir(arg_expression).is_some() { + if clippy_utils::higher::Range::hir(cx, arg_expression).is_some() { let range_expr = snippet(cx, arg_expression.span, "?").to_string(); let sugg = snippet(cx, arg_expression.span, ".."); diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 13b93d2c0097..82ded453616d 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,42 +1,87 @@ use super::UNUSED_ENUMERATE_INDEX; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::{expr_or_init, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_span::sym; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && is_type_diagnostic_item(cx, ty, sym::Enumerate) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + iter_expr: &'tcx Expr<'tcx>, + pat: &Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && cx.typeck_results().expr_ty(iter_expr).is_diag_item(cx, sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, iter_expr) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) { - span_lint_and_then( + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); + span_lint_hir_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg.span, + enumerate_call.hir_id, + enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], + spans, Applicability::MachineApplicable, ); }, ); } } + +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() + { + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check(cx, recv, param.pat, ty_spans, body.value); + } +} diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 2f6950b4380c..56d535c4f262 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -1,5 +1,6 @@ +use clippy_utils::res::MaybeResPath; use clippy_utils::ty::{has_iter_method, implements_trait}; -use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg}; +use clippy_utils::{get_parent_expr, is_integer_const, sugg}; use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr, walk_local}; @@ -47,7 +48,7 @@ impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for IncrementVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { // If node is a variable - if let Some(def_id) = path_to_local(expr) { + if let Some(def_id) = expr.res_local_id() { if let Some(parent) = get_parent_expr(self.cx, expr) { let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); if *state == IncrementVisitorVarState::IncrOnce { @@ -175,7 +176,7 @@ impl<'tcx> Visitor<'tcx> for InitializeVisitor<'_, 'tcx> { } // If node is the desired variable, see how it's used - if path_to_local_id(expr, self.var_id) { + if expr.res_local_id() == Some(self.var_id) { if self.past_loop { self.state = InitializeVisitorState::DontWarn; return; @@ -255,7 +256,7 @@ fn is_conditional(expr: &Expr<'_>) -> bool { /// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the /// actual `Iterator` that the loop uses. -pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { +pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applicability: &mut Applicability) -> String { let impls_iterator = cx .tcx .get_diagnostic_item(sym::Iterator) @@ -263,7 +264,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic if impls_iterator { format!( "{}", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren() + sugg::Sugg::hir_with_applicability(cx, arg, "_", applicability).maybe_paren() ) } else { // (&x).into_iter() ==> x.iter() @@ -281,12 +282,12 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic }; format!( "{}.{method_name}()", - sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_paren(), + sugg::Sugg::hir_with_applicability(cx, caller, "_", applicability).maybe_paren(), ) }, _ => format!( "{}.into_iter()", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren() + sugg::Sugg::hir_with_applicability(cx, arg, "_", applicability).maybe_paren() ), } } diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index 845edb9cae15..d4285db0abfc 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -10,19 +10,19 @@ use rustc_hir::{Block, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind, Path, use rustc_lint::LateContext; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { - let (init, let_info) = match (loop_block.stmts, loop_block.expr) { + let (init, let_info, els) = match (loop_block.stmts, loop_block.expr) { ([stmt, ..], _) => match stmt.kind { StmtKind::Let(LetStmt { init: Some(e), - els: None, + els, pat, ty, .. - }) => (*e, Some((*pat, *ty))), - StmtKind::Semi(e) | StmtKind::Expr(e) => (e, None), + }) => (*e, Some((*pat, *ty)), *els), + StmtKind::Semi(e) | StmtKind::Expr(e) => (e, None, None), _ => return, }, - ([], Some(e)) => (e, None), + ([], Some(e)) => (e, None, None), _ => return, }; let has_trailing_exprs = loop_block.stmts.len() + usize::from(loop_block.expr.is_some()) > 1; @@ -38,14 +38,26 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_blo if_let.let_expr, has_trailing_exprs, let_info, - if_let.if_then, + Some(if_let.if_then), ); + } else if els.and_then(|x| x.expr).is_some_and(is_simple_break_expr) + && let Some((pat, _)) = let_info + { + could_be_while_let(cx, expr, pat, init, has_trailing_exprs, let_info, None); } else if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal) = init.kind && arm1.guard.is_none() && arm2.guard.is_none() && is_simple_break_expr(arm2.body) { - could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs, let_info, arm1.body); + could_be_while_let( + cx, + expr, + arm1.pat, + scrutinee, + has_trailing_exprs, + let_info, + Some(arm1.body), + ); } } @@ -70,7 +82,7 @@ fn could_be_while_let<'tcx>( let_expr: &'tcx Expr<'_>, has_trailing_exprs: bool, let_info: Option<(&Pat<'_>, Option<&Ty<'_>>)>, - inner_expr: &Expr<'_>, + inner_expr: Option<&Expr<'_>>, ) { if has_trailing_exprs && (needs_ordered_drop(cx, cx.typeck_results().expr_ty(let_expr)) @@ -85,7 +97,7 @@ fn could_be_while_let<'tcx>( // 1) it was ugly with big bodies; // 2) it was not indented properly; // 3) it wasn’t very smart (see #675). - let inner_content = if let Some((pat, ty)) = let_info + let inner_content = if let Some(((pat, ty), inner_expr)) = let_info.zip(inner_expr) // Prevent trivial reassignments such as `let x = x;` or `let _ = …;`, but // keep them if the type has been explicitly specified. && (!is_trivial_assignment(pat, peel_blocks(inner_expr)) || ty.is_some()) diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 6000ff7a3609..2545f81f1afa 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -2,13 +2,14 @@ use std::ops::ControlFlow; use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::visitors::is_res_used; -use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable, is_res_lang_ctor, is_trait_method}; +use clippy_utils::{as_some_pattern, get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Closure, Expr, ExprKind, HirId, LangItem, LetStmt, Mutability, PatKind, UnOp}; +use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::ty::adjustment::Adjust; @@ -18,12 +19,11 @@ use rustc_span::symbol::sym; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { if_then, let_pat, let_expr, label, .. }) = higher::WhileLet::hir(expr) // check for `Some(..)` pattern - && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind - && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) + && let Some(some_pat) = as_some_pattern(cx, let_pat) // check for call to `Iterator::next` && let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind && method_name.ident.name == sym::next - && is_trait_method(cx, let_expr, sym::Iterator) + && cx.ty_based_def(let_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr) // get the loop containing the match expression && !uses_iter(cx, &iter_expr_struct, if_then) diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index 9071c9c95f9d..a323c7cf8307 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -2,13 +2,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_lint_allowed; use itertools::Itertools; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; -use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource, find_attr}; use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; use rustc_middle::lint::LevelAndSource; use rustc_session::impl_lint_pass; -use rustc_span::{Span, SyntaxContext, sym}; +use rustc_span::{Span, SyntaxContext}; use std::collections::BTreeMap; use std::collections::btree_map::Entry; @@ -146,7 +147,8 @@ struct BodyVisitor<'a, 'tcx> { } fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { - (cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export)) + (cx.effective_visibilities.is_exported(def_id) + || find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::MacroExport { .. })) && !cx.tcx.is_doc_hidden(def_id) } diff --git a/clippy_lints/src/manual_abs_diff.rs b/clippy_lints/src/manual_abs_diff.rs index 288f27db8ca2..22de5e8268bd 100644 --- a/clippy_lints/src/manual_abs_diff.rs +++ b/clippy_lints/src/manual_abs_diff.rs @@ -2,10 +2,11 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::If; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::HasSession as _; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment}; +use clippy_utils::ty::peel_and_count_ty_refs; +use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -104,10 +105,10 @@ impl ManualAbsDiff { fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<(Ty<'tcx>, usize)> { let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF); let is_duration = - |ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); + |ty: Ty<'_>| ty.is_diag_item(cx, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); - let (b_ty, b_n_refs) = peel_middle_ty_refs(cx.typeck_results().expr_ty(b)); + let (b_ty, b_n_refs, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(b)); (a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs)) } diff --git a/clippy_lints/src/manual_assert.rs b/clippy_lints/src/manual_assert.rs index 76cb22864779..c34e0d33e713 100644 --- a/clippy_lints/src/manual_assert.rs +++ b/clippy_lints/src/manual_assert.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_panic, root_macro_call}; +use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::{higher, is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -50,32 +51,32 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert { // Should this have a config value? && !is_else_clause(cx.tcx, expr) { - let mut applicability = Applicability::MachineApplicable; - let mut comments = span_extract_comment(cx.sess().source_map(), expr.span); - if !comments.is_empty() { - comments += "\n"; - } - let cond_sugg = !sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability); - let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; - let sugg = format!("assert!({cond_sugg}, {format_args_snip}){semicolon}"); - // we show to the user the suggestion without the comments, but when applying the fix, include the - // comments in the block span_lint_and_then( cx, MANUAL_ASSERT, expr.span, "only a `panic!` in `if`-then statement", |diag| { - // comments can be noisy, do not show them to the user + let mut applicability = Applicability::MachineApplicable; + let mut comments = span_extract_comment(cx.sess().source_map(), expr.span); if !comments.is_empty() { - diag.tool_only_span_suggestion( - expr.span.shrink_to_lo(), - "add comments back", - comments, - applicability, - ); + comments += "\n"; } - diag.span_suggestion(expr.span, "try instead", sugg, applicability); + let cond_sugg = !sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability); + let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; + + let indent = indent_of(cx, expr.span); + let full_sugg = reindent_multiline( + format!("{comments}assert!({cond_sugg}, {format_args_snip}){semicolon}").as_str(), + true, + indent, + ); + diag.span_suggestion_verbose( + expr.span, + "replace `if`-then-`panic!` with `assert!`", + full_sugg, + applicability, + ); }, ); } diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index ba1ad599e116..bee3b19b597c 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, - FnRetTy, GenericBound, ImplItem, Item, Node, OpaqueTy, TraitRef, Ty, TyKind, + FnRetTy, GenericBound, Node, OpaqueTy, TraitRef, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::middle::resolve_bound_vars::ResolvedArg; @@ -60,8 +60,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { && let ExprKind::Block(block, _) = body.value.kind && block.stmts.is_empty() && let Some(closure_body) = desugared_async_block(cx, block) - && let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) = - cx.tcx.hir_node_by_def_id(fn_def_id) + && let Some(vis_span_opt) = match cx.tcx.hir_node_by_def_id(fn_def_id) { + Node::Item(item) => Some(Some(item.vis_span)), + Node::ImplItem(impl_item) => Some(impl_item.vis_span()), + _ => None, + } && !span.from_expansion() { let header_span = span.with_hi(ret_ty.span.hi()); @@ -72,7 +75,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { header_span, "this function can be simplified using the `async fn` syntax", |diag| { - if let Some(vis_snip) = vis_span.get_source_text(cx) + if let Some(vis_span) = vis_span_opt + && let Some(vis_snip) = vis_span.get_source_text(cx) && let Some(header_snip) = header_span.get_source_text(cx) && let Some(ret_pos) = position_before_rarrow(&header_snip) && let Some((_, ret_snip)) = suggested_ret(cx, output) diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 42fe386d2c3c..54387e36d6c8 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -3,13 +3,11 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::higher::If; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::visitors::is_const_evaluatable; -use clippy_utils::{ - MaybePath, eq_expr_value, is_diag_trait_item, is_in_const_context, is_trait_method, path_res, path_to_local_id, - peel_blocks, peel_blocks_with_stmt, sym, -}; +use clippy_utils::{eq_expr_value, is_in_const_context, peel_blocks, peel_blocks_with_stmt, sym}; use itertools::Itertools; use rustc_errors::{Applicability, Diag}; use rustc_hir::def::Res; @@ -292,10 +290,12 @@ fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx /// # ; /// ``` fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option> { - if let ExprKind::MethodCall(seg_second, receiver, [arg_second], _) = &expr.kind - && (cx.typeck_results().expr_ty_adjusted(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord)) + if let ExprKind::MethodCall(seg_second, receiver, [arg_second], _) = expr.kind + && (cx.typeck_results().expr_ty_adjusted(receiver).is_floating_point() + || cx.ty_based_def(expr).assoc_fn_parent(cx).is_diag_item(cx, sym::Ord)) && let ExprKind::MethodCall(seg_first, input, [arg_first], _) = &receiver.kind - && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord)) + && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() + || cx.ty_based_def(receiver).assoc_fn_parent(cx).is_diag_item(cx, sym::Ord)) { let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point(); let (min, max) = match (seg_first.ident.name, seg_second.ident.name) { @@ -331,18 +331,18 @@ fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) fn segment<'tcx>(cx: &LateContext<'_>, func: &Expr<'tcx>) -> Option> { match func.kind { ExprKind::Path(QPath::Resolved(None, path)) => { - let id = path.res.opt_def_id()?; - match cx.tcx.get_diagnostic_name(id) { + let def = path.res.opt_def(cx)?; + match cx.tcx.get_diagnostic_name(def.1) { Some(sym::cmp_min) => Some(FunctionType::CmpMin), Some(sym::cmp_max) => Some(FunctionType::CmpMax), - _ if is_diag_trait_item(cx, id, sym::Ord) => { + _ if def.assoc_fn_parent(cx).is_diag_item(cx, sym::Ord) => { Some(FunctionType::OrdOrFloat(path.segments.last().expect("infallible"))) }, _ => None, } }, ExprKind::Path(QPath::TypeRelative(ty, seg)) => { - matches!(path_res(cx, ty), Res::PrimTy(PrimTy::Float(_))).then(|| FunctionType::OrdOrFloat(seg)) + matches!(ty.basic_res(), Res::PrimTy(PrimTy::Float(_))).then(|| FunctionType::OrdOrFloat(seg)) }, _ => None, } @@ -435,7 +435,7 @@ fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Opt let first = BinaryOp::new(first)?; let second = BinaryOp::new(second)?; if let PatKind::Binding(_, binding, _, None) = &last_arm.pat.kind - && path_to_local_id(peel_blocks_with_stmt(last_arm.body), *binding) + && peel_blocks_with_stmt(last_arm.body).res_local_id() == Some(*binding) && last_arm.guard.is_none() { // Proceed as normal @@ -516,7 +516,7 @@ fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> }, span: first_expr.span.to(second_expr.span), make_assignment: Some(maybe_input_first_path), - hir_with_ignore_attr: Some(first_expr.hir_id()), + hir_with_ignore_attr: Some(first_expr.hir_id), }) } else { None @@ -655,8 +655,8 @@ fn is_clamp_meta_pattern<'tcx>( let (min, max) = (second_expr, first_expr); let refers_to_input = match input_hir_ids { Some((first_hir_id, second_hir_id)) => { - path_to_local_id(peel_blocks(first_bin.left), first_hir_id) - && path_to_local_id(peel_blocks(second_bin.left), second_hir_id) + peel_blocks(first_bin.left).res_local_id() == Some(first_hir_id) + && peel_blocks(second_bin.left).res_local_id() == Some(second_hir_id) }, None => eq_expr_value(cx, first_bin.left, second_bin.left), }; diff --git a/clippy_lints/src/manual_div_ceil.rs b/clippy_lints/src/manual_div_ceil.rs deleted file mode 100644 index ed0cce754b95..000000000000 --- a/clippy_lints/src/manual_div_ceil.rs +++ /dev/null @@ -1,215 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_context; -use clippy_utils::sugg::{Sugg, has_enclosing_paren}; -use clippy_utils::{SpanlessEq, sym}; -use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp}; -use rustc_data_structures::packed::Pu128; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self}; -use rustc_session::impl_lint_pass; -use rustc_span::source_map::Spanned; - -declare_clippy_lint! { - /// ### What it does - /// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation - /// of `x.div_ceil(y)`. - /// - /// ### Why is this bad? - /// It's simpler, clearer and more readable. - /// - /// ### Example - /// ```no_run - /// let x: i32 = 7; - /// let y: i32 = 4; - /// let div = (x + (y - 1)) / y; - /// ``` - /// Use instead: - /// ```no_run - /// #![feature(int_roundings)] - /// let x: i32 = 7; - /// let y: i32 = 4; - /// let div = x.div_ceil(y); - /// ``` - #[clippy::version = "1.83.0"] - pub MANUAL_DIV_CEIL, - complexity, - "manually reimplementing `div_ceil`" -} - -pub struct ManualDivCeil { - msrv: Msrv, -} - -impl ManualDivCeil { - #[must_use] - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -impl_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]); - -impl<'tcx> LateLintPass<'tcx> for ManualDivCeil { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { - let mut applicability = Applicability::MachineApplicable; - - if let ExprKind::Binary(div_op, div_lhs, div_rhs) = expr.kind - && div_op.node == BinOpKind::Div - && check_int_ty_and_feature(cx, div_lhs) - && check_int_ty_and_feature(cx, div_rhs) - && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = div_lhs.kind - && self.msrv.meets(cx, msrvs::DIV_CEIL) - { - // (x + (y - 1)) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, div_rhs) - { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - return; - } - - // ((y - 1) + x) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, div_rhs) - { - build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability); - return; - } - - // (x + y - 1) / y - if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Sub - && add_op.node == BinOpKind::Add - && check_literal(inner_rhs) - && check_eq_expr(cx, add_rhs, div_rhs) - { - build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability); - } - - // (x + (Y - 1)) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, div_rhs) { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - } - - // ((Y - 1) + x) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, div_rhs) { - build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability); - } - - // (x - (-Y - 1)) / Y - if inner_op.node == BinOpKind::Sub - && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = div_rhs.kind - && differ_by_one(abs_div_rhs, inner_rhs) - { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - } - } - } -} - -/// Checks if two expressions represent non-zero integer literals such that `small_expr + 1 == -/// large_expr`. -fn differ_by_one(small_expr: &Expr<'_>, large_expr: &Expr<'_>) -> bool { - if let ExprKind::Lit(small) = small_expr.kind - && let ExprKind::Lit(large) = large_expr.kind - && let LitKind::Int(s, _) = small.node - && let LitKind::Int(l, _) = large.node - { - Some(l.get()) == s.get().checked_add(1) - } else if let ExprKind::Unary(UnOp::Neg, small_inner_expr) = small_expr.kind - && let ExprKind::Unary(UnOp::Neg, large_inner_expr) = large_expr.kind - { - differ_by_one(large_inner_expr, small_inner_expr) - } else { - false - } -} - -fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expr); - match expr_ty.peel_refs().kind() { - ty::Uint(_) => true, - ty::Int(_) => cx.tcx.features().enabled(sym::int_roundings), - _ => false, - } -} - -fn check_literal(expr: &Expr<'_>) -> bool { - if let ExprKind::Lit(lit) = expr.kind - && let LitKind::Int(Pu128(1), _) = lit.node - { - return true; - } - false -} - -fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool { - SpanlessEq::new(cx).eq_expr(lhs, rhs) -} - -fn build_suggestion( - cx: &LateContext<'_>, - expr: &Expr<'_>, - lhs: &Expr<'_>, - rhs: &Expr<'_>, - applicability: &mut Applicability, -) { - let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren(); - let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric() - && matches!( - lhs.kind, - ExprKind::Lit(Spanned { - node: LitKind::Int(_, LitIntType::Unsuffixed), - .. - }) | ExprKind::Unary( - UnOp::Neg, - Expr { - kind: ExprKind::Lit(Spanned { - node: LitKind::Int(_, LitIntType::Unsuffixed), - .. - }), - .. - } - ) - ) { - format!("_{}", cx.typeck_results().expr_ty(rhs)) - } else { - String::new() - }; - let dividend_sugg_str = dividend_sugg.into_string(); - // If `dividend_sugg` has enclosing paren like `(-2048)` and we need to add type suffix in the - // suggestion message, we want to make a suggestion string before `div_ceil` like - // `(-2048_{type_suffix})`. - let suggestion_before_div_ceil = if has_enclosing_paren(÷nd_sugg_str) { - format!( - "{}{})", - ÷nd_sugg_str[..dividend_sugg_str.len() - 1].to_string(), - type_suffix - ) - } else { - format!("{dividend_sugg_str}{type_suffix}") - }; - let divisor_snippet = snippet_with_context(cx, rhs.span, expr.span.ctxt(), "..", applicability); - - let sugg = format!("{suggestion_before_div_ceil}.div_ceil({})", divisor_snippet.0); - - span_lint_and_sugg( - cx, - MANUAL_DIV_CEIL, - expr.span, - "manually reimplementing `div_ceil`", - "consider using `.div_ceil()`", - sugg, - *applicability, - ); -} diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index bd2785fea270..a81c4dc6a793 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -1,9 +1,10 @@ use clippy_config::Conf; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_from_proc_macro, path_to_local}; use rustc_errors::Applicability; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -138,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { // Checking all possible scenarios using a function would be a hopeless task, as we have // 16 possible alignments of constants/operands. For now, let's use `partition`. && let mut exprs = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs] - && exprs.iter_mut().partition_in_place(|i| path_to_local(i).is_some()) == 2 + && exprs.iter_mut().partition_in_place(|i| i.res_local_id().is_some()) == 2 && !expr.span.in_external_macro(cx.sess().source_map()) && ( is_not_const(cx.tcx, cx.tcx.hir_enclosing_body_owner(expr.hir_id).into()) @@ -146,13 +147,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { ) && let [first, second, const_1, const_2] = exprs && let ecx = ConstEvalCtxt::new(cx) - && let Some(const_1) = ecx.eval(const_1) - && let Some(const_2) = ecx.eval(const_2) - && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) + && let ctxt = expr.span.ctxt() + && let Some(const_1) = ecx.eval_local(const_1, ctxt) + && let Some(const_2) = ecx.eval_local(const_2, ctxt) + && first.res_local_id().is_some_and(|f| second.res_local_id().is_some_and(|s| f == s)) // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in // case somebody does that for some reason - && (is_infinity(&const_1) && is_neg_infinity(&const_2) - || is_neg_infinity(&const_1) && is_infinity(&const_2)) + && (const_1.is_pos_infinity() && const_2.is_neg_infinity() + || const_1.is_neg_infinity() && const_2.is_pos_infinity()) && let Some(local_snippet) = first.span.get_source_text(cx) { let variant = match (kind.node, lhs_kind.node, rhs_kind.node) { @@ -201,21 +203,3 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { } } } - -fn is_infinity(constant: &Constant<'_>) -> bool { - match constant { - // FIXME(f16_f128): add f16 and f128 when constants are available - Constant::F32(float) => *float == f32::INFINITY, - Constant::F64(float) => *float == f64::INFINITY, - _ => false, - } -} - -fn is_neg_infinity(constant: &Constant<'_>) -> bool { - match constant { - // FIXME(f16_f128): add f16 and f128 when constants are available - Constant::F32(float) => *float == f32::NEG_INFINITY, - Constant::F64(float) => *float == f64::NEG_INFINITY, - _ => false, - } -} diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs index b3ee45cc0209..ccb8d4272bfa 100644 --- a/clippy_lints/src/manual_hash_one.rs +++ b/clippy_lints/src/manual_hash_one.rs @@ -1,9 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::SpanRangeExt; +use clippy_utils::sym; use clippy_utils::visitors::{is_local_used, local_used_once}; -use clippy_utils::{is_trait_method, path_to_local_id, sym}; use rustc_errors::Applicability; use rustc_hir::{BindingMode, ExprKind, LetStmt, Node, PatKind, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -81,8 +82,8 @@ impl LateLintPass<'_> for ManualHashOne { && !hash_expr.span.from_expansion() && let ExprKind::MethodCall(seg, hashed_value, [ref_to_hasher], _) = hash_expr.kind && seg.ident.name == sym::hash - && is_trait_method(cx, hash_expr, sym::Hash) - && path_to_local_id(ref_to_hasher.peel_borrows(), hasher) + && cx.ty_based_def(hash_expr).opt_parent(cx).is_diag_item(cx, sym::Hash) + && ref_to_hasher.peel_borrows().res_local_id() == Some(hasher) && let maybe_finish_stmt = stmts.next() // There should be no more statements referencing `hasher` diff --git a/clippy_lints/src/manual_ignore_case_cmp.rs b/clippy_lints/src/manual_ignore_case_cmp.rs index f7d9ec1fae8e..25057b4aeaa2 100644 --- a/clippy_lints/src/manual_ignore_case_cmp.rs +++ b/clippy_lints/src/manual_ignore_case_cmp.rs @@ -1,8 +1,8 @@ use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sym; -use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::ExprKind::{Binary, Lit, MethodCall}; @@ -58,7 +58,7 @@ fn get_ascii_type<'a>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'_>) -> Op if needs_ref_to_cmp(cx, ty) || ty.is_str() || ty.is_slice() - || matches!(get_type_diagnostic_name(cx, ty), Some(sym::OsStr | sym::OsString)) + || matches!(ty.opt_diag_name(cx), Some(sym::OsStr | sym::OsString)) { return Some((expr.span, ToAscii(is_lower, ty_raw))); } @@ -72,8 +72,8 @@ fn get_ascii_type<'a>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'_>) -> Op fn needs_ref_to_cmp(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { ty.is_char() || *ty.kind() == ty::Uint(UintTy::U8) - || is_type_diagnostic_item(cx, ty, sym::Vec) - || is_type_lang_item(cx, ty, LangItem::String) + || ty.is_diag_item(cx, sym::Vec) + || ty.is_lang_item(cx, LangItem::String) } impl LateLintPass<'_> for ManualIgnoreCaseCmp { diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 2eebb2430fd9..a0f946d22fa6 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -2,8 +2,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::matching_root_macro_call; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::sugg::Sugg; -use clippy_utils::{higher, is_in_const_context, path_to_local, peel_ref_operators, sym}; +use clippy_utils::{higher, is_in_const_context, peel_ref_operators, sym}; use rustc_ast::LitKind::{Byte, Char}; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; @@ -109,7 +110,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { start: Some(start), end: Some(end), limits: RangeLimits::Closed, - }) = higher::Range::hir(receiver) + span: _, + }) = higher::Range::hir(cx, receiver) && !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_)) { let arg = peel_ref_operators(cx, arg); @@ -125,7 +127,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { } fn get_ty_sugg<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'_>) -> Option<(Span, Ty<'tcx>)> { - let local_hid = path_to_local(arg)?; + let local_hid = arg.res_local_id()?; if let Node::Param(Param { ty_span, span, .. }) = cx.tcx.parent_hir_node(local_hid) // `ty_span` and `span` are the same for inferred type, thus a type suggestion must be given && ty_span == span diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 2705ef20b795..0f3d8b336675 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -2,16 +2,14 @@ use crate::question_mark::{QUESTION_MARK, QuestionMark}; use clippy_config::types::MatchLintBehaviour; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLetOrMatch; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{ - MaybePath, is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, path_res, peel_blocks, -}; +use clippy_utils::{is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, peel_blocks}; use rustc_ast::BindingMode; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def::{CtorOf, DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{Arm, Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LintContext}; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; @@ -131,39 +129,25 @@ fn is_arms_disjointed(cx: &LateContext<'_>, arm1: &Arm<'_>, arm2: &Arm<'_>) -> b /// Returns `true` if the given pattern is a variant of an enum. pub fn is_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { - struct Pat<'hir>(&'hir rustc_hir::Pat<'hir>); - - impl<'hir> MaybePath<'hir> for Pat<'hir> { - fn qpath_opt(&self) -> Option<&QPath<'hir>> { - match self.0.kind { - PatKind::Struct(ref qpath, fields, _) - if fields - .iter() - .all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))) => - { - Some(qpath) - }, - PatKind::TupleStruct(ref qpath, pats, _) - if pats - .iter() - .all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))) => - { - Some(qpath) - }, - PatKind::Expr(&PatExpr { - kind: PatExprKind::Path(ref qpath), - .. - }) => Some(qpath), - _ => None, - } - } - - fn hir_id(&self) -> HirId { - self.0.hir_id - } - } - - let res = path_res(cx, &Pat(pat)); + let path = match pat.kind { + PatKind::Struct(ref qpath, fields, _) + if fields + .iter() + .all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))) => + { + (qpath, pat.hir_id) + }, + PatKind::TupleStruct(ref qpath, pats, _) + if pats + .iter() + .all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))) => + { + (qpath, pat.hir_id) + }, + PatKind::Expr(e) if let Some((qpath, id)) = e.opt_qpath() => (qpath, id), + _ => return false, + }; + let res = path.res(cx); matches!( res, Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(CtorOf::Variant, _), _) @@ -199,7 +183,13 @@ fn emit_manual_let_else( format!("{{ {sn_else} }}") }; let sn_bl = replace_in_pattern(cx, span, ident_map, pat, &mut app, true); - let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};"); + let sugg = if sn_expr.ends_with('}') { + // let-else statement expressions are not allowed to end with `}` + // https://rust-lang.github.io/rfcs/3137-let-else.html#let-pattern--if--else--else- + format!("let {sn_bl} = ({sn_expr}) else {else_bl};") + } else { + format!("let {sn_bl} = {sn_expr} else {else_bl};") + }; diag.span_suggestion(span, "consider writing", sugg, app); }, ); @@ -384,7 +374,7 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo } let ty = typeck_results.pat_ty(pat); // Option and Result are allowed, everything else isn't. - if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) { + if !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) { has_disallowed = true; } }); diff --git a/clippy_lints/src/manual_main_separator_str.rs b/clippy_lints/src/manual_main_separator_str.rs index f54ccf2c87b0..e78f6affda2a 100644 --- a/clippy_lints/src/manual_main_separator_str.rs +++ b/clippy_lints/src/manual_main_separator_str.rs @@ -1,7 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{is_trait_method, peel_hir_expr_refs}; +use clippy_utils::peel_hir_expr_refs; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind, Mutability, QPath}; @@ -52,7 +53,7 @@ impl LateLintPass<'_> for ManualMainSeparatorStr { && path.ident.name == sym::to_string && let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind && let Res::Def(DefKind::Const, receiver_def_id) = path.res - && is_trait_method(cx, target, sym::ToString) + && cx.ty_based_def(target).opt_parent(cx).is_diag_item(cx, sym::ToString) && cx.tcx.is_diagnostic_item(sym::path_main_separator, receiver_def_id) && let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() && ty.is_str() diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index 922db174e3d4..5cf90eecaa97 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -1,12 +1,12 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; -use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym}; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::source::snippet_with_context; +use clippy_utils::{as_some_pattern, is_none_pattern, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind, QPath, is_range_literal}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -60,8 +60,8 @@ impl LateLintPass<'_> for ManualOptionAsSlice { } match expr.kind { ExprKind::Match(scrutinee, [arm1, arm2], _) => { - if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1) - || is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2) + if is_none_pattern(cx, arm2.pat) && check_arms(cx, arm2, arm1) + || is_none_pattern(cx, arm1.pat) && check_arms(cx, arm1, arm2) { check_as_ref(cx, scrutinee, span, self.msrv); } @@ -123,8 +123,7 @@ fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span, msrv: Msrv) { fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind && seg.ident.name == sym::as_ref - && let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind() - && cx.tcx.is_diagnostic_item(sym::Option, adtdef.did()) + && cx.typeck_results().expr_ty(callee).is_diag_item(cx, sym::Option) && msrv.meets( cx, if clippy_utils::is_in_const_context(cx) { @@ -134,27 +133,28 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { }, ) { - if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) { - span_lint_and_sugg( - cx, - MANUAL_OPTION_AS_SLICE, - span, - "use `Option::as_slice`", - "use", - format!("{snippet}.as_slice()"), - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, MANUAL_OPTION_AS_SLICE, span, "use `Option_as_slice`"); - } + span_lint_and_then( + cx, + MANUAL_OPTION_AS_SLICE, + span, + "manual implementation of `Option::as_slice`", + |diag| { + let mut app = Applicability::MachineApplicable; + let callee = snippet_with_context(cx, callee.span, expr.span.ctxt(), "_", &mut app).0; + diag.span_suggestion_verbose( + span, + "use `Option::as_slice` directly", + format!("{callee}.as_slice()"), + app, + ); + }, + ); } } fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { - if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind - && let Res::Def(DefKind::Ctor(..), def_id) = path.res + if let Some([binding]) = as_some_pattern(cx, pat) && let PatKind::Binding(_mode, _hir_id, ident, _inner_pat) = binding.kind - && clippy_utils::is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) { Some(ident.name) } else { @@ -189,7 +189,7 @@ fn check_arms(cx: &LateContext<'_>, none_arm: &Arm<'_>, some_arm: &Arm<'_>) -> b fn returns_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { match expr.kind { - ExprKind::Path(_) => clippy_utils::is_path_diagnostic_item(cx, expr, sym::default_fn), + ExprKind::Path(_) => expr.res(cx).is_diag_item(cx, sym::default_fn), ExprKind::Closure(cl) => is_empty_slice(cx, cx.tcx.hir_body(cl.body).value), _ => false, } @@ -206,7 +206,7 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { ExprKind::Index(arr, range, _) => match arr.kind { ExprKind::Array([]) => is_range_literal(range), ExprKind::Array(_) => { - let Some(range) = clippy_utils::higher::Range::hir(range) else { + let Some(range) = clippy_utils::higher::Range::hir(cx, range) else { return false; }; range.end.is_some_and(|e| clippy_utils::is_integer_const(cx, e, 0)) @@ -214,11 +214,11 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => false, }, ExprKind::Array([]) => true, - ExprKind::Call(def, []) => clippy_utils::is_path_diagnostic_item(cx, def, sym::default_fn), + ExprKind::Call(def, []) => def.res(cx).is_diag_item(cx, sym::default_fn), _ => false, } } fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - clippy_utils::is_path_diagnostic_item(cx, expr, sym::slice_from_ref) + expr.basic_res().is_diag_item(cx, sym::slice_from_ref) } diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index 41e07e26bff0..d993ed48eac4 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -1,13 +1,15 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_in_const_context; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_context; -use clippy_utils::{is_in_const_context, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::SyntaxContext; declare_clippy_lint! { /// ### What it does @@ -58,13 +60,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { && add_lhs.span.ctxt() == ctxt && add_rhs.span.ctxt() == ctxt && !expr.span.in_external_macro(cx.sess().source_map()) - && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs) - && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs) + && let Some(const1) = check_for_unsigned_int_constant(cx, ctxt, rem_rhs) + && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, ctxt, add_lhs, add_rhs) && let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind && rem2_op.node == BinOpKind::Rem && const1 == const2 - && let Some(hir_id) = path_to_local(rem2_lhs) - && let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs) + && let Some(hir_id) = rem2_lhs.res_local_id() + && let Some(const3) = check_for_unsigned_int_constant(cx, ctxt, rem2_rhs) // Also ensures the const is nonzero since zero can't be a divisor && const2 == const3 && rem2_lhs.span.ctxt() == ctxt @@ -103,16 +105,21 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { // constant along with the other expression unchanged if so fn check_for_either_unsigned_int_constant<'a>( cx: &'a LateContext<'_>, + ctxt: SyntaxContext, left: &'a Expr<'_>, right: &'a Expr<'_>, ) -> Option<(u128, &'a Expr<'a>)> { - check_for_unsigned_int_constant(cx, left) + check_for_unsigned_int_constant(cx, ctxt, left) .map(|int_const| (int_const, right)) - .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left))) + .or_else(|| check_for_unsigned_int_constant(cx, ctxt, right).map(|int_const| (int_const, left))) } -fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option { - let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr)?; +fn check_for_unsigned_int_constant<'a>( + cx: &'a LateContext<'_>, + ctxt: SyntaxContext, + expr: &'a Expr<'_>, +) -> Option { + let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr, ctxt)?; match int_const { FullInt::S(s) => s.try_into().ok(), FullInt::U(u) => Some(u), diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 7fb88763e640..674f0da818f5 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -2,8 +2,8 @@ use clippy_config::Conf; use clippy_utils::SpanlessEq; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::{get_type_diagnostic_name, is_type_lang_item}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::ExprKind::Assign; @@ -189,7 +189,7 @@ fn check_to_owned( && let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_chars, chars_expr_def_id) && let ty = cx.typeck_results().expr_ty(str_expr).peel_refs() - && is_type_lang_item(cx, ty, hir::LangItem::String) + && ty.is_lang_item(cx, hir::LangItem::String) && SpanlessEq::new(cx).eq_expr(left_expr, str_expr) && let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind && let hir::ExprKind::Closure(closure) = closure_expr.kind @@ -250,7 +250,7 @@ fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool { let ty = cx.typeck_results().expr_ty(expr).peel_refs(); - let required = match get_type_diagnostic_name(cx, ty) { + let required = match ty.opt_diag_name(cx) { Some(sym::BinaryHeap) => msrvs::BINARY_HEAP_RETAIN, Some(sym::BTreeSet) => msrvs::BTREE_SET_RETAIN, Some(sym::BTreeMap) => msrvs::BTREE_MAP_RETAIN, @@ -264,7 +264,7 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { let ty = cx.typeck_results().expr_ty(expr).peel_refs(); - matches!(get_type_diagnostic_name(cx, ty), Some(sym::BTreeMap | sym::HashMap)) + matches!(ty.opt_diag_name(cx), Some(sym::BTreeMap | sym::HashMap)) } fn make_span_lint_and_sugg(cx: &LateContext<'_>, span: Span, sugg: String) { diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs index 06ee00c2cef3..e8db44698d9c 100644 --- a/clippy_lints/src/manual_rotate.rs +++ b/clippy_lints/src/manual_rotate.rs @@ -56,20 +56,14 @@ impl Display for ShiftDirection { } } -fn parse_shift<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, -) -> Option<(ShiftDirection, u128, &'tcx Expr<'tcx>)> { +fn parse_shift<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<(ShiftDirection, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { if let ExprKind::Binary(op, l, r) = expr.kind { let dir = match op.node { BinOpKind::Shl => ShiftDirection::Left, BinOpKind::Shr => ShiftDirection::Right, _ => return None, }; - let const_expr = ConstEvalCtxt::new(cx).eval(r)?; - if let Constant::Int(shift) = const_expr { - return Some((dir, shift, l)); - } + return Some((dir, l, r)); } None } @@ -78,40 +72,62 @@ impl LateLintPass<'_> for ManualRotate { fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if let ExprKind::Binary(op, l, r) = expr.kind && let BinOpKind::Add | BinOpKind::BitOr = op.node - && let Some((l_shift_dir, l_amount, l_expr)) = parse_shift(cx, l) - && let Some((r_shift_dir, r_amount, r_expr)) = parse_shift(cx, r) - { - if l_shift_dir == r_shift_dir { - return; - } - if !clippy_utils::eq_expr_value(cx, l_expr, r_expr) { - return; - } - let Some(bit_width) = (match cx.typeck_results().expr_ty(expr).kind() { + && let Some((l_shift_dir, l_expr, l_amount)) = parse_shift(l) + && let Some((r_shift_dir, r_expr, r_amount)) = parse_shift(r) + && l_shift_dir != r_shift_dir + && clippy_utils::eq_expr_value(cx, l_expr, r_expr) + && let Some(bit_width) = match cx.typeck_results().expr_ty(expr).kind() { ty::Int(itype) => itype.bit_width(), ty::Uint(itype) => itype.bit_width(), _ => return, - }) else { - return; - }; - if l_amount + r_amount == u128::from(bit_width) { - let (shift_function, amount) = if l_amount < r_amount { + } + { + let const_eval = ConstEvalCtxt::new(cx); + + let ctxt = expr.span.ctxt(); + let (shift_function, amount) = if let Some(Constant::Int(l_amount_val)) = + const_eval.eval_local(l_amount, ctxt) + && let Some(Constant::Int(r_amount_val)) = const_eval.eval_local(r_amount, ctxt) + && l_amount_val + r_amount_val == u128::from(bit_width) + { + if l_amount_val < r_amount_val { (l_shift_dir, l_amount) } else { (r_shift_dir, r_amount) + } + } else { + let (amount1, binop, minuend, amount2, shift_direction) = match (l_amount.kind, r_amount.kind) { + (_, ExprKind::Binary(binop, minuend, other)) => (l_amount, binop, minuend, other, l_shift_dir), + (ExprKind::Binary(binop, minuend, other), _) => (r_amount, binop, minuend, other, r_shift_dir), + _ => return, }; - let mut applicability = Applicability::MachineApplicable; - let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren(); - span_lint_and_sugg( - cx, - MANUAL_ROTATE, - expr.span, - "there is no need to manually implement bit rotation", - "this expression can be rewritten as", - format!("{expr_sugg}.{shift_function}({amount})"), - Applicability::MachineApplicable, - ); - } + + if let Some(Constant::Int(minuend)) = const_eval.eval_local(minuend, ctxt) + && clippy_utils::eq_expr_value(cx, amount1, amount2) + // (x << s) | (x >> bit_width - s) + && ((binop.node == BinOpKind::Sub && u128::from(bit_width) == minuend) + // (x << s) | (x >> (bit_width - 1) ^ s) + || (binop.node == BinOpKind::BitXor && u128::from(bit_width).checked_sub(minuend) == Some(1))) + { + // NOTE: we take these from the side that _doesn't_ have the binop, since it's probably simpler + (shift_direction, amount1) + } else { + return; + } + }; + + let mut applicability = Applicability::MachineApplicable; + let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren(); + let amount = sugg::Sugg::hir_with_applicability(cx, amount, "_", &mut applicability); + span_lint_and_sugg( + cx, + MANUAL_ROTATE, + expr.span, + "there is no need to manually implement bit rotation", + "this expression can be rewritten as", + format!("{expr_sugg}.{shift_function}({amount})"), + Applicability::MachineApplicable, + ); } } } diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs index 0c09a47c9651..de12fa29d02c 100644 --- a/clippy_lints/src/manual_slice_size_calculation.rs +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -2,6 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::peel_and_count_ty_refs; use clippy_utils::{expr_or_init, is_in_const_context, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; @@ -102,7 +103,7 @@ fn simplify_half<'tcx>( && let ExprKind::MethodCall(method_path, receiver, [], _) = expr1.kind && method_path.ident.name == sym::len && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && let (receiver_ty, refs_count) = clippy_utils::ty::walk_ptrs_ty_depth(receiver_ty) + && let (receiver_ty, refs_count, _) = peel_and_count_ty_refs(receiver_ty) && let ty::Slice(ty1) = receiver_ty.kind() // expr2 is `size_of::()`? && let ExprKind::Call(func, []) = expr2.kind diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 07cce4046ca4..b668f391d67a 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -16,7 +16,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext as _}; use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Symbol, sym}; +use rustc_span::{Symbol, SyntaxContext, sym}; use std::iter; declare_clippy_lint! { @@ -92,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { return; } - let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then); + let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then, expr.span.ctxt()); if !strippings.is_empty() && self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) { let kind_word = match strip_kind { StripKind::Prefix => "prefix", @@ -166,8 +166,8 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E } // Returns the length of the `expr` if it's a constant string or char. -fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - let value = ConstEvalCtxt::new(cx).eval(expr)?; +fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option { + let value = ConstEvalCtxt::new(cx).eval_local(expr, ctxt)?; match value { Constant::Str(value) => Some(value.len() as u128), Constant::Char(value) => Some(value.len_utf8() as u128), @@ -176,13 +176,18 @@ fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { } // Tests if `expr` equals the length of the pattern. -fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool { +fn eq_pattern_length<'tcx>( + cx: &LateContext<'tcx>, + pattern: &Expr<'_>, + expr: &'tcx Expr<'_>, + ctxt: SyntaxContext, +) -> bool { if let ExprKind::Lit(Spanned { node: LitKind::Int(n, _), .. }) = expr.kind { - constant_length(cx, pattern).is_some_and(|length| n == length) + constant_length(cx, pattern, ctxt).is_some_and(|length| n == length) } else { len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg)) } @@ -215,6 +220,7 @@ fn find_stripping<'tcx>( target: Res, pattern: &'tcx Expr<'_>, expr: &'tcx Expr<'tcx>, + ctxt: SyntaxContext, ) -> (Vec<&'tcx Expr<'tcx>>, FxHashMap) { struct StrippingFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, @@ -223,6 +229,7 @@ fn find_stripping<'tcx>( pattern: &'tcx Expr<'tcx>, results: Vec<&'tcx Expr<'tcx>>, bindings: FxHashMap, + ctxt: SyntaxContext, } impl<'tcx> Visitor<'tcx> for StrippingFinder<'_, 'tcx> { @@ -230,13 +237,13 @@ fn find_stripping<'tcx>( if is_ref_str(self.cx, ex) && let unref = peel_ref(ex) && let ExprKind::Index(indexed, index, _) = &unref.kind - && let Some(higher::Range { start, end, .. }) = higher::Range::hir(index) + && let Some(higher::Range { start, end, .. }) = higher::Range::hir(self.cx, index) && let ExprKind::Path(path) = &indexed.kind && self.cx.qpath_res(path, ex.hir_id) == self.target { match (self.strip_kind, start, end) { (StripKind::Prefix, Some(start), None) => { - if eq_pattern_length(self.cx, self.pattern, start) { + if eq_pattern_length(self.cx, self.pattern, start, self.ctxt) { self.results.push(ex); return; } @@ -252,7 +259,7 @@ fn find_stripping<'tcx>( && let Some(left_arg) = len_arg(self.cx, left) && let ExprKind::Path(left_path) = &left_arg.kind && self.cx.qpath_res(left_path, left_arg.hir_id) == self.target - && eq_pattern_length(self.cx, self.pattern, right) + && eq_pattern_length(self.cx, self.pattern, right, self.ctxt) { self.results.push(ex); return; @@ -280,6 +287,7 @@ fn find_stripping<'tcx>( pattern, results: vec![], bindings: FxHashMap::default(), + ctxt, }; walk_expr(&mut finder, expr); (finder.results, finder.bindings) diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 39e5289c62ae..b07d4fe81f8a 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; 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 rustc_errors::Applicability; use rustc_hir as hir; @@ -205,15 +205,18 @@ fn lint_map_unit_fn( ) { let var_arg = &map_args.0; - let (map_type, variant, lint) = if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Option) { + let (map_type, variant, lint) = if cx.typeck_results().expr_ty(var_arg).is_diag_item(cx, sym::Option) { ("Option", "Some", OPTION_MAP_UNIT_FN) - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Result) { + } else if cx.typeck_results().expr_ty(var_arg).is_diag_item(cx, sym::Result) { ("Result", "Ok", RESULT_MAP_UNIT_FN) } else { return; }; let fn_arg = &map_args.1[0]; + #[expect(clippy::items_after_statements, reason = "the const is only used below")] + const SUGG_MSG: &str = "use `if let` instead"; + if is_unit_function(cx, fn_arg) { let mut applicability = Applicability::MachineApplicable; let msg = suggestion_msg("function", map_type); @@ -226,7 +229,7 @@ fn lint_map_unit_fn( ); span_lint_and_then(cx, lint, expr.span, msg, |diag| { - diag.span_suggestion(stmt.span, "try", suggestion, applicability); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability); }); } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) { let msg = suggestion_msg("closure", map_type); @@ -242,7 +245,7 @@ fn lint_map_unit_fn( 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", suggestion, applicability); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability); } else { let suggestion = format!( "if let {0}({1}) = {2} {{ ... }}", @@ -250,7 +253,7 @@ fn lint_map_unit_fn( snippet(cx, binding.pat.span, "_"), snippet(cx, var_arg.span, "_"), ); - diag.span_suggestion(stmt.span, "try", suggestion, Applicability::HasPlaceholders); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, Applicability::HasPlaceholders); } }); } diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index e0cb5d14d3c9..1ebbd209ae52 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{higher, is_res_lang_ctor, sym}; +use clippy_utils::{as_some_pattern, higher, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -55,10 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { }; if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result.ok(, _) - && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation && ok_path.ident.name == sym::ok - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) - && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) + && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) + && let Some([ok_pat]) = as_some_pattern(cx, let_pat) //get operation && let ctxt = expr.span.ctxt() && let_expr.span.ctxt() == ctxt && let_pat.span.ctxt() == ctxt diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index aaf559fc4439..79f737f07eb1 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -1,18 +1,17 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher::IfLetOrMatch; use clippy_utils::msrvs::Msrv; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet; use clippy_utils::visitors::is_local_used; -use clippy_utils::{ - SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, - peel_ref_operators, -}; +use clippy_utils::{SpanlessEq, get_ref_operators, is_unit_expr, peel_blocks_with_stmt, peel_ref_operators}; use rustc_ast::BorrowKind; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; +use rustc_span::symbol::Ident; use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; @@ -35,7 +34,7 @@ pub(super) fn check_if_let<'tcx>( check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn check_arm<'tcx>( cx: &LateContext<'tcx>, outer_is_match: bool, @@ -50,26 +49,28 @@ fn check_arm<'tcx>( if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr) && let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner { IfLetOrMatch::IfLet(scrutinee, pat, _, els, _) => Some((scrutinee, pat, els)), - IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none()) - // if there are more than two arms, collapsing would be non-trivial - // one of the arms must be "wild-like" - && let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a)) - { - let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]); - Some((scrutinee, then.pat, Some(els.body))) - } else { - None + IfLetOrMatch::Match(scrutinee, arms, ..) => { + if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none()) + // if there are more than two arms, collapsing would be non-trivial + // one of the arms must be "wild-like" + && let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a)) + { + let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]); + Some((scrutinee, then.pat, Some(els.body))) + } else { + None + } }, } && outer_pat.span.eq_ctxt(inner_scrutinee.span) // match expression must be a local binding // match { .. } - && let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee)) + && let Some(binding_id) = peel_ref_operators(cx, inner_scrutinee).res_local_id() && !pat_contains_disallowed_or(cx, inner_then_pat, msrv) // the binding must come from the pattern of the containing match arm // .... => match { .. } - && let (Some(binding_span), is_innermost_parent_pat_struct) - = find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id) + && let (Some((binding_ident, binding_span)), is_innermost_parent_pat_struct) = + find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id) // the "else" branches must be equal && match (outer_else_body, inner_else_body) { (None, None) => true, @@ -77,9 +78,7 @@ 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 - && outer_guard.is_none_or( - |e| !is_local_used(cx, e, binding_id) - ) + && outer_guard.is_none_or(|e| !is_local_used(cx, e, binding_id)) // ...or anywhere in the inner expression && match inner { IfLetOrMatch::IfLet(_, _, body, els, _) => { @@ -103,7 +102,7 @@ fn check_arm<'tcx>( // collapsing patterns need an explicit field name in struct pattern matching // ex: Struct {x: Some(1)} let replace_msg = if is_innermost_parent_pat_struct { - format!(", prefixed by `{}`:", snippet(cx, binding_span, "their field name")) + format!(", prefixed by `{binding_ident}: `") } else { String::new() }; @@ -135,21 +134,24 @@ fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { kind: PatExprKind::Path(qpath), hir_id, .. - }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), + }) => cx + .qpath_res(qpath, *hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone), _ => false, } } -fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option, bool) { - let mut span = None; +fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option<(Ident, Span)>, bool) { + let mut binding = None; let mut is_innermost_parent_pat_struct = false; - pat.walk_short(|p| match &p.kind { + pat.walk_short(|p| match p.kind { // ignore OR patterns PatKind::Or(_) => false, - PatKind::Binding(_bm, _, _ident, _) => { + PatKind::Binding(_bm, _, ident, _) => { let found = p.hir_id == hir_id; if found { - span = Some(p.span); + binding = Some((ident, p.span)); } !found }, @@ -158,7 +160,7 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi true }, }); - (span, is_innermost_parent_pat_struct) + (binding, is_innermost_parent_pat_struct) } /// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`, diff --git a/clippy_lints/src/matches/infallible_destructuring_match.rs b/clippy_lints/src/matches/infallible_destructuring_match.rs index 93d7683d2af8..be4b4f346dbc 100644 --- a/clippy_lints/src/matches/infallible_destructuring_match.rs +++ b/clippy_lints/src/matches/infallible_destructuring_match.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs}; +use clippy_utils::{peel_blocks, strip_pat_refs}; use rustc_errors::Applicability; use rustc_hir::{ExprKind, LetStmt, MatchSource, PatKind, QPath}; use rustc_lint::LateContext; @@ -17,7 +18,7 @@ pub(crate) fn check(cx: &LateContext<'_>, local: &LetStmt<'_>) -> bool { && args.len() == 1 && let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind && let body = peel_blocks(arms[0].body) - && path_to_local_id(body, arg) + && body.res_local_id() == Some(arg) { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index abf723fa6f4c..da68f8421c16 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -1,9 +1,9 @@ +use clippy_utils::as_some_expr; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id}; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -53,28 +53,26 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr) } -// function called for each expression: +/// Checks whether resolves to `Some(target)` +// NOTE: called for each expression: // Some(x) => if { // // } else { // // } -// Returns true if resolves to `Some(x)`, `false` otherwise fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` - && let ExprKind::Call(callee, [arg]) = inner_expr.kind + && let Some(arg) = as_some_expr(cx, inner_expr) { - return ctxt == expr.span.ctxt() - && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) - && path_to_local_id(arg, target); + return ctxt == expr.span.ctxt() && arg.res_local_id() == Some(target); } false } fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) { - return is_res_lang_ctor(cx, path_res(cx, inner_expr), OptionNone); + return inner_expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone); } false } @@ -98,7 +96,7 @@ pub(super) fn check_match<'tcx>( expr: &'tcx Expr<'_>, ) { let ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, ty, sym::Option) + if ty.is_diag_item(cx, sym::Option) && let [first_arm, second_arm] = arms && first_arm.guard.is_none() && second_arm.guard.is_none() diff --git a/clippy_lints/src/matches/manual_map.rs b/clippy_lints/src/matches/manual_map.rs index de57d1eee924..f111da60bbd5 100644 --- a/clippy_lints/src/matches/manual_map.rs +++ b/clippy_lints/src/matches/manual_map.rs @@ -2,8 +2,7 @@ use super::MANUAL_MAP; use super::manual_utils::{SomeExpr, check_with}; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_res_lang_ctor, path_res}; - +use clippy_utils::res::{MaybeDef, MaybeQPath}; use rustc_hir::LangItem::OptionSome; use rustc_hir::{Arm, Block, BlockCheckMode, Expr, ExprKind, Pat, UnsafeSource}; use rustc_lint::LateContext; @@ -91,7 +90,7 @@ fn get_some_expr<'tcx>( // TODO: Allow more complex expressions. match expr.kind { ExprKind::Call(callee, [arg]) - if ctxt == expr.span.ctxt() && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) => + if ctxt == expr.span.ctxt() && callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) => { Some(SomeExpr::new_no_negated(arg, needs_unsafe_block)) }, diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index edbb556fd976..3259655c9a1f 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{option_arg_ty, peel_mid_ty_refs_is_mutable}; -use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res, peel_blocks, span_contains_comment}; +use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs}; +use clippy_utils::{as_some_expr, get_parent_expr, is_none_expr, peel_blocks, span_contains_comment}; use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::{LateContext, LintContext}; @@ -72,7 +73,10 @@ fn is_variant_or_wildcard(cx: &LateContext<'_>, pat: &Pat<'_>, can_be_wild: bool true }, PatKind::TupleStruct(qpath, ..) => { - is_res_lang_ctor(cx, cx.qpath_res(&qpath, pat.hir_id), ResultErr) == must_match_err + cx.qpath_res(&qpath, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, ResultErr) + == must_match_err }, PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) => { is_variant_or_wildcard(cx, pat, can_be_wild, must_match_err) @@ -102,8 +106,7 @@ fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &' /// Check if `expr` contains `Some(ident)`, possibly as a block fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, ty: Ty<'tcx>) -> bool { - if let ExprKind::Call(body_callee, [body_arg]) = peel_blocks(expr).kind - && is_res_lang_ctor(cx, path_res(cx, body_callee), OptionSome) + if let Some(body_arg) = as_some_expr(cx, peel_blocks(expr)) && cx.typeck_results().expr_ty(body_arg) == ty && let ExprKind::Path(QPath::Resolved( _, @@ -120,7 +123,7 @@ fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, t /// Check if `expr` is `None`, possibly as a block fn is_none(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone) + is_none_expr(cx, peel_blocks(expr)) } /// Suggest replacing `expr` by `scrutinee.METHOD()`, where `METHOD` is either `ok` or @@ -135,15 +138,11 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren(); let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee); - let (_, n_ref, mutability) = peel_mid_ty_refs_is_mutable(scrutinee_ty); - let prefix = if n_ref > 0 { - if mutability == Mutability::Mut { - ".as_mut()" - } else { - ".as_ref()" - } - } else { - "" + let (_, _, mutability) = peel_and_count_ty_refs(scrutinee_ty); + let prefix = match mutability { + Some(Mutability::Mut) => ".as_mut()", + Some(Mutability::Not) => ".as_ref()", + None => "", }; let sugg = format!("{scrut}{prefix}.{method}()"); diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 8c3f52542d91..abbc43d8e9b0 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -1,4 +1,5 @@ use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline}; use rustc_ast::{BindingMode, ByRef}; use rustc_errors::Applicability; @@ -10,8 +11,9 @@ use rustc_span::sym; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{expr_type_is_certain, get_type_diagnostic_name, implements_trait}; -use clippy_utils::{is_default_equivalent, is_lint_allowed, path_res, peel_blocks, span_contains_comment}; +use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_copy}; +use clippy_utils::usage::local_used_after_expr; +use clippy_utils::{is_default_equivalent, is_lint_allowed, peel_blocks, span_contains_comment}; use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT}; @@ -31,7 +33,7 @@ fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { } } -fn get_none<'tcx>(cx: &LateContext<'_>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> { +fn get_none<'tcx>(cx: &LateContext<'_>, arm: &Arm<'tcx>, allow_wildcard: bool) -> Option<&'tcx Expr<'tcx>> { if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind && let Some(def_id) = path.res.opt_def_id() // Since it comes from a pattern binding, we need to get the parent to actually match @@ -48,7 +50,9 @@ fn get_none<'tcx>(cx: &LateContext<'_>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'t && cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id) { Some(arm.body) - } else if let PatKind::Wild = arm.pat.kind { + } else if let PatKind::Wild = arm.pat.kind + && allow_wildcard + { // We consider that the `Some` check will filter it out if it's not right. Some(arm.body) } else { @@ -62,11 +66,11 @@ fn get_some_and_none_bodies<'tcx>( arm2: &'tcx Arm<'tcx>, ) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> { if let Some(binding_id) = get_some(cx, arm1.pat) - && let Some(body_none) = get_none(cx, arm2) + && let Some(body_none) = get_none(cx, arm2, true) { Some(((arm1.body, binding_id), body_none)) - } else if let Some(binding_id) = get_some(cx, arm2.pat) - && let Some(body_none) = get_none(cx, arm1) + } else if let Some(body_none) = get_none(cx, arm1, false) + && let Some(binding_id) = get_some(cx, arm2.pat) { Some(((arm2.body, binding_id), body_none)) } else { @@ -84,7 +88,9 @@ fn handle( binding_id: HirId, ) { // Only deal with situations where both alternatives return the same non-adjusted type. - if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) { + if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) + || !safe_to_move_scrutinee(cx, expr, condition) + { return; } @@ -114,7 +120,8 @@ fn handle( && is_default_equivalent(cx, peel_blocks(body_none)) { // We now check if the condition is a None variant, in which case we need to specify the type - if path_res(cx, condition) + if condition + .res(cx) .opt_def_id() .is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant()) { @@ -155,7 +162,7 @@ fn handle( && cx.typeck_results().expr_adjustments(body_some).is_empty() && let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx) && let Some(indent) = indent_of(cx, expr.span) - && ConstEvalCtxt::new(cx).eval_simple(body_none).is_some() + && ConstEvalCtxt::new(cx).eval_local(body_none, expr.span.ctxt()).is_some() { let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent)); let mut app = Applicability::MachineApplicable; @@ -174,13 +181,36 @@ fn handle( } fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static str> { - match get_type_diagnostic_name(cx, ty)? { + match ty.opt_diag_name(cx)? { sym::Option => Some("Option"), sym::Result => Some("Result"), _ => None, } } +/// Checks whether it is safe to move scrutinee. +/// It is not safe to move if: +/// 1. `scrutinee` is a `Result` that doesn't implemenet `Copy`, mainly because the `Err` +/// variant is not copyable. +/// 2. `expr` is a local variable that is used after the if-let-else expression. +/// ```rust,ignore +/// let foo: Result = Ok(0); +/// let v = if let Ok(v) = foo { v } else { 1 }; +/// let bar = foo; +/// ``` +fn safe_to_move_scrutinee(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>) -> bool { + if let Some(hir_id) = scrutinee.res_local_id() + && let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee) + && scrutinee_ty.is_diag_item(cx, sym::Result) + && !is_copy(cx, scrutinee_ty) + && local_used_after_expr(cx, hir_id, expr) + { + false + } else { + true + } +} + pub fn check_match<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index dbae71bbb1b0..09e49c16624c 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -1,17 +1,17 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN; use crate::matches::MATCH_AS_REF; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function}; +use clippy_utils::ty::{is_copy, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, is_res_lang_ctor, - path_res, path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, + CaptureKind, as_some_pattern, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, + is_none_expr, is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::def::Res; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -30,10 +30,10 @@ pub(super) fn check_with<'tcx, F>( where F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option>, { - let (scrutinee_ty, ty_ref_count, ty_mutability) = - peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); - if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option) - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option)) + let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(scrutinee)); + let ty_mutability = ty_mutability.unwrap_or(Mutability::Mut); + + if !(scrutinee_ty.is_diag_item(cx, sym::Option) && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Option)) { return None; } @@ -43,16 +43,16 @@ where try_parse_pattern(cx, then_pat, expr_ctxt), else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)), ) { - (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, true) }, - (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, false) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, true) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, false) }, _ => return None, @@ -137,7 +137,7 @@ where { snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() } else { - if path_to_local_id(some_expr.expr, id) + if some_expr.expr.res_local_id() == Some(id) && !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id) && binding_ref.is_some() { @@ -189,9 +189,9 @@ pub struct SuggInfo<'a> { fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Call(func, [arg]) - if path_to_local_id(arg, binding) + if arg.res_local_id() == Some(binding) && cx.typeck_results().expr_adjustments(arg).is_empty() - && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) => + && !is_unsafe_fn(cx, cx.typeck_results().expr_ty(func).peel_refs()) => { Some(func) }, @@ -254,13 +254,9 @@ pub(super) fn try_parse_pattern<'tcx>( match pat.kind { PatKind::Wild => Some(OptionPat::Wild), PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) if is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone) => Some(OptionPat::None), - PatKind::TupleStruct(ref qpath, [pattern], _) - if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt => + _ if is_none_pattern(cx, pat) => Some(OptionPat::None), + _ if let Some([pattern]) = as_some_pattern(cx, pat) + && pat.span.ctxt() == ctxt => { Some(OptionPat::Some { pattern, ref_count }) }, @@ -270,7 +266,7 @@ pub(super) fn try_parse_pattern<'tcx>( f(cx, pat, 0, ctxt) } -// Checks for the `None` value. -fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone) +/// Checks for the `None` value, possibly in a block. +fn is_none_arm_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + is_none_expr(cx, peel_blocks(expr)) } diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 1cb4b512a30e..30d703df4df4 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -1,67 +1,89 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_none_arm, is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::option_arg_ty; +use clippy_utils::{as_some_expr, as_some_pattern, is_none_arm, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, Mutability, 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_mut = 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]) + if let [arm1, arm2] = arms + && arm1.guard.is_none() + && arm2.guard.is_none() + && let Some(arm_ref_mutbl) = if is_none_arm(cx, arm1) { + as_ref_some_arm(cx, arm2) + } else if is_none_arm(cx, arm2) { + as_ref_some_arm(cx, arm1) } else { None + } + && let output_ty = cx.typeck_results().expr_ty(expr) + && let input_ty = cx.typeck_results().expr_ty(ex) + && let Some(input_ty) = option_arg_ty(cx, input_ty) + && let Some(output_ty) = option_arg_ty(cx, output_ty) + && let ty::Ref(_, output_ty, output_mutbl) = *output_ty.kind() + { + let method = match arm_ref_mutbl { + Mutability::Not => "as_ref", + Mutability::Mut => "as_mut", }; - if let Some(rb) = arm_ref_mut { - let suggestion = match rb { - Mutability::Not => "as_ref", - Mutability::Mut => "as_mut", - }; - let output_ty = cx.typeck_results().expr_ty(expr); - let input_ty = cx.typeck_results().expr_ty(ex); + // ``` + // let _: Option<&T> = match opt { + // Some(ref mut t) => Some(t), + // None => None, + // }; + // ``` + // We need to suggest `t.as_ref()` in order downcast the reference from `&mut` to `&`. + // We may or may not need to cast the type as well, for which we'd need `.map()`, and that could + // theoretically take care of the reference downcasting as well, but we chose to keep these two + // operations separate + let need_as_ref = arm_ref_mutbl == Mutability::Mut && output_mutbl == Mutability::Not; - let cast = if let ty::Adt(_, args) = input_ty.kind() - && let input_ty = args.type_at(0) - && let ty::Adt(_, args) = output_ty.kind() - && let output_ty = args.type_at(0) - && let ty::Ref(_, output_ty, _) = *output_ty.kind() - && input_ty != output_ty - { - ".map(|x| x as _)" - } else { - "" - }; + let cast = if input_ty == output_ty { "" } else { ".map(|x| x as _)" }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - MATCH_AS_REF, - expr.span, - format!("use `{suggestion}()` instead"), - "try", - format!( - "{}.{suggestion}(){cast}", - snippet_with_applicability(cx, ex.span, "_", &mut applicability), - ), - applicability, - ); - } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then( + cx, + MATCH_AS_REF, + expr.span, + format!("manual implementation of `Option::{method}`"), + |diag| { + if need_as_ref { + diag.note("but the type is coerced to a non-mutable reference, and so `as_ref` can used instead"); + diag.span_suggestion_verbose( + expr.span, + "use `Option::as_ref()`", + format!( + "{}.as_ref(){cast}", + Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + ), + applicability, + ); + } else { + diag.span_suggestion_verbose( + expr.span, + format!("use `Option::{method}()` directly"), + format!( + "{}.{method}(){cast}", + Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + ), + applicability, + ); + } + }, + ); } } // 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 let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind - && is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionSome) +fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { + if let Some([first_pat, ..]) = as_some_pattern(cx, arm.pat) && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., ident, _) = first_pat.kind - && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind - && is_res_lang_ctor(cx, path_res(cx, e), LangItem::OptionSome) + && let Some(arg) = as_some_expr(cx, peel_blocks(arm.body)) && let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind && path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 5816da5695eb..b5f631e8fea3 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -1,17 +1,18 @@ +//! Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` + use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment}; use rustc_ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{Arm, Attribute, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath}; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LintContext}; 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_if_let<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, @@ -20,16 +21,42 @@ pub(crate) fn check_if_let<'tcx>( then_expr: &'tcx Expr<'_>, else_expr: &'tcx Expr<'_>, ) { - find_matches_sugg( - cx, - let_expr, - IntoIterator::into_iter([ - (&[][..], Some(let_pat), then_expr, None), - (&[][..], None, else_expr, None), - ]), - expr, - true, - ); + if !span_contains_comment(cx.sess().source_map(), expr.span) + && cx.typeck_results().expr_ty(expr).is_bool() + && let Some(b0) = find_bool_lit(then_expr) + && let Some(b1) = find_bool_lit(else_expr) + && b0 != b1 + { + if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, let_pat.hir_id) && is_some_wild(let_pat.kind) { + return; + } + + // 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 = snippet_with_applicability(cx, let_pat.span, "..", &mut applicability); + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = let_expr; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = let_expr.kind + && 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, + "if let .. else expression looks like `matches!` macro", + "try", + format!( + "{}matches!({}, {pat})", + if b0 { "" } else { "!" }, + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), + ), + applicability, + ); + } } pub(super) fn check_match<'tcx>( @@ -38,55 +65,80 @@ pub(super) fn check_match<'tcx>( 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)), - 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>, &'a Expr<'b>, Option<&'a Expr<'b>>)>, -{ - if !span_contains_comment(cx.sess().source_map(), expr.span) - && iter.len() >= 2 - && cx.typeck_results().expr_ty(expr).is_bool() - && let Some((_, last_pat_opt, last_expr, _)) = iter.next_back() - && let iter_without_last = iter.clone() - && let Some((first_attrs, _, first_expr, first_guard)) = iter.next() - && let Some(b0) = find_bool_lit(&first_expr.kind) - && let Some(b1) = find_bool_lit(&last_expr.kind) + if let Some((last_arm, arms_without_last)) = arms.split_last() + && let Some((first_arm, middle_arms)) = arms_without_last.split_first() + && !span_contains_comment(cx.sess().source_map(), e.span) + && cx.typeck_results().expr_ty(e).is_bool() + && let Some(b0) = find_bool_lit(first_arm.body) + && let Some(b1) = find_bool_lit(last_arm.body) && b0 != b1 - && (first_guard.is_none() || iter.len() == 0) - && first_attrs.is_empty() - && iter.all(|arm| find_bool_lit(&arm.2.kind).is_some_and(|b| b == b0) && arm.3.is_none() && arm.0.is_empty()) + // We handle two cases: + && ( + // - There are no middle arms, i.e., 2 arms in total + // + // In that case, the first arm may or may not have a guard, because this: + // ```rs + // match e { + // Either::Left $(if $guard)|+ => true, // or `false`, but then we'll need `!matches!(..)` + // _ => false, + // } + // ``` + // can always become this: + // ```rs + // matches!(e, Either::Left $(if $guard)|+) + // ``` + middle_arms.is_empty() + + // - (added in #6216) There are middle arms + // + // In that case, neither they nor the first arm may have guards + // -- otherwise, they couldn't be combined into an or-pattern in `matches!` + // + // This: + // ```rs + // match e { + // Either3::First => true, + // Either3::Second => true, + // _ /* matches `Either3::Third` */ => false, + // } + // ``` + // can become this: + // ```rs + // matches!(e, Either3::First | Either3::Second) + // ``` + // + // But this: + // ```rs + // match e { + // Either3::First if X => true, + // Either3::Second => true, + // _ => false, + // } + // ``` + // cannot be transformed. + // + // We set an additional constraint of all of them needing to return the same bool, + // so we don't lint things like: + // ```rs + // match e { + // Either3::First => true, + // Either3::Second => false, + // _ => false, + // } + // ``` + // This is not *strictly* necessary, but it simplifies the logic a bit + || arms_without_last.iter().all(|arm| { + cx.tcx.hir_attrs(arm.hir_id).is_empty() && arm.guard.is_none() && find_bool_lit(arm.body) == Some(b0) + }) + ) { - if let Some(last_pat) = last_pat_opt - && !is_wild(last_pat) - { + if !is_wild(last_arm.pat) { return false; } - for arm in iter_without_last.clone() { - if let Some(pat) = arm.1 - && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) - && is_some(pat.kind) - { + for arm in arms_without_last { + let pat = arm.pat; + if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some_wild(pat.kind) { return false; } } @@ -96,14 +148,12 @@ where 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)) - }) + arms_without_last + .iter() + .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability)) .join(" | ") }; - let pat_and_guard = if let Some(g) = first_guard { + let pat_and_guard = if let Some(g) = first_arm.guard { format!( "{pat} if {}", snippet_with_applicability(cx, g.span, "..", &mut applicability) @@ -113,8 +163,8 @@ where }; // 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 + let mut ex_new = scrutinee; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = scrutinee.kind && let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { ex_new = ex_inner; @@ -122,11 +172,8 @@ where 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" } - ), + e.span, + "match expression looks like `matches!` macro", "try", format!( "{}matches!({}, {pat_and_guard})", @@ -142,11 +189,11 @@ where } /// Extract a `bool` or `{ bool }` -fn find_bool_lit(ex: &ExprKind<'_>) -> Option { - match ex { +fn find_bool_lit(ex: &Expr<'_>) -> Option { + match ex.kind { ExprKind::Lit(Spanned { node: LitKind::Bool(b), .. - }) => Some(*b), + }) => Some(b), ExprKind::Block( rustc_hir::Block { stmts: [], @@ -168,8 +215,9 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option { } } -fn is_some(path_kind: PatKind<'_>) -> bool { - match path_kind { +/// Checks whether a pattern is `Some(_)` +fn is_some_wild(pat_kind: PatKind<'_>) -> bool { + match pat_kind { PatKind::TupleStruct(QPath::Resolved(_, path), [first, ..], _) if is_wild(first) => { let name = path.segments[0].ident; name.name == rustc_span::sym::Some diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index ae277da089fd..be914429edb4 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same}; +use clippy_utils::{SpanlessEq, fulfill_or_allowed, hash_expr, is_lint_allowed, search_same}; use core::cmp::Ordering; use core::{iter, slice}; use itertools::Itertools; @@ -18,11 +19,7 @@ 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 hash = |&(_, arm): &(_, &Arm<'_>)| hash_expr(cx, arm.body); let arena = DroplessArena::default(); let normalized_pats: Vec<_> = arms @@ -35,9 +32,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .iter() .enumerate() .map(|(i, pat)| { - normalized_pats[i + 1..] - .iter() - .enumerate() + (normalized_pats[i + 1..].iter().enumerate()) .find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j)) .unwrap_or(normalized_pats.len()) }) @@ -48,16 +43,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .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_some(j) - }) - .unwrap_or(0) + iter::zip( + normalized_pats[..i].iter().enumerate().rev(), + 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_some(j) + }) + .unwrap_or(0) }) .collect(); @@ -68,8 +62,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { let check_eq_with_pat = |expr_a: &Expr<'_>, expr_b: &Expr<'_>| { let mut local_map: HirIdMap = HirIdMap::default(); let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - if let Some(a_id) = path_to_local(a) - && let Some(b_id) = path_to_local(b) + if let Some(a_id) = a.res_local_id() + && let Some(b_id) = b.res_local_id() && let entry = match local_map.entry(a_id) { HirIdMapEntry::Vacant(entry) => entry, // check if using the same bindings as before @@ -158,12 +152,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .map(|(_, arm)| arm.pat.span.get_source_text(cx)) .collect::>>() { - let mut suggs = src + let suggs = src .iter() .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new())) + .chain([(dest.pat.span, pat_snippets.iter().join(" | "))]) .collect_vec(); - suggs.push((dest.pat.span, pat_snippets.iter().join(" | "))); diag.multipart_suggestion_verbose( "otherwise merge the patterns into a single arm", suggs, @@ -396,10 +390,7 @@ impl<'a> NormalizedPat<'a> { if lpath != rpath { return false; } - lpats - .iter() - .zip(rpats.iter()) - .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + iter::zip(lpats, rpats).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) }, (Self::Path(x), Self::Path(y)) => x == y, (Self::LitStr(x), Self::LitStr(y)) => x == y, @@ -409,7 +400,7 @@ impl<'a> NormalizedPat<'a> { (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)) + lpats.len() == rpats.len() && iter::zip(lpats, rpats).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)) => { @@ -418,16 +409,12 @@ impl<'a> NormalizedPat<'a> { 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())) + iter::zip(&pats[..front.len()], front) + .chain(iter::zip(&pats[pats.len() - back.len()..], back)) .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())) + (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => iter::zip(lfront, rfront) + .chain(iter::zip(lback.iter().rev(), rback.iter().rev())) .all(|(x, y)| x.has_overlapping_values(y)), // Enums can mix unit variants with tuple/struct variants. These can never overlap. diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index eb8b16e1561b..3f8f2dc0e132 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,8 +1,8 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::sym; -use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; @@ -58,7 +58,7 @@ impl MatchExprVisitor<'_, '_> { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); - if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str { + if ty.is_lang_item(self.cx, LangItem::String) || ty.kind() == &ty::Str { return ControlFlow::Break(case_method); } } diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 70a03ff93762..fa44a56af182 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::SpanRangeExt; -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}; @@ -16,8 +16,7 @@ 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)) => + if adt_def.is_enum() && !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) => { adt_def }, @@ -109,7 +108,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { }, _, ) => path_prefix.with_prefix(path.segments), - _ => (), + QPath::TypeRelative(..) => (), } }); } diff --git a/clippy_lints/src/matches/match_wild_err_arm.rs b/clippy_lints/src/matches/match_wild_err_arm.rs index 8ce8453360f7..e38ba801c0bf 100644 --- a/clippy_lints/src/matches/match_wild_err_arm.rs +++ b/clippy_lints/src/matches/match_wild_err_arm.rs @@ -1,6 +1,6 @@ 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::res::MaybeDef; use clippy_utils::visitors::is_local_used; use clippy_utils::{is_in_const_context, is_wild, peel_blocks_with_stmt}; use rustc_hir::{Arm, Expr, PatKind}; @@ -16,7 +16,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<' } let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); - if is_type_diagnostic_item(cx, ex_ty, sym::Result) { + if ex_ty.is_diag_item(cx, sym::Result) { for arm in arms { if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { let path_str = rustc_hir_pretty::qpath_to_string(&cx.tcx, path); diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index b04db03f8d2e..c9b6821ad98f 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -1,10 +1,10 @@ use super::NEEDLESS_MATCH; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts}; +use clippy_utils::ty::same_type_modulo_regions; use clippy_utils::{ - SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res, - peel_blocks_with_stmt, + SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, over, peel_blocks_with_stmt, }; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; @@ -104,8 +104,8 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool return false; } let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr); - if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) { - return is_res_lang_ctor(cx, path_res(cx, else_expr), OptionNone) + if let_expr_ty.is_diag_item(cx, sym::Option) { + return else_expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) || eq_expr_value(cx, if_let.let_expr, else_expr); } return eq_expr_value(cx, if_let.let_expr, else_expr); @@ -122,7 +122,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_> // Compare match_expr ty with local in `let local = match match_expr {..}` Node::LetStmt(local) => { let results = cx.typeck_results(); - return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr)); + return same_type_modulo_regions(results.node_type(local.hir_id), results.expr_ty(expr)); }, // compare match_expr ty with RetTy in `fn foo() -> RetTy` Node::Item(item) => { @@ -133,7 +133,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_> .instantiate_identity() .output() .skip_binder(); - return same_type_and_consts(output, cx.typeck_results().expr_ty(expr)); + return same_type_modulo_regions(output, cx.typeck_results().expr_ty(expr)); } }, // check the parent expr for this whole block `{ match match_expr {..} }` diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs index d3136c89178e..d76218e6305b 100644 --- a/clippy_lints/src/matches/overlapping_arms.rs +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{ConstEvalCtxt, FullInt, mir_to_const}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt}; use clippy_utils::diagnostics::span_lint_and_note; use core::cmp::Ordering; use rustc_hir::{Arm, Expr, PatKind, RangeEnd}; @@ -35,12 +35,12 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) let lhs_const = if let Some(lhs) = lhs { ConstEvalCtxt::new(cx).eval_pat_expr(lhs)? } else { - mir_to_const(cx.tcx, ty.numeric_min_val(cx.tcx)?)? + Constant::new_numeric_min(cx.tcx, ty)? }; let rhs_const = if let Some(rhs) = rhs { ConstEvalCtxt::new(cx).eval_pat_expr(rhs)? } else { - mir_to_const(cx.tcx, ty.numeric_max_val(cx.tcx)?)? + Constant::new_numeric_max(cx.tcx, ty)? }; let lhs_val = lhs_const.int_value(cx.tcx, ty)?; let rhs_val = rhs_const.int_value(cx.tcx, ty)?; diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 7c6d45e42400..d39e315cae1f 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::matching_root_macro_call; use clippy_utils::msrvs::Msrv; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used}; -use clippy_utils::{is_in_const_context, path_to_local, sym}; +use clippy_utils::{is_in_const_context, sym}; use rustc_ast::{BorrowKind, LitKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -164,7 +165,7 @@ fn get_pat_binding<'tcx>( guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>, ) -> Option { - if let Some(local) = path_to_local(guard_expr) + if let Some(local) = guard_expr.res_local_id() && !is_local_used(cx, outer_arm.body, local) { let mut span = None; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index c936c96f9719..a0f88cf911d8 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -1,10 +1,11 @@ use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::walk_span_to_context; use clippy_utils::sugg::{Sugg, make_unop}; -use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; +use clippy_utils::ty::needs_ordered_drop; use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr_without_closures}; -use clippy_utils::{higher, is_expn_of, is_trait_method, sym}; +use clippy_utils::{higher, is_expn_of, sym}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; @@ -216,7 +217,7 @@ fn find_method_sugg_for_if_let<'tcx>( if keyword == "while" && let ExprKind::MethodCall(method_path, _, [], _) = let_expr.kind && method_path.ident.name == sym::next - && is_trait_method(cx, let_expr, sym::Iterator) + && cx.ty_based_def(let_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return; } @@ -269,66 +270,61 @@ fn find_method_sugg_for_if_let<'tcx>( } 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); - - if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) { - let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span)); - let result_expr = match &op.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - _ => op, - }; - let mut app = Applicability::MachineApplicable; - let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); - let mut sugg = format!("{receiver_sugg}.{good_method}"); - - if let Some(guard) = maybe_guard { - // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! - // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, - // counter to the intuition that it should be `Guard::IfLet`, so we need another check - // to see that there aren't any let chains anywhere in the guard, as that would break - // if we suggest `t.is_none() && (let X = y && z)` for: - // `match t { None if let X = y && z => true, _ => false }` - let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| { - if matches!(expr.kind, ExprKind::Let(..)) { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some(); - - if has_nested_let_chain { - return; + if let Ok(arms) = arms.try_into() // TODO: use `slice::as_array` once stabilized + && let Some((good_method, maybe_guard)) = found_good_method(cx, arms) + { + let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span)); + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + let mut app = Applicability::MachineApplicable; + let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); + let mut sugg = format!("{receiver_sugg}.{good_method}"); + + if let Some(guard) = maybe_guard { + // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! + // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, + // counter to the intuition that it should be `Guard::IfLet`, so we need another check + // to see that there aren't any let chains anywhere in the guard, as that would break + // if we suggest `t.is_none() && (let X = y && z)` for: + // `match t { None if let X = y && z => true, _ => false }` + let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| { + if matches!(expr.kind, ExprKind::Let(..)) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } + }) + .is_some(); - let guard = Sugg::hir(cx, guard, ".."); - let _ = write!(sugg, " && {}", guard.maybe_paren()); + if has_nested_let_chain { + return; } - span_lint_and_sugg( - cx, - REDUNDANT_PATTERN_MATCHING, - span, - format!("redundant pattern matching, consider using `{good_method}`"), - "try", - sugg, - app, - ); + let guard = Sugg::hir(cx, guard, ".."); + let _ = write!(sugg, " && {}", guard.maybe_paren()); } + + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN_MATCHING, + span, + format!("redundant pattern matching, consider using `{good_method}`"), + "try", + sugg, + app, + ); } } fn found_good_method<'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], - node: (&PatKind<'_>, &PatKind<'_>), + arms: &'tcx [Arm<'tcx>; 2], ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { - match node { - (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _)) - if patterns_left.len() == 1 && patterns_right.len() == 1 => - { - if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + match (&arms[0].pat.kind, &arms[1].pat.kind) { + (PatKind::TupleStruct(path_left, [pattern_left], _), PatKind::TupleStruct(path_right, [pattern_right], _)) => { + if let (PatKind::Wild, PatKind::Wild) = (&pattern_left.kind, &pattern_right.kind) { find_good_method_for_match( cx, arms, @@ -356,7 +352,7 @@ fn found_good_method<'tcx>( } }, ( - PatKind::TupleStruct(path_left, patterns, _), + PatKind::TupleStruct(path_left, [pattern], _), PatKind::Expr(PatExpr { kind: PatExprKind::Path(path_right), .. @@ -367,9 +363,9 @@ fn found_good_method<'tcx>( kind: PatExprKind::Path(path_left), .. }), - PatKind::TupleStruct(path_right, patterns, _), - ) if patterns.len() == 1 => { - if let PatKind::Wild = patterns[0].kind { + PatKind::TupleStruct(path_right, [pattern], _), + ) => { + if let PatKind::Wild = pattern.kind { find_good_method_for_match( cx, arms, @@ -396,8 +392,8 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { - if let PatKind::Wild = patterns[0].kind { + (PatKind::TupleStruct(path_left, [pattern], _), PatKind::Wild) => { + if let PatKind::Wild = pattern.kind { get_good_method(cx, arms, path_left) } else { None @@ -420,37 +416,29 @@ fn get_ident(path: &QPath<'_>) -> Option { let name = path.segments[0].ident; Some(name) }, - _ => None, + QPath::TypeRelative(..) => None, } } fn get_good_method<'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { - if let Some(name) = get_ident(path_left) { - let (expected_item_left, should_be_left, should_be_right) = match name.as_str() { - "Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"), - "Err" => (Item::Lang(ResultErr), "is_err()", "is_ok()"), - "Some" => (Item::Lang(OptionSome), "is_some()", "is_none()"), - "None" => (Item::Lang(OptionNone), "is_none()", "is_some()"), - "Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"), - "Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"), - "V4" => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"), - "V6" => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"), - _ => return None, - }; - return find_good_method_for_matches_macro( - cx, - arms, - path_left, - expected_item_left, - should_be_left, - should_be_right, - ); - } - None + let ident = get_ident(path_left)?; + + let (expected_item_left, should_be_left, should_be_right) = match ident.name { + sym::Ok => (Item::Lang(ResultOk), "is_ok()", "is_err()"), + sym::Err => (Item::Lang(ResultErr), "is_err()", "is_ok()"), + sym::Some => (Item::Lang(OptionSome), "is_some()", "is_none()"), + sym::None => (Item::Lang(OptionNone), "is_none()", "is_some()"), + sym::Ready => (Item::Lang(PollReady), "is_ready()", "is_pending()"), + sym::Pending => (Item::Lang(PollPending), "is_pending()", "is_ready()"), + sym::V4 => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"), + sym::V6 => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"), + _ => return None, + }; + find_good_method_for_matches_macro(cx, arms, path_left, expected_item_left, should_be_left, should_be_right) } #[derive(Clone, Copy)] @@ -473,7 +461,7 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte Item::Diag(expected_ty, expected_variant) => { let ty = cx.typeck_results().pat_ty(pat); - if is_type_diagnostic_item(cx, ty, expected_ty) { + if ty.is_diag_item(cx, expected_ty) { let variant = ty .ty_adt_def() .expect("struct pattern type is not an ADT") @@ -490,7 +478,7 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte #[expect(clippy::too_many_arguments)] fn find_good_method_for_match<'a, 'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, path_right: &QPath<'_>, expected_item_left: Item, @@ -525,7 +513,7 @@ fn find_good_method_for_match<'a, 'tcx>( fn find_good_method_for_matches_macro<'a, 'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, expected_item_left: Item, should_be_left: &'a str, 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 index ae09c2e87d6b..1130d82ab78f 100644 --- a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs +++ b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -7,7 +7,7 @@ use super::REST_PAT_IN_FULLY_BOUND_STRUCTS; pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { if !pat.span.from_expansion() - && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(_)) = pat.kind + && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(dotdot)) = pat.kind && let Some(def_id) = path.res.opt_def_id() && let ty = cx.tcx.type_of(def_id).instantiate_identity() && let ty::Adt(def, _) = ty.kind() @@ -15,14 +15,18 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { && fields.len() == def.non_enum_variant().fields.len() && !def.non_enum_variant().is_field_list_non_exhaustive() { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( cx, REST_PAT_IN_FULLY_BOUND_STRUCTS, pat.span, "unnecessary use of `..` pattern in struct binding. All fields were already bound", |diag| { - diag.help("consider removing `..` from this binding"); + diag.span_suggestion_verbose( + dotdot, + "consider removing `..` from this binding", + "", + rustc_errors::Applicability::MachineApplicable, + ); }, ); } diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 81fecc87256c..bac35e7f8c70 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -4,7 +4,7 @@ use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{first_line_of_span, indent_of, snippet}; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; -use clippy_utils::{get_attr, is_lint_allowed, sym}; +use clippy_utils::{get_builtin_attr, is_lint_allowed, sym}; use itertools::Itertools; use rustc_ast::Mutability; use rustc_data_structures::fx::FxIndexSet; @@ -183,7 +183,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() - && get_attr( + && get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index bcf079b70070..44c4d7a31ff3 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -2,10 +2,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{ SpanRangeExt, expr_block, snippet, snippet_block_with_context, snippet_with_applicability, snippet_with_context, }; -use clippy_utils::ty::implements_trait; -use clippy_utils::{ - is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs, -}; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs}; +use clippy_utils::{is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs}; use core::ops::ControlFlow; use rustc_arena::DroplessArena; use rustc_errors::{Applicability, Diag}; @@ -24,17 +22,16 @@ use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; /// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty /// match arms. fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool { - if let Some(ff) = span.get_source_range(cx) - && let Some(text) = ff.as_str() - { - text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*") - } else { - false - } + span.check_source_text(cx, |text| text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")) } -#[rustfmt::skip] -pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>, contains_comments: bool) { +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + ex: &'tcx Expr<'_>, + arms: &'tcx [Arm<'_>], + expr: &'tcx Expr<'_>, + contains_comments: bool, +) { if let [arm1, arm2] = arms && !arms.iter().any(|arm| arm.guard.is_some() || arm.pat.span.from_expansion()) && !expr.span.from_expansion() @@ -133,7 +130,7 @@ fn report_single_pattern( let (pat, pat_ref_count) = peel_hir_pat_refs(arm.pat); let (msg, sugg) = if let PatKind::Expr(_) = pat.kind - && let (ty, ty_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(ex)) + && let (ty, ty_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(ex)) && let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait() && let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait() && (ty.is_integral() @@ -226,13 +223,13 @@ enum PatState<'a> { Wild, /// A std enum we know won't be extended. Tracks the states of each variant separately. /// - /// This is not used for `Option` since it uses the current pattern to track it's state. - StdEnum(&'a mut [PatState<'a>]), + /// This is not used for `Option` since it uses the current pattern to track its state. + StdEnum(&'a mut [Self]), /// Either the initial state for a pattern or a non-std enum. There is currently no need to /// distinguish these cases. /// /// For non-std enums there's no need to track the state of sub-patterns as the state of just - /// this pattern on it's own is enough for linting. Consider two cases: + /// this pattern on its own is enough for linting. Consider two cases: /// * This enum has no wild match. This case alone is enough to determine we can lint. /// * This enum has a wild match and therefore all sub-patterns also have a wild match. /// @@ -380,7 +377,11 @@ impl<'a> PatState<'a> { self.add_pat(cx, pat) }, PatKind::Tuple([sub_pat], pos) - if pos.as_opt_usize().is_none() || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 => + // `pat` looks like `(sub_pat)`, without a `..` -- has only one sub-pattern + if pos.as_opt_usize().is_none() + // `pat` looks like `(sub_pat, ..)` or `(.., sub_pat)`, but its type is a unary tuple, + // so it still only has one sub-pattern + || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 => { self.add_pat(cx, sub_pat) }, diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index af90cb5e6733..401b8468135b 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_parent_expr; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::option_arg_ty; -use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; -use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_hir::{Expr, ExprKind, LangItem, MatchSource}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::{hygiene, sym}; @@ -22,10 +23,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine // val, // }; if let ExprKind::Call(match_fun, [try_arg]) = scrutinee.kind - && let ExprKind::Path(ref match_fun_path) = match_fun.kind - && matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..)) + && let ExprKind::Path(match_fun_path) = match_fun.kind + && cx.tcx.qpath_is_lang_item(match_fun_path, LangItem::TryTraitBranch) && let ExprKind::Call(err_fun, [err_arg]) = try_arg.kind - && is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr) + && err_fun.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) && let Some(return_ty) = find_return_type(cx, &expr.kind) { let (prefix, suffix, err_ty) = if let Some(ty) = result_error_type(cx, return_ty) { diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index e39916f733d5..0f32f89666a0 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,14 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::res::MaybeDef; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; use clippy_utils::{ - is_default_equivalent, is_expr_used_or_unified, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core, + as_some_expr, is_default_equivalent, is_expr_used_or_unified, is_none_expr, peel_ref_operators, std_or_core, }; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -129,7 +129,7 @@ impl_lint_pass!(MemReplace => [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_OPTION_WITH_SOME, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) -> bool { - if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) { + if is_none_expr(cx, src) { // Since this is a late pass (already type-checked), // and we already know that the second argument is an // `Option`, we do not need to check the first @@ -162,8 +162,7 @@ fn check_replace_option_with_some( expr_span: Span, msrv: Msrv, ) -> bool { - if let ExprKind::Call(src_func, [src_arg]) = src.kind - && is_res_lang_ctor(cx, path_res(cx, src_func), OptionSome) + if let Some(src_arg) = as_some_expr(cx, src) && msrv.meets(cx, msrvs::OPTION_REPLACE) { // We do not have to check for a `const` context here, because `core::mem::replace()` and @@ -269,14 +268,11 @@ fn check_replace_with_default( ), |diag| { if !expr.span.from_expansion() { - let suggestion = format!("{top_crate}::mem::take({})", snippet(cx, dest.span, "")); + let mut applicability = Applicability::MachineApplicable; + let (dest_snip, _) = snippet_with_context(cx, dest.span, expr.span.ctxt(), "", &mut applicability); + let suggestion = format!("{top_crate}::mem::take({dest_snip})"); - diag.span_suggestion( - expr.span, - "consider using", - suggestion, - Applicability::MachineApplicable, - ); + diag.span_suggestion(expr.span, "consider using", suggestion, applicability); } }, ); diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints/src/methods/bytecount.rs index 0498f317442a..77ac181ee633 100644 --- a/clippy_lints/src/methods/bytecount.rs +++ b/clippy_lints/src/methods/bytecount.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::is_local_used; -use clippy_utils::{path_to_local_id, peel_blocks, peel_ref_operators, strip_pat_refs}; +use clippy_utils::{peel_blocks, peel_ref_operators, strip_pat_refs}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; @@ -23,10 +23,14 @@ pub(super) fn check<'tcx>( && let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind && let ExprKind::Binary(ref op, l, r) = body.value.kind && op.node == BinOpKind::Eq - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv).peel_refs(), sym::SliceIter) + && cx + .typeck_results() + .expr_ty(filter_recv) + .peel_refs() + .is_diag_item(cx, sym::SliceIter) && let operand_is_arg = (|expr| { let expr = peel_ref_operators(cx, peel_blocks(expr)); - path_to_local_id(expr, arg_id) + expr.res_local_id() == Some(arg_id) }) && let needle = if operand_is_arg(l) { r diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs index b8cc5ddd845c..baea49296cd7 100644 --- a/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -17,7 +17,7 @@ pub(super) fn check<'tcx>( && let Some(impl_id) = cx.tcx.impl_of_assoc(bytes_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_str() && let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs() - && (ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String)) + && (ty.is_str() || ty.is_lang_item(cx, hir::LangItem::String)) { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index 02fc09170e59..40d521d61c11 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sym; -use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let caller_type = if ty.is_str() { "str" - } else if is_type_lang_item(cx, ty, LangItem::String) { + } else if ty.is_lang_item(cx, LangItem::String) { "String" } else { return; diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs index 6f9702f6c6c3..b4b10e972f6d 100644 --- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; use clippy_utils::sym; -use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; @@ -43,7 +43,7 @@ pub(super) fn check<'tcx>( || ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit())) && !ext_str.chars().skip(1).all(|c| c.is_ascii_digit()) && let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs() - && (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String)) + && (recv_ty.is_str() || recv_ty.is_lang_item(cx, LangItem::String)) { span_lint_and_then( cx, diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs index de27a45ba4d9..4b0fc7eba8e3 100644 --- a/clippy_lints/src/methods/chars_cmp.rs +++ b/clippy_lints/src/methods/chars_cmp.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::method_chain_args; +use clippy_utils::res::MaybeQPath; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{method_chain_args, path_def_id}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, Lint}; @@ -17,7 +18,7 @@ pub(super) fn check( ) -> bool { if let Some(args) = method_chain_args(info.chain, chain_methods) && let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind - && let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id)) + && let Some(id) = fun.res(cx).opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) && Some(id) == cx.tcx.lang_items().option_some_variant() { let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index 6e24cabca8bb..67def8767bb0 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_range_full; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::res::MaybeDef; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, QPath}; use rustc_lint::LateContext; @@ -29,9 +29,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span fn match_acceptable_type(cx: &LateContext<'_>, expr: &Expr<'_>, types: &[rustc_span::Symbol]) -> bool { let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); - types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty)) + types.iter().any(|&ty| expr_ty.is_diag_item(cx, ty)) // String type is a lang item but not a diagnostic item for now so we need a separate check - || is_type_lang_item(cx, expr_ty, LangItem::String) + || expr_ty.is_lang_item(cx, LangItem::String) } fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) { diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs index 0a456d1057ad..8980a22ad613 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -2,29 +2,16 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_copy; use rustc_errors::Applicability; -use rustc_hir::{BindingMode, ByRef, Expr, ExprKind, MatchSource, Node, PatKind, QPath}; +use rustc_hir::{BindingMode, ByRef, Expr, ExprKind, MatchSource, Node, PatKind}; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::{self}; -use rustc_span::symbol::{Symbol, sym}; use super::CLONE_ON_COPY; /// Checks for the `CLONE_ON_COPY` lint. -#[allow(clippy::too_many_lines)] -pub(super) fn check( - cx: &LateContext<'_>, - expr: &Expr<'_>, - method_name: Symbol, - receiver: &Expr<'_>, - args: &[Expr<'_>], -) { - let arg = if method_name == sym::clone && args.is_empty() { - receiver - } else { - return; - }; +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { if cx .typeck_results() .type_dependent_def_id(expr.hir_id) @@ -34,10 +21,10 @@ pub(super) fn check( { return; } - let arg_adjustments = cx.typeck_results().expr_adjustments(arg); + let arg_adjustments = cx.typeck_results().expr_adjustments(receiver); let arg_ty = arg_adjustments .last() - .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target); + .map_or_else(|| cx.typeck_results().expr_ty(receiver), |a| a.target); let ty = cx.typeck_results().expr_ty(expr); if let ty::Ref(_, inner, _) = arg_ty.kind() @@ -60,7 +47,8 @@ pub(super) fn check( // ? is a Call, makes sure not to rec *x?, but rather (*x)? ExprKind::Call(hir_callee, [_]) => matches!( hir_callee.kind, - ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, ..)) + ExprKind::Path(qpath) + if cx.tcx.qpath_is_lang_item(qpath, rustc_hir::LangItem::TryTraitBranch) ), ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true, ExprKind::Match(_, _, MatchSource::TryDesugar(_) | MatchSource::AwaitDesugar) @@ -76,7 +64,7 @@ pub(super) fn check( }; let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0; + let snip = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "_", &mut app).0; let deref_count = arg_adjustments .iter() diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints/src/methods/clone_on_ref_ptr.rs index 65583c6a9811..238e1fe988b3 100644 --- a/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -3,21 +3,12 @@ use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::symbol::{Symbol, sym}; +use rustc_middle::ty::{self, IsSuggestable}; +use rustc_span::symbol::sym; use super::CLONE_ON_REF_PTR; -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - method_name: Symbol, - receiver: &hir::Expr<'_>, - args: &[hir::Expr<'_>], -) { - if !(args.is_empty() && method_name == sym::clone) { - return; - } +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>) { let obj_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); if let ty::Adt(adt, subst) = obj_ty.kind() @@ -39,12 +30,22 @@ pub(super) fn check( // Sometimes unnecessary ::<_> after Rc/Arc/Weak let mut app = Applicability::Unspecified; let snippet = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "..", &mut app).0; - diag.span_suggestion( - expr.span, - "try", - format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)), - app, - ); + let generic = subst.type_at(0); + if generic.is_suggestable(cx.tcx, true) { + diag.span_suggestion( + expr.span, + "try", + format!("{caller_type}::<{generic}>::clone(&{snippet})"), + app, + ); + } else { + diag.span_suggestion( + expr.span, + "try", + format!("{caller_type}::::clone(&{snippet})"), + Applicability::HasPlaceholders, + ); + } }, ); } diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints/src/methods/cloned_instead_of_copied.rs index f50fb627b89a..d80d6f7810f5 100644 --- a/clippy_lints/src/methods/cloned_instead_of_copied.rs +++ b/clippy_lints/src/methods/cloned_instead_of_copied.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::{get_iterator_item_ty, is_copy}; use rustc_errors::Applicability; use rustc_hir::Expr; @@ -19,7 +19,9 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, { subst.type_at(0) }, - _ if is_trait_method(cx, expr, sym::Iterator) && msrv.meets(cx, msrvs::ITERATOR_COPIED) => { + _ if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && msrv.meets(cx, msrvs::ITERATOR_COPIED) => + { match get_iterator_item_ty(cx, recv_ty) { // ::Item Some(ty) => ty, diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints/src/methods/double_ended_iterator_last.rs index 578865c32918..8ba8264b713c 100644 --- a/clippy_lints/src/methods/double_ended_iterator_last.rs +++ b/clippy_lints/src/methods/double_ended_iterator_last.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait}; -use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym}; +use clippy_utils::{is_mutable, path_to_local_with_projections, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, Node, PatKind}; use rustc_lint::LateContext; @@ -13,7 +14,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp let typeck = cx.typeck_results(); // if the "last" method is that of Iterator - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) // if self implements DoubleEndedIterator && let Some(deiter_id) = cx.tcx.get_diagnostic_item(sym::DoubleEndedIterator) && let self_type = cx.typeck_results().expr_ty(self_expr) diff --git a/clippy_lints/src/methods/drain_collect.rs b/clippy_lints/src/methods/drain_collect.rs index cbf713a3b17c..9b0c29057740 100644 --- a/clippy_lints/src/methods/drain_collect.rs +++ b/clippy_lints/src/methods/drain_collect.rs @@ -1,7 +1,7 @@ use crate::methods::DRAIN_COLLECT; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_lang_item; use clippy_utils::{is_range_full, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, Path, QPath}; @@ -35,8 +35,8 @@ fn check_vec(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_> /// Checks `std::string::String` fn check_string(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool { - is_type_lang_item(cx, expr, LangItem::String) - && is_type_lang_item(cx, recv, LangItem::String) + expr.is_lang_item(cx, LangItem::String) + && recv.is_lang_item(cx, LangItem::String) && matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path))) } diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs index 91ddaca07d8b..6e9aebcf18ae 100644 --- a/clippy_lints/src/methods/err_expect.rs +++ b/clippy_lints/src/methods/err_expect.rs @@ -1,7 +1,8 @@ use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{has_debug_impl, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::has_debug_impl; use rustc_errors::Applicability; use rustc_lint::LateContext; use rustc_middle::ty; @@ -16,7 +17,7 @@ pub(super) fn check( err_span: Span, msrv: Msrv, ) { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) // Grabs the `Result` type && let result_type = cx.typeck_results().expr_ty(recv) // Tests if the T type in a `Result` is not None @@ -40,7 +41,7 @@ pub(super) fn check( /// Given a `Result` type, return its data (`T`). fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if is_type_diagnostic_item(cx, ty, sym::Result) => args.types().next(), + ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().next(), _ => None, } } diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 818e26f8aa1d..288f966991ac 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -1,14 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::visitors::for_each_expr; use clippy_utils::{contains_return, is_inside_always_const_context, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_span::Span; use rustc_span::symbol::sym; -use rustc_span::{Span, Symbol}; use std::borrow::Cow; use std::ops::ControlFlow; @@ -20,20 +20,15 @@ pub(super) fn check<'tcx>( format_args_storage: &FormatArgsStorage, expr: &hir::Expr<'_>, method_span: Span, - name: Symbol, receiver: &'tcx hir::Expr<'tcx>, - args: &'tcx [hir::Expr<'tcx>], + arg: &'tcx hir::Expr<'tcx>, ) { - if name == sym::expect - && let [arg] = args - && let arg_root = get_arg_root(cx, arg) - && contains_call(cx, arg_root) - && !contains_return(arg_root) - { + let arg_root = get_arg_root(cx, arg); + if contains_call(cx, arg_root) && !contains_return(arg_root) { let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); - let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) { + let closure_args = if receiver_type.is_diag_item(cx, sym::Option) { "||" - } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) { + } else if receiver_type.is_diag_item(cx, sym::Result) { "|_|" } else { return; @@ -54,7 +49,7 @@ pub(super) fn check<'tcx>( cx, EXPECT_FUN_CALL, span_replace_word, - format!("function call inside of `{name}`"), + "function call inside of `expect`", "try", format!("unwrap_or_else({closure_args} panic!({sugg}))"), applicability, @@ -69,7 +64,7 @@ pub(super) fn check<'tcx>( cx, EXPECT_FUN_CALL, span_replace_word, - format!("function call inside of `{name}`"), + "function call inside of `expect`", "try", format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"), applicability, @@ -88,7 +83,7 @@ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Ex if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { let arg_type = cx.typeck_results().expr_ty(receiver); let base_type = arg_type.peel_refs(); - base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String) + base_type.is_str() || base_type.is_lang_item(cx, hir::LangItem::String) } { receiver } else { diff --git a/clippy_lints/src/methods/extend_with_drain.rs b/clippy_lints/src/methods/extend_with_drain.rs index db60061904f6..829c1226a859 100644 --- a/clippy_lints/src/methods/extend_with_drain.rs +++ b/clippy_lints/src/methods/extend_with_drain.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sym; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; @@ -10,7 +10,7 @@ use super::EXTEND_WITH_DRAIN; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if is_type_diagnostic_item(cx, ty, sym::Vec) + if ty.is_diag_item(cx, sym::Vec) //check source object && let ExprKind::MethodCall(src_method, drain_vec, [drain_arg], _) = &arg.kind && src_method.ident.name == sym::drain @@ -18,10 +18,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: //check if actual src type is mutable for code suggestion && let immutable = src_ty.is_mutable_ptr() && let src_ty = src_ty.peel_refs() - && is_type_diagnostic_item(cx, src_ty, sym::Vec) + && src_ty.is_diag_item(cx, sym::Vec) //check drain range && let src_ty_range = cx.typeck_results().expr_ty(drain_arg).peel_refs() - && is_type_lang_item(cx, src_ty_range, LangItem::RangeFull) + && src_ty_range.is_lang_item(cx, LangItem::RangeFull) { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/filetype_is_file.rs b/clippy_lints/src/methods/filetype_is_file.rs index 35008c39c084..a964cbf657b4 100644 --- a/clippy_lints/src/methods/filetype_is_file.rs +++ b/clippy_lints/src/methods/filetype_is_file.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::get_parent_expr; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; @@ -10,7 +10,7 @@ use super::FILETYPE_IS_FILE; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); - if !is_type_diagnostic_item(cx, ty, sym::FileType) { + if !ty.is_diag_item(cx, sym::FileType) { return; } diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 2da0f8341b17..26b19848fe1b 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{is_panic, matching_root_macro_call, root_macro_call}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{SpanlessEq, higher, is_trait_method, path_to_local_id, peel_blocks, sym}; +use clippy_utils::{SpanlessEq, higher, peel_blocks, sym}; use hir::{Body, HirId, MatchSource, Pat}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -134,16 +134,16 @@ impl<'tcx> OffendingFilterExpr<'tcx> { _ => map_arg, } // .map(|y| y[.acceptable_method()].unwrap()) - && let simple_equal = (path_to_local_id(receiver, filter_param_id) - && path_to_local_id(map_arg_peeled, map_param_id)) + && let simple_equal = (receiver.res_local_id() == Some(filter_param_id) + && map_arg_peeled.res_local_id() == Some(map_param_id)) && let eq_fallback = (|a: &Expr<'_>, b: &Expr<'_>| { // in `filter(|x| ..)`, replace `*x` with `x` let a_path = if !is_filter_param_ref && let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind { expr_path } else { a }; // let the filter closure arg and the map closure arg be equal - path_to_local_id(a_path, filter_param_id) - && path_to_local_id(b, map_param_id) + a_path.res_local_id() == Some(filter_param_id) + && b.res_local_id() == Some(map_param_id) && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) }) && (simple_equal @@ -166,7 +166,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> { let expr_uses_local = |pat: &Pat<'_>, expr: &Expr<'_>| { if let PatKind::TupleStruct(QPath::Resolved(_, path), [subpat], _) = pat.kind && let PatKind::Binding(_, local_id, ident, _) = subpat.kind - && path_to_local_id(expr.peel_blocks(), local_id) + && expr.peel_blocks().res_local_id() == Some(local_id) && let Some(local_variant_def_id) = path.res.opt_def_id() && local_variant_def_id == variant_def_id { @@ -204,7 +204,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> { _ => return None, }; - if path_to_local_id(scrutinee, map_param_id) + if scrutinee.res_local_id() == Some(map_param_id) // else branch should be a `panic!` or `unreachable!` macro call && let Some(mac) = root_macro_call(else_.peel_blocks().span) && (is_panic(cx, mac.def_id) || cx.tcx.opt_item_name(mac.def_id) == Some(sym::unreachable)) @@ -247,7 +247,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> { } else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() // we know for a fact that the wildcard pattern is the second arm && let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind - && path_to_local_id(scrutinee, filter_param_id) + && scrutinee.res_local_id() == Some(filter_param_id) && let PatKind::TupleStruct(QPath::Resolved(_, path), ..) = arm.pat.kind && let Some(variant_def_id) = path.res.opt_def_id() { @@ -266,8 +266,8 @@ fn is_filter_some_map_unwrap( filter_arg: &Expr<'_>, map_arg: &Expr<'_>, ) -> bool { - let iterator = is_trait_method(cx, expr, sym::Iterator); - let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option); + let iterator = cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator); + let option = cx.typeck_results().expr_ty(filter_recv).is_diag_item(cx, sym::Option); (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) } @@ -275,12 +275,12 @@ fn is_filter_some_map_unwrap( /// is `filter(|x| x.is_ok()).map(|x| x.unwrap())` fn is_filter_ok_map_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>, filter_arg: &Expr<'_>, map_arg: &Expr<'_>) -> bool { // result has no filter, so we only check for iterators - let iterator = is_trait_method(cx, expr, sym::Iterator); + let iterator = cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator); iterator && is_ok_filter_map(cx, filter_arg, map_arg) } /// lint use of `filter().map()` or `find().map()` for `Iterators` -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] pub(super) fn check( cx: &LateContext<'_>, expr: &Expr<'_>, @@ -398,7 +398,7 @@ fn is_find_or_filter<'a>( filter_arg: &Expr<'_>, map_arg: &Expr<'_>, ) -> Option<(Ident, CheckResult<'a>)> { - if is_trait_method(cx, map_recv, sym::Iterator) + if cx.ty_based_def(map_recv).opt_parent(cx).is_diag_item(cx, sym::Iterator) // filter(|x| ...is_some())... && let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind && let filter_body = cx.tcx.hir_body(filter_body_id) diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index 94944bd9445b..b803d1a3309d 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,10 +1,9 @@ use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::ty::is_copy; -use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks, -}; +use clippy_utils::{CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, peel_blocks}; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -16,7 +15,7 @@ use rustc_span::{Span, sym}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { if !expr.span.in_external_macro(cx.sess().source_map()) - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let ExprKind::Closure(closure) = arg.kind && let body = cx.tcx.hir_body(closure.body) && let value = peel_blocks(body.value) diff --git a/clippy_lints/src/methods/filter_map_identity.rs b/clippy_lints/src/methods/filter_map_identity.rs index b04d761d4860..c6e30710eef9 100644 --- a/clippy_lints/src/methods/filter_map_identity.rs +++ b/clippy_lints/src/methods/filter_map_identity.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_expr_identity_function, is_expr_untyped_identity_function, is_trait_method}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::{is_expr_identity_function, is_expr_untyped_identity_function}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::ExprKind; @@ -19,7 +20,7 @@ fn is_identity(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) { - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let Some(applicability) = is_identity(cx, filter_map_arg) { // check if the iterator is from an empty array, see issue #12653 diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints/src/methods/filter_map_next.rs index 9f3c346042ff..698025d58e54 100644 --- a/clippy_lints/src/methods/filter_map_next.rs +++ b/clippy_lints/src/methods/filter_map_next.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::is_trait_method; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir as hir; @@ -16,7 +16,7 @@ pub(super) fn check<'tcx>( arg: &'tcx hir::Expr<'_>, msrv: Msrv, ) { - if is_trait_method(cx, expr, sym::Iterator) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { if !msrv.meets(cx, msrvs::ITERATOR_FIND_MAP) { return; } diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs index 72f83b245a0c..e1a9a79e20ee 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints/src/methods/filter_next.rs @@ -10,51 +10,68 @@ use rustc_span::sym; use super::FILTER_NEXT; -/// lint use of `filter().next()` for `Iterators` +#[derive(Copy, Clone)] +pub(super) enum Direction { + Forward, + Backward, +} + +/// lint use of `filter().next()` for `Iterator` and `filter().next_back()` for +/// `DoubleEndedIterator` pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, recv: &'tcx hir::Expr<'_>, filter_arg: &'tcx hir::Expr<'_>, + direction: Direction, ) { - // lint if caller of `.filter().next()` is an Iterator - let recv_impls_iterator = cx + // lint if caller of `.filter().next()` is an Iterator or `.filter().next_back()` is a + // DoubleEndedIterator + let (required_trait, next_method, find_method) = match direction { + Direction::Forward => (sym::Iterator, "next", "find"), + Direction::Backward => (sym::DoubleEndedIterator, "next_back", "rfind"), + }; + if !cx .tcx - .get_diagnostic_item(sym::Iterator) - .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])); - if recv_impls_iterator { - let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ - `.find(..)` instead"; - let filter_snippet = snippet(cx, filter_arg.span, ".."); - if filter_snippet.lines().count() <= 1 { - let iter_snippet = snippet(cx, recv.span, ".."); - // add note if not multi-line - span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| { - let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv) - && let hir::Node::Pat(pat) = cx.tcx.hir_node(id) - && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind - { - (Applicability::Unspecified, Some((pat.span, ident))) - } else { - (Applicability::MachineApplicable, None) - }; + .get_diagnostic_item(required_trait) + .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])) + { + return; + } + let msg = format!( + "called `filter(..).{next_method}()` on an `{}`. This is more succinctly expressed by calling \ + `.{find_method}(..)` instead", + required_trait.as_str() + ); + let filter_snippet = snippet(cx, filter_arg.span, ".."); + if filter_snippet.lines().count() <= 1 { + let iter_snippet = snippet(cx, recv.span, ".."); + // add note if not multi-line + span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| { + let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv) + && let hir::Node::Pat(pat) = cx.tcx.hir_node(id) + && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind + { + (Applicability::Unspecified, Some((pat.span, ident))) + } else { + (Applicability::MachineApplicable, None) + }; - diag.span_suggestion( - expr.span, - "try", - format!("{iter_snippet}.find({filter_snippet})"), - applicability, - ); + diag.span_suggestion( + expr.span, + "try", + format!("{iter_snippet}.{find_method}({filter_snippet})"), + applicability, + ); - if let Some((pat_span, ident)) = pat { - diag.span_help( - pat_span, - format!("you will also need to make `{ident}` mutable, because `find` takes `&mut self`"), - ); - } - }); - } else { - span_lint(cx, FILTER_NEXT, expr.span, msg); - } + if let Some((pat_span, ident)) = pat { + diag.span_help( + pat_span, + format!("you will also need to make `{ident}` mutable, because `{find_method}` takes `&mut self`"), + ); + } + }); + } else { + span_lint(cx, FILTER_NEXT, expr.span, msg); } } diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints/src/methods/flat_map_identity.rs index 0c2ecfbc8ffd..043adb8434a0 100644 --- a/clippy_lints/src/methods/flat_map_identity.rs +++ b/clippy_lints/src/methods/flat_map_identity.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_expr_untyped_identity_function, is_trait_method}; +use clippy_utils::is_expr_untyped_identity_function; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -14,7 +15,9 @@ pub(super) fn check<'tcx>( flat_map_arg: &'tcx hir::Expr<'_>, flat_map_span: Span, ) { - if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, flat_map_arg) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && is_expr_untyped_identity_function(cx, flat_map_arg) + { span_lint_and_sugg( cx, FLAT_MAP_IDENTITY, diff --git a/clippy_lints/src/methods/flat_map_option.rs b/clippy_lints/src/methods/flat_map_option.rs index 3242dcadb701..fb0e8f9d91b5 100644 --- a/clippy_lints/src/methods/flat_map_option.rs +++ b/clippy_lints/src/methods/flat_map_option.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -7,10 +7,9 @@ use rustc_middle::ty; use rustc_span::{Span, sym}; use super::FLAT_MAP_OPTION; -use clippy_utils::ty::is_type_diagnostic_item; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { - if !is_trait_method(cx, expr, sym::Iterator) { + if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return; } let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); @@ -19,7 +18,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg _ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx), _ => return, }; - if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::Option) { + if !sig.output().skip_binder().is_diag_item(cx, sym::Option) { return; } span_lint_and_sugg( diff --git a/clippy_lints/src/methods/format_collect.rs b/clippy_lints/src/methods/format_collect.rs index 1b28596d50da..7b2fe00d3218 100644 --- a/clippy_lints/src/methods/format_collect.rs +++ b/clippy_lints/src/methods/format_collect.rs @@ -1,7 +1,7 @@ use super::FORMAT_COLLECT; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_format_macro, root_macro_call_first_node}; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::MaybeDef; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; use rustc_span::Span; @@ -17,7 +17,7 @@ fn peel_non_expn_blocks<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx> } pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { - if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String) + if cx.typeck_results().expr_ty(expr).is_lang_item(cx, LangItem::String) && let ExprKind::Closure(closure) = map_arg.kind && let body = cx.tcx.hir_body(closure.body) && let Some(value) = peel_non_expn_blocks(body.value) diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints/src/methods/from_iter_instead_of_collect.rs index d664eaaac704..11671f377e66 100644 --- a/clippy_lints/src/methods/from_iter_instead_of_collect.rs +++ b/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -1,9 +1,10 @@ use std::fmt::Write as _; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_path_diagnostic_item, sugg}; use rustc_ast::join_path_idents; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -15,7 +16,7 @@ use rustc_span::sym; use super::FROM_ITER_INSTEAD_OF_COLLECT; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>], func: &Expr<'_>) { - if is_path_diagnostic_item(cx, func, sym::from_iter_fn) + if func.res(cx).is_diag_item(cx, sym::from_iter_fn) && let arg_ty = cx.typeck_results().expr_ty(&args[0]) && let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) && implements_trait(cx, arg_ty, iter_id, &[]) diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints/src/methods/get_first.rs index 2e1d71ce284d..88e9f2fdaee3 100644 --- a/clippy_lints/src/methods/get_first.rs +++ b/clippy_lints/src/methods/get_first.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -37,7 +37,7 @@ pub(super) fn check<'tcx>( format!("{slice_name}.first()"), app, ); - } else if is_type_diagnostic_item(cx, identity, sym::VecDeque) { + } else if identity.is_diag_item(cx, sym::VecDeque) { let mut app = Applicability::MachineApplicable; let slice_name = snippet_with_applicability(cx, recv.span, "..", &mut app); span_lint_and_sugg( diff --git a/clippy_lints/src/methods/get_unwrap.rs b/clippy_lints/src/methods/get_unwrap.rs index 9daad1a8a949..d273558a7006 100644 --- a/clippy_lints/src/methods/get_unwrap.rs +++ b/clippy_lints/src/methods/get_unwrap.rs @@ -2,7 +2,6 @@ use super::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::get_parent_expr; 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_lint::LateContext; @@ -22,16 +21,17 @@ pub(super) fn check<'tcx>( let expr_ty = cx.typeck_results().expr_ty(recv); let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() { "slice" - } else if is_type_diagnostic_item(cx, expr_ty, sym::Vec) { - "Vec" - } else if is_type_diagnostic_item(cx, expr_ty, sym::VecDeque) { - "VecDeque" - } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::HashMap) { - "HashMap" - } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::BTreeMap) { - "BTreeMap" } else { - return; // caller is not a type that we want to lint + match expr_ty + .ty_adt_def() + .and_then(|def| cx.tcx.get_diagnostic_name(def.did())) + { + Some(sym::Vec) => "Vec", + Some(sym::VecDeque) => "VecDeque", + Some(sym::HashMap) if !is_mut => "HashMap", + Some(sym::BTreeMap) if !is_mut => "BTreeMap", + _ => return, // caller is not a type that we want to lint + } }; let mut span = expr.span; diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 8a976d1b4dc0..57c0ba25ebbf 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::implements_trait; -use clippy_utils::{is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs, sym}; +use clippy_utils::sym; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -10,12 +11,12 @@ use rustc_span::Symbol; use super::IMPLICIT_CLONE; pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && is_clone_like(cx, method_name, method_def_id) + if let Some(method_parent_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id).opt_parent(cx) + && is_clone_like(cx, method_name, method_parent_id) && let return_type = cx.typeck_results().expr_ty(expr) && let input_type = cx.typeck_results().expr_ty(recv) - && let (input_type, ref_count) = peel_middle_ty_refs(input_type) - && !(ref_count > 0 && is_diag_trait_item(cx, method_def_id, sym::ToOwned)) + && let (input_type, ref_count, _) = peel_and_count_ty_refs(input_type) + && !(ref_count > 0 && method_parent_id.is_diag_item(cx, sym::ToOwned)) && let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did())) && return_type == input_type && let Some(clone_trait) = cx.tcx.lang_items().clone_trait() @@ -40,19 +41,15 @@ pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, re } /// Returns true if the named method can be used to clone the receiver. -pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: hir::def_id::DefId) -> bool { +pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_parent_id: hir::def_id::DefId) -> bool { match method_name { - sym::to_os_string => is_diag_item_method(cx, method_def_id, sym::OsStr), - sym::to_owned => is_diag_trait_item(cx, method_def_id, sym::ToOwned), - sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path), - sym::to_string => is_diag_trait_item(cx, method_def_id, sym::ToString), - sym::to_vec => cx - .tcx - .impl_of_assoc(method_def_id) - .filter(|&impl_did| { - cx.tcx.type_of(impl_did).instantiate_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none() - }) - .is_some(), + sym::to_os_string => method_parent_id.opt_impl_ty(cx).is_diag_item(cx, sym::OsStr), + sym::to_owned => method_parent_id.is_diag_item(cx, sym::ToOwned), + sym::to_path_buf => method_parent_id.opt_impl_ty(cx).is_diag_item(cx, sym::Path), + sym::to_string => method_parent_id.is_diag_item(cx, sym::ToString), + sym::to_vec => method_parent_id + .opt_impl_ty(cx) + .is_some_and(|ty| ty.instantiate_identity().is_slice()), _ => false, } } diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints/src/methods/inefficient_to_string.rs index 4ed7de81ea3d..7a49a5f31f03 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints/src/methods/inefficient_to_string.rs @@ -1,31 +1,26 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::peel_and_count_ty_refs; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; -use rustc_span::symbol::{Symbol, sym}; +use rustc_span::symbol::sym; use super::INEFFICIENT_TO_STRING; -/// Checks for the `INEFFICIENT_TO_STRING` lint -pub fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - method_name: Symbol, - receiver: &hir::Expr<'_>, - args: &[hir::Expr<'_>], -) { - if args.is_empty() - && method_name == sym::to_string - && let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, msrv: Msrv) { + if let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::to_string_method, to_string_meth_did) && let Some(args) = cx.typeck_results().node_args_opt(expr.hir_id) && let arg_ty = cx.typeck_results().expr_ty_adjusted(receiver) && let self_ty = args.type_at(0) - && let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty) + && let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty) && deref_count >= 1 + // Since Rust 1.82, the specialized `ToString` is properly called + && !msrv.meets(cx, msrvs::SPECIALIZED_TO_STRING_FOR_REFS) && specializes_tostring(cx, deref_self_ty) { span_lint_and_then( @@ -57,7 +52,7 @@ fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { return true; } - if is_type_lang_item(cx, ty, hir::LangItem::String) { + if ty.is_lang_item(cx, hir::LangItem::String) { return true; } diff --git a/clippy_lints/src/methods/inspect_for_each.rs b/clippy_lints/src/methods/inspect_for_each.rs index 3706f3b670ba..194ddf45e6f1 100644 --- a/clippy_lints/src/methods/inspect_for_each.rs +++ b/clippy_lints/src/methods/inspect_for_each.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; @@ -8,7 +8,7 @@ use super::INSPECT_FOR_EACH; /// lint use of `inspect().for_each()` for `Iterators` pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { - if is_trait_method(cx, expr, sym::Iterator) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; span_lint_and_help( diff --git a/clippy_lints/src/methods/into_iter_on_ref.rs b/clippy_lints/src/methods/into_iter_on_ref.rs index bedeb63367d0..c4b116af4871 100644 --- a/clippy_lints/src/methods/into_iter_on_ref.rs +++ b/clippy_lints/src/methods/into_iter_on_ref.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::has_iter_method; use rustc_errors::Applicability; use rustc_hir as hir; @@ -10,17 +10,10 @@ use rustc_span::symbol::{Symbol, sym}; use super::INTO_ITER_ON_REF; -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - method_span: Span, - method_name: Symbol, - receiver: &hir::Expr<'_>, -) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, receiver: &hir::Expr<'_>) { let self_ty = cx.typeck_results().expr_ty_adjusted(receiver); if let ty::Ref(..) = self_ty.kind() - && method_name == sym::into_iter - && is_trait_method(cx, expr, sym::IntoIterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::IntoIterator) && let Some((kind, method_name)) = ty_has_iter_method(cx, self_ty) { span_lint_and_sugg( diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints/src/methods/io_other_error.rs index 9276261606e1..b081e804859a 100644 --- a/clippy_lints/src/methods/io_other_error.rs +++ b/clippy_lints/src/methods/io_other_error.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{expr_or_init, is_path_diagnostic_item, sym}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::{expr_or_init, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; @@ -10,7 +11,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args && !expr.span.from_expansion() && !error_kind.span.from_expansion() && let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind - && is_path_diagnostic_item(cx, path, sym::io_error_new) + && path.ty_rel_def(cx).is_diag_item(cx, sym::io_error_new) && let ExprKind::Path(QPath::Resolved(_, init_path)) = &expr_or_init(cx, error_kind).kind && let [.., error_kind_ty, error_kind_variant] = init_path.segments && cx.tcx.is_diagnostic_item(sym::io_errorkind, error_kind_ty.res.def_id()) diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints/src/methods/ip_constant.rs index a2ac4e54334e..d5e4dac5e452 100644 --- a/clippy_lints/src/methods/ip_constant.rs +++ b/clippy_lints/src/methods/ip_constant.rs @@ -1,10 +1,10 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; +use rustc_data_structures::smallvec::SmallVec; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath, TyKind}; use rustc_lint::LateContext; use rustc_span::sym; -use smallvec::SmallVec; use super::IP_CONSTANT; @@ -17,10 +17,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args cx.tcx.get_diagnostic_name(func_def_id), Some(sym::Ipv4Addr | sym::Ipv6Addr) ) + && let ecx = ConstEvalCtxt::new(cx) + && let ctxt = expr.span.ctxt() && let Some(args) = args .iter() .map(|arg| { - if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ConstEvalCtxt::new(cx).eval(arg) { + if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ecx.eval_local(arg, ctxt) { u8::try_from(constant).ok() } else { None diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs index 9c32e9ac539d..2aeb6f42d05c 100644 --- a/clippy_lints/src/methods/is_digit_ascii_radix.rs +++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( return; } - if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix) { + if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix, expr.span.ctxt()) { 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"), diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index 545bef1a4c5b..add01b6a0837 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -1,7 +1,8 @@ use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::{is_assert_macro, root_macro_call}; -use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local}; +use clippy_utils::res::MaybeResPath; +use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context}; use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -45,7 +46,8 @@ fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool { /// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization /// value depends on a `#[cfg(…)]` directive. fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> { - while let Some(init) = path_to_local(expr) + while let Some(init) = expr + .res_local_id() .and_then(|id| find_binding_init(cx, id)) .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty()) .filter(|init| !is_under_cfg(cx, init.hir_id)) diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints/src/methods/iter_cloned_collect.rs index b4ab313fe98d..b1a0b658a84c 100644 --- a/clippy_lints/src/methods/iter_cloned_collect.rs +++ b/clippy_lints/src/methods/iter_cloned_collect.rs @@ -1,6 +1,7 @@ use crate::methods::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::{get_iterator_item_ty, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::get_iterator_item_ty; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -16,7 +17,7 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'_>, ) { let expr_ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, expr_ty, sym::Vec) + if expr_ty.is_diag_item(cx, sym::Vec) && let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv)) && let ty::Adt(_, args) = expr_ty.kind() && let Some(iter_item_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty(recv)) diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints/src/methods/iter_count.rs index 6b64cc8b50ae..ea2508cd7f38 100644 --- a/clippy_lints/src/methods/iter_count.rs +++ b/clippy_lints/src/methods/iter_count.rs @@ -1,7 +1,7 @@ use super::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; 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; @@ -13,21 +13,21 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E let ty = cx.typeck_results().expr_ty(recv); let caller_type = if derefs_to_slice(cx, recv, ty).is_some() { "slice" - } else if is_type_diagnostic_item(cx, ty, sym::Vec) { + } else if ty.is_diag_item(cx, sym::Vec) { "Vec" - } else if is_type_diagnostic_item(cx, ty, sym::VecDeque) { + } else if ty.is_diag_item(cx, sym::VecDeque) { "VecDeque" - } else if is_type_diagnostic_item(cx, ty, sym::HashSet) { + } else if ty.is_diag_item(cx, sym::HashSet) { "HashSet" - } else if is_type_diagnostic_item(cx, ty, sym::HashMap) { + } else if ty.is_diag_item(cx, sym::HashMap) { "HashMap" - } else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) { + } else if ty.is_diag_item(cx, sym::BTreeMap) { "BTreeMap" - } else if is_type_diagnostic_item(cx, ty, sym::BTreeSet) { + } else if ty.is_diag_item(cx, sym::BTreeSet) { "BTreeSet" - } else if is_type_diagnostic_item(cx, ty, sym::LinkedList) { + } else if ty.is_diag_item(cx, sym::LinkedList) { "LinkedList" - } else if is_type_diagnostic_item(cx, ty, sym::BinaryHeap) { + } else if ty.is_diag_item(cx, sym::BinaryHeap) { "BinaryHeap" } else { return; diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs index adeff375c8aa..8d95b70c6a4b 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints/src/methods/iter_filter.rs @@ -1,3 +1,4 @@ +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::get_iterator_item_ty; use hir::ExprKind; use rustc_lint::{LateContext, LintContext}; @@ -6,7 +7,7 @@ use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; -use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment, sym}; +use clippy_utils::{get_parent_expr, peel_blocks, span_contains_comment, sym}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::QPath; @@ -107,7 +108,7 @@ fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { if let Some(expr) = get_parent_expr(cx, expr) && let ExprKind::MethodCall(path, _, [_], _) = expr.kind && path.ident.name == sym::map - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return true; } @@ -141,7 +142,7 @@ fn expression_type( filter_arg: &hir::Expr<'_>, filter_span: Span, ) -> Option { - if !is_trait_method(cx, expr, sym::Iterator) + if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) || parent_is_map(cx, expr) || span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi())) { diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index cbb1b450e60f..2d6bc36dc535 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -1,8 +1,8 @@ use super::ITER_KV_MAP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{pat_is_wild, sym}; use rustc_hir::{Body, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; @@ -38,7 +38,7 @@ pub(super) fn check<'tcx>( _ => return, } && let ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs() - && (is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap)) + && (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap)) { let mut applicability = rustc_errors::Applicability::MachineApplicable; let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability); diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints/src/methods/iter_next_slice.rs index fd4650e1e45f..8c8c8c1dd855 100644 --- a/clippy_lints/src/methods/iter_next_slice.rs +++ b/clippy_lints/src/methods/iter_next_slice.rs @@ -1,7 +1,7 @@ use super::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{get_parent_expr, higher}; use rustc_ast::ast; use rustc_errors::Applicability; @@ -30,7 +30,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen, - }) = higher::Range::hir(index_expr) + span: _, + }) = higher::Range::hir(cx, index_expr) && let hir::ExprKind::Lit(start_lit) = &start_expr.kind && let ast::LitKind::Int(start_idx, _) = start_lit.node { @@ -75,6 +76,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal } fn is_vec_or_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool { - is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) + cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) || matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) } diff --git a/clippy_lints/src/methods/iter_nth.rs b/clippy_lints/src/methods/iter_nth.rs index 1fdbd81bf240..6d1ee32e5026 100644 --- a/clippy_lints/src/methods/iter_nth.rs +++ b/clippy_lints/src/methods/iter_nth.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::sym; -use clippy_utils::ty::get_type_diagnostic_name; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -16,7 +16,7 @@ pub(super) fn check<'tcx>( iter_span: Span, nth_span: Span, ) -> bool { - let caller_type = match get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(iter_recv).peel_refs()) { + let caller_type = match cx.typeck_results().expr_ty(iter_recv).peel_refs().opt_diag_name(cx) { Some(sym::Vec) => "`Vec`", Some(sym::VecDeque) => "`VecDeque`", _ if cx.typeck_results().expr_ty_adjusted(iter_recv).peel_refs().is_slice() => "slice", diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints/src/methods/iter_nth_zero.rs index 4bdf589f4876..e0107b75e414 100644 --- a/clippy_lints/src/methods/iter_nth_zero.rs +++ b/clippy_lints/src/methods/iter_nth_zero.rs @@ -1,7 +1,8 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_lang_item_or_ctor; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_lang_item_or_ctor, is_trait_method}; use hir::{LangItem, OwnerNode}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -13,8 +14,8 @@ use super::ITER_NTH_ZERO; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { if let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) && let def_id = item.owner_id.to_def_id() - && is_trait_method(cx, expr, sym::Iterator) - && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) && !is_lang_item_or_ctor(cx, def_id, LangItem::IteratorNext) { let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 83e565562af0..cdef98be14af 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -3,10 +3,9 @@ use std::iter::once; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig}; -use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core, sym}; +use clippy_utils::{as_some_expr, get_expr_use_or_unification_node, is_none_expr, std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -67,8 +66,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), - ExprKind::Path(ref p) if is_res_lang_ctor(cx, cx.qpath_res(p, recv.hir_id), OptionNone) => None, - ExprKind::Call(f, [arg]) if is_res_lang_ctor(cx, path_res(cx, f), OptionSome) => Some(arg), + _ if is_none_expr(cx, recv) => None, + _ if let Some(arg) = as_some_expr(cx, recv) => Some(arg), _ => return, }; let iter_type = match method_name { diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index fa8f9d640ee6..8cff3bd815a1 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::expr_or_init; use clippy_utils::higher::VecArgs; -use clippy_utils::{expr_or_init, is_trait_method}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; @@ -58,7 +59,7 @@ fn check<'tcx>( message: &'static str, note: &'static str, ) { - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let Some(len) = get_iterator_length(cx, recv) && let Some(skipped) = expr_as_u128(cx, arg) && skipped > len diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index f851ebe91f37..d43dc23a86b2 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -10,8 +10,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty::{self, BorrowKind}; use rustc_span::{Symbol, sym}; -use super::ITER_OVEREAGER_CLONED; -use crate::redundant_clone::REDUNDANT_CLONE; +use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED}; #[derive(Clone, Copy)] pub(super) enum Op<'a> { @@ -96,7 +95,7 @@ pub(super) fn check<'tcx>( } let (lint, msg, trailing_clone) = match op { - Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_ITER_CLONED, "unneeded cloning of iterator items", ""), Op::LaterCloned | Op::FixClosure(_, _) => ( ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", diff --git a/clippy_lints/src/methods/iter_skip_next.rs b/clippy_lints/src/methods/iter_skip_next.rs index fedb7c22eded..661188c90ed6 100644 --- a/clippy_lints/src/methods/iter_skip_next.rs +++ b/clippy_lints/src/methods/iter_skip_next.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::snippet; -use clippy_utils::{is_trait_method, path_to_local}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::{BindingMode, Node, PatKind}; @@ -11,20 +11,20 @@ use super::ITER_SKIP_NEXT; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { // lint if caller of skip is an Iterator - if is_trait_method(cx, expr, sym::Iterator) { - let mut application = Applicability::MachineApplicable; + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { + let mut applicability = Applicability::MachineApplicable; span_lint_and_then( cx, ITER_SKIP_NEXT, expr.span.trim_start(recv.span).unwrap(), "called `skip(..).next()` on an iterator", |diag| { - if let Some(id) = path_to_local(recv) + if let Some(id) = recv.res_local_id() && let Node::Pat(pat) = cx.tcx.hir_node(id) && let PatKind::Binding(ann, _, _, _) = pat.kind && ann != BindingMode::MUT { - application = Applicability::Unspecified; + applicability = Applicability::Unspecified; diag.span_help( pat.span, format!("for this change `{}` has to be mutable", snippet(cx, pat.span, "..")), @@ -35,7 +35,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr expr.span.trim_start(recv.span).unwrap(), "use `nth` instead", format!(".nth({})", snippet(cx, arg.span, "..")), - application, + applicability, ); }, ); diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints/src/methods/iter_skip_zero.rs index 39e440e784f6..cae31e7c9606 100644 --- a/clippy_lints/src/methods/iter_skip_zero.rs +++ b/clippy_lints/src/methods/iter_skip_zero.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_from_proc_macro, is_trait_method}; +use clippy_utils::is_from_proc_macro; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -10,14 +11,16 @@ use super::ITER_SKIP_ZERO; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { if !expr.span.from_expansion() - && is_trait_method(cx, expr, sym::Iterator) - && let Some(arg) = ConstEvalCtxt::new(cx).eval(arg_expr).and_then(|constant| { - if let Constant::Int(arg) = constant { - Some(arg) - } else { - None - } - }) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let Some(arg) = ConstEvalCtxt::new(cx) + .eval_local(arg_expr, expr.span.ctxt()) + .and_then(|constant| { + if let Constant::Int(arg) = constant { + Some(arg) + } else { + None + } + }) && arg == 0 && !is_from_proc_macro(cx, expr) { diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints/src/methods/iterator_step_by_zero.rs index 90d5d9df55ee..11dde2429adf 100644 --- a/clippy_lints/src/methods/iterator_step_by_zero.rs +++ b/clippy_lints/src/methods/iterator_step_by_zero.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -8,7 +8,7 @@ use rustc_span::sym; use super::ITERATOR_STEP_BY_ZERO; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg) { span_lint( diff --git a/clippy_lints/src/methods/join_absolute_paths.rs b/clippy_lints/src/methods/join_absolute_paths.rs index 2ad070793cbb..e84b7452c758 100644 --- a/clippy_lints/src/methods/join_absolute_paths.rs +++ b/clippy_lints/src/methods/join_absolute_paths.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::expr_or_init; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -13,7 +13,7 @@ use super::JOIN_ABSOLUTE_PATHS; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if (is_type_diagnostic_item(cx, ty, sym::Path) || is_type_diagnostic_item(cx, ty, sym::PathBuf)) + if (ty.is_diag_item(cx, sym::Path) || ty.is_diag_item(cx, sym::PathBuf)) && let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind && let LitKind::Str(symbol, _) = spanned.node && let sym_str = symbol.as_str() diff --git a/clippy_lints/src/methods/lib.rs b/clippy_lints/src/methods/lib.rs new file mode 100644 index 000000000000..84038283bcf8 --- /dev/null +++ b/clippy_lints/src/methods/lib.rs @@ -0,0 +1,70 @@ +use clippy_utils::sym; +use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_hir::Mutability; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum SelfKind { + Value, + Ref, + RefMut, + No, // When we want the first argument type to be different than `Self` +} + +impl SelfKind { + pub(super) fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if ty == parent_ty { + true + } else if let Some(boxed_ty) = ty.boxed_ty() { + boxed_ty == parent_ty + } else if let ty::Adt(adt_def, args) = ty.kind() + && matches!(cx.tcx.get_diagnostic_name(adt_def.did()), Some(sym::Rc | sym::Arc)) + { + args.types().next() == Some(parent_ty) + } else { + false + } + } + + fn matches_ref<'a>(cx: &LateContext<'a>, mutability: Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if let ty::Ref(_, t, m) = *ty.kind() { + return m == mutability && t == parent_ty; + } + + let trait_sym = match mutability { + Mutability::Not => sym::AsRef, + Mutability::Mut => sym::AsMut, + }; + + let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { + return false; + }; + implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) + } + + fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + !matches_value(cx, parent_ty, ty) + && !matches_ref(cx, Mutability::Not, parent_ty, ty) + && !matches_ref(cx, Mutability::Mut, parent_ty, ty) + } + + match self { + Self::Value => matches_value(cx, parent_ty, ty), + Self::Ref => matches_ref(cx, Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), + Self::RefMut => matches_ref(cx, Mutability::Mut, parent_ty, ty), + Self::No => matches_none(cx, parent_ty, ty), + } + } + + #[must_use] + pub(super) fn description(self) -> &'static str { + match self { + Self::Value => "`self` by value", + Self::Ref => "`self` by reference", + Self::RefMut => "`self` by mutable reference", + Self::No => "no `self`", + } + } +} diff --git a/clippy_lints/src/methods/lines_filter_map_ok.rs b/clippy_lints/src/methods/lines_filter_map_ok.rs new file mode 100644 index 000000000000..baf5b6f93f63 --- /dev/null +++ b/clippy_lints/src/methods/lines_filter_map_ok.rs @@ -0,0 +1,85 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::LINES_FILTER_MAP_OK; + +pub(super) fn check_flatten(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, msrv: Msrv) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx + .typeck_results() + .expr_ty_adjusted(recv) + .is_diag_item(cx, sym::IoLines) + && msrv.meets(cx, msrvs::MAP_WHILE) + { + emit(cx, recv, "flatten", call_span); + } +} + +pub(super) fn check_filter_or_flat_map( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + method_name: &'static str, + method_arg: &Expr<'_>, + call_span: Span, + msrv: Msrv, +) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx + .typeck_results() + .expr_ty_adjusted(recv) + .is_diag_item(cx, sym::IoLines) + && match method_arg.kind { + // Detect `Result::ok` + ExprKind::Path(ref qpath) => cx + .qpath_res(qpath, method_arg.hir_id) + .is_diag_item(cx, sym::result_ok_method), + // Detect `|x| x.ok()` + ExprKind::Closure(&Closure { body, .. }) => { + if let Body { + params: [param], value, .. + } = cx.tcx.hir_body(body) + && let ExprKind::MethodCall(method, receiver, [], _) = value.kind + { + method.ident.name == sym::ok + && receiver.res_local_id() == Some(param.pat.hir_id) + && cx.ty_based_def(*value).is_diag_item(cx, sym::result_ok_method) + } else { + false + } + }, + _ => false, + } + && msrv.meets(cx, msrvs::MAP_WHILE) + { + emit(cx, recv, method_name, call_span); + } +} + +fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, method_name: &'static str, call_span: Span) { + span_lint_and_then( + cx, + LINES_FILTER_MAP_OK, + call_span, + format!("`{method_name}()` will run forever if the iterator repeatedly produces an `Err`"), + |diag| { + diag.span_note( + recv.span, + "this expression returning a `std::io::Lines` may produce \ + an infinite number of `Err` in case of a read error", + ); + diag.span_suggestion( + call_span, + "replace with", + "map_while(Result::ok)", + Applicability::MaybeIncorrect, + ); + }, + ); +} diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs index bc96815944d5..1a5b180b0c86 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{IntoSpan, SpanRangeExt}; use clippy_utils::ty::get_field_by_name; use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures}; -use clippy_utils::{ExprUseNode, expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, sym}; +use clippy_utils::{ExprUseNode, expr_use_ctxt, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; @@ -18,9 +19,10 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: if let ExprKind::Closure(c) = arg.kind && matches!(c.kind, ClosureKind::Closure) && let typeck = cx.typeck_results() - && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id) - && (is_diag_trait_item(cx, fn_id, sym::Iterator) - || ((is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result)) + && let Some(fn_def) = typeck.type_dependent_def(expr.hir_id) + && (fn_def.opt_parent(cx).is_diag_item(cx, sym::Iterator) + || ((fn_def.opt_parent(cx).opt_impl_ty(cx).is_diag_item(cx, sym::Option) + || fn_def.opt_parent(cx).opt_impl_ty(cx).is_diag_item(cx, sym::Result)) && msrv.meets(cx, msrvs::OPTION_RESULT_INSPECT))) && let body = cx.tcx.hir_body(c.body) && let [param] = body.params @@ -29,7 +31,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: && let ExprKind::Block(block, _) = body.value.kind && let Some(final_expr) = block.expr && !block.stmts.is_empty() - && path_to_local_id(final_expr, arg_id) + && final_expr.res_local_id() == Some(arg_id) && typeck.expr_adjustments(final_expr).is_empty() { let mut requires_copy = false; @@ -46,7 +48,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: if let ExprKind::Closure(c) = e.kind { // Nested closures don't need to treat returns specially. let _: Option = for_each_expr(cx, cx.tcx.hir_body(c.body).value, |e| { - if path_to_local_id(e, arg_id) { + if e.res_local_id() == Some(arg_id) { let (kind, same_ctxt) = check_use(cx, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { @@ -64,7 +66,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: }); } else if matches!(e.kind, ExprKind::Ret(_)) { ret_count += 1; - } else if path_to_local_id(e, arg_id) { + } else if e.res_local_id() == Some(arg_id) { let (kind, same_ctxt) = check_use(cx, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (UseKind::Return(..), false) => { diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs index 93325ca488e4..8f65858987b9 100644 --- a/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/clippy_lints/src/methods/manual_is_variant_and.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{get_parent_expr, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -30,8 +30,8 @@ pub(super) fn check( } // 2. the caller of `map()` is neither `Option` nor `Result` - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result); + let is_option = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option); + let is_result = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Result); if !is_option && !is_result { return; } @@ -208,7 +208,7 @@ pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) { && cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did()) && args.type_at(0).is_bool() && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), flavor.symbol()) + && cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.symbol()) && let Ok(map_func) = MapFunc::try_from(map_expr) { return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); diff --git a/clippy_lints/src/methods/manual_next_back.rs b/clippy_lints/src/methods/manual_next_back.rs index 9a03559b2237..b064f978588c 100644 --- a/clippy_lints/src/methods/manual_next_back.rs +++ b/clippy_lints/src/methods/manual_next_back.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; use rustc_hir::Expr; @@ -20,8 +20,8 @@ pub(super) fn check<'tcx>( .tcx .get_diagnostic_item(sym::DoubleEndedIterator) .is_some_and(|double_ended_iterator| implements_trait(cx, rev_recv_ty, double_ended_iterator, &[])) - && is_trait_method(cx, rev_call, sym::Iterator) - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(rev_call).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs index 077957fa44dc..a8e30e44488c 100644 --- a/clippy_lints/src/methods/manual_ok_or.rs +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id}; use rustc_errors::Applicability; use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::{Expr, ExprKind, PatKind}; @@ -19,9 +18,13 @@ pub(super) fn check<'tcx>( ) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Option) + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .is_diag_item(cx, sym::Option) && let ExprKind::Call(err_path, [err_arg]) = or_expr.kind - && is_res_lang_ctor(cx, path_res(cx, err_path), ResultErr) + && err_path.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) && is_ok_wrapping(cx, map_expr) && let Some(recv_snippet) = recv.span.get_source_text(cx) && let Some(err_arg_snippet) = err_arg.span.get_source_text(cx) @@ -42,14 +45,21 @@ pub(super) fn check<'tcx>( fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { match map_expr.kind { - ExprKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, map_expr.hir_id), ResultOk) => true, + ExprKind::Path(ref qpath) + if cx + .qpath_res(qpath, map_expr.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, ResultOk) => + { + true + }, ExprKind::Closure(closure) => { let body = cx.tcx.hir_body(closure.body); if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind && let ExprKind::Call(callee, [ok_arg]) = body.value.kind - && is_res_lang_ctor(cx, path_res(cx, callee), ResultOk) + && callee.res(cx).ctor_parent(cx).is_lang_item(cx, ResultOk) { - path_to_local_id(ok_arg, param_id) + ok_arg.res_local_id() == Some(param_id) } else { false } diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints/src/methods/manual_repeat_n.rs index 83b57cca17bf..1a7628ed43a4 100644 --- a/clippy_lints/src/methods/manual_repeat_n.rs +++ b/clippy_lints/src/methods/manual_repeat_n.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_context}; -use clippy_utils::{expr_use_ctxt, fn_def_id, is_trait_method, std_or_core}; +use clippy_utils::{expr_use_ctxt, fn_def_id, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; @@ -17,7 +18,7 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) { if !expr.span.from_expansion() - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind && let Some(def_id) = fn_def_id(cx, repeat_expr) && cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id) diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index c785b23bc9c4..2196ce92b0ab 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{path_res, sym}; +use clippy_utils::sym; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; @@ -83,7 +84,7 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { // `T::MAX` and `T::MIN` constants if let hir::ExprKind::Path(hir::QPath::TypeRelative(base, seg)) = expr.kind - && let Res::PrimTy(_) = path_res(cx, base) + && matches!(base.basic_res(), Res::PrimTy(_)) { match seg.ident.name { sym::MAX => return Some(MinMax::Max), diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index a811dd1cee18..4fe14f8053c9 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints/src/methods/manual_str_repeat.rs @@ -1,8 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_path_diagnostic_item; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; @@ -35,14 +34,14 @@ fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option { } } else { let ty = cx.typeck_results().expr_ty(e); - if is_type_lang_item(cx, ty, LangItem::String) - || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).is_some_and(Ty::is_str)) - || (is_type_diagnostic_item(cx, ty, sym::Cow) && get_ty_param(ty).is_some_and(Ty::is_str)) + if ty.is_lang_item(cx, LangItem::String) + || (ty.is_lang_item(cx, LangItem::OwnedBox) && get_ty_param(ty).is_some_and(Ty::is_str)) + || (ty.is_diag_item(cx, sym::Cow) && get_ty_param(ty).is_some_and(Ty::is_str)) { Some(RepeatKind::String) } else { let ty = ty.peel_refs(); - (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String)).then_some(RepeatKind::String) + (ty.is_str() || ty.is_lang_item(cx, LangItem::String)).then_some(RepeatKind::String) } } } @@ -55,8 +54,11 @@ pub(super) fn check( take_arg: &Expr<'_>, ) { if let ExprKind::Call(repeat_fn, [repeat_arg]) = take_self_arg.kind - && is_path_diagnostic_item(cx, repeat_fn, sym::iter_repeat) - && is_type_lang_item(cx, cx.typeck_results().expr_ty(collect_expr), LangItem::String) + && repeat_fn.basic_res().is_diag_item(cx, sym::iter_repeat) + && cx + .typeck_results() + .expr_ty(collect_expr) + .is_lang_item(cx, LangItem::String) && let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id) && let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) && cx.tcx.trait_of_assoc(take_id) == Some(iter_trait_id) diff --git a/clippy_lints/src/methods/manual_try_fold.rs b/clippy_lints/src/methods/manual_try_fold.rs index 23dba47f60f4..f2e127bedde5 100644 --- a/clippy_lints/src/methods/manual_try_fold.rs +++ b/clippy_lints/src/methods/manual_try_fold.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_from_proc_macro, is_trait_method}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; @@ -20,7 +21,7 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) { if !fold_span.in_external_macro(cx.sess().source_map()) - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let init_ty = cx.typeck_results().expr_ty(init) && let Some(try_trait) = cx.tcx.lang_items().try_trait() && implements_trait(cx, init_ty, try_trait, &[]) diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints/src/methods/map_all_any_identity.rs index ac11baa2d54c..ad950f75f813 100644 --- a/clippy_lints/src/methods/map_all_any_identity.rs +++ b/clippy_lints/src/methods/map_all_any_identity.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_expr_identity_function; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_expr_identity_function, is_trait_method}; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -8,7 +9,7 @@ use rustc_span::{Span, sym}; use super::MAP_ALL_ANY_IDENTITY; -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] pub(super) fn check( cx: &LateContext<'_>, expr: &Expr<'_>, @@ -19,8 +20,8 @@ pub(super) fn check( any_arg: &Expr<'_>, method: &str, ) { - if is_trait_method(cx, expr, sym::Iterator) - && is_trait_method(cx, recv, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx.ty_based_def(recv).opt_parent(cx).is_diag_item(cx, sym::Iterator) && is_expr_identity_function(cx, any_arg) && let map_any_call_span = map_call_span.with_hi(any_call_span.hi()) && let Some(map_arg) = map_arg.span.get_source_text(cx) diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index 748be9bfcc62..1bc29c9c1dd1 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::peel_blocks; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item, should_call_clone_as_function}; -use clippy_utils::{is_diag_trait_item, peel_blocks}; +use clippy_utils::ty::{is_copy, should_call_clone_as_function}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem}; @@ -18,14 +19,13 @@ use super::MAP_CLONE; // If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't // run this lint because it would overlap with `useless_asref` which provides a better suggestion // in this case. -fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> bool { - if is_diag_trait_item(cx, method_id, sym::Iterator) { +fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_parent_id: DefId) -> bool { + if method_parent_id.is_diag_item(cx, sym::Iterator) { return true; } // We check if it's an `Option` or a `Result`. - if let Some(id) = cx.tcx.impl_of_assoc(method_id) { - let identity = cx.tcx.type_of(id).instantiate_identity(); - if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) { + if let Some(ty) = method_parent_id.opt_impl_ty(cx) { + if !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result) { return false; } } else { @@ -42,8 +42,8 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> } pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: Msrv) { - if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && should_run_lint(cx, e, method_id) + if let Some(parent_id) = cx.typeck_results().type_dependent_def_id(e.hir_id).opt_parent(cx) + && should_run_lint(cx, e, parent_id) { match arg.kind { hir::ExprKind::Closure(&hir::Closure { body, .. }) => { diff --git a/clippy_lints/src/methods/map_collect_result_unit.rs b/clippy_lints/src/methods/map_collect_result_unit.rs index e944eac91e7a..1112fbc2a1c7 100644 --- a/clippy_lints/src/methods/map_collect_result_unit.rs +++ b/clippy_lints/src/methods/map_collect_result_unit.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -12,7 +12,7 @@ use super::MAP_COLLECT_RESULT_UNIT; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, iter: &hir::Expr<'_>, map_fn: &hir::Expr<'_>) { // return of collect `Result<(),_>` let collect_ret_ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, collect_ret_ty, sym::Result) + if collect_ret_ty.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = collect_ret_ty.kind() && let Some(result_t) = args.types().next() && result_t.is_unit() diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs index 41beda9c5cb4..f7da24bed2b8 100644 --- a/clippy_lints/src/methods/map_err_ignore.rs +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; @@ -9,7 +9,11 @@ use super::MAP_ERR_IGNORE; pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Result) + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .is_diag_item(cx, sym::Result) && let ExprKind::Closure(&Closure { capture_clause: CaptureBy::Ref, body, diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index 750f933330a2..e4ae14b6cf59 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_trait_method, span_contains_comment}; +use clippy_utils::span_contains_comment; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -40,7 +40,7 @@ fn try_get_caller_ty_name_and_method_name( caller_expr: &Expr<'_>, map_arg: &Expr<'_>, ) -> Option<(&'static str, &'static str)> { - if is_trait_method(cx, expr, sym::Iterator) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { if is_map_to_option(cx, map_arg) { // `(...).map(...)` has type `impl Iterator> Some(("Iterator", "filter_map")) @@ -69,7 +69,7 @@ fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool { _ => map_closure_ty.fn_sig(cx.tcx), }; let map_closure_return_ty = cx.tcx.instantiate_bound_regions_with_erased(map_closure_sig.output()); - is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) + map_closure_return_ty.is_diag_item(cx, sym::Option) }, _ => false, } diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints/src/methods/map_identity.rs index a98cfff8bfbd..fa394526bdf1 100644 --- a/clippy_lints/src/methods/map_identity.rs +++ b/clippy_lints/src/methods/map_identity.rs @@ -1,7 +1,8 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; -use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections}; +use clippy_utils::ty::is_copy; +use clippy_utils::{is_expr_untyped_identity_function, is_mutable, path_to_local_with_projections}; use rustc_errors::Applicability; use rustc_hir::{self as hir, ExprKind, Node, PatKind}; use rustc_lint::{LateContext, LintContext}; @@ -21,54 +22,52 @@ pub(super) fn check( ) { let caller_ty = cx.typeck_results().expr_ty(caller); - if (is_trait_method(cx, expr, sym::Iterator) - || is_type_diagnostic_item(cx, caller_ty, sym::Result) - || is_type_diagnostic_item(cx, caller_ty, sym::Option)) + if (cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + || caller_ty.is_diag_item(cx, sym::Result) + || caller_ty.is_diag_item(cx, sym::Option)) && is_expr_untyped_identity_function(cx, map_arg) && let Some(call_span) = expr.span.trim_start(caller.span) { - let main_sugg = (call_span, String::new()); - let mut app = if is_copy(cx, caller_ty) { - // there is technically a behavioral change here for `Copy` iterators, where - // `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and - // changing it to `iter.next()` mutates iter directly - Applicability::Unspecified - } else { - Applicability::MachineApplicable - }; + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + let main_sugg = (call_span, String::new()); + let mut app = if is_copy(cx, caller_ty) { + // there is technically a behavioral change here for `Copy` iterators, where + // `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and + // changing it to `iter.next()` mutates iter directly + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; - let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr(); - if needs_to_be_mutable && !is_mutable(cx, caller) { - if let Some(hir_id) = path_to_local_with_projections(caller) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - && let PatKind::Binding(_, _, ident, _) = pat.kind - { - // We can reach the binding -- suggest making it mutable - let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))]; + let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr(); + if needs_to_be_mutable && !is_mutable(cx, caller) { + if let Some(hir_id) = path_to_local_with_projections(caller) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + && let PatKind::Binding(_, _, ident, _) = pat.kind + { + // We can reach the binding -- suggest making it mutable + let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))]; - let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app); + let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app); - span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { diag.multipart_suggestion( format!("remove the call to `{name}`, and make `{ident}` mutable"), suggs, app, ); - }); - } else { - // If we can't make the binding mutable, prevent the suggestion from being automatically applied, - // and add a complementary help message. - app = Applicability::Unspecified; - - let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) - && let ExprKind::MethodCall(method, ..) = expr.kind - { - Some(method.ident) } else { - None - }; + // If we can't make the binding mutable, prevent the suggestion from being automatically applied, + // and add a complementary help message. + app = Applicability::Unspecified; + + let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) + && let ExprKind::MethodCall(method, ..) = expr.kind + { + Some(method.ident) + } else { + None + }; - span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); let note = if let Some(method_requiring_mut) = method_requiring_mut { @@ -77,18 +76,10 @@ pub(super) fn check( "this must be made mutable".to_string() }; diag.span_note(caller.span, note); - }); + } + } else { + diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); } - } else { - span_lint_and_sugg( - cx, - MAP_IDENTITY, - main_sugg.0, - MSG, - format!("remove the call to `{name}`"), - main_sugg.1, - app, - ); - } + }); } } diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index df5a0de3392b..62bdc4a3e411 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::usage::mutated_variables; use rustc_errors::Applicability; use rustc_hir as hir; @@ -22,8 +22,8 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) -> bool { // lint if the caller of `map()` is an `Option` or a `Result`. - 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); + let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); + let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) { return false; diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs index a2a522a60687..f60387fe86f7 100644 --- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -63,10 +63,10 @@ pub(super) fn check( receiver: &Expr<'_>, arg: &Expr<'_>, msrv: Msrv, - method_call_span: Span, + method_name_span: Span, ) { let mut applicability = Applicability::MaybeIncorrect; - if let Some(range) = higher::Range::hir(receiver) + if let Some(range) = higher::Range::hir(cx, receiver) && let ExprKind::Closure(Closure { body, .. }) = arg.kind && let body_hir = cx.tcx.hir_body(*body) && let Body { @@ -105,7 +105,7 @@ pub(super) fn check( // collate all our parts here and then remove those that are empty. let mut parts = vec![ ( - receiver.span.to(method_call_span), + ex.span.with_hi(method_name_span.hi()), format!("{exec_context}::iter::{method_to_use_name}"), ), new_span, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 49ca81dafc28..5b917e2bfefa 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -55,6 +55,8 @@ mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; +mod lib; +mod lines_filter_map_ok; mod manual_c_str_literals; mod manual_contains; mod manual_inspect; @@ -79,6 +81,7 @@ mod needless_character_iteration; mod needless_collect; mod needless_option_as_deref; mod needless_option_take; +mod new_ret_no_self; mod no_effect_replace; mod obfuscated_if_else; mod ok_expect; @@ -91,6 +94,7 @@ mod or_fun_call; mod or_then_unwrap; mod path_buf_push_overwrite; mod path_ends_with_ext; +mod ptr_offset_with_cast; mod range_zip_with_len; mod read_line_without_trim; mod readonly_write_lock; @@ -101,9 +105,8 @@ mod return_and_then; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; +mod should_implement_trait; mod single_char_add_str; -mod single_char_insert_string; -mod single_char_push_string; mod skip_while_next; mod sliced_string_as_bytes; mod stable_sort_primitive; @@ -131,10 +134,10 @@ mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; mod unnecessary_map_or; mod unnecessary_min_or_max; +mod unnecessary_option_map_or_else; mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; @@ -147,20 +150,17 @@ mod zst_offset; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::macros::FormatArgsStorage; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::{contains_return, iter_input_pats, peel_blocks, sym}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; -use rustc_abi::ExternAbi; use rustc_data_structures::fx::FxHashSet; -use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; +use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, TraitRef, Ty}; +use rustc_middle::ty::TraitRef; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol, kw}; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -476,6 +476,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `ok().expect(..)`. /// + /// Note: This lint only triggers for code marked compatible + /// with versions of the compiler older than Rust 1.82.0. + /// /// ### Why is this bad? /// Because you usually call `expect()` on the `Result` /// directly to get a better error message. @@ -1079,9 +1082,9 @@ declare_clippy_lint! { /// `T` implements `ToString` directly (like `&&str` or `&&String`). /// /// ### Why is this bad? - /// This bypasses the specialized implementation of - /// `ToString` and instead goes through the more expensive string formatting - /// facilities. + /// In versions of the compiler before Rust 1.82.0, this bypasses the specialized + /// implementation of `ToString` and instead goes through the more expensive string + /// formatting facilities. /// /// ### Example /// ```no_run @@ -1725,6 +1728,43 @@ declare_clippy_lint! { "Check for offset calculations on raw pointers to zero-sized types" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of the `offset` pointer method with a `usize` casted to an + /// `isize`. + /// + /// ### Why is this bad? + /// If we’re always increasing the pointer address, we can avoid the numeric + /// cast by using the `add` method instead. + /// + /// ### Example + /// ```no_run + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.offset(offset as isize); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```no_run + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.add(offset); + /// } + /// ``` + #[clippy::version = "1.30.0"] + pub PTR_OFFSET_WITH_CAST, + complexity, + "unneeded pointer offset cast" +} + declare_clippy_lint! { /// ### What it does /// Checks for `FileType::is_file()`. @@ -4424,7 +4464,7 @@ declare_clippy_lint! { /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. /// /// ### Why is this bad? - /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. + /// The default implementation calls `read` for each byte, which can be very inefficient for data that's not in memory, such as `File`. /// /// ### Example /// ```no_run @@ -4576,6 +4616,104 @@ declare_clippy_lint! { "hardcoded localhost IP address" } +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Iterator::cloned` where the original value could be used + /// instead. + /// + /// ### Why is this bad? + /// It is not always possible for the compiler to eliminate useless allocations and + /// deallocations generated by redundant `clone()`s. + /// + /// ### Example + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().cloned().map(|x| x.len()); + /// ``` + /// Use instead: + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().map(|x| x.len()); + /// ``` + #[clippy::version = "1.90.0"] + pub REDUNDANT_ITER_CLONED, + perf, + "detects redundant calls to `Iterator::cloned`" +} + +declare_clippy_lint! { + /// Checks for usage of `.map_or_else()` "map closure" for `Option` type. + /// + /// ### Why is this bad? + /// This can be written more concisely by using `unwrap_or_else()`. + /// + /// ### Example + /// ```no_run + /// let k = 10; + /// let x: Option = Some(4); + /// let y = x.map_or_else(|| 2 * k, |n| n); + /// ``` + /// Use instead: + /// ```no_run + /// let k = 10; + /// let x: Option = Some(4); + /// let y = x.unwrap_or_else(|| 2 * k); + /// ``` + #[clippy::version = "1.88.0"] + pub UNNECESSARY_OPTION_MAP_OR_ELSE, + suspicious, + "making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` + /// when `lines` has type `std::io::Lines`. + /// + /// ### Why is this bad? + /// `Lines` instances might produce a never-ending stream of `Err`, in which case + /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an + /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, + /// even in the absence of explicit loops in the user code. + /// + /// This situation can arise when working with user-provided paths. On some platforms, + /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, + /// but any later attempt to read from `fs` will return an error. + /// + /// ### Known problems + /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` + /// instance in all cases. There are two cases where the suggestion might not be + /// appropriate or necessary: + /// + /// - If the `Lines` instance can never produce any error, or if an error is produced + /// only once just before terminating the iterator, using `map_while()` is not + /// necessary but will not do any harm. + /// - If the `Lines` instance can produce intermittent errors then recover and produce + /// successful results, using `map_while()` would stop at the first error. + /// + /// ### Example + /// ```no_run + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); + /// // If "some-path" points to a directory, the next statement never terminates: + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + /// Use instead: + /// ```no_run + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + #[clippy::version = "1.70.0"] + pub LINES_FILTER_MAP_OK, + suspicious, + "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4665,6 +4803,7 @@ impl_lint_pass!(Methods => [ UNINIT_ASSUMED_INIT, MANUAL_SATURATING_ARITHMETIC, ZST_OFFSET, + PTR_OFFSET_WITH_CAST, FILETYPE_IS_FILE, OPTION_AS_REF_DEREF, UNNECESSARY_LAZY_EVALUATIONS, @@ -4755,6 +4894,9 @@ impl_lint_pass!(Methods => [ IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, IP_CONSTANT, + REDUNDANT_ITER_CLONED, + UNNECESSARY_OPTION_MAP_OR_ELSE, + LINES_FILTER_MAP_OK, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4762,8 +4904,8 @@ impl_lint_pass!(Methods => [ /// come from expansion. pub fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind - && !args.iter().any(|e| e.span.from_expansion()) - && !receiver.span.from_expansion() + && !args.iter().any(|e| e.range_span().unwrap_or(e.span).from_expansion()) + && !receiver.range_span().unwrap_or(receiver.span).from_expansion() { Some((path.ident.name, receiver, args, path.ident.span, call_span)) } else { @@ -4777,8 +4919,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods { return; } - self.check_methods(cx, expr); - match expr.kind { ExprKind::Call(func, args) => { from_iter_instead_of_collect::check(cx, expr, args, func); @@ -4789,24 +4929,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods { swap_with_temporary::check(cx, expr, func, args); ip_constant::check(cx, expr, func, args); }, - ExprKind::MethodCall(method_call, receiver, args, _) => { - let method_span = method_call.ident.span; - or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args); - expect_fun_call::check( - cx, - &self.format_args, - expr, - method_span, - method_call.ident.name, - receiver, - args, - ); - clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); - clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); - inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); - single_char_add_str::check(cx, expr, receiver, args); - into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver); - unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv); + ExprKind::MethodCall(..) => { + self.check_methods(cx, expr); }, ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { @@ -4821,53 +4945,21 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } } - #[allow(clippy::too_many_lines)] fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { if impl_item.span.in_external_macro(cx.sess().source_map()) { return; } - let name = impl_item.ident.name; - let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; - let item = cx.tcx.hir_expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { + let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; + let item = cx.tcx.hir_expect_item(parent); + let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); + let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); - // if this impl block implements a trait, lint in trait definition instead - if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { - // check missing trait implementations - for method_config in &TRAIT_METHODS { - if name == method_config.method_name - && sig.decl.inputs.len() == method_config.param_count - && method_config.output_type.matches(&sig.decl.output) - // in case there is no first arg, since we already have checked the number of arguments - // it's should be always true - && first_arg_ty_opt.is_none_or(|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) - { - span_lint_and_help( - cx, - SHOULD_IMPLEMENT_TRAIT, - impl_item.span, - format!( - "method `{}` can be confused for the standard trait method `{}::{}`", - method_config.method_name, method_config.trait_name, method_config.method_name - ), - None, - format!( - "consider implementing the trait `{}` or choosing a less ambiguous method name", - method_config.trait_name - ), - ); - } - } - } + should_implement_trait::check_impl_item(cx, impl_item, self_ty, implements_trait, first_arg_ty_opt, sig); if sig.decl.implicit_self.has_implicit_self() && !(self.avoid_breaking_exported_api @@ -4877,7 +4969,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { { wrong_self_convention::check( cx, - name, + impl_item.ident.name, self_ty, first_arg_ty, first_arg.pat.span, @@ -4885,28 +4977,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods { false, ); } - } - // if this impl block implements a trait, lint in trait definition instead - if implements_trait { - return; - } - - if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { - let ret_ty = return_ty(cx, impl_item.owner_id); - - if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) { - return; - } - - if name == sym::new && ret_ty != self_ty { - span_lint( - cx, - NEW_RET_NO_SELF, - impl_item.span, - "methods called `new` usually return `Self`", - ); - } + new_ret_no_self::check_impl_item(cx, impl_item, self_ty, implements_trait); } } @@ -4915,59 +4987,44 @@ impl<'tcx> LateLintPass<'tcx> for Methods { return; } - if let TraitItemKind::Fn(ref sig, _) = item.kind - && sig.decl.implicit_self.has_implicit_self() - && let Some(first_arg_hir_ty) = sig.decl.inputs.first() - && let Some(&first_arg_ty) = cx - .tcx - .fn_sig(item.owner_id) - .instantiate_identity() - .inputs() - .skip_binder() - .first() - { - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); - wrong_self_convention::check( - cx, - item.ident.name, - self_ty, - first_arg_ty, - first_arg_hir_ty.span, - false, - true, - ); - } + if let TraitItemKind::Fn(ref sig, _) = item.kind { + if sig.decl.implicit_self.has_implicit_self() + && let Some(first_arg_hir_ty) = sig.decl.inputs.first() + && let Some(&first_arg_ty) = cx + .tcx + .fn_sig(item.owner_id) + .instantiate_identity() + .inputs() + .skip_binder() + .first() + { + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); + wrong_self_convention::check( + cx, + item.ident.name, + self_ty, + first_arg_ty, + first_arg_hir_ty.span, + false, + true, + ); + } - if item.ident.name == sym::new - && let TraitItemKind::Fn(_, _) = item.kind - && let ret_ty = return_ty(cx, item.owner_id) - && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() - && !ret_ty.contains(self_ty) - { - span_lint( - cx, - NEW_RET_NO_SELF, - item.span, - "methods called `new` usually return `Self`", - ); + new_ret_no_self::check_trait_item(cx, item); } } } impl Methods { - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Handle method calls whose receiver and arguments may not come from expansion if let Some((name, recv, args, span, call_span)) = method_call(expr) { match (name, args) { - ( - sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub, - [_arg], - ) => { + (sym::add | sym::sub | sym::wrapping_add | sym::wrapping_sub, [_arg]) => { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => { @@ -4997,7 +5054,6 @@ impl Methods { } }, (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, false); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5042,9 +5098,13 @@ impl Methods { (sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv), (sym::cloned, []) => { cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv); - option_as_ref_cloned::check(cx, recv, span); + if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = + method_call(recv) + { + option_as_ref_cloned::check(cx, span, method, as_ref_recv, as_ref_ident_span); + } }, - (sym::collect, []) if is_trait_method(cx, expr, sym::Iterator) => { + (sym::collect, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) => { needless_collect::check(cx, span, expr, recv, call_span); match method_call(recv) { Some((name @ (sym::cloned | sym::copied), recv2, [], _, _)) => { @@ -5065,17 +5125,26 @@ impl Methods { _ => {}, } }, - (sym::count, []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { - Some((sym::cloned, recv2, [], _, _)) => { - iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned, false); - }, - Some((name2 @ (sym::into_iter | sym::iter | sym::iter_mut), recv2, [], _, _)) => { - iter_count::check(cx, expr, recv2, name2); - }, - Some((sym::map, _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg), - Some((sym::filter, recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg), - Some((sym::bytes, recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), - _ => {}, + (sym::count, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) => { + match method_call(recv) { + Some((sym::cloned, recv2, [], _, _)) => { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::RmCloned, + false, + ); + }, + Some((name2 @ (sym::into_iter | sym::iter | sym::iter_mut), recv2, [], _, _)) => { + iter_count::check(cx, expr, recv2, name2); + }, + Some((sym::map, _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg), + Some((sym::filter, recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg), + Some((sym::bytes, recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), + _ => {}, + } }, (sym::min | sym::max, [arg]) => { unnecessary_min_or_max::check(cx, expr, name, recv, arg); @@ -5144,52 +5213,61 @@ impl Methods { } }, (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); + unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); + lines_filter_map_ok::check_filter_or_flat_map( + cx, + expr, + recv, + "filter_map", + arg, + call_span, + self.msrv, + ); }, (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); + unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap); }, (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); + lines_filter_map_ok::check_filter_or_flat_map( + cx, expr, recv, "flat_map", arg, call_span, self.msrv, + ); }, - (sym::flatten, []) => match method_call(recv) { - Some((sym::map, recv, [map_arg], map_span, _)) => { - map_flatten::check(cx, expr, recv, map_arg, map_span); - }, - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - true, - ), - _ => {}, - }, - (sym::fold, [init, acc]) => { - manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); - unnecessary_fold::check(cx, expr, init, acc, span); - }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); + (sym::flatten, []) => { match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::map, recv, [map_arg], map_span, _)) => { + map_flatten::check(cx, expr, recv, map_arg, map_span); + }, Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( cx, expr, recv, recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, + iter_overeager_cloned::Op::LaterCloned, + true, ), _ => {}, } + lines_filter_map_ok::check_flatten(cx, expr, recv, call_span, self.msrv); + }, + (sym::fold, [init, acc]) => { + manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); + unnecessary_fold::check(cx, expr, init, acc, span); + }, + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, }, (sym::get, [arg]) => { get_first::check(cx, expr, recv, arg); @@ -5250,7 +5328,6 @@ impl Methods { }, (name @ (sym::map | sym::map_err), [m_arg]) => { if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, self.msrv); map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); manual_is_variant_and::check_map(cx, expr); @@ -5294,6 +5371,7 @@ impl Methods { }, (sym::map_or_else, [def, map]) => { result_map_or_else_none::check(cx, expr, recv, def, map); + unnecessary_option_map_or_else::check(cx, expr, recv, def, map); unnecessary_result_map_or_else::check(cx, expr, recv, def, map); }, (sym::next, []) => { @@ -5307,7 +5385,9 @@ impl Methods { iter_overeager_cloned::Op::LaterCloned, false, ), - (sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg), + (sym::filter, [arg]) => { + filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Forward); + }, (sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), (sym::iter, []) => iter_next_slice::check(cx, expr, recv2), (sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg), @@ -5317,6 +5397,14 @@ impl Methods { } } }, + (sym::next_back, []) => { + if let Some((name2, recv2, args2, _, _)) = method_call(recv) + && let (sym::filter, [arg]) = (name2, args2) + && self.msrv.meets(cx, msrvs::DOUBLE_ENDED_ITERATOR_RFIND) + { + filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Backward); + } + }, (sym::nth, [n_arg]) => match method_call(recv) { Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5334,6 +5422,11 @@ impl Methods { }, _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, + (sym::offset | sym::wrapping_offset, [arg]) => { + zst_offset::check(cx, expr, recv); + + ptr_offset_with_cast::check(cx, name, expr, recv, arg, self.msrv); + }, (sym::ok_or_else, [arg]) => { unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); }, @@ -5442,7 +5535,7 @@ impl Methods { } unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); }, - (sym::try_into, []) if is_trait_method(cx, expr, sym::TryInto) => { + (sym::try_into, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::TryInto) => { unnecessary_fallible_conversions::check_method(cx, expr); }, (sym::to_owned, []) => { @@ -5487,7 +5580,14 @@ impl Methods { option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); }, Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, name); + obfuscated_if_else::check( + cx, + expr, + t_recv, + t_arg, + then_method, + obfuscated_if_else::Unwrap::Or(u_arg), + ); }, _ => {}, } @@ -5504,9 +5604,8 @@ impl Methods { expr, t_recv, t_arg, - None, then_method, - sym::unwrap_or_default, + obfuscated_if_else::Unwrap::OrDefault, ); }, _ => {}, @@ -5523,9 +5622,8 @@ impl Methods { expr, t_recv, t_arg, - Some(u_arg), then_method, - sym::unwrap_or_else, + obfuscated_if_else::Unwrap::OrElse(u_arg), ); }, _ => { @@ -5552,8 +5650,18 @@ impl Methods { } // Handle method calls whose receiver and arguments may come from expansion if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind { + let method_span = path.ident.span; + + // Those methods do their own method name checking as they deal with multiple methods. + or_fun_call::check(cx, expr, method_span, path.ident.name, recv, args, self.msrv); + unnecessary_to_owned::check(cx, expr, path.ident.name, recv, args, self.msrv); + match (path.ident.name, args) { - (sym::expect, [_]) => { + (sym::clone, []) => { + clone_on_ref_ptr::check(cx, expr, recv); + clone_on_copy::check(cx, expr, recv); + }, + (sym::expect, [arg]) => { unwrap_expect_used::check( cx, expr, @@ -5563,6 +5671,7 @@ impl Methods { self.allow_expect_in_tests, unwrap_expect_used::Variant::Expect, ); + expect_fun_call::check(cx, &self.format_args, expr, method_span, recv, arg); }, (sym::expect_err, [_]) => { unwrap_expect_used::check( @@ -5575,6 +5684,15 @@ impl Methods { unwrap_expect_used::Variant::Expect, ); }, + (sym::insert_str | sym::push_str, _) => { + single_char_add_str::check(cx, expr, recv, args); + }, + (sym::into_iter, []) => { + into_iter_on_ref::check(cx, expr, method_span, recv); + }, + (sym::to_string, []) => { + inefficient_to_string::check(cx, expr, recv, self.msrv); + }, (sym::unwrap, []) => { unwrap_expect_used::check( cx, @@ -5645,183 +5763,3 @@ fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExpr lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info); lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info); } - -const FN_HEADER: hir::FnHeader = hir::FnHeader { - safety: hir::HeaderSafety::Normal(hir::Safety::Safe), - constness: hir::Constness::NotConst, - asyncness: hir::IsAsync::NotAsync, - abi: ExternAbi::Rust, -}; - -struct ShouldImplTraitCase { - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - // implicit self kind expected (none, self, &self, ...) - self_kind: SelfKind, - // checks against the output type - output_type: OutType, - // certain methods with explicit lifetimes can't implement the equivalent trait method - lint_explicit_lifetime: bool, -} -impl ShouldImplTraitCase { - const fn new( - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - self_kind: SelfKind, - output_type: OutType, - lint_explicit_lifetime: bool, - ) -> ShouldImplTraitCase { - ShouldImplTraitCase { - trait_name, - method_name, - param_count, - fn_header, - self_kind, - output_type, - lint_explicit_lifetime, - } - } - - fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { - self.lint_explicit_lifetime - || !impl_item.generics.params.iter().any(|p| { - matches!( - p.kind, - hir::GenericParamKind::Lifetime { - kind: hir::LifetimeParamKind::Explicit - } - ) - }) - } -} - -#[rustfmt::skip] -const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), -]; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum SelfKind { - Value, - Ref, - RefMut, - No, // When we want the first argument type to be different than `Self` -} - -impl SelfKind { - fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if ty == parent_ty { - true - } else if let Some(boxed_ty) = ty.boxed_ty() { - boxed_ty == parent_ty - } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { - if let ty::Adt(_, args) = ty.kind() { - args.types().next() == Some(parent_ty) - } else { - false - } - } else { - false - } - } - - fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if let ty::Ref(_, t, m) = *ty.kind() { - return m == mutability && t == parent_ty; - } - - let trait_sym = match mutability { - hir::Mutability::Not => sym::AsRef, - hir::Mutability::Mut => sym::AsMut, - }; - - let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { - return false; - }; - implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) - } - - fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - !matches_value(cx, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty) - } - - match self { - Self::Value => matches_value(cx, parent_ty, ty), - Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), - Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), - Self::No => matches_none(cx, parent_ty, ty), - } - } - - #[must_use] - fn description(self) -> &'static str { - match self { - Self::Value => "`self` by value", - Self::Ref => "`self` by reference", - Self::RefMut => "`self` by mutable reference", - Self::No => "no `self`", - } - } -} - -#[derive(Clone, Copy)] -enum OutType { - Unit, - Bool, - Any, - Ref, -} - -impl OutType { - fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { - let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); - match (self, ty) { - (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, - (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, - (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, - (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, - (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), - _ => false, - } - } -} - -fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { - expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness -} diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs index 4235af882b0c..c9264e747b56 100644 --- a/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::expr_custom_deref_adjustment; -use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::peel_and_count_ty_refs; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; @@ -10,11 +11,10 @@ use super::MUT_MUTEX_LOCK; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) - && let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) - && ref_depth >= 1 + && let (_, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv)) && let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Mutex) + && cx.tcx.type_of(impl_id).is_diag_item(cx, sym::Mutex) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs index 635d06330e05..22baad40b449 100644 --- a/clippy_lints/src/methods/needless_as_bytes.rs +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; @@ -10,7 +10,7 @@ use super::NEEDLESS_AS_BYTES; pub fn check(cx: &LateContext<'_>, prev_method: Symbol, method: Symbol, prev_recv: &Expr<'_>, span: Span) { let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs(); - if is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str() { + if ty1.is_lang_item(cx, LangItem::String) || ty1.is_str() { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app); span_lint_and_sugg( diff --git a/clippy_lints/src/methods/needless_character_iteration.rs b/clippy_lints/src/methods/needless_character_iteration.rs index 71c1576cd57d..948ed8a25746 100644 --- a/clippy_lints/src/methods/needless_character_iteration.rs +++ b/clippy_lints/src/methods/needless_character_iteration.rs @@ -1,3 +1,4 @@ +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp}; use rustc_lint::LateContext; @@ -8,7 +9,7 @@ use super::NEEDLESS_CHARACTER_ITERATION; use super::utils::get_last_chain_binding_hir_id; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym}; +use clippy_utils::{peel_blocks, sym}; fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> { while let ExprKind::AddrOf(_, _, e) = expr.kind { @@ -32,7 +33,7 @@ fn handle_expr( // `is_ascii`, then only `.all()` should warn. if revert != is_all && method.ident.name == sym::is_ascii - && path_to_local_id(receiver, first_param) + && receiver.res_local_id() == Some(first_param) && let char_arg_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs() && *char_arg_ty.kind() == ty::Char && let Some(snippet) = before_chars.get_source_text(cx) @@ -75,8 +76,8 @@ fn handle_expr( // If we have `!is_ascii`, then only `.any()` should warn. And if the condition is // `is_ascii`, then only `.all()` should warn. if revert != is_all - && is_path_diagnostic_item(cx, fn_path, sym::char_is_ascii) - && path_to_local_id(peels_expr_ref(arg), first_param) + && fn_path.ty_rel_def(cx).is_diag_item(cx, sym::char_is_ascii) + && peels_expr_ref(arg).res_local_id() == Some(first_param) && let Some(snippet) = before_chars.get_source_text(cx) { span_lint_and_sugg( diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 0075bf166cc1..055fdcabdd21 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -2,15 +2,11 @@ use std::ops::ControlFlow; use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{ - get_type_diagnostic_name, has_non_owning_mutable_access, make_normalized_projection, make_projection, -}; -use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, is_trait_method, path_to_local, - path_to_local_id, sym, -}; +use clippy_utils::ty::{has_non_owning_mutable_access, make_normalized_projection, make_projection}; +use clippy_utils::{CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, sym}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; @@ -42,11 +38,14 @@ pub(super) fn check<'tcx>( Node::Expr(parent) => { check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); + let sugg: String; + let mut app; + if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { - let mut app = Applicability::MachineApplicable; + app = Applicability::MachineApplicable; let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name.ident.name { + sugg = match name.ident.name { sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( @@ -82,23 +81,29 @@ pub(super) fn check<'tcx>( }, _ => return, }; - - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - call_span.with_hi(parent.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - app, - ); + } else if let ExprKind::Index(_, index, _) = parent.kind { + app = Applicability::MaybeIncorrect; + let snip = snippet_with_applicability(cx, index.span, "_", &mut app); + sugg = format!("nth({snip}).unwrap()"); + } else { + return; } + + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + call_span.with_hi(parent.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + app, + ); }, Node::LetStmt(l) => { if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind && let ty = cx.typeck_results().expr_ty(collect_expr) && matches!( - get_type_diagnostic_name(cx, ty), + ty.opt_diag_name(cx), Some(sym::Vec | sym::VecDeque | sym::BinaryHeap | sym::LinkedList) ) && let iter_ty = cx.typeck_results().expr_ty(iter_expr) @@ -339,14 +344,18 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { if args.is_empty() && method_name.ident.name == sym::collect - && is_trait_method(self.cx, expr, sym::Iterator) + && self + .cx + .ty_based_def(expr) + .opt_parent(self.cx) + .is_diag_item(self.cx, sym::Iterator) { self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); self.visit_expr(recv); return; } - if path_to_local_id(recv, self.target) { + if recv.res_local_id() == Some(self.target) { if self .illegal_mutable_capture_ids .intersection(&self.current_mutably_captured_ids) @@ -384,7 +393,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { return; } - if let Some(hir_id) = path_to_local(recv) + if let Some(hir_id) = recv.res_local_id() && let Some(index) = self.hir_id_uses_map.remove(&hir_id) { if self @@ -402,7 +411,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { } } // Check if the collection is used for anything else - if path_to_local_id(expr, self.target) { + if expr.res_local_id() == Some(self.target) { self.seen_other = true; } else { walk_expr(self, expr); @@ -464,7 +473,7 @@ impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if path_to_local_id(expr, self.id) { + if expr.res_local_id() == Some(self.id) { self.count += 1; } else { walk_expr(self, expr); @@ -549,13 +558,17 @@ impl<'tcx> Visitor<'tcx> for IteratorMethodCheckVisitor<'_, 'tcx> { && (recv.hir_id == self.hir_id_of_expr || self .hir_id_of_let_binding - .is_some_and(|hid| path_to_local_id(recv, hid))) - && !is_trait_method(self.cx, expr, sym::Iterator) + .is_some_and(|hid| recv.res_local_id() == Some(hid))) + && !self + .cx + .ty_based_def(expr) + .opt_parent(self.cx) + .is_diag_item(self.cx, sym::Iterator) { return ControlFlow::Break(()); } else if let ExprKind::Assign(place, value, _span) = &expr.kind && value.hir_id == self.hir_id_of_expr - && let Some(id) = path_to_local(place) + && let Some(id) = place.res_local_id() { // our iterator was directly assigned to a variable self.hir_id_of_let_binding = Some(id); diff --git a/clippy_lints/src/methods/needless_option_as_deref.rs b/clippy_lints/src/methods/needless_option_as_deref.rs index d77d044340dc..06e6a3c70b87 100644 --- a/clippy_lints/src/methods/needless_option_as_deref.rs +++ b/clippy_lints/src/methods/needless_option_as_deref.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::sym; use clippy_utils::usage::local_used_after_expr; -use clippy_utils::{path_res, sym}; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_hir::def::Res; @@ -15,9 +15,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name 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 outer_ty.is_diag_item(cx, sym::Option) && outer_ty == typeck.expr_ty(recv) { if name == sym::as_deref_mut && recv.is_syntactic_place_expr() { - let Res::Local(binding_id) = path_res(cx, recv) else { + let Res::Local(binding_id) = *recv.basic_res() else { return; }; diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs index 1544a12e6ba8..1622fdb88bd5 100644 --- a/clippy_lints/src/methods/needless_option_take.rs +++ b/clippy_lints/src/methods/needless_option_take.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; @@ -35,7 +35,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' 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) + expr_type.is_diag_item(cx, sym::Option) } /// Returns the string of the function call that creates the temporary. diff --git a/clippy_lints/src/methods/new_ret_no_self.rs b/clippy_lints/src/methods/new_ret_no_self.rs new file mode 100644 index 000000000000..2aa28fbb5f40 --- /dev/null +++ b/clippy_lints/src/methods/new_ret_no_self.rs @@ -0,0 +1,46 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::return_ty; +use clippy_utils::ty::contains_ty_adt_constructor_opaque; +use rustc_hir::{ImplItem, TraitItem}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::sym; + +use super::NEW_RET_NO_SELF; + +pub(super) fn check_impl_item<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &'tcx ImplItem<'_>, + self_ty: Ty<'tcx>, + implements_trait: bool, +) { + // if this impl block implements a trait, lint in trait definition instead + if !implements_trait + && impl_item.ident.name == sym::new + && let ret_ty = return_ty(cx, impl_item.owner_id) + && ret_ty != self_ty + && !contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + impl_item.span, + "methods called `new` usually return `Self`", + ); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) { + if trait_item.ident.name == sym::new + && let ret_ty = return_ty(cx, trait_item.owner_id) + && let self_ty = ty::TraitRef::identity(cx.tcx, trait_item.owner_id.to_def_id()).self_ty() + && !ret_ty.contains(self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + trait_item.span, + "methods called `new` usually return `Self`", + ); + } +} diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints/src/methods/no_effect_replace.rs index 32f32f1b2167..9fa51f78c99d 100644 --- a/clippy_lints/src/methods/no_effect_replace.rs +++ b/clippy_lints/src/methods/no_effect_replace.rs @@ -1,6 +1,6 @@ use clippy_utils::SpanlessEq; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::MaybeDef; use rustc_ast::LitKind; use rustc_hir::{ExprKind, LangItem}; use rustc_lint::LateContext; @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( arg2: &'tcx rustc_hir::Expr<'_>, ) { let ty = cx.typeck_results().expr_ty(expr).peel_refs(); - if !(ty.is_str() || is_type_lang_item(cx, ty, LangItem::String)) { + if !(ty.is_str() || ty.is_lang_item(cx, LangItem::String)) { return; } diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints/src/methods/obfuscated_if_else.rs index 604b48656aea..b2466bbd982d 100644 --- a/clippy_lints/src/methods/obfuscated_if_else.rs +++ b/clippy_lints/src/methods/obfuscated_if_else.rs @@ -5,25 +5,24 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, sym}; use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::ExprKind; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Symbol; +#[expect(clippy::needless_pass_by_value)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - then_recv: &'tcx hir::Expr<'_>, - then_arg: &'tcx hir::Expr<'_>, - unwrap_arg: Option<&'tcx hir::Expr<'_>>, + expr: &'tcx Expr<'_>, + then_recv: &'tcx Expr<'_>, + then_arg: &'tcx Expr<'_>, then_method_name: Symbol, - unwrap_method_name: Symbol, + unwrap: Unwrap<'tcx>, ) { let recv_ty = cx.typeck_results().expr_ty(then_recv); if recv_ty.is_bool() { let then_eager = switch_to_eager_eval(cx, then_arg); - let unwrap_eager = unwrap_arg.is_none_or(|arg| switch_to_eager_eval(cx, arg)); + let unwrap_eager = unwrap.arg().is_none_or(|arg| switch_to_eager_eval(cx, arg)); let mut applicability = if then_eager && unwrap_eager { Applicability::MachineApplicable @@ -40,18 +39,17 @@ pub(super) fn check<'tcx>( _ => return, }; - // FIXME: Add `unwrap_or_else` and `unwrap_or_default` symbol - let els = match unwrap_method_name { - sym::unwrap_or => snippet_with_applicability(cx, unwrap_arg.unwrap().span, "..", &mut applicability), - sym::unwrap_or_else if let ExprKind::Closure(closure) = unwrap_arg.unwrap().kind => { - let body = cx.tcx.hir_body(closure.body); - snippet_with_applicability(cx, body.value.span, "..", &mut applicability) - }, - sym::unwrap_or_else if let ExprKind::Path(_) = unwrap_arg.unwrap().kind => { - snippet_with_applicability(cx, unwrap_arg.unwrap().span, "_", &mut applicability) + "()" + let els = match unwrap { + Unwrap::Or(arg) => snippet_with_applicability(cx, arg.span, "..", &mut applicability), + Unwrap::OrElse(arg) => match arg.kind { + ExprKind::Closure(closure) => { + let body = cx.tcx.hir_body(closure.body); + snippet_with_applicability(cx, body.value.span, "..", &mut applicability) + }, + ExprKind::Path(_) => snippet_with_applicability(cx, arg.span, "_", &mut applicability) + "()", + _ => return, }, - sym::unwrap_or_default => "Default::default()".into(), - _ => return, + Unwrap::OrDefault => "Default::default()".into(), }; let sugg = format!( @@ -83,3 +81,18 @@ pub(super) fn check<'tcx>( ); } } + +pub(super) enum Unwrap<'tcx> { + Or(&'tcx Expr<'tcx>), + OrElse(&'tcx Expr<'tcx>), + OrDefault, +} + +impl<'tcx> Unwrap<'tcx> { + fn arg(&self) -> Option<&'tcx Expr<'tcx>> { + match self { + Self::Or(a) | Self::OrElse(a) => Some(a), + Self::OrDefault => None, + } + } +} diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs index e10bc0216e5f..c9c1f4865b81 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints/src/methods/ok_expect.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::{has_debug_impl, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::has_debug_impl; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; @@ -9,7 +10,7 @@ use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) // lint if the caller of `ok()` is a `Result` && let result_type = cx.typeck_results().expr_ty(recv) && let Some(error_type) = get_error_type(cx, result_type) @@ -29,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr /// Given a `Result` type, return its error type (`E`). fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if is_type_diagnostic_item(cx, ty, sym::Result) => args.types().nth(1), + ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().nth(1), _ => None, } } diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index 37a8e25bef96..1b520a9edb56 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -1,7 +1,7 @@ +use clippy_utils::res::MaybeDef; use rustc_data_structures::fx::FxHashMap; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{paths, sym}; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; @@ -13,7 +13,7 @@ use rustc_span::source_map::Spanned; use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS}; fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty) + ty.is_diag_item(cx, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty) } pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints/src/methods/option_as_ref_cloned.rs index 3c38deca6cd1..156c624488eb 100644 --- a/clippy_lints/src/methods/option_as_ref_cloned.rs +++ b/clippy_lints/src/methods/option_as_ref_cloned.rs @@ -1,23 +1,31 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::sym; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; -use super::{OPTION_AS_REF_CLONED, method_call}; +use super::OPTION_AS_REF_CLONED; -pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) { - if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = - method_call(cloned_recv) - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option) +pub(super) fn check( + cx: &LateContext<'_>, + cloned_ident_span: Span, + as_ref_method: Symbol, + as_ref_recv: &Expr<'_>, + as_ref_ident_span: Span, +) { + if cx + .typeck_results() + .expr_ty(as_ref_recv) + .peel_refs() + .is_diag_item(cx, sym::Option) { span_lint_and_sugg( cx, OPTION_AS_REF_CLONED, as_ref_ident_span.to(cloned_ident_span), - format!("cloning an `Option<_>` using `.{method}().cloned()`"), + format!("cloning an `Option<_>` using `.{as_ref_method}().cloned()`"), "this can be written more concisely by cloning the `Option<_>` directly", "clone".into(), Applicability::MachineApplicable, diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index 906ead16fd0d..3d489075ce8a 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::peel_blocks; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{path_to_local_id, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -23,7 +23,7 @@ pub(super) fn check( let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); let option_ty = cx.typeck_results().expr_ty(as_ref_recv); - if !is_type_diagnostic_item(cx, option_ty, sym::Option) { + if !option_ty.is_diag_item(cx, sym::Option) { return; } @@ -51,7 +51,7 @@ pub(super) fn check( match &closure_expr.kind { hir::ExprKind::MethodCall(_, receiver, [], _) => { - if path_to_local_id(receiver, closure_body.params[0].pat.hir_id) + if receiver.res_local_id() == Some(closure_body.params[0].pat.hir_id) && let adj = cx .typeck_results() .expr_adjustments(receiver) @@ -72,7 +72,7 @@ pub(super) fn check( if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind && let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind { - path_to_local_id(inner2, closure_body.params[0].pat.hir_id) + inner2.res_local_id() == Some(closure_body.params[0].pat.hir_id) } else { false } diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 1a273f77fb7d..817388915f18 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_none_expr; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_res_lang_ctor, path_def_id, path_res}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -37,8 +37,8 @@ pub(super) fn check<'tcx>( def_arg: &'tcx hir::Expr<'_>, map_arg: &'tcx hir::Expr<'_>, ) { - 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); + let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); + let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); // There are two variants of this `map_or` lint: // (1) using `map_or` as an adapter from `Result` to `Option` @@ -49,12 +49,12 @@ pub(super) fn check<'tcx>( return; } - if !is_res_lang_ctor(cx, path_res(cx, def_arg), OptionNone) { + if !is_none_expr(cx, def_arg) { // nothing to lint! return; } - let f_arg_is_some = is_res_lang_ctor(cx, path_res(cx, map_arg), OptionSome); + let f_arg_is_some = map_arg.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome); if is_option { let self_snippet = snippet(cx, recv.span, ".."); @@ -62,7 +62,7 @@ pub(super) fn check<'tcx>( && let arg_snippet = snippet(cx, fn_decl_span, "..") && let body = cx.tcx.hir_body(body) && let Some((func, [arg_char])) = reduce_unit_expression(body.value) - && let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id)) + && let Some(id) = func.res(cx).opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) && Some(id) == cx.tcx.lang_items().option_some_variant() { let func_snippet = snippet(cx, arg_char.span, ".."); diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 4ba8e0109042..32a9b4fe7c58 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::ty::is_copy; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -27,7 +28,7 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) { // lint if the caller of `map()` is an `Option` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) { + if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) { if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) { // Replacing `.map().unwrap_or()` with `.map_or(, )` can sometimes lead to // borrowck errors, see #10579 for one such instance. diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 04f0e3c0479e..aed4a0075c2f 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -2,8 +2,11 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; +use clippy_utils::higher::VecArgs; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::{expr_type_is_certain, implements_trait}; use clippy_utils::visitors::for_each_expr; use clippy_utils::{ contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment, peel_blocks, sym, @@ -17,7 +20,6 @@ use {rustc_ast as ast, rustc_hir as hir}; use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; /// Checks for the `OR_FUN_CALL` lint. -#[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, @@ -25,179 +27,8 @@ pub(super) fn check<'tcx>( name: Symbol, receiver: &'tcx hir::Expr<'_>, args: &'tcx [hir::Expr<'_>], + msrv: Msrv, ) { - /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, - /// `or_insert(T::new())` or `or_insert(T::default())`. - /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, - /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. - fn check_unwrap_or_default( - cx: &LateContext<'_>, - name: Symbol, - receiver: &hir::Expr<'_>, - fun: &hir::Expr<'_>, - call_expr: Option<&hir::Expr<'_>>, - span: Span, - method_span: Span, - ) -> bool { - if !expr_type_is_certain(cx, receiver) { - return false; - } - - let is_new = |fun: &hir::Expr<'_>| { - if let hir::ExprKind::Path(ref qpath) = fun.kind { - let path = last_path_segment(qpath).ident.name; - matches!(path, sym::new) - } else { - false - } - }; - - let output_type_implements_default = |fun| { - let fun_ty = cx.typeck_results().expr_ty(fun); - if let ty::FnDef(def_id, args) = fun_ty.kind() { - let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); - cx.tcx - .get_diagnostic_item(sym::Default) - .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) - } else { - false - } - }; - - let sugg = match (name, call_expr.is_some()) { - (sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default, - (sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default, - _ => return false, - }; - - let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); - let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { - cx.tcx - .inherent_impls(adt_def.did()) - .iter() - .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) - .find_map(|assoc| { - if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 - { - Some(assoc.def_id) - } else { - None - } - }) - }) else { - return false; - }; - let in_sugg_method_implementation = { - matches!( - suggested_method_def_id.as_local(), - Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id - ) - }; - if in_sugg_method_implementation { - return false; - } - - // needs to target Default::default in particular or be *::new and have a Default impl - // available - if (is_new(fun) && output_type_implements_default(fun)) - || match call_expr { - Some(call_expr) => is_default_equivalent(cx, call_expr), - None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun), - } - { - span_lint_and_sugg( - cx, - UNWRAP_OR_DEFAULT, - method_span.with_hi(span.hi()), - format!("use of `{name}` to construct default value"), - "try", - format!("{sugg}()"), - Applicability::MachineApplicable, - ); - - true - } else { - false - } - } - - /// Checks for `*or(foo())`. - #[expect(clippy::too_many_arguments)] - fn check_or_fn_call<'tcx>( - cx: &LateContext<'tcx>, - name: Symbol, - method_span: Span, - self_expr: &hir::Expr<'_>, - arg: &'tcx hir::Expr<'_>, - // `Some` if fn has second argument - second_arg: Option<&hir::Expr<'_>>, - span: Span, - // None if lambda is required - fun_span: Option, - ) -> bool { - // (path, fn_has_argument, methods, suffix) - const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [ - (sym::BTreeEntry, false, &[sym::or_insert], "with"), - (sym::HashMapEntry, false, &[sym::or_insert], "with"), - ( - sym::Option, - false, - &[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or], - "else", - ), - (sym::Option, false, &[sym::get_or_insert], "with"), - (sym::Option, true, &[sym::and], "then"), - (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), - (sym::Result, true, &[sym::and], "then"), - ]; - - if KNOW_TYPES.iter().any(|k| k.2.contains(&name)) - && switch_to_lazy_eval(cx, arg) - && !contains_return(arg) - && let self_ty = cx.typeck_results().expr_ty(self_expr) - && let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES - .iter() - .find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0) && i.2.contains(&name)) - { - let ctxt = span.ctxt(); - let mut app = Applicability::HasPlaceholders; - let sugg = { - let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { - (false, Some(fun_span)) => (fun_span, false), - _ => (arg.span, true), - }; - - let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; - let snip = if use_lambda { - let l_arg = if fn_has_arguments { "_" } else { "" }; - format!("|{l_arg}| {snip}") - } else { - snip.into_owned() - }; - - if let Some(f) = second_arg { - let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; - format!("{snip}, {f}") - } else { - snip - } - }; - let span_replace_word = method_span.with_hi(span.hi()); - span_lint_and_sugg( - cx, - OR_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("{name}_{suffix}({sugg})"), - app, - ); - true - } else { - false - } - } - if let [arg] = args { let inner_arg = peel_blocks(arg); for_each_expr(cx, inner_arg, |ex| { @@ -217,11 +48,11 @@ pub(super) fn check<'tcx>( }; (!inner_fun_has_args && !is_nested_expr - && check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span)) + && check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span, msrv)) || check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, fun_span) }, hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) if !is_nested_expr => { - check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span) + check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span, msrv) }, hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, None) @@ -265,6 +96,191 @@ pub(super) fn check<'tcx>( } } +/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, +/// `or_insert(T::new())` or `or_insert(T::default())`. +/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, +/// `or_insert_with(T::new)` or `or_insert_with(T::default)`. +#[expect(clippy::too_many_arguments)] +fn check_unwrap_or_default( + cx: &LateContext<'_>, + name: Symbol, + receiver: &hir::Expr<'_>, + fun: &hir::Expr<'_>, + call_expr: Option<&hir::Expr<'_>>, + span: Span, + method_span: Span, + msrv: Msrv, +) -> bool { + let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); + + // Check MSRV, but only for `Result::unwrap_or_default` + if receiver_ty.is_diag_item(cx, sym::Result) && !msrv.meets(cx, msrvs::RESULT_UNWRAP_OR_DEFAULT) { + return false; + } + + if !expr_type_is_certain(cx, receiver) { + return false; + } + + let is_new = |fun: &hir::Expr<'_>| { + if let hir::ExprKind::Path(ref qpath) = fun.kind { + let path = last_path_segment(qpath).ident.name; + matches!(path, sym::new) + } else { + false + } + }; + + let output_type_implements_default = |fun| { + let fun_ty = cx.typeck_results().expr_ty(fun); + if let ty::FnDef(def_id, args) = fun_ty.kind() { + let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); + cx.tcx + .get_diagnostic_item(sym::Default) + .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) + } else { + false + } + }; + + let sugg = match (name, call_expr.is_some()) { + (sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default, + (sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default, + _ => return false, + }; + + let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { + cx.tcx + .inherent_impls(adt_def.did()) + .iter() + .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) + .find_map(|assoc| { + if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 { + Some(assoc.def_id) + } else { + None + } + }) + }) else { + return false; + }; + let in_sugg_method_implementation = { + matches!( + suggested_method_def_id.as_local(), + Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id + ) + }; + if in_sugg_method_implementation { + return false; + } + + // `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a + // non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases. + if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() { + return false; + } + + // needs to target Default::default in particular or be *::new and have a Default impl + // available + if (is_new(fun) && output_type_implements_default(fun)) + || match call_expr { + Some(call_expr) => is_default_equivalent(cx, call_expr), + None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun), + } + { + span_lint_and_sugg( + cx, + UNWRAP_OR_DEFAULT, + method_span.with_hi(span.hi()), + format!("use of `{name}` to construct default value"), + "try", + format!("{sugg}()"), + Applicability::MachineApplicable, + ); + + true + } else { + false + } +} + +/// Checks for `*or(foo())`. +#[expect(clippy::too_many_arguments)] +fn check_or_fn_call<'tcx>( + cx: &LateContext<'tcx>, + name: Symbol, + method_span: Span, + self_expr: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + // `Some` if fn has second argument + second_arg: Option<&hir::Expr<'_>>, + span: Span, + // None if lambda is required + fun_span: Option, +) -> bool { + // (path, fn_has_argument, methods, suffix) + const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [ + (sym::BTreeEntry, false, &[sym::or_insert], "with"), + (sym::HashMapEntry, false, &[sym::or_insert], "with"), + ( + sym::Option, + false, + &[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or], + "else", + ), + (sym::Option, false, &[sym::get_or_insert], "with"), + (sym::Option, true, &[sym::and], "then"), + (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), + (sym::Result, true, &[sym::and], "then"), + ]; + + if KNOW_TYPES.iter().any(|k| k.2.contains(&name)) + && switch_to_lazy_eval(cx, arg) + && !contains_return(arg) + && let self_ty = cx.typeck_results().expr_ty(self_expr) + && let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES + .iter() + .find(|&&i| self_ty.is_diag_item(cx, i.0) && i.2.contains(&name)) + { + let ctxt = span.ctxt(); + let mut app = Applicability::HasPlaceholders; + let sugg = { + let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { + (false, Some(fun_span)) => (fun_span, false), + _ => (arg.span, true), + }; + + let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; + let snip = if use_lambda { + let l_arg = if fn_has_arguments { "_" } else { "" }; + format!("|{l_arg}| {snip}") + } else { + snip.into_owned() + }; + + if let Some(f) = second_arg { + let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; + format!("{snip}, {f}") + } else { + snip + } + }; + let span_replace_word = method_span.with_hi(span.hi()); + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("{name}_{suffix}({sugg})"), + app, + ); + true + } else { + false + } +} + fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { let body = cx.tcx.hir_body(body); diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 1a760ea733d7..07199b84f39e 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::lang_items::LangItem; use rustc_hir::{Expr, ExprKind}; @@ -21,14 +20,14 @@ pub(super) fn check<'tcx>( let title; let or_arg_content: Span; - if is_type_diagnostic_item(cx, ty, sym::Option) { + if ty.is_diag_item(cx, 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) { + } else if ty.is_diag_item(cx, 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; @@ -60,7 +59,7 @@ pub(super) fn check<'tcx>( fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option { if let ExprKind::Call(some_expr, [arg]) = expr.kind - && is_res_lang_ctor(cx, path_res(cx, some_expr), item) + && some_expr.res(cx).ctor_parent(cx).is_lang_item(cx, item) { Some(arg.span.source_callsite()) } else { diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs index 32752ef7435f..18046ff24f9d 100644 --- a/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -12,7 +12,11 @@ use super::PATH_BUF_PUSH_OVERWRITE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::PathBuf) + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .is_diag_item(cx, sym::PathBuf) && let ExprKind::Lit(lit) = arg.kind && let LitKind::Str(ref path_lit, _) = lit.node && let pushed_path = Path::new(path_lit.as_str()) diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints/src/methods/path_ends_with_ext.rs index d3f513e7abd2..87d1aa62b2c2 100644 --- a/clippy_lints/src/methods/path_ends_with_ext.rs +++ b/clippy_lints/src/methods/path_ends_with_ext.rs @@ -1,8 +1,8 @@ use super::PATH_ENDS_WITH_EXT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_ast::{LitKind, StrStyle}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -23,7 +23,11 @@ pub(super) fn check( msrv: Msrv, allowed_dotfiles: &FxHashSet<&'static str>, ) { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::Path) + if cx + .typeck_results() + .expr_ty(recv) + .peel_refs() + .is_diag_item(cx, sym::Path) && !path.span.from_expansion() && let ExprKind::Lit(lit) = path.kind && let LitKind::Str(path, StrStyle::Cooked) = lit.node diff --git a/clippy_lints/src/methods/ptr_offset_with_cast.rs b/clippy_lints/src/methods/ptr_offset_with_cast.rs new file mode 100644 index 000000000000..d19d3b8eb89d --- /dev/null +++ b/clippy_lints/src/methods/ptr_offset_with_cast.rs @@ -0,0 +1,82 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Symbol; +use std::fmt; + +use super::PTR_OFFSET_WITH_CAST; + +pub(super) fn check( + cx: &LateContext<'_>, + method: Symbol, + expr: &Expr<'_>, + recv: &Expr<'_>, + arg: &Expr<'_>, + msrv: Msrv, +) { + // `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions + // became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable. + if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) { + return; + } + + let method = match method { + sym::offset => Method::Offset, + sym::wrapping_offset => Method::WrappingOffset, + _ => return, + }; + + if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() { + return; + } + + // Check if the argument to the method call is a cast from usize. + let cast_lhs_expr = match arg.kind { + ExprKind::Cast(lhs, _) if cx.typeck_results().expr_ty(lhs).is_usize() => lhs, + _ => return, + }; + + let ExprKind::MethodCall(method_name, _, _, _) = expr.kind else { + return; + }; + + let msg = format!("use of `{method}` with a `usize` casted to an `isize`"); + span_lint_and_then(cx, PTR_OFFSET_WITH_CAST, expr.span, msg, |diag| { + diag.multipart_suggestion( + format!("use `{}` instead", method.suggestion()), + vec![ + (method_name.ident.span, method.suggestion().to_string()), + (arg.span.with_lo(cast_lhs_expr.span.hi()), String::new()), + ], + Applicability::MachineApplicable, + ); + }); +} + +#[derive(Copy, Clone)] +enum Method { + Offset, + WrappingOffset, +} + +impl Method { + #[must_use] + fn suggestion(self) -> &'static str { + match self { + Self::Offset => "add", + Self::WrappingOffset => "wrapping_add", + } + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Offset => write!(f, "offset"), + Self::WrappingOffset => write!(f, "wrapping_offset"), + } + } +} diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints/src/methods/range_zip_with_len.rs index 3a5e32172086..7ece83ba7ca3 100644 --- a/clippy_lints/src/methods/range_zip_with_len.rs +++ b/clippy_lints/src/methods/range_zip_with_len.rs @@ -1,17 +1,17 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::{SpanlessEq, higher, is_integer_const, is_trait_method}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{SpanRangeExt as _, snippet_with_applicability}; +use clippy_utils::{SpanlessEq, get_parent_expr, higher, is_integer_const, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{Expr, ExprKind, Node, Pat, PatKind, QPath}; use rustc_lint::LateContext; -use rustc_span::sym; use super::RANGE_ZIP_WITH_LEN; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) // range expression in `.zip()` call: `0..x.len()` - && let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg) + && let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(cx, zip_arg) && is_integer_const(cx, start, 0) // `.len()` call && let ExprKind::MethodCall(len_path, len_recv, [], _) = end.kind @@ -21,14 +21,93 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' && let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind && SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments) { - span_lint_and_sugg( + span_lint_and_then( cx, RANGE_ZIP_WITH_LEN, expr.span, "using `.zip()` with a range and `.len()`", - "try", - format!("{}.iter().enumerate()", snippet(cx, recv.span, "_")), - Applicability::MachineApplicable, + |diag| { + // If the iterator content is consumed by a pattern with exactly two elements, swap + // the order of those elements. Otherwise, the suggestion will be marked as + // `Applicability::MaybeIncorrect` (because it will be), and a note will be added + // to the diagnostic to underline the swapping of the index and the content. + let pat = methods_pattern(cx, expr).or_else(|| for_loop_pattern(cx, expr)); + let invert_bindings = if let Some(pat) = pat + && pat.span.eq_ctxt(expr.span) + && let PatKind::Tuple([first, second], _) = pat.kind + { + Some((first.span, second.span)) + } else { + None + }; + let mut app = Applicability::MachineApplicable; + let mut suggestions = vec![( + expr.span, + format!( + "{}.iter().enumerate()", + snippet_with_applicability(cx, recv.span, "_", &mut app) + ), + )]; + if let Some((left, right)) = invert_bindings + && let Some(snip_left) = left.get_source_text(cx) + && let Some(snip_right) = right.get_source_text(cx) + { + suggestions.extend([(left, snip_right.to_string()), (right, snip_left.to_string())]); + } else { + app = Applicability::MaybeIncorrect; + } + diag.multipart_suggestion("use", suggestions, app); + if app != Applicability::MachineApplicable { + diag.note("the order of the element and the index will be swapped"); + } + }, ); } } + +/// If `expr` is the argument of a `for` loop, return the loop pattern. +fn for_loop_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { + cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { + if let Node::Expr(ancestor_expr) = node + && let Some(for_loop) = higher::ForLoop::hir(ancestor_expr) + && for_loop.arg.hir_id == expr.hir_id + { + Some(for_loop.pat) + } else { + None + } + }) +} + +/// If `expr` is the receiver of an `Iterator` method which consumes the iterator elements and feed +/// them to a closure, return the pattern of the closure. +fn methods_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { + if let Some(parent_expr) = get_parent_expr(cx, expr) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let ExprKind::MethodCall(method, recv, [arg], _) = parent_expr.kind + && recv.hir_id == expr.hir_id + && matches!( + method.ident.name, + sym::all + | sym::any + | sym::filter_map + | sym::find_map + | sym::flat_map + | sym::for_each + | sym::is_partitioned + | sym::is_sorted_by_key + | sym::map + | sym::map_while + | sym::position + | sym::rposition + | sym::try_for_each + ) + && let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + { + Some(param.pat) + } else { + None + } +} diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 6738bbf0a12b..a6dfbada5348 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -1,8 +1,8 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_local_use_after_expr; use clippy_utils::{get_parent_expr, sym}; use rustc_ast::LitKind; @@ -32,7 +32,7 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool { pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { let recv_ty = cx.typeck_results().expr_ty(recv); - if is_type_diagnostic_item(cx, recv_ty, sym::Stdin) + if recv_ty.is_diag_item(cx, sym::Stdin) && let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind && let Res::Local(local_id) = path.res { @@ -45,7 +45,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< if args.is_empty() && segment.ident.name == sym::parse && let parse_result_ty = cx.typeck_results().expr_ty(parent) - && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) + && parse_result_ty.is_diag_item(cx, sym::Result) && let ty::Adt(_, substs) = parse_result_ty.kind() && let Some(ok_ty) = substs[0].as_type() && parse_fails_on_trailing_newline(ok_ty) diff --git a/clippy_lints/src/methods/readonly_write_lock.rs b/clippy_lints/src/methods/readonly_write_lock.rs index 40b6becd4532..a98a807d1a3c 100644 --- a/clippy_lints/src/methods/readonly_write_lock.rs +++ b/clippy_lints/src/methods/readonly_write_lock.rs @@ -1,8 +1,8 @@ use super::READONLY_WRITE_LOCK; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::mir::{enclosing_mir, visit_local_usage}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Node, PatKind}; use rustc_lint::LateContext; @@ -13,14 +13,17 @@ fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind && path.ident.name == sym::unwrap { - is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result) + cx.typeck_results() + .expr_ty(receiver) + .peel_refs() + .is_diag_item(cx, sym::Result) } else { false } } pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, receiver: &Expr<'_>) { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::RwLock) + if cx.typeck_results().expr_ty(receiver).peel_refs().is_diag_item(cx, sym::RwLock) && let Node::Expr(unwrap_call_expr) = cx.tcx.parent_hir_node(expr.hir_id) && is_unwrap_call(cx, unwrap_call_expr) && let parent = cx.tcx.parent_hir_node(unwrap_call_expr.hir_id) diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints/src/methods/repeat_once.rs index 7837517ed5d8..57a7254605f9 100644 --- a/clippy_lints/src/methods/repeat_once.rs +++ b/clippy_lints/src/methods/repeat_once.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( recv: &'tcx Expr<'_>, repeat_arg: &'tcx Expr<'_>, ) { - if ConstEvalCtxt::new(cx).eval(repeat_arg) == Some(Constant::Int(1)) { + if ConstEvalCtxt::new(cx).eval_local(repeat_arg, expr.span.ctxt()) == Some(Constant::Int(1)) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); if ty.is_str() { span_lint_and_sugg( @@ -36,7 +36,7 @@ pub(super) fn check<'tcx>( format!("{}.to_vec()", snippet(cx, recv.span, r#""...""#)), Applicability::MachineApplicable, ); - } else if is_type_lang_item(cx, ty, LangItem::String) { + } else if ty.is_lang_item(cx, LangItem::String) { span_lint_and_sugg( cx, REPEAT_ONCE, diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints/src/methods/result_map_or_else_none.rs index af619c9e3bb1..d5477b9be4c1 100644 --- a/clippy_lints/src/methods/result_map_or_else_none.rs +++ b/clippy_lints/src/methods/result_map_or_else_none.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{is_none_expr, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -19,13 +19,13 @@ pub(super) fn check<'tcx>( map_arg: &'tcx hir::Expr<'_>, ) { // lint if the caller of `map_or_else()` is a `Result` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) // We check that it is mapped as `Some`. - && is_res_lang_ctor(cx, path_res(cx, map_arg), OptionSome) + && map_arg.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = def_arg.kind && let body = cx.tcx.hir_body(body) // And finally we check that we return a `None` in the "else case". - && is_res_lang_ctor(cx, path_res(cx, peel_blocks(body.value)), OptionNone) + && is_none_expr(cx, peel_blocks(body.value)) { let msg = "called `map_or_else(|_| None, Some)` on a `Result` value"; let self_snippet = snippet(cx, recv.span, ".."); diff --git a/clippy_lints/src/methods/return_and_then.rs b/clippy_lints/src/methods/return_and_then.rs index 54f38a322b8d..8f47306c8947 100644 --- a/clippy_lints/src/methods/return_and_then.rs +++ b/clippy_lints/src/methods/return_and_then.rs @@ -1,3 +1,4 @@ +use clippy_utils::res::MaybeDef; use rustc_errors::Applicability; use rustc_hir::{self as hir, Node}; use rustc_lint::LateContext; @@ -7,7 +8,6 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; -use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::visitors::for_each_unconsumed_temporary; use clippy_utils::{peel_blocks, potential_return_of_enclosing_body}; @@ -26,7 +26,7 @@ pub(super) fn check<'tcx>( } let recv_type = cx.typeck_results().expr_ty(recv); - if !matches!(get_type_diagnostic_name(cx, recv_type), Some(sym::Option | sym::Result)) { + if !matches!(recv_type.opt_diag_name(cx), Some(sym::Option | sym::Result)) { return; } diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs index 855babb797a2..8732eba6d4e8 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints/src/methods/search_is_some.rs @@ -1,8 +1,8 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::deref_closure_args; -use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs, sym}; +use clippy_utils::{is_receiver_of_method_call, strip_pat_refs, sym}; use hir::ExprKind; use rustc_errors::Applicability; use rustc_hir as hir; @@ -14,7 +14,7 @@ use super::SEARCH_IS_SOME; /// lint searching an Iterator followed by `is_some()` /// or calling `find()` on a string followed by `is_some()` or `is_none()` -#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +#[expect(clippy::too_many_arguments, clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'_>, expr: &'tcx hir::Expr<'_>, @@ -27,89 +27,81 @@ pub(super) fn check<'tcx>( ) { let option_check_method = if is_some { "is_some" } else { "is_none" }; // lint if caller of search is an Iterator - if is_trait_method(cx, is_some_recv, sym::Iterator) { + if cx + .ty_based_def(is_some_recv) + .opt_parent(cx) + .is_diag_item(cx, sym::Iterator) + { let msg = format!("called `{option_check_method}()` after searching an `Iterator` with `{search_method}`"); let search_snippet = snippet(cx, search_arg.span, ".."); - if search_snippet.lines().count() <= 1 { - // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` - // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` - let mut applicability = Applicability::MachineApplicable; - let any_search_snippet = if search_method == sym::find - && let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind - && let closure_body = cx.tcx.hir_body(body) - && let Some(closure_arg) = closure_body.params.first() - { - if let PatKind::Ref(..) = closure_arg.pat.kind { - Some(search_snippet.replacen('&', "", 1)) - } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind { - // `find()` provides a reference to the item, but `any` does not, - // so we should fix item usages for suggestion - if let Some(closure_sugg) = deref_closure_args(cx, search_arg) { - applicability = closure_sugg.applicability; - Some(closure_sugg.suggestion) - } else { - Some(search_snippet.to_string()) - } + // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` + // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` + let mut applicability = Applicability::MachineApplicable; + let any_search_snippet = if search_method == sym::find + && let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind + && let closure_body = cx.tcx.hir_body(body) + && let Some(closure_arg) = closure_body.params.first() + { + if let PatKind::Ref(..) = closure_arg.pat.kind { + Some(search_snippet.replacen('&', "", 1)) + } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind { + // `find()` provides a reference to the item, but `any` does not, + // so we should fix item usages for suggestion + if let Some(closure_sugg) = deref_closure_args(cx, search_arg) { + applicability = closure_sugg.applicability; + Some(closure_sugg.suggestion) } else { - None + Some(search_snippet.to_string()) } } else { None - }; - // add note if not multi-line - if is_some { - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - method_span.with_hi(expr.span.hi()), - msg, - "consider using", - format!( - "any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ), - applicability, - ); - } else { - let iter = snippet(cx, search_recv.span, ".."); - let sugg = if is_receiver_of_method_call(cx, expr) { - format!( - "(!{iter}.any({}))", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ) - } else { - format!( - "!{iter}.any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ) - }; - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - expr.span, - msg, - "consider using", - sugg, - applicability, - ); } } else { - let hint = format!( - "this is more succinctly expressed by calling `any()`{}", - if option_check_method == "is_none" { - " with negation" - } else { - "" - } + None + }; + // add note if not multi-line + if is_some { + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + msg, + "consider using", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + applicability, + ); + } else { + let iter = snippet(cx, search_recv.span, ".."); + let sugg = if is_receiver_of_method_call(cx, expr) { + format!( + "(!{iter}.any({}))", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ) + } else { + format!( + "!{iter}.any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ) + }; + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + expr.span, + msg, + "consider using", + sugg, + applicability, ); - span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, msg, None, hint); } } // lint if `find()` is called by `String` or `&str` else if search_method == sym::find { let is_string_or_str_slice = |e| { let self_ty = cx.typeck_results().expr_ty(e).peel_refs(); - if is_type_lang_item(cx, self_ty, hir::LangItem::String) { + if self_ty.is_lang_item(cx, hir::LangItem::String) { true } else { self_ty.is_str() diff --git a/clippy_lints/src/methods/should_implement_trait.rs b/clippy_lints/src/methods/should_implement_trait.rs new file mode 100644 index 000000000000..599ff696f6ae --- /dev/null +++ b/clippy_lints/src/methods/should_implement_trait.rs @@ -0,0 +1,156 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::{is_bool, sym}; +use rustc_abi::ExternAbi; +use rustc_hir::{self as hir, FnRetTy, FnSig, GenericParamKind, ImplItem, LifetimeParamKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::edition::Edition::{self, Edition2015, Edition2021}; +use rustc_span::{Symbol, kw}; + +use super::SHOULD_IMPLEMENT_TRAIT; +use super::lib::SelfKind; + +pub(super) fn check_impl_item<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &'tcx ImplItem<'_>, + self_ty: Ty<'tcx>, + impl_implements_trait: bool, + first_arg_ty_opt: Option>, + sig: &FnSig<'_>, +) { + // if this impl block implements a trait, lint in trait definition instead + if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) + // check missing trait implementations + && let Some(method_config) = TRAIT_METHODS.iter().find(|case| case.method_name == impl_item.ident.name) + && sig.decl.inputs.len() == method_config.param_count + && method_config.output_type.matches(&sig.decl.output) + // in case there is no first arg, since we already have checked the number of arguments + // it's should be always true + && first_arg_ty_opt + .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) + && sig.header.is_safe() + && !sig.header.is_const() + && !sig.header.is_async() + && sig.header.abi == ExternAbi::Rust + && method_config.lifetime_param_cond(impl_item) + && method_config.in_prelude_since <= cx.tcx.sess.edition() + { + span_lint_and_help( + cx, + SHOULD_IMPLEMENT_TRAIT, + impl_item.span, + format!( + "method `{}` can be confused for the standard trait method `{}::{}`", + method_config.method_name, method_config.trait_name, method_config.method_name + ), + None, + format!( + "consider implementing the trait `{}` or choosing a less ambiguous method name", + method_config.trait_name + ), + ); + } +} + +struct ShouldImplTraitCase { + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + // implicit self kind expected (none, self, &self, ...) + self_kind: SelfKind, + // checks against the output type + output_type: OutType, + // certain methods with explicit lifetimes can't implement the equivalent trait method + lint_explicit_lifetime: bool, + in_prelude_since: Edition, +} + +impl ShouldImplTraitCase { + const fn new( + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + self_kind: SelfKind, + output_type: OutType, + lint_explicit_lifetime: bool, + in_prelude_since: Edition, + ) -> ShouldImplTraitCase { + ShouldImplTraitCase { + trait_name, + method_name, + param_count, + self_kind, + output_type, + lint_explicit_lifetime, + in_prelude_since, + } + } + + fn lifetime_param_cond(&self, impl_item: &ImplItem<'_>) -> bool { + self.lint_explicit_lifetime + || !impl_item.generics.params.iter().any(|p| { + matches!( + p.kind, + GenericParamKind::Lifetime { + kind: LifetimeParamKind::Explicit + } + ) + }) + } +} + +#[rustfmt::skip] +const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, SelfKind::RefMut, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, SelfKind::Ref, OutType::Bool, true, Edition2015), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, SelfKind::No, OutType::Any, true, Edition2021), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, SelfKind::Ref, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, SelfKind::RefMut, OutType::Any, false, Edition2015), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, SelfKind::Value, OutType::Any, true, Edition2015), +]; + +#[derive(Clone, Copy)] +enum OutType { + Unit, + Bool, + Any, + Ref, +} + +impl OutType { + fn matches(self, ty: &FnRetTy<'_>) -> bool { + let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); + match (self, ty) { + (Self::Unit, &FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &FnRetTy::Return(ty)) if is_unit(ty) => true, + (Self::Bool, &FnRetTy::Return(ty)) if is_bool(ty) => true, + (Self::Any, &FnRetTy::Return(ty)) if !is_unit(ty) => true, + (Self::Ref, &FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), + _ => false, + } + } +} diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints/src/methods/single_char_add_str.rs index ef3d7acdc01e..1248d1658d77 100644 --- a/clippy_lints/src/methods/single_char_add_str.rs +++ b/clippy_lints/src/methods/single_char_add_str.rs @@ -1,14 +1,80 @@ -use crate::methods::{single_char_insert_string, single_char_push_string}; -use rustc_hir as hir; +use super::SINGLE_CHAR_ADD_STR; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; +use rustc_ast::BorrowKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::sym; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - match cx.tcx.get_diagnostic_name(fn_def_id) { - Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args), - Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args), - _ => {}, + let mut applicability = Applicability::MachineApplicable; + let (short_name, arg, extra) = match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_insert_str) => ( + "insert", + &args[1], + Some(|applicability| { + format!( + "{}, ", + snippet_with_applicability(cx, args[0].span, "..", applicability) + ) + }), + ), + Some(sym::string_push_str) => ("push", &args[0], None), + _ => return, + }; + + if let Some(extension_string) = str_literal_to_char_literal(cx, arg, &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character string literal"), + format!("consider using `{short_name}` with a character literal"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); + } else if let ExprKind::AddrOf(BorrowKind::Ref, _, inner) = arg.kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = inner.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + let extension_string = match ( + snippet_with_applicability(cx, method_arg.span.source_callsite(), "_", &mut applicability), + is_ref_char(cx, method_arg), + ) { + (snippet, false) => snippet, + (snippet, true) => format!("*{snippet}").into(), + }; + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character converted to string"), + format!("consider using `{short_name}` without `to_string()`"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); } } } + +fn is_ref_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(cx.typeck_results().expr_ty(expr).kind(), ty::Ref(_, ty, _) if ty.is_char()) +} + +fn is_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() +} diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs deleted file mode 100644 index 4a1d25deade9..000000000000 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `insert_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character converted to string", - "consider using `insert` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs deleted file mode 100644 index bc271d593925..000000000000 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ /dev/null @@ -1,65 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `push_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let sugg = format!("{base_string_snippet}.push({extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character converted to string", - "consider using `push` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/skip_while_next.rs b/clippy_lints/src/methods/skip_while_next.rs index 9f0b6c34ea2e..2452c499b9ce 100644 --- a/clippy_lints/src/methods/skip_while_next.rs +++ b/clippy_lints/src/methods/skip_while_next.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -9,7 +9,7 @@ use super::SKIP_WHILE_NEXT; /// lint use of `skip_while().next()` for `Iterators` pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { // lint if caller of `.skip_while().next()` is an Iterator - if is_trait_method(cx, expr, sym::Iterator) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { span_lint_and_help( cx, SKIP_WHILE_NEXT, diff --git a/clippy_lints/src/methods/sliced_string_as_bytes.rs b/clippy_lints/src/methods/sliced_string_as_bytes.rs index 6d4cfdb34f31..4aff194923a6 100644 --- a/clippy_lints/src/methods/sliced_string_as_bytes.rs +++ b/clippy_lints/src/methods/sliced_string_as_bytes.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal}; use rustc_lint::LateContext; @@ -11,7 +11,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { if let ExprKind::Index(indexed, index, _) = recv.kind && is_range_literal(index) && let ty = cx.typeck_results().expr_ty(indexed).peel_refs() - && (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String)) + && (ty.is_str() || ty.is_lang_item(cx, LangItem::String)) { let mut applicability = Applicability::MaybeIncorrect; let stringish = snippet_with_applicability(cx, indexed.span, "_", &mut applicability); diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 51dd4ac313a6..fff203296bce 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -1,14 +1,15 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_context; use clippy_utils::usage::local_used_after_expr; use clippy_utils::visitors::{Descend, for_each_expr}; -use clippy_utils::{is_diag_item_method, path_to_local_id, paths, sym}; +use clippy_utils::{paths, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{ - BindingMode, Expr, ExprKind, HirId, LangItem, LetStmt, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind, + BindingMode, Expr, ExprKind, HirId, LangItem, LetStmt, MatchSource, Node, Pat, PatKind, Stmt, StmtKind, }; use rustc_lint::LateContext; use rustc_middle::ty; @@ -214,7 +215,7 @@ fn indirect_usage<'tcx>( { let mut path_to_binding = None; let _: Option = for_each_expr(cx, init_expr, |e| { - if path_to_local_id(e, binding) { + if e.res_local_id() == Some(binding) { path_to_binding = Some(e); } ControlFlow::Continue(Descend::from(path_to_binding.is_none())) @@ -271,7 +272,6 @@ struct IterUsage { span: Span, } -#[allow(clippy::too_many_lines)] fn parse_iter_usage<'tcx>( cx: &LateContext<'tcx>, ctxt: SyntaxContext, @@ -304,7 +304,7 @@ fn parse_iter_usage<'tcx>( }; }, (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => { - if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) { + if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval_local(idx_expr, ctxt) { let span = if name.ident.as_str() == "nth" { e.span } else if let Some((_, Node::Expr(next_expr))) = iter.next() @@ -332,12 +332,12 @@ fn parse_iter_usage<'tcx>( let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() { match e.kind { ExprKind::Call( - Expr { - kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)), + &Expr { + kind: ExprKind::Path(qpath), .. }, [_], - ) => { + ) if cx.tcx.qpath_is_lang_item(qpath, LangItem::TryTraitBranch) => { let parent_span = e.span.parent_callsite().unwrap(); if parent_span.ctxt() == ctxt { (Some(UnwrapKind::QuestionMark), parent_span) @@ -351,7 +351,9 @@ fn parse_iter_usage<'tcx>( && cx .typeck_results() .type_dependent_def_id(e.hir_id) - .is_some_and(|id| is_diag_item_method(cx, id, sym::Option)) => + .opt_parent(cx) + .opt_impl_ty(cx) + .is_diag_item(cx, sym::Option) => { (Some(UnwrapKind::Unwrap), e.span) }, diff --git a/clippy_lints/src/methods/string_extend_chars.rs b/clippy_lints/src/methods/string_extend_chars.rs index f11a41f90f1a..1fc0633f4f95 100644 --- a/clippy_lints/src/methods/string_extend_chars.rs +++ b/clippy_lints/src/methods/string_extend_chars.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_lang_item; use clippy_utils::{method_chain_args, sym}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -10,7 +10,7 @@ use super::STRING_EXTEND_CHARS; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if !is_type_lang_item(cx, obj_ty, hir::LangItem::String) { + if !obj_ty.is_lang_item(cx, hir::LangItem::String) { return; } if let Some(arglists) = method_chain_args(arg, &[sym::chars]) { @@ -22,7 +22,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr } else { "" } - } else if is_type_lang_item(cx, self_ty, hir::LangItem::String) { + } else if self_ty.is_lang_item(cx, hir::LangItem::String) { "&" } else { return; diff --git a/clippy_lints/src/methods/string_lit_chars_any.rs b/clippy_lints/src/methods/string_lit_chars_any.rs index f0f9d30d3000..48e89c2998ef 100644 --- a/clippy_lints/src/methods/string_lit_chars_any.rs +++ b/clippy_lints/src/methods/string_lit_chars_any.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_from_proc_macro, is_trait_method, path_to_local}; use itertools::Itertools; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -19,14 +20,14 @@ pub(super) fn check<'tcx>( body: &Expr<'_>, msrv: Msrv, ) { - if is_trait_method(cx, expr, sym::Iterator) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) && let PatKind::Binding(_, arg, _, _) = param.pat.kind && let ExprKind::Lit(lit_kind) = recv.kind && let LitKind::Str(val, _) = lit_kind.node && let ExprKind::Binary(kind, lhs, rhs) = body.kind && let BinOpKind::Eq = kind.node - && let Some(lhs_path) = path_to_local(lhs) - && let Some(rhs_path) = path_to_local(rhs) + && let Some(lhs_path) = lhs.res_local_id() + && let Some(rhs_path) = rhs.res_local_id() && let scrutinee = match (lhs_path == arg, rhs_path == arg) { (true, false) => rhs, (false, true) => lhs, diff --git a/clippy_lints/src/methods/suspicious_command_arg_space.rs b/clippy_lints/src/methods/suspicious_command_arg_space.rs index c60a49067ec0..3e9c677fe34a 100644 --- a/clippy_lints/src/methods/suspicious_command_arg_space.rs +++ b/clippy_lints/src/methods/suspicious_command_arg_space.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_errors::{Applicability, Diag}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; @@ -10,7 +10,7 @@ use super::SUSPICIOUS_COMMAND_ARG_SPACE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if is_type_diagnostic_item(cx, ty, sym::Command) + if ty.is_diag_item(cx, sym::Command) && let hir::ExprKind::Lit(lit) = &arg.kind && let ast::LitKind::Str(s, _) = &lit.node && let Some((arg1, arg2)) = s.as_str().split_once(' ') diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints/src/methods/suspicious_map.rs index 788014d9bb63..ece97c1f758c 100644 --- a/clippy_lints/src/methods/suspicious_map.rs +++ b/clippy_lints/src/methods/suspicious_map.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::expr_or_init; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::usage::mutated_variables; -use clippy_utils::{expr_or_init, is_trait_method}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -8,7 +9,10 @@ use rustc_span::sym; use super::SUSPICIOUS_MAP; pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) { - if is_trait_method(cx, count_recv, sym::Iterator) + if cx + .ty_based_def(count_recv) + .opt_parent(cx) + .is_diag_item(cx, sym::Iterator) && let hir::ExprKind::Closure(closure) = expr_or_init(cx, map_arg).kind && let closure_body = cx.tcx.hir_body(closure.body) && !cx.typeck_results().expr_ty(closure_body.value).is_unit() diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs index 9876681ddbb3..be1481ebb996 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints/src/methods/suspicious_splitn.rs @@ -10,8 +10,7 @@ use super::SUSPICIOUS_SPLITN; pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) { if count <= 1 && let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_assoc(call_id) - && cx.tcx.impl_trait_ref(impl_id).is_none() + && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(call_id) && let self_ty = cx.tcx.type_of(impl_id).instantiate_identity() && (self_ty.is_slice() || self_ty.is_str()) { diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints/src/methods/suspicious_to_owned.rs index ffc237e3c24c..bcd1f11931fc 100644 --- a/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_diag_trait_item; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -11,10 +10,13 @@ use rustc_span::sym; use super::SUSPICIOUS_TO_OWNED; pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && is_diag_trait_item(cx, method_def_id, sym::ToOwned) + if cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .opt_parent(cx) + .is_diag_item(cx, sym::ToOwned) && let input_type = cx.typeck_results().expr_ty(expr) - && is_type_diagnostic_item(cx, input_type, sym::Cow) + && input_type.is_diag_item(cx, sym::Cow) { let mut app = Applicability::MaybeIncorrect; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; diff --git a/clippy_lints/src/methods/unbuffered_bytes.rs b/clippy_lints/src/methods/unbuffered_bytes.rs index dd5566f8c8ba..fdddd5e2771d 100644 --- a/clippy_lints/src/methods/unbuffered_bytes.rs +++ b/clippy_lints/src/methods/unbuffered_bytes.rs @@ -1,6 +1,6 @@ use super::UNBUFFERED_BYTES; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::ty::implements_trait; use rustc_hir as hir; use rustc_lint::LateContext; @@ -8,7 +8,7 @@ use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { // Lint if the `.bytes()` call is from the `Read` trait and the implementor is not buffered. - if is_trait_method(cx, expr, sym::IoRead) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::IoRead) && let Some(buf_read) = cx.tcx.get_diagnostic_item(sym::IoBufRead) && let ty = cx.typeck_results().expr_ty_adjusted(recv) && !implements_trait(cx, ty, buf_read, &[]) diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index 6371fe644282..5e247a50358e 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_path_diagnostic_item; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::ty::is_uninit_value_valid_for_ty; use rustc_hir as hir; use rustc_lint::LateContext; @@ -10,7 +10,7 @@ use super::UNINIT_ASSUMED_INIT; /// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if let hir::ExprKind::Call(callee, []) = recv.kind - && is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit) + && callee.ty_rel_def(cx).is_diag_item(cx, sym::maybe_uninit_uninit) && !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)) { span_lint( diff --git a/clippy_lints/src/methods/unit_hash.rs b/clippy_lints/src/methods/unit_hash.rs index 3c7955bc4698..9defd5626eb4 100644 --- a/clippy_lints/src/methods/unit_hash.rs +++ b/clippy_lints/src/methods/unit_hash.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir::Expr; @@ -9,7 +9,7 @@ use rustc_span::sym; use super::UNIT_HASH; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { - if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { span_lint_and_then( cx, UNIT_HASH, diff --git a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs b/clippy_lints/src/methods/unnecessary_fallible_conversions.rs index 0ec2d8b4fc31..23546cad0af7 100644 --- a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs +++ b/clippy_lints/src/methods/unnecessary_fallible_conversions.rs @@ -181,7 +181,6 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Exp QPath::TypeRelative(_, seg) => Some(SpansKind::Fn { fn_span: seg.ident.span, }), - QPath::LangItem(_, _) => unreachable!("`TryFrom` and `TryInto` are not lang items"), }; check( diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index d260e0ef6e19..72f1c42da2ee 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,15 +1,16 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_copy; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; +use clippy_utils::ty::{is_copy, option_arg_ty}; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id, sym}; +use clippy_utils::{as_some_expr, sym}; use core::ops::ControlFlow; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::Symbol; +use rustc_span::Span; +use std::fmt::Display; use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP}; @@ -17,9 +18,10 @@ pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, - name: Symbol, + call_span: Span, + kind: Kind, ) { - if !is_trait_method(cx, expr, sym::Iterator) { + if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return; } @@ -44,61 +46,87 @@ pub(super) fn check<'tcx>( let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { // Check if the closure is .filter_map(|x| Some(x)) - if name == sym::filter_map - && let hir::ExprKind::Call(expr, args) = body.value.kind - && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome) - && let hir::ExprKind::Path(_) = args[0].kind + if kind.is_filter_map() + && let Some(arg) = as_some_expr(cx, body.value) + && let hir::ExprKind::Path(_) = arg.kind { span_lint( cx, UNNECESSARY_FILTER_MAP, - expr.span, + call_span, String::from("this call to `.filter_map(..)` is unnecessary"), ); return; } - if name == sym::filter_map { - "map(..)" - } else { - "map(..).next()" + match kind { + Kind::FilterMap => "map(..)", + Kind::FindMap => "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()) && in_ty == subst.type_at(0) => - { - if name == sym::filter_map { - "filter(..)" - } else { - "find(..)" - } - }, - _ => return, + let ty = cx.typeck_results().expr_ty(body.value); + if option_arg_ty(cx, ty).is_some_and(|t| t == in_ty) { + match kind { + Kind::FilterMap => "filter(..)", + Kind::FindMap => "find(..)", + } + } else { + return; } } else { return; }; span_lint( cx, - if name == sym::filter_map { - UNNECESSARY_FILTER_MAP - } else { - UNNECESSARY_FIND_MAP + match kind { + Kind::FilterMap => UNNECESSARY_FILTER_MAP, + Kind::FindMap => UNNECESSARY_FIND_MAP, }, - expr.span, - format!("this `.{name}(..)` can be written more simply using `.{sugg}`"), + call_span, + format!("this `.{kind}(..)` can be written more simply using `.{sugg}`"), ); } } +#[derive(Clone, Copy)] +pub(super) enum Kind { + FilterMap, + FindMap, +} + +impl Kind { + fn is_filter_map(self) -> bool { + matches!(self, Self::FilterMap) + } +} + +impl Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::FilterMap => f.write_str("filter_map"), + Self::FindMap => f.write_str("find_map"), + } + } +} + // returns (found_mapping, found_filtering) fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) { match expr.kind { + hir::ExprKind::Path(ref path) + if cx + .qpath_res(path, expr.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone) => + { + // None + (false, true) + }, hir::ExprKind::Call(func, args) => { - if is_res_lang_ctor(cx, path_res(cx, func), OptionSome) { - if path_to_local_id(&args[0], arg_id) { + if func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) { + if args[0].res_local_id() == Some(arg_id) { + // Some(arg_id) return (false, false); } + // Some(not arg_id) return (true, false); } (true, true) @@ -106,10 +134,12 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc hir::ExprKind::MethodCall(segment, recv, [arg], _) => { if segment.ident.name == sym::then_some && cx.typeck_results().expr_ty(recv).is_bool() - && path_to_local_id(arg, arg_id) + && arg.res_local_id() == Some(arg_id) { + // bool.then_some(arg_id) (false, true) } else { + // bool.then_some(not arg_id) (true, true) } }, @@ -133,9 +163,6 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc let else_check = check_expression(cx, arg_id, else_arm); (if_check.0 | else_check.0, if_check.1 | else_check.1) }, - hir::ExprKind::Path(ref path) if is_res_lang_ctor(cx, cx.qpath_res(path, expr.hir_id), OptionNone) => { - (false, true) - }, _ => (true, true), } } diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 8e3cc9abe832..bd471e0b18e3 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs}; +use clippy_utils::{peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -74,8 +75,8 @@ fn check_fold_with_op( && let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind && let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind - && path_to_local_id(left_expr, first_arg_id) - && (replacement.has_args || path_to_local_id(right_expr, second_arg_id)) + && left_expr.res_local_id() == Some(first_arg_id) + && (replacement.has_args || right_expr.res_local_id() == Some(second_arg_id)) { let mut applicability = Applicability::MachineApplicable; @@ -115,7 +116,7 @@ pub(super) fn check( fold_span: Span, ) { // Check that this is a call to Iterator::fold rather than just some function called fold - if !is_trait_method(cx, expr, sym::Iterator) { + if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return; } diff --git a/clippy_lints/src/methods/unnecessary_get_then_check.rs b/clippy_lints/src/methods/unnecessary_get_then_check.rs index 39fce2c40c91..10ea0c0c3e23 100644 --- a/clippy_lints/src/methods/unnecessary_get_then_check.rs +++ b/clippy_lints/src/methods/unnecessary_get_then_check.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -11,11 +11,11 @@ use rustc_span::{Span, sym}; use super::UNNECESSARY_GET_THEN_CHECK; fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - is_type_diagnostic_item(cx, ty, sym::HashSet) || is_type_diagnostic_item(cx, ty, sym::BTreeSet) + ty.is_diag_item(cx, sym::HashSet) || ty.is_diag_item(cx, sym::BTreeSet) } fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) + ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap) } pub(super) fn check( diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 20cf35363d13..4142f9f75773 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -1,10 +1,11 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::{get_iterator_item_ty, implements_trait}; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local}; +use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr}; use core::ops::ControlFlow; use itertools::Itertools; use rustc_errors::Applicability; @@ -50,7 +51,7 @@ pub fn check_for_loop_iter( // check whether `expr` is mutable fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(hir_id) = path_to_local(expr) + if let Some(hir_id) = expr.res_local_id() && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) { matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs index efd1a718504c..3290bdd77824 100644 --- a/clippy_lints/src/methods/unnecessary_join.rs +++ b/clippy_lints/src/methods/unnecessary_join.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::MaybeDef; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; @@ -20,8 +20,8 @@ pub(super) fn check<'tcx>( let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); if let ty::Ref(_, ref_type, _) = collect_output_adjusted_type.kind() // the turbofish for collect is ::> - && let ty::Slice(slice) = ref_type.kind() - && is_type_lang_item(cx, *slice, LangItem::String) + && let ty::Slice(slice) = *ref_type.kind() + && slice.is_lang_item(cx, LangItem::String) // the argument for join is "" && let ExprKind::Lit(spanned) = &join_arg.kind && let LitKind::Str(symbol, _) = spanned.node diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 71e606add526..2869547650f3 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{eager_or_lazy, is_from_proc_macro, usage}; use hir::FnRetTy; use rustc_errors::Applicability; @@ -19,8 +19,8 @@ pub(super) fn check<'tcx>( arg: &'tcx hir::Expr<'_>, simplify_using: &str, ) -> bool { - 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); + let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); + let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); let is_bool = cx.typeck_results().expr_ty(recv).is_bool(); if (is_option || is_result || is_bool) diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index cc4448192d3e..da6f03931e24 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{MaybePath, is_res_lang_ctor, last_path_segment, path_res, sym}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::{is_none_expr, last_path_segment, sym}; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::LateContext; @@ -22,6 +23,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -37,21 +39,24 @@ pub(super) fn check( } let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind { - let Some(qpath) = call.qpath_opt() else { return }; - - let args = last_path_segment(qpath).args.map(|args| args.args); - let res = cx.qpath_res(qpath, call.hir_id()); - - if is_res_lang_ctor(cx, res, hir::LangItem::OptionSome) { - (sym::Some, call_args, get_ty_from_args(args, 0)) - } else if is_res_lang_ctor(cx, res, hir::LangItem::ResultOk) { - (sym::Ok, call_args, get_ty_from_args(args, 0)) - } else if is_res_lang_ctor(cx, res, hir::LangItem::ResultErr) { - (sym::Err, call_args, get_ty_from_args(args, 1)) + if let Some((qpath, hir_id)) = call.opt_qpath() + && let args = last_path_segment(qpath).args.map(|args| args.args) + && let Some(did) = cx.qpath_res(qpath, hir_id).ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { + (sym::Some, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_ok_variant() { + (sym::Ok, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_err_variant() { + (sym::Err, call_args, get_ty_from_args(args, 1)) + } else { + return; + } } else { return; } - } else if is_res_lang_ctor(cx, path_res(cx, init), hir::LangItem::OptionNone) { + } else if is_none_expr(cx, init) { let call_args: &[hir::Expr<'_>] = &[]; (sym::None, call_args, None) } else { diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index 1f5e3de6e7a2..0c01be4b1875 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -3,10 +3,11 @@ use std::borrow::Cow; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::sugg::{Sugg, make_binop}; -use clippy_utils::ty::{get_type_diagnostic_name, implements_trait, is_copy}; +use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::visitors::is_local_used; -use clippy_utils::{get_parent_expr, is_from_proc_macro, path_to_local_id}; +use clippy_utils::{get_parent_expr, is_from_proc_macro}; use rustc_ast::LitKind::Bool; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; @@ -54,7 +55,7 @@ pub(super) fn check<'a>( return; }; - let variant = match get_type_diagnostic_name(cx, recv_ty) { + let variant = match recv_ty.opt_diag_name(cx) { Some(sym::Option) => Variant::Some, Some(sym::Result) => Variant::Ok, Some(_) | None => return, @@ -75,11 +76,11 @@ pub(super) fn check<'a>( // .map_or(true, |x| x != y) // .map_or(true, |x| y != x) - swapped comparison && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) - && let non_binding_location = if path_to_local_id(l, hir_id) { r } else { l } + && let non_binding_location = if l.res_local_id() == Some(hir_id) { r } else { l } && switch_to_eager_eval(cx, non_binding_location) - // xor, because if its both then that's a strange edge case and + // if its both then that's a strange edge case and // we can just ignore it, since by default clippy will error on this - && (path_to_local_id(l, hir_id) ^ path_to_local_id(r, hir_id)) + && (l.res_local_id() == Some(hir_id)) != (r.res_local_id() == Some(hir_id)) && !is_local_used(cx, non_binding_location, hir_id) && let typeck_results = cx.typeck_results() && let l_ty = typeck_results.expr_ty(l) diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs index b87d81b71026..bf91a469e7f0 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use super::UNNECESSARY_MIN_OR_MAX; -use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; @@ -25,8 +25,9 @@ pub(super) fn check<'tcx>( && let Some(fn_name) = cx.tcx.get_diagnostic_name(id) && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { - if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv) - && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg) + let ctxt = expr.span.ctxt(); + if let Some(left) = ecx.eval_local(recv, ctxt) + && let Some(right) = ecx.eval_local(arg, ctxt) { let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else { return; diff --git a/clippy_lints/src/methods/unnecessary_option_map_or_else.rs b/clippy_lints/src/methods/unnecessary_option_map_or_else.rs new file mode 100644 index 000000000000..265619e26376 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_option_map_or_else.rs @@ -0,0 +1,111 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{expr_or_init, find_binding_init, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Body, BodyId, Closure, Expr, ExprKind, HirId, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::UNNECESSARY_OPTION_MAP_OR_ELSE; +use super::utils::get_last_chain_binding_hir_id; + +fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) { + let msg = "unused \"map closure\" when calling `Option::map_or_else` value"; + let mut applicability = Applicability::MachineApplicable; + let self_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability); + let err_snippet = snippet_with_applicability(cx, def_arg.span, "..", &mut applicability); + span_lint_and_sugg( + cx, + UNNECESSARY_OPTION_MAP_OR_ELSE, + expr.span, + msg, + "consider using `unwrap_or_else`", + format!("{self_snippet}.unwrap_or_else({err_snippet})"), + Applicability::MachineApplicable, + ); +} + +fn handle_qpath( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + def_arg: &Expr<'_>, + expected_hir_id: HirId, + qpath: QPath<'_>, +) { + if let QPath::Resolved(_, path) = qpath + && let Res::Local(hir_id) = path.res + && expected_hir_id == hir_id + { + emit_lint(cx, expr, recv, def_arg); + } +} + +fn handle_closure(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, body_id: BodyId) { + let body = cx.tcx.hir_body(body_id); + handle_fn_body(cx, expr, recv, def_arg, body); +} + +fn handle_fn_body(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, body: &Body<'_>) { + if let Some(first_param) = body.params.first() { + let body_expr = peel_blocks(body.value); + match body_expr.kind { + ExprKind::Path(qpath) => { + handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath); + }, + // If this is a block (that wasn't peeled off), then it means there are statements. + ExprKind::Block(block, _) => { + if let Some(block_expr) = block.expr + // First we ensure that this is a "binding chain" (each statement is a binding + // of the previous one) and that it is a binding of the closure argument. + && let Some(last_chain_binding_id) = + get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts) + && let ExprKind::Path(qpath) = block_expr.kind + { + handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath); + } + }, + _ => {}, + } + } +} + +/// lint use of `_.map_or_else(|err| err, |n| n)` for `Option`s. +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, map_arg: &Expr<'_>) { + // lint if the caller of `map_or_else()` is an `Option` + if !cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) { + return; + } + match map_arg.kind { + // If the second argument is a closure, we can check its body. + ExprKind::Closure(&Closure { body, .. }) => { + handle_closure(cx, expr, recv, def_arg, body); + }, + ExprKind::Path(qpath) => { + let res = cx.qpath_res(&qpath, map_arg.hir_id); + match res { + // Case 1: Local variable (could be a closure) + Res::Local(hir_id) => { + if let Some(init_expr) = find_binding_init(cx, hir_id) { + let origin = expr_or_init(cx, init_expr); + if let ExprKind::Closure(&Closure { body, .. }) = origin.kind { + handle_closure(cx, expr, recv, def_arg, body); + } + } + }, + // Case 2: Function definition + Res::Def(DefKind::Fn, def_id) => { + if let Some(local_def_id) = def_id.as_local() + && let Some(body) = cx.tcx.hir_maybe_body_owned_by(local_def_id) + { + handle_fn_body(cx, expr, recv, def_arg, body); + } + }, + _ => (), + } + }, + _ => (), + } +} diff --git a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs b/clippy_lints/src/methods/unnecessary_result_map_or_else.rs index f84d0d6dff0a..1f6bb60414ae 100644 --- a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs +++ b/clippy_lints/src/methods/unnecessary_result_map_or_else.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::peel_blocks; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath}; @@ -51,7 +51,7 @@ pub(super) fn check<'tcx>( map_arg: &'tcx Expr<'_>, ) { // lint if the caller of `map_or_else()` is a `Result` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) && let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind && let body = cx.tcx.hir_body(body) && let Some(first_param) = body.params.first() diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index baaa36ed235e..4a3e4c092f3b 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::std_or_core; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_trait_method, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::LateContext; @@ -139,7 +140,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp ] = &closure_body.params && let ExprKind::MethodCall(method_path, left_expr, [right_expr], _) = closure_body.value.kind && method_path.ident.name == sym::cmp - && is_trait_method(cx, closure_body.value, sym::Ord) + && cx + .ty_based_def(closure_body.value) + .opt_parent(cx) + .is_diag_item(cx, sym::Ord) { let (closure_body, closure_arg, reverse) = if mirrored_exprs(left_expr, left_ident, right_expr, right_ident) { ( diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index c1f4904af7c4..a6a39cb6ab30 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -2,13 +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, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_and_count_ty_refs}; use clippy_utils::visitors::find_all_ret_expressions; -use clippy_utils::{ - fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs, - return_ty, sym, -}; +use clippy_utils::{fn_def_id, get_parent_expr, is_expr_temporary_value, return_ty, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; @@ -34,12 +32,12 @@ pub fn check<'tcx>( args: &'tcx [Expr<'_>], msrv: Msrv, ) { - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + if let Some(method_parent_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id).opt_parent(cx) && args.is_empty() { - if is_cloned_or_copied(cx, method_name, method_def_id) { + if is_cloned_or_copied(cx, method_name, method_parent_id) { unnecessary_iter_cloned::check(cx, expr, method_name, receiver); - } else if is_to_owned_like(cx, expr, method_name, method_def_id) { + } else if is_to_owned_like(cx, expr, method_name, method_parent_id) { if check_split_call_arg(cx, expr, method_name, receiver) { return; } @@ -47,7 +45,7 @@ pub fn check<'tcx>( // `check_addr_of_expr` and `check_into_iter_call_arg` determine whether the call is unnecessary // based on its context, that is, whether it is a referent in an `AddrOf` expression, an // argument in a `into_iter` call, or an argument in the call of some other function. - if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) { + if check_addr_of_expr(cx, expr, method_name, method_parent_id, receiver) { return; } if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) { @@ -65,12 +63,12 @@ pub fn check<'tcx>( /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its /// call of a `to_owned`-like function is unnecessary. -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn check_addr_of_expr( cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, - method_def_id: DefId, + method_parent_id: DefId, receiver: &Expr<'_>, ) -> bool { if let Some(parent) = get_parent_expr(cx, expr) @@ -119,8 +117,8 @@ fn check_addr_of_expr( }, ] = adjustments[..] && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && let (target_ty, n_target_refs) = peel_middle_ty_refs(*target_ty) - && let (receiver_ty, n_receiver_refs) = peel_middle_ty_refs(receiver_ty) + && let (target_ty, n_target_refs, _) = peel_and_count_ty_refs(*target_ty) + && let (receiver_ty, n_receiver_refs, _) = peel_and_count_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 @@ -132,7 +130,7 @@ fn check_addr_of_expr( // `redundant_clone`, but copyable arrays are not. && (*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)) + || is_cow_into_owned(cx, method_name, method_parent_id)) && let Some(receiver_snippet) = receiver.span.get_source_text(cx) { if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { @@ -158,7 +156,7 @@ fn check_addr_of_expr( // *or* this is a `Cow::into_owned()` call (which would be the wrong into_owned receiver (str != Cow) // but that's ok for Cow::into_owned specifically) && (cx.typeck_results().expr_ty_adjusted(receiver).peel_refs() == target_ty - || is_cow_into_owned(cx, method_name, method_def_id)) + || is_cow_into_owned(cx, method_name, method_parent_id)) { if n_receiver_refs > 0 { span_lint_and_sugg( @@ -219,7 +217,7 @@ fn check_into_iter_call_arg( && let Some(item_ty) = get_iterator_item_ty(cx, parent_ty) && let Some(receiver_snippet) = receiver.span.get_source_text(cx) // If the receiver is a `Cow`, we can't remove the `into_owned` generally, see https://github.com/rust-lang/rust-clippy/issues/13624. - && !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::Cow) + && !cx.typeck_results().expr_ty(receiver).is_diag_item(cx, sym::Cow) // Calling `iter()` on a temporary object can lead to false positives. #14242 && !is_expr_temporary_value(cx, receiver) { @@ -319,7 +317,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb // We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x` // implements `AsRef` but does not implement `Deref`. In this case, we have to // add `.as_ref()` to the suggestion. - let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String) + let as_ref = if cx.typeck_results().expr_ty(expr).is_lang_item(cx, LangItem::String) && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref) && cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, sym::Target) != Some(cx.tcx.types.str_) @@ -385,7 +383,7 @@ fn check_other_call_arg<'tcx>( && let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder() && let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id) && let Some(input) = fn_sig.inputs().get(i) - && let (input, n_refs) = peel_middle_ty_refs(*input) + && let (input, n_refs, _) = peel_and_count_ty_refs(*input) && let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input) && let Some(sized_def_id) = cx.tcx.lang_items().sized_trait() && let Some(meta_sized_def_id) = cx.tcx.lang_items().meta_sized_trait() @@ -614,21 +612,26 @@ fn has_lifetime(ty: Ty<'_>) -> bool { } /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`. -fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { - matches!(method_name, sym::cloned | sym::copied) && is_diag_trait_item(cx, method_def_id, sym::Iterator) +fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_parent_id: DefId) -> bool { + matches!(method_name, sym::cloned | sym::copied) && method_parent_id.is_diag_item(cx, sym::Iterator) } /// Returns true if the named method can be used to convert the receiver to its "owned" /// representation. -fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool { - is_cow_into_owned(cx, method_name, method_def_id) - || (method_name != sym::to_string && is_clone_like(cx, method_name, method_def_id)) - || is_to_string_on_string_like(cx, call_expr, method_name, method_def_id) +fn is_to_owned_like<'a>( + cx: &LateContext<'a>, + call_expr: &Expr<'a>, + method_name: Symbol, + method_parent_id: DefId, +) -> bool { + is_cow_into_owned(cx, method_name, method_parent_id) + || (method_name != sym::to_string && is_clone_like(cx, method_name, method_parent_id)) + || is_to_string_on_string_like(cx, call_expr, method_name, method_parent_id) } /// Returns true if the named method is `Cow::into_owned`. -fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { - method_name == sym::into_owned && is_diag_item_method(cx, method_def_id, sym::Cow) +fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_parent_id: DefId) -> bool { + method_name == sym::into_owned && method_parent_id.opt_impl_ty(cx).is_diag_item(cx, sym::Cow) } /// Returns true if the named method is `ToString::to_string` and it's called on a type that @@ -637,9 +640,9 @@ fn is_to_string_on_string_like<'a>( cx: &LateContext<'_>, call_expr: &'a Expr<'a>, method_name: Symbol, - method_def_id: DefId, + method_parent_id: DefId, ) -> bool { - if method_name != sym::to_string || !is_diag_trait_item(cx, method_def_id, sym::ToString) { + if method_name != sym::to_string || !method_parent_id.is_diag_item(cx, sym::ToString) { return false; } @@ -672,12 +675,12 @@ fn std_map_key<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { } fn is_str_and_string(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool { - original_arg_ty.is_str() && is_type_lang_item(cx, arg_ty, LangItem::String) + original_arg_ty.is_str() && arg_ty.is_lang_item(cx, LangItem::String) } fn is_slice_and_vec(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool { (original_arg_ty.is_slice() || original_arg_ty.is_array() || original_arg_ty.is_array_slice()) - && is_type_diagnostic_item(cx, arg_ty, sym::Vec) + && arg_ty.is_diag_item(cx, sym::Vec) } // This function will check the following: diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index af4ade3cc0f7..000000000000 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,138 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - // If we call a method on a `std::iter::Enumerate` instance - if is_type_diagnostic_item(cx, recv_ty, sym::Enumerate) - // If we are calling a method of the `Iterator` trait - && is_trait_method(cx, call_expr, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs index 027215e3b4d7..73a407be4f21 100644 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::is_never_like; use clippy_utils::{is_in_test, is_inside_always_const_context, is_lint_allowed}; use rustc_hir::Expr; use rustc_lint::{LateContext, Lint}; @@ -45,9 +46,9 @@ pub(super) fn check( ) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let (kind, none_value, none_prefix) = if is_type_diagnostic_item(cx, ty, sym::Option) && !is_err { + let (kind, none_value, none_prefix) = if ty.is_diag_item(cx, sym::Option) && !is_err { ("an `Option`", "None", "") - } else if is_type_diagnostic_item(cx, ty, sym::Result) + } else if ty.is_diag_item(cx, sym::Result) && let ty::Adt(_, substs) = ty.kind() && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() { diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs index 38fad239f679..972304d79e75 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints/src/methods/useless_asref.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth}; -use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs}; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs, should_call_clone_as_function}; +use clippy_utils::{get_parent_expr, peel_blocks, strip_pat_refs}; use rustc_errors::Applicability; use rustc_hir::{self as hir, LangItem}; use rustc_lint::LateContext; @@ -42,16 +43,16 @@ fn get_enum_ty(enum_ty: Ty<'_>) -> Option> { pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbol, recvr: &hir::Expr<'_>) { // when we get here, we've already checked that the call name is "as_ref" or "as_mut" // check if the call is to the actual `AsRef` or `AsMut` trait - let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { + let Some(def) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { return; }; - if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) { + if def.opt_parent(cx).is_diag_item(cx, sym::AsRef) || def.opt_parent(cx).is_diag_item(cx, sym::AsMut) { // check if the type after `as_ref` or `as_mut` is the same as before let rcv_ty = cx.typeck_results().expr_ty(recvr); let res_ty = cx.typeck_results().expr_ty(expr); - let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); - let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); + let (base_res_ty, res_depth, _) = peel_and_count_ty_refs(res_ty); + let (base_rcv_ty, rcv_depth, _) = peel_and_count_ty_refs(rcv_ty); if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { if let Some(parent) = get_parent_expr(cx, expr) { // allow the `as_ref` or `as_mut` if it is followed by another method call @@ -79,10 +80,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbo applicability, ); } - } else if let Some(impl_id) = cx.tcx.impl_of_assoc(def_id) - && let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def() - && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Option | sym::Result)) - { + } else if matches!( + def.opt_parent(cx).opt_impl_ty(cx).opt_diag_name(cx), + Some(sym::Option | sym::Result) + ) { let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs(); let res_ty = cx.typeck_results().expr_ty(expr).peel_refs(); @@ -137,7 +138,7 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { // no autoderefs && !cx.typeck_results().expr_adjustments(obj).iter() .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))) - && path_to_local_id(obj, local_id) + && obj.res_local_id() == Some(local_id) { true } else { @@ -146,7 +147,7 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { }, hir::ExprKind::Call(call, [recv]) => { if let hir::ExprKind::Path(qpath) = call.kind - && path_to_local_id(recv, local_id) + && recv.res_local_id() == Some(local_id) { check_qpath(cx, qpath, call.hir_id) } else { diff --git a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs index 22df1f3f485e..c6f54159c7a7 100644 --- a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs +++ b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_inside_always_const_context; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource}; use rustc_lint::LateContext; @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<' && segment.ident.name == sym::new_unchecked && let [init_arg] = args && is_inside_always_const_context(cx.tcx, expr.hir_id) - && is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero) + && cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::NonZero) && msrv.meets(cx, msrvs::CONST_UNWRAP) { let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index b0cc7a785bc3..1e1b124b4486 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -1,5 +1,5 @@ -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{get_parent_expr, path_to_local_id, usage}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::{get_parent_expr, usage}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, QPath, Stmt, StmtKind}; use rustc_lint::LateContext; @@ -20,7 +20,7 @@ pub(super) fn derefs_to_slice<'tcx>( match ty.kind() { ty::Slice(_) => true, ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => may_slice(cx, boxed), - ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec), + ty::Adt(..) => ty.is_diag_item(cx, sym::Vec), ty::Array(_, size) => size.try_to_target_usize(cx.tcx).is_some(), ty::Ref(_, inner, _) => may_slice(cx, *inner), _ => false, @@ -130,7 +130,7 @@ impl<'tcx> CloneOrCopyVisitor<'_, 'tcx> { fn is_binding(&self, expr: &Expr<'tcx>) -> bool { self.binding_hir_ids .iter() - .any(|hir_id| path_to_local_id(expr, *hir_id)) + .any(|&hir_id| expr.res_local_id() == Some(hir_id)) } } diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints/src/methods/vec_resize_to_zero.rs index bfb481f4fc09..5debaab2067b 100644 --- a/clippy_lints/src/methods/vec_resize_to_zero.rs +++ b/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -19,7 +19,11 @@ pub(super) fn check<'tcx>( ) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Vec) + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .is_diag_item(cx, sym::Vec) && let ExprKind::Lit(Spanned { node: LitKind::Int(Pu128(0), _), .. diff --git a/clippy_lints/src/methods/verbose_file_reads.rs b/clippy_lints/src/methods/verbose_file_reads.rs index 8ed61637eca2..5727843302d6 100644 --- a/clippy_lints/src/methods/verbose_file_reads.rs +++ b/clippy_lints/src/methods/verbose_file_reads.rs @@ -1,6 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_trait_method; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; @@ -19,9 +18,13 @@ pub(super) fn check<'tcx>( recv: &'tcx Expr<'_>, (msg, help): (&'static str, &'static str), ) { - if is_trait_method(cx, expr, sym::IoRead) + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::IoRead) && matches!(recv.kind, ExprKind::Path(QPath::Resolved(None, _))) - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(recv).peel_refs(), sym::File) + && cx + .typeck_results() + .expr_ty_adjusted(recv) + .peel_refs() + .is_diag_item(cx, sym::File) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then(cx, VERBOSE_FILE_READS, expr.span, msg, |diag| { diff --git a/clippy_lints/src/methods/waker_clone_wake.rs b/clippy_lints/src/methods/waker_clone_wake.rs index b5f34a9be2e2..3081d7f91f06 100644 --- a/clippy_lints/src/methods/waker_clone_wake.rs +++ b/clippy_lints/src/methods/waker_clone_wake.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' if let Some(did) = ty.ty_adt_def() && cx.tcx.is_diagnostic_item(sym::Waker, did.did()) && let ExprKind::MethodCall(_, waker_ref, &[], _) = recv.kind - && is_trait_method(cx, recv, sym::Clone) + && cx.ty_based_def(recv).opt_parent(cx).is_diag_item(cx, sym::Clone) { let mut applicability = Applicability::MachineApplicable; let snippet = snippet_with_applicability(cx, waker_ref.span.source_callsite(), "..", &mut applicability); diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index ad9b3c364542..12a6f345168f 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -1,12 +1,13 @@ -use crate::methods::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; +use itertools::Itertools; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, Symbol}; use std::fmt; use super::WRONG_SELF_CONVENTION; +use super::lib::SelfKind; #[rustfmt::skip] const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ @@ -61,26 +62,25 @@ impl Convention { impl fmt::Display for Convention { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { - Self::Eq(this) => format!("`{this}`").fmt(f), - Self::StartsWith(this) => format!("`{this}*`").fmt(f), - Self::EndsWith(this) => format!("`*{this}`").fmt(f), - Self::NotEndsWith(this) => format!("`~{this}`").fmt(f), + Self::Eq(this) => write!(f, "`{this}`"), + Self::StartsWith(this) => write!(f, "`{this}*`"), + Self::EndsWith(this) => write!(f, "`*{this}`"), + Self::NotEndsWith(this) => write!(f, "`~{this}`"), Self::IsSelfTypeCopy(is_true) => { - format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f) + write!(f, "`self` type is{} `Copy`", if is_true { "" } else { " not" }) }, Self::ImplementsTrait(is_true) => { let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") }; - format!("method{negation} implement{s_suffix} a trait").fmt(f) + write!(f, "method{negation} implement{s_suffix} a trait") }, Self::IsTraitItem(is_true) => { let suffix = if is_true { " is" } else { " is not" }; - format!("method{suffix} a trait item").fmt(f) + write!(f, "method{suffix} a trait item") }, } } } -#[allow(clippy::too_many_arguments)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, item_name: Symbol, @@ -115,18 +115,9 @@ pub(super) fn check<'tcx>( let s = conventions .iter() - .filter_map(|conv| { - if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_))) - || matches!(conv, Convention::ImplementsTrait(_)) - || matches!(conv, Convention::IsTraitItem(_)) - { - None - } else { - Some(conv.to_string()) - } - }) - .collect::>() - .join(" and "); + .filter(|conv| !(cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))) + .filter(|conv| !matches!(conv, Convention::ImplementsTrait(_) | Convention::IsTraitItem(_))) + .format(" and "); format!("methods with the following characteristics: ({s})") } else { @@ -140,11 +131,7 @@ pub(super) fn check<'tcx>( first_arg_span, format!( "{suggestion} usually take {}", - &self_kinds - .iter() - .map(|k| k.description()) - .collect::>() - .join(" or ") + self_kinds.iter().map(|k| k.description()).format(" or ") ), None, "consider choosing a less ambiguous name", diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index dbce29a86318..1ceee836732a 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -5,8 +5,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{Visitor, walk_item, walk_trait_item}; use rustc_hir::{ - GenericParamKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, - UsePath, + GenericParamKind, HirId, Impl, ImplItem, ImplItemImplKind, ImplItemKind, Item, ItemKind, ItemLocalId, Node, Pat, + PatKind, TraitItem, UsePath, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; @@ -256,7 +256,11 @@ fn is_not_in_trait_impl(cx: &LateContext<'_>, pat: &Pat<'_>, ident: Ident) -> bo } fn get_param_name(impl_item: &ImplItem<'_>, cx: &LateContext<'_>, ident: Ident) -> Option { - if let Some(trait_item_def_id) = impl_item.trait_item_def_id { + if let ImplItemImplKind::Trait { + trait_item_def_id: Ok(trait_item_def_id), + .. + } = impl_item.impl_kind + { let trait_param_names = cx.tcx.fn_arg_idents(trait_item_def_id); let ImplItemKind::Fn(_, body_id) = impl_item.kind else { diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index 64eafc0ebccd..ba62853c7457 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -1,9 +1,11 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_trait_method, sym}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::sym; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::SyntaxContext; use std::cmp::Ordering::{Equal, Greater, Less}; declare_clippy_lint! { @@ -60,7 +62,7 @@ enum MinMax { Max, } -fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { +fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { match expr.kind { ExprKind::Call(path, args) => { if let ExprKind::Path(ref qpath) = path.kind { @@ -68,8 +70,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM .qpath_res(qpath, path.hir_id) .opt_def_id() .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) { - Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min), - Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max), + Some(sym::cmp_min) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Min), + Some(sym::cmp_max) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Max), _ => None, }) } else { @@ -77,10 +79,12 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM } }, ExprKind::MethodCall(path, receiver, args @ [_], _) => { - if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { + if cx.typeck_results().expr_ty(receiver).is_floating_point() + || cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Ord) + { match path.ident.name { - sym::max => fetch_const(cx, Some(receiver), args, MinMax::Max), - sym::min => fetch_const(cx, Some(receiver), args, MinMax::Min), + sym::max => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Max), + sym::min => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Min), _ => None, } } else { @@ -91,12 +95,13 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM } } -fn fetch_const<'a, 'tcx>( - cx: &LateContext<'tcx>, +fn fetch_const<'a>( + cx: &LateContext<'_>, + ctxt: SyntaxContext, receiver: Option<&'a Expr<'a>>, args: &'a [Expr<'a>], m: MinMax, -) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { +) -> Option<(MinMax, Constant, &'a Expr<'a>)> { let mut args = receiver.into_iter().chain(args); let first_arg = args.next()?; let second_arg = args.next()?; @@ -104,7 +109,7 @@ fn fetch_const<'a, 'tcx>( return None; } let ecx = ConstEvalCtxt::new(cx); - match (ecx.eval_simple(first_arg), ecx.eval_simple(second_arg)) { + match (ecx.eval_local(first_arg, ctxt), ecx.eval_local(second_arg, ctxt)) { (Some(c), None) => Some((m, c, second_arg)), (None, Some(c)) => Some((m, c, first_arg)), // otherwise ignore diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 09ee6f7037c6..19e9910dfe9d 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,57 +1,11 @@ -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir, span_lint_hir_and_then}; -use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats, - last_path_segment, -}; +use clippy_utils::{SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, last_path_segment}; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{ - BinOpKind, BindingMode, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind, -}; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::def_id::LocalDefId; - -use crate::ref_patterns::REF_PATTERNS; - -declare_clippy_lint! { - /// ### What it does - /// Checks for function arguments and let bindings denoted as - /// `ref`. - /// - /// ### Why is this bad? - /// The `ref` declaration makes the function take an owned - /// value, but turns the argument into a reference (which means that the value - /// is destroyed when exiting the function). This adds not much value: either - /// take a reference type, or take an owned value and create references in the - /// body. - /// - /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The - /// type of `x` is more obvious with the former. - /// - /// ### Known problems - /// If the argument is dereferenced within the function, - /// removing the `ref` will lead to errors. This can be fixed by removing the - /// dereferences, e.g., changing `*x` to `x` within the function. - /// - /// ### Example - /// ```no_run - /// fn foo(ref _x: u8) {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// fn foo(_x: &u8) {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub TOPLEVEL_REF_ARG, - style, - "an entire binding declared as `ref`, in a function argument or a `let` statement" -} declare_clippy_lint! { /// ### What it does @@ -140,79 +94,13 @@ declare_clippy_lint! { } declare_lint_pass!(LintPass => [ - TOPLEVEL_REF_ARG, USED_UNDERSCORE_BINDING, USED_UNDERSCORE_ITEMS, SHORT_CIRCUIT_STATEMENT, ]); impl<'tcx> LateLintPass<'tcx> for LintPass { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - k: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, - body: &'tcx Body<'_>, - _: Span, - _: LocalDefId, - ) { - if !matches!(k, FnKind::Closure) { - for arg in iter_input_pats(decl, body) { - if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind - && is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) - && !arg.span.in_external_macro(cx.tcx.sess.source_map()) - { - span_lint_hir( - cx, - TOPLEVEL_REF_ARG, - arg.hir_id, - arg.pat.span, - "`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \ - Consider using a reference type instead", - ); - } - } - } - } - fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let StmtKind::Let(local) = stmt.kind - && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind - && let Some(init) = local.init - // Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue. - && is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id) - && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) - { - let ctxt = local.span.ctxt(); - let mut app = Applicability::MachineApplicable; - let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app); - let (mutopt, initref) = if mutabl == Mutability::Mut { - ("mut ", sugg_init.mut_addr()) - } else { - ("", sugg_init.addr()) - }; - let tyopt = if let Some(ty) = local.ty { - let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0; - format!(": &{mutopt}{ty_snip}") - } else { - String::new() - }; - span_lint_hir_and_then( - cx, - TOPLEVEL_REF_ARG, - init.hir_id, - local.pat.span, - "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", - |diag| { - diag.span_suggestion( - stmt.span, - "try", - format!("let {name}{tyopt} = {initref};", name = snippet(cx, name.span, ".."),), - app, - ); - }, - ); - } if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::Binary(binop, a, b) = &expr.kind && matches!(binop.node, BinOpKind::And | BinOpKind::Or) diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index fffaf40c9d14..0eb5c36a28a2 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -8,7 +8,7 @@ use super::UNNEEDED_WILDCARD_PATTERN; pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { if let PatKind::TupleStruct(_, _, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind - && let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) + && let Some(rest_index) = patterns.iter().position(Pat::is_rest) { if let Some((left_index, left_pat)) = patterns[..rest_index] .iter() diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index 788a04357b1e..35d06780bcb8 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -11,7 +11,7 @@ use rustc_ast::{BinOpKind, LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; use rustc_errors::{Applicability, Diag}; -use rustc_hir::{Body, Expr, ExprKind}; +use rustc_hir::{Block, Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; @@ -135,12 +135,12 @@ fn assert_len_expr<'hir>( cx: &LateContext<'_>, expr: &'hir Expr<'hir>, ) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { - let (cmp, asserted_len, slice_len) = if let Some( - higher::IfLetOrMatch::Match(cond, [_, then], _) - ) = higher::IfLetOrMatch::parse(cx, expr) - && let ExprKind::Binary(bin_op, left, right) = &cond.kind + let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) + && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind + && let ExprKind::Binary(bin_op, left, right) = &condition.kind // check if `then` block has a never type expression - && cx.typeck_results().expr_ty(then.body).is_never() + && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind + && cx.typeck_results().expr_ty(then_expr).is_never() { len_comparison(bin_op.node, left, right)? } else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| { @@ -220,14 +220,14 @@ impl<'hir> IndexEntry<'hir> { /// /// E.g. for `5` this returns `Some(5)`, for `..5` this returns `Some(4)`, /// for `..=5` this returns `Some(5)` -fn upper_index_expr(expr: &Expr<'_>) -> Option { +fn upper_index_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { if let ExprKind::Lit(lit) = &expr.kind && let LitKind::Int(Pu128(index), _) = lit.node { Some(index as usize) } else if let Some(higher::Range { end: Some(end), limits, .. - }) = higher::Range::hir(expr) + }) = higher::Range::hir(cx, expr) && let ExprKind::Lit(lit) = &end.kind && let LitKind::Int(Pu128(index @ 1..), _) = lit.node { @@ -244,7 +244,7 @@ fn upper_index_expr(expr: &Expr<'_>) -> Option { fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap>>) { if let ExprKind::Index(slice, index_lit, _) = expr.kind && cx.typeck_results().expr_ty_adjusted(slice).peel_refs().is_slice() - && let Some(index) = upper_index_expr(index_lit) + && let Some(index) = upper_index_expr(cx, index_lit) { let hash = hash_expr(cx, slice); diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index a6be7581c9a3..a63ad9786262 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -158,13 +158,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { let mir = cx.tcx.optimized_mir(def_id); if let Ok(()) = is_min_const_fn(cx, mir, self.msrv) - && let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) = - cx.tcx.hir_node_by_def_id(def_id) + && let node = cx.tcx.hir_node_by_def_id(def_id) + && let Some((item_span, vis_span_opt)) = match node { + hir::Node::Item(item) => Some((item.span, Some(item.vis_span))), + hir::Node::ImplItem(impl_item) => Some((impl_item.span, impl_item.vis_span())), + _ => None, + } { - let suggestion = if vis_span.is_empty() { "const " } else { " const" }; + let (sugg_span, suggestion) = if let Some(vis_span) = vis_span_opt + && !vis_span.is_empty() + { + (vis_span.shrink_to_hi(), " const") + } else { + (item_span.shrink_to_lo(), "const ") + }; span_lint_and_then(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`", |diag| { diag.span_suggestion_verbose( - vis_span.shrink_to_hi(), + sugg_span, "make the function `const`", suggestion, Applicability::MachineApplicable, diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 7772051eb5c6..1c62caa1c827 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -16,7 +16,7 @@ use rustc_hir::Attribute; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::Visibility; +use rustc_middle::ty::{AssocContainer, Visibility}; use rustc_session::impl_lint_pass; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::symbol::kw; @@ -246,12 +246,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { // If the method is an impl for a trait, don't doc. - if let Some(cid) = cx.tcx.associated_item(impl_item.owner_id).impl_container(cx.tcx) { - if cx.tcx.impl_trait_ref(cid).is_some() { + match cx.tcx.associated_item(impl_item.owner_id).container { + AssocContainer::Trait | AssocContainer::TraitImpl(_) => { note_prev_span_then_ret!(self.prev_span, impl_item.span); - } - } else { - note_prev_span_then_ret!(self.prev_span, impl_item.span); + }, + AssocContainer::InherentImpl => {}, } let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id()); diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index 8822b32b1c3d..15b773c2c64f 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -1,9 +1,9 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sym; use clippy_utils::visitors::{Visitable, for_each_expr}; -use clippy_utils::{is_path_lang_item, sym}; use rustc_ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::{DefKind, Res}; @@ -112,11 +112,9 @@ fn should_lint<'tcx>( if let ExprKind::MethodCall(path, recv, ..) = &expr.kind { let recv_ty = typeck_results.expr_ty(recv).peel_refs(); - if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) { + if path.ident.name == sym::debug_struct && recv_ty.is_diag_item(cx, sym::Formatter) { has_debug_struct = true; - } else if path.ident.name == sym::finish_non_exhaustive - && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct) - { + } else if path.ident.name == sym::finish_non_exhaustive && recv_ty.is_diag_item(cx, sym::DebugStruct) { has_finish_non_exhaustive = true; } } @@ -137,7 +135,7 @@ fn as_field_call<'tcx>( ) -> Option { if let ExprKind::MethodCall(path, recv, [debug_field, _], _) = &expr.kind && let recv_ty = typeck_results.expr_ty(recv).peel_refs() - && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct) + && recv_ty.is_diag_item(cx, sym::DebugStruct) && path.ident.name == sym::field && let ExprKind::Lit(lit) = &debug_field.kind && let LitKind::Str(sym, ..) = lit.node @@ -180,7 +178,9 @@ fn check_struct<'tcx>( .fields() .iter() .filter_map(|field| { - if field_accesses.contains(&field.ident.name) || is_path_lang_item(cx, field.ty, LangItem::PhantomData) { + if field_accesses.contains(&field.ident.name) + || field.ty.basic_res().is_lang_item(cx, LangItem::PhantomData) + { None } else { Some((field.span, "this field is unused")) diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 28555a610900..bccc72c2a516 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -3,7 +3,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::AssocItemContainer; +use rustc_middle::ty::AssocContainer; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -166,8 +166,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let assoc_item = cx.tcx.associated_item(impl_item.owner_id); let container_id = assoc_item.container_id(cx.tcx); let trait_def_id = match assoc_item.container { - AssocItemContainer::Trait => Some(container_id), - AssocItemContainer::Impl => cx.tcx.impl_trait_ref(container_id).map(|t| t.skip_binder().def_id), + AssocContainer::Trait => Some(container_id), + AssocContainer::TraitImpl(_) => Some(cx.tcx.impl_trait_id(container_id)), + AssocContainer::InherentImpl => None, }; if let Some(trait_def_id) = trait_def_id diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index 9cc93bf06531..8e9400e9d583 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -70,7 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods { .tcx .associated_items(item.owner_id) .in_definition_order() - .filter_map(|assoc_item| assoc_item.trait_item_def_id) + .filter_map(|assoc_item| assoc_item.expect_trait_impl().ok()) .collect(); for assoc in cx diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 3b44d4b60d32..ddd4271960e1 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id, sym}; +use clippy_utils::res::MaybeResPath; +use clippy_utils::{get_parent_expr, sym}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, LetStmt, Node, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -84,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Find a write to a local variable. let var = if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind - && let Some(var) = path_to_local(lhs) + && let Some(var) = lhs.res_local_id() && expr.span.desugaring_kind().is_none() { var @@ -325,7 +326,7 @@ impl<'tcx> Visitor<'tcx> for ReadVisitor<'_, 'tcx> { return; } - if path_to_local_id(expr, self.var) + if expr.res_local_id() == Some(self.var) // Check that this is a read, not a write. && !is_in_assignment_position(self.cx, expr) { diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 98614baffcea..f132b90ac4f2 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -1,12 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_ast::ast::{self, Inline, ItemKind, ModKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; -use rustc_span::{FileName, SourceFile, Span, SyntaxContext}; -use std::ffi::OsStr; -use std::path::{Component, Path}; +use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; declare_clippy_lint! { /// ### What it does @@ -60,107 +59,97 @@ declare_clippy_lint! { /// mod.rs /// lib.rs /// ``` - #[clippy::version = "1.57.0"] pub SELF_NAMED_MODULE_FILES, restriction, "checks that module layout is consistent" } -pub struct ModStyle; - impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]); +pub struct ModState { + contains_external: bool, + has_path_attr: bool, + mod_file: Arc, +} + +#[derive(Default)] +pub struct ModStyle { + working_dir: Option, + module_stack: Vec, +} + impl EarlyLintPass for ModStyle { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + self.working_dir = cx.sess().opts.working_dir.local_path().map(Path::to_path_buf); + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow { return; } + if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, mod_spans, ..)) = &item.kind { + let has_path_attr = item.attrs.iter().any(|attr| attr.has_name(sym::path)); + if !has_path_attr && let Some(current) = self.module_stack.last_mut() { + current.contains_external = true; + } + let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo()); + self.module_stack.push(ModState { + contains_external: false, + has_path_attr, + mod_file, + }); + } + } - let files = cx.sess().source_map().files(); - - let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else { + fn check_item_post(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + { return; - }; - - // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives - // `[path, to]` but not foo - let mut folder_segments = FxIndexSet::default(); - // `mod_folders` is all the unique folder names that contain a mod.rs file - let mut mod_folders = FxHashSet::default(); - // `file_map` maps file names to the full path including the file name - // `{ foo => path/to/foo.rs, .. } - let mut file_map = FxHashMap::default(); - for file in files.iter() { - if let FileName::Real(name) = &file.name - && let Some(lp) = name.local_path() - && file.cnum == LOCAL_CRATE - { - // [#8887](https://github.com/rust-lang/rust-clippy/issues/8887) - // Only check files in the current crate. - // Fix false positive that crate dependency in workspace sub directory - // is checked unintentionally. - 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) - && let Some((file, path)) = file_map.get(folder) - { - span_lint_and_then( - cx, - SELF_NAMED_MODULE_FILES, - Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), - format!("`mod.rs` files are required, found `{}`", path.display()), - |diag| { - let mut correct = path.to_path_buf(); - correct.pop(); - correct.push(folder); - correct.push("mod.rs"); - diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),)); - }, - ); + if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, ..)) = &item.kind + && let Some(current) = self.module_stack.pop() + && !current.has_path_attr + { + let Some(path) = self + .working_dir + .as_ref() + .and_then(|src| try_trim_file_path_prefix(¤t.mod_file, src)) + else { + return; + }; + if current.contains_external { + check_self_named_module(cx, path, ¤t.mod_file); } + check_mod_module(cx, path, ¤t.mod_file); } } } -/// 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<'a>( - path: &'a Path, - folder_segments: &mut FxIndexSet<&'a OsStr>, - mod_folders: &mut FxHashSet<&'a OsStr>, -) { - let mut comp = path.components().rev().peekable(); - let _: Option<_> = comp.next(); - if path.ends_with("mod.rs") { - mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); +fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { + if !path.ends_with("mod.rs") { + let mut mod_folder = path.with_extension(""); + span_lint_and_then( + cx, + SELF_NAMED_MODULE_FILES, + Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), + format!("`mod.rs` files are required, found `{}`", path.display()), + |diag| { + mod_folder.push("mod.rs"); + diag.help(format!("move `{}` to `{}`", path.display(), mod_folder.display())); + }, + ); } - let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None }); - folder_segments.extend(folders); } -/// Checks every path for the presence of `mod.rs` files and emits the lint if found. /// We should not emit a lint for test modules in the presence of `mod.rs`. /// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test) /// for code-sharing between tests. -fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { +fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { if path.ends_with("mod.rs") && !path.starts_with("tests") { span_lint_and_then( cx, @@ -177,3 +166,17 @@ fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &Source ); } } + +fn try_trim_file_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> { + if let FileName::Real(name) = &file.name + && let Some(mut path) = name.local_path() + && file.cnum == LOCAL_CRATE + { + if !path.is_relative() { + path = path.strip_prefix(prefix).ok()?; + } + Some(path) + } else { + None + } +} diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index c6c27e22b90e..bc5e72270f4e 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -1,3 +1,4 @@ +use clippy_utils::desugar_await; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::{Descend, Visitable, for_each_expr}; use core::ops::ControlFlow::Continue; @@ -97,6 +98,13 @@ fn collect_unsafe_exprs<'tcx>( ) { for_each_expr(cx, node, |expr| { match expr.kind { + // The `await` itself will desugar to two unsafe calls, but we should ignore those. + // Instead, check the expression that is `await`ed + _ if let Some(e) = desugar_await(expr) => { + collect_unsafe_exprs(cx, e, unsafe_ops); + return Continue(Descend::No); + }, + ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)), ExprKind::Field(e, _) => { diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index d98c70e7f5a8..588afd85afb0 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -1,31 +1,51 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_hir}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher; -use rustc_hir::{self as hir, AmbigArg, intravisit}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, AmbigArg, BorrowKind, Expr, ExprKind, HirId, Mutability, TyKind, intravisit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for instances of `mut mut` references. /// /// ### Why is this bad? - /// Multiple `mut`s don't add anything meaningful to the - /// source. This is either a copy'n'paste error, or it shows a fundamental - /// misunderstanding of references. + /// This is usually just a typo or a misunderstanding of how references work. /// /// ### Example /// ```no_run - /// # let mut y = 1; - /// let x = &mut &mut y; + /// let x = &mut &mut 1; + /// + /// let mut x = &mut 1; + /// let y = &mut x; + /// + /// fn foo(x: &mut &mut u32) {} + /// ``` + /// Use instead + /// ```no_run + /// let x = &mut 1; + /// + /// let mut x = &mut 1; + /// let y = &mut *x; // reborrow + /// + /// fn foo(x: &mut u32) {} /// ``` #[clippy::version = "pre 1.29.0"] pub MUT_MUT, pedantic, - "usage of double-mut refs, e.g., `&mut &mut ...`" + "usage of double mut-refs, e.g., `&mut &mut ...`" } -declare_lint_pass!(MutMut => [MUT_MUT]); +impl_lint_pass!(MutMut => [MUT_MUT]); + +#[derive(Default)] +pub(crate) struct MutMut { + seen_tys: FxHashSet, +} impl<'tcx> LateLintPass<'tcx> for MutMut { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { @@ -33,17 +53,48 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { } fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) { - if let hir::TyKind::Ref(_, mty) = ty.kind - && mty.mutbl == hir::Mutability::Mut - && let hir::TyKind::Ref(_, mty) = mty.ty.kind - && mty.mutbl == hir::Mutability::Mut + if let TyKind::Ref(_, mty) = ty.kind + && mty.mutbl == Mutability::Mut + && let TyKind::Ref(_, mty2) = mty.ty.kind + && mty2.mutbl == Mutability::Mut && !ty.span.in_external_macro(cx.sess().source_map()) { - span_lint( + if self.seen_tys.contains(&ty.hir_id) { + // we have 2+ `&mut`s, e.g., `&mut &mut &mut x` + // and we have already flagged on the outermost `&mut &mut (&mut x)`, + // so don't flag the inner `&mut &mut (x)` + return; + } + + // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off + // all extra ones at once + let (mut t, mut t2) = (mty.ty, mty2.ty); + let mut many_muts = false; + loop { + // this should allow us to remember all the nested types, so that the `contains` + // above fails faster + self.seen_tys.insert(t.hir_id); + if let TyKind::Ref(_, next) = t2.kind + && next.mutbl == Mutability::Mut + { + (t, t2) = (t2, next.ty); + many_muts = true; + } else { + break; + } + } + + let mut applicability = Applicability::MaybeIncorrect; + let sugg = snippet_with_applicability(cx.sess(), t.span, "..", &mut applicability); + let suffix = if many_muts { "s" } else { "" }; + span_lint_and_sugg( cx, MUT_MUT, ty.span, - "generally you want to avoid `&mut &mut _` if possible", + "a type of form `&mut &mut _`", + format!("remove the extra `&mut`{suffix}"), + sugg.to_string(), + applicability, ); } } @@ -54,7 +105,7 @@ pub struct MutVisitor<'a, 'tcx> { } impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if expr.span.in_external_macro(self.cx.sess().source_map()) { return; } @@ -68,24 +119,60 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { // Let's ignore the generated code. intravisit::walk_expr(self, arg); intravisit::walk_expr(self, body); - } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind { - if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { - span_lint_hir( + } else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e2) = e.kind { + if !expr.span.eq_ctxt(e.span) { + return; + } + + // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off + // all extra ones at once + let (mut e, mut e2) = (e, e2); + let mut many_muts = false; + loop { + if !e.span.eq_ctxt(e2.span) { + return; + } + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind { + (e, e2) = (e2, next); + many_muts = true; + } else { + break; + } + } + + let mut applicability = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability); + let suffix = if many_muts { "s" } else { "" }; + span_lint_hir_and_then( self.cx, MUT_MUT, expr.hir_id, expr.span, - "generally you want to avoid `&mut &mut _` if possible", + "an expression of form `&mut &mut _`", + |diag| { + diag.span_suggestion( + expr.span, + format!("remove the extra `&mut`{suffix}"), + sugg, + applicability, + ); + }, ); - } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() + } else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) { - span_lint_hir( + let mut applicability = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability).mut_addr_deref(); + span_lint_hir_and_then( self.cx, MUT_MUT, expr.hir_id, expr.span, - "this expression mutably borrows a mutable reference. Consider reborrowing", + "this expression mutably borrows a mutable reference", + |diag| { + diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability); + }, ); } } diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index fe2157ca533a..2fef8404f824 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -1,7 +1,12 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_hir::Expr; -use rustc_lint::{LateContext, LateLintPass}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; +use clippy_utils::source::{IntoSpan, SpanRangeExt}; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::ty_from_hir_ty; +use rustc_errors::{Applicability, Diag}; +use rustc_hir::{self as hir, Expr, ExprKind, Item, ItemKind, LetStmt, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::mir::Mutability; use rustc_middle::ty::{self, IntTy, Ty, UintTy}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -88,24 +93,83 @@ declare_clippy_lint! { declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]); +// NOTE: we don't use `check_expr` because that would make us lint every _use_ of such mutexes, not +// just their definitions impl<'tcx> LateLintPass<'tcx> for Mutex { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let ty = cx.typeck_results().expr_ty(expr); - if let ty::Adt(_, subst) = ty.kind() - && is_type_diagnostic_item(cx, ty, sym::Mutex) + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if !item.span.from_expansion() + && let ItemKind::Static(_, _, ty, body_id) = item.kind { - let mutex_param = subst.type_at(0); - if let Some(atomic_name) = get_atomic_name(mutex_param) { - let msg = format!( - "consider using an `{atomic_name}` instead of a `Mutex` here; if you just want the locking \ - behavior and not the internal type, consider using `Mutex<()>`" - ); - match *mutex_param.kind() { - ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), - ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), - _ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg), + let body = cx.tcx.hir_body(body_id); + let mid_ty = ty_from_hir_ty(cx, ty); + check_expr(cx, body.value.peel_blocks(), &TypeAscriptionKind::Required(ty), mid_ty); + } + } + fn check_local(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx LetStmt<'_>) { + if !stmt.span.from_expansion() + && let Some(init) = stmt.init + { + let mid_ty = cx.typeck_results().expr_ty(init); + check_expr(cx, init.peel_blocks(), &TypeAscriptionKind::Optional(stmt.ty), mid_ty); + } + } +} + +/// Whether the type ascription `: Mutex` (which we'll suggest replacing with `AtomicX`) is +/// required +enum TypeAscriptionKind<'tcx> { + /// Yes; for us, this is the case for statics + Required(&'tcx hir::Ty<'tcx>), + /// No; the ascription might've been necessary in an expression like: + /// ```ignore + /// let mutex: Mutex = Mutex::new(0); + /// ``` + /// to specify the type of `0`, but since `AtomicX` already refers to a concrete type, we won't + /// need this ascription anymore. + Optional(Option<&'tcx hir::Ty<'tcx>>), +} + +fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty_ascription: &TypeAscriptionKind<'tcx>, ty: Ty<'tcx>) { + if let ty::Adt(_, subst) = ty.kind() + && ty.is_diag_item(cx, sym::Mutex) + && let mutex_param = subst.type_at(0) + && let Some(atomic_name) = get_atomic_name(mutex_param) + { + let msg = "using a `Mutex` where an atomic would do"; + let diag = |diag: &mut Diag<'_, _>| { + // if `expr = Mutex::new(arg)`, we can try emitting a suggestion + if let ExprKind::Call(qpath, [arg]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(_mutex, new)) = qpath.kind + && new.ident.name == sym::new + { + let mut applicability = Applicability::MaybeIncorrect; + let arg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability); + let mut suggs = vec![(expr.span, format!("std::sync::atomic::{atomic_name}::new({arg})"))]; + match ty_ascription { + TypeAscriptionKind::Required(ty_ascription) => { + suggs.push((ty_ascription.span, format!("std::sync::atomic::{atomic_name}"))); + }, + TypeAscriptionKind::Optional(Some(ty_ascription)) => { + // See https://github.com/rust-lang/rust-clippy/pull/15386 for why this is + // required + let colon_ascription = (cx.sess().source_map()) + .span_extend_to_prev_char_before(ty_ascription.span, ':', true) + .with_leading_whitespace(cx) + .into_span(); + suggs.push((colon_ascription, String::new())); + }, + TypeAscriptionKind::Optional(None) => {}, // nothing to remove/replace } + diag.multipart_suggestion("try", suggs, applicability); + } else { + diag.help(format!("consider using an `{atomic_name}` instead")); } + diag.help("if you just want the locking behavior and not the internal type, consider using `Mutex<()>`"); + }; + match *mutex_param.kind() { + ty::Uint(t) if t != UintTy::Usize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag), + ty::Int(t) if t != IntTy::Isize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag), + _ => span_lint_and_then(cx, MUTEX_ATOMIC, expr.span, msg, diag), } } } @@ -135,7 +199,8 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { IntTy::I128 => None, } }, - ty::RawPtr(_, _) => Some("AtomicPtr"), + // `AtomicPtr` only accepts `*mut T` + ty::RawPtr(_, Mutability::Mut) => Some("AtomicPtr"), _ => None, } } diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index c7c4976aeb7b..25fcc7ee568e 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -147,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn path_has_args(p: &QPath<'_>) -> bool { match *p { QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(), - _ => false, + QPath::Resolved(..) => false, } } diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index b8601f77e249..55208ae708b9 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -1,9 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::source::{indent_of, snippet, snippet_block}; -use rustc_ast::{Block, Label, ast}; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use clippy_utils::higher; +use clippy_utils::source::{indent_of, snippet_block, snippet_with_context}; +use rustc_ast::Label; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, LoopSource, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::Span; +use rustc_span::{ExpnKind, Span}; declare_clippy_lint! { /// ### What it does @@ -127,9 +130,11 @@ declare_clippy_lint! { declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]); -impl EarlyLintPass for NeedlessContinue { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { - if !expr.span.from_expansion() { +impl<'tcx> LateLintPass<'tcx> for NeedlessContinue { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + // We cannot use `from_expansion` because for loops, while loops and while let loops are desugared + // into `loop` expressions. + if !matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(..)) { check_and_warn(cx, expr); } } @@ -188,19 +193,19 @@ impl EarlyLintPass for NeedlessContinue { /// /// - The expression is a `continue` node. /// - The expression node is a block with the first statement being a `continue`. -fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&Label>) -> bool { +fn needless_continue_in_else(else_expr: &Expr<'_>, label: Option<&Label>) -> bool { match else_expr.kind { - ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label), - ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()), + ExprKind::Block(else_block, _) => is_first_block_stmt_continue(else_block, label), + ExprKind::Continue(l) => compare_labels(label, l.label.as_ref()), _ => false, } } -fn is_first_block_stmt_continue(block: &Block, label: Option<&Label>) -> bool { +fn is_first_block_stmt_continue(block: &Block<'_>, label: Option<&Label>) -> bool { block.stmts.first().is_some_and(|stmt| match stmt.kind { - ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { - if let ast::ExprKind::Continue(ref l) = e.kind { - compare_labels(label, l.as_ref()) + StmtKind::Semi(e) | StmtKind::Expr(e) => { + if let ExprKind::Continue(l) = e.kind { + compare_labels(label, l.label.as_ref()) } else { false } @@ -222,20 +227,34 @@ fn compare_labels(loop_label: Option<&Label>, continue_label: Option<&Label>) -> } /// If `expr` is a loop expression (while/while let/for/loop), calls `func` with -/// the AST object representing the loop block of `expr`. -fn with_loop_block(expr: &ast::Expr, mut func: F) +/// the HIR object representing the loop block of `expr`. +fn with_loop_block(expr: &Expr<'_>, mut func: F) where - F: FnMut(&Block, Option<&Label>), + F: FnMut(&Block<'_>, Option<&Label>), { - if let ast::ExprKind::While(_, loop_block, label) - | ast::ExprKind::ForLoop { - body: loop_block, - label, - .. + if let Some(higher::ForLoop { body, label, .. }) = higher::ForLoop::hir(expr) + && let ExprKind::Block(block, _) = &body.kind + { + func(block, label.as_ref()); + return; } - | ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind + + if let Some(higher::While { body, label, .. }) = higher::While::hir(expr) + && let ExprKind::Block(block, _) = &body.kind { - func(loop_block, label.as_ref()); + func(block, label.as_ref()); + return; + } + + if let Some(higher::WhileLet { if_then, label, .. }) = higher::WhileLet::hir(expr) + && let ExprKind::Block(block, _) = &if_then.kind + { + func(block, label.as_ref()); + return; + } + + if let ExprKind::Loop(block, label, LoopSource::Loop, ..) = expr.kind { + func(block, label.as_ref()); } } @@ -247,17 +266,18 @@ where /// - The `if` condition expression, /// - The `then` block, and /// - The `else` expression. -fn with_if_expr(stmt: &ast::Stmt, mut func: F) +fn with_if_expr(expr: &Expr<'_>, mut func: F) where - F: FnMut(&ast::Expr, &ast::Expr, &Block, &ast::Expr), + F: FnMut(&Expr<'_>, &Expr<'_>, &Block<'_>, &Expr<'_>), { - match stmt.kind { - ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { - if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind { - func(e, cond, if_block, else_expr); - } - }, - _ => {}, + if let Some(higher::If { + cond, + then, + r#else: Some(r#else), + }) = higher::If::hir(expr) + && let ExprKind::Block(then, _) = then.kind + { + func(expr, cond, then, r#else); } } @@ -269,20 +289,21 @@ enum LintType { } /// Data we pass around for construction of help messages. -struct LintData<'a> { +#[derive(Debug)] +struct LintData<'hir> { /// The `if` expression encountered in the above loop. - if_expr: &'a ast::Expr, + if_expr: &'hir Expr<'hir>, /// The condition expression for the above `if`. - if_cond: &'a ast::Expr, + if_cond: &'hir Expr<'hir>, /// The `then` block of the `if` statement. - if_block: &'a Block, + if_block: &'hir Block<'hir>, /// The `else` block of the `if` statement. /// Note that we only work with `if` exprs that have an `else` branch. - else_expr: &'a ast::Expr, + else_expr: &'hir Expr<'hir>, /// The 0-based index of the `if` statement in the containing loop block. - stmt_idx: usize, + stmt_idx: Option, /// The statements of the loop block. - loop_block: &'a Block, + loop_block: &'hir Block<'hir>, } const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant"; @@ -299,7 +320,7 @@ const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause"; const DROP_CONTINUE_EXPRESSION_MSG: &str = "consider dropping the `continue` expression"; -fn emit_warning(cx: &EarlyContext<'_>, data: &LintData<'_>, header: &str, typ: LintType) { +fn emit_warning(cx: &LateContext<'_>, data: &LintData<'_>, header: &str, typ: LintType) { // snip is the whole *help* message that appears after the warning. // message is the warning message. // expr is the expression which the lint warning message refers to. @@ -325,8 +346,15 @@ fn emit_warning(cx: &EarlyContext<'_>, data: &LintData<'_>, header: &str, typ: L ); } -fn suggestion_snippet_for_continue_inside_if(cx: &EarlyContext<'_>, data: &LintData<'_>) -> String { - let cond_code = snippet(cx, data.if_cond.span, ".."); +fn suggestion_snippet_for_continue_inside_if(cx: &LateContext<'_>, data: &LintData<'_>) -> String { + let mut applicability = Applicability::MachineApplicable; + let (cond_code, _) = snippet_with_context( + cx, + data.if_cond.span, + data.if_expr.span.ctxt(), + "..", + &mut applicability, + ); let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)); @@ -339,8 +367,15 @@ fn suggestion_snippet_for_continue_inside_if(cx: &EarlyContext<'_>, data: &LintD ) } -fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &LintData<'_>) -> String { - let cond_code = snippet(cx, data.if_cond.span, ".."); +fn suggestion_snippet_for_continue_inside_else(cx: &LateContext<'_>, data: &LintData<'_>) -> String { + let mut applicability = Applicability::MachineApplicable; + let (cond_code, _) = snippet_with_context( + cx, + data.if_cond.span, + data.if_expr.span.ctxt(), + "..", + &mut applicability, + ); // Region B let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span))); @@ -352,18 +387,32 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin let indent = span_of_first_expr_in_block(data.if_block) .and_then(|span| indent_of(cx, span)) .unwrap_or(0); - let to_annex = data.loop_block.stmts[data.stmt_idx + 1..] - .iter() - .map(|stmt| { - let span = cx.sess().source_map().stmt_span(stmt.span, data.loop_block.span); + let to_annex = if let Some(stmt_idx) = data.stmt_idx { + let mut lines = data.loop_block.stmts[stmt_idx + 1..] + .iter() + .map(|stmt| { + let span = cx.sess().source_map().stmt_span(stmt.span, data.loop_block.span); + let snip = snippet_block(cx, span, "..", None); + snip.lines() + .map(|line| format!("{}{line}", " ".repeat(indent))) + .collect::>() + .join("\n") + }) + .collect::>(); + if let Some(expr) = data.loop_block.expr { + let span = expr.span; let snip = snippet_block(cx, span, "..", None); - snip.lines() + let expr_lines = snip + .lines() .map(|line| format!("{}{line}", " ".repeat(indent))) .collect::>() - .join("\n") - }) - .collect::>() - .join("\n"); + .join("\n"); + lines.push(expr_lines); + } + lines.join("\n") + } else { + String::new() + }; let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0); format!( @@ -373,46 +422,53 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin ) } -fn check_last_stmt_in_expr(inner_expr: &ast::Expr, func: &F) +fn check_last_stmt_in_expr(cx: &LateContext<'_>, inner_expr: &Expr<'_>, func: &F) where F: Fn(Option<&Label>, Span), { - match &inner_expr.kind { - ast::ExprKind::Continue(continue_label) => { - func(continue_label.as_ref(), inner_expr.span); + match inner_expr.kind { + ExprKind::Continue(continue_label) => { + func(continue_label.label.as_ref(), inner_expr.span); }, - ast::ExprKind::If(_, then_block, else_block) => { - check_last_stmt_in_block(then_block, func); + ExprKind::If(_, then_block, else_block) if let ExprKind::Block(then_block, _) = then_block.kind => { + check_last_stmt_in_block(cx, then_block, func); if let Some(else_block) = else_block { - check_last_stmt_in_expr(else_block, func); + check_last_stmt_in_expr(cx, else_block, func); } }, - ast::ExprKind::Match(_, arms, _) => { + ExprKind::Match(_, arms, _) => { + let match_ty = cx.typeck_results().expr_ty(inner_expr); + if !match_ty.is_unit() && !match_ty.is_never() { + return; + } for arm in arms { - if let Some(expr) = &arm.body { - check_last_stmt_in_expr(expr, func); - } + check_last_stmt_in_expr(cx, arm.body, func); } }, - ast::ExprKind::Block(b, _) => { - check_last_stmt_in_block(b, func); + ExprKind::Block(b, _) => { + check_last_stmt_in_block(cx, b, func); }, _ => {}, } } -fn check_last_stmt_in_block(b: &Block, func: &F) +fn check_last_stmt_in_block(cx: &LateContext<'_>, b: &Block<'_>, func: &F) where F: Fn(Option<&Label>, Span), { + if let Some(expr) = b.expr { + check_last_stmt_in_expr(cx, expr, func); + return; + } + if let Some(last_stmt) = b.stmts.last() - && let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind + && let StmtKind::Expr(inner_expr) | StmtKind::Semi(inner_expr) = last_stmt.kind { - check_last_stmt_in_expr(inner_expr, func); + check_last_stmt_in_expr(cx, inner_expr, func); } } -fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { +fn check_and_warn(cx: &LateContext<'_>, expr: &Expr<'_>) { with_loop_block(expr, |loop_block, label| { let p = |continue_label: Option<&Label>, span: Span| { if compare_labels(label, continue_label) { @@ -427,16 +483,51 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { } }; - let stmts = &loop_block.stmts; + let stmts = loop_block.stmts; for (i, stmt) in stmts.iter().enumerate() { let mut maybe_emitted_in_if = false; - with_if_expr(stmt, |if_expr, cond, then_block, else_expr| { + if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind { + with_if_expr(expr, |if_expr, cond, then_block, else_expr| { + let data = &LintData { + if_expr, + if_cond: cond, + if_block: then_block, + else_expr, + stmt_idx: Some(i), + loop_block, + }; + + maybe_emitted_in_if = true; + if needless_continue_in_else(else_expr, label) { + emit_warning( + cx, + data, + DROP_ELSE_BLOCK_AND_MERGE_MSG, + LintType::ContinueInsideElseBlock, + ); + } else if is_first_block_stmt_continue(then_block, label) { + emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock); + } else { + maybe_emitted_in_if = false; + } + }); + } + + if i == stmts.len() - 1 && loop_block.expr.is_none() && !maybe_emitted_in_if { + check_last_stmt_in_block(cx, loop_block, &p); + } + } + + if let Some(expr) = loop_block.expr { + let mut maybe_emitted_in_if = false; + + with_if_expr(expr, |if_expr, cond, then_block, else_expr| { let data = &LintData { if_expr, if_cond: cond, if_block: then_block, else_expr, - stmt_idx: i, + stmt_idx: None, loop_block, }; @@ -455,8 +546,8 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { } }); - if i == stmts.len() - 1 && !maybe_emitted_in_if { - check_last_stmt_in_block(loop_block, &p); + if !maybe_emitted_in_if { + check_last_stmt_in_block(cx, loop_block, &p); } } }); @@ -491,8 +582,12 @@ fn erode_from_back(s: &str) -> String { if ret.is_empty() { s.to_string() } else { ret } } -fn span_of_first_expr_in_block(block: &Block) -> Option { - block.stmts.first().map(|stmt| stmt.span) +fn span_of_first_expr_in_block(block: &Block<'_>) -> Option { + block + .stmts + .first() + .map(|stmt| stmt.span) + .or(block.expr.map(|expr| expr.span)) } #[cfg(test)] diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 3a6ccc2bca99..d03188f1d39b 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -1,3 +1,4 @@ +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind, TyKind}; @@ -7,8 +8,8 @@ use rustc_span::Span; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; +use clippy_utils::sym; use clippy_utils::ty::has_iter_method; -use clippy_utils::{is_trait_method, sym}; declare_clippy_lint! { /// ### What it does @@ -65,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { ExprKind::Array(..) | ExprKind::Call(..) | ExprKind::Path(..) ) && method_name.ident.name == sym::for_each - && is_trait_method(cx, expr, sym::Iterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) // Checks the type of the `iter` method receiver is NOT a user defined type. && has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some() // Skip the lint if the body is not block because this is simpler than `for` loop. diff --git a/clippy_lints/src/needless_if.rs b/clippy_lints/src/needless_ifs.rs similarity index 88% rename from clippy_lints/src/needless_if.rs rename to clippy_lints/src/needless_ifs.rs index c90019f6ee16..8ec7e47ccc5b 100644 --- a/clippy_lints/src/needless_if.rs +++ b/clippy_lints/src/needless_ifs.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::If; use clippy_utils::is_from_proc_macro; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -29,13 +29,13 @@ declare_clippy_lint! { /// really_expensive_condition_with_side_effects(&mut i); /// ``` #[clippy::version = "1.72.0"] - pub NEEDLESS_IF, + pub NEEDLESS_IFS, complexity, "checks for empty if branches" } -declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]); +declare_lint_pass!(NeedlessIfs => [NEEDLESS_IFS]); -impl LateLintPass<'_> for NeedlessIf { +impl LateLintPass<'_> for NeedlessIfs { fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { if let StmtKind::Expr(expr) = stmt.kind && let Some(If { @@ -56,12 +56,13 @@ impl LateLintPass<'_> for NeedlessIf { src.bytes() .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace()) }) - && let Some(cond_snippet) = cond.span.get_source_text(cx) + && let Some(cond_span) = walk_span_to_context(cond.span, expr.span.ctxt()) + && let Some(cond_snippet) = cond_span.get_source_text(cx) && !is_from_proc_macro(cx, expr) { span_lint_and_sugg( cx, - NEEDLESS_IF, + NEEDLESS_IFS, stmt.span, "this `if` branch is empty", "you can remove it", diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index a914267cf500..464a91959a8e 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::path_to_local; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{SourceText, SpanRangeExt, snippet}; use clippy_utils::ty::needs_ordered_drop; use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures, is_local_used}; @@ -116,7 +116,7 @@ impl LocalAssign { } Some(Self { - lhs_id: path_to_local(lhs)?, + lhs_id: lhs.res_local_id()?, rhs_span: rhs.span.source_callsite(), span, }) diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index ad6313e391bd..4bcd26c74f57 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -33,7 +33,7 @@ declare_clippy_lint! { } declare_lint_pass!(NeedlessMaybeSized => [NEEDLESS_MAYBE_SIZED]); -#[allow(clippy::struct_field_names)] +#[expect(clippy::struct_field_names)] struct Bound<'tcx> { /// The [`DefId`] of the type parameter the bound refers to param: DefId, diff --git a/clippy_lints/src/needless_parens_on_range_literals.rs b/clippy_lints/src/needless_parens_on_range_literals.rs index 021a11593f3a..f270ba7277cb 100644 --- a/clippy_lints/src/needless_parens_on_range_literals.rs +++ b/clippy_lints/src/needless_parens_on_range_literals.rs @@ -74,7 +74,7 @@ fn check_for_parens(cx: &LateContext<'_>, e: &Expr<'_>, is_start: bool) { impl<'tcx> LateLintPass<'tcx> for NeedlessParensOnRangeLiterals { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some(higher::Range { start, end, .. }) = higher::Range::hir(expr) { + if let Some(higher::Range { start, end, .. }) = higher::Range::hir(cx, expr) { if let Some(start) = start { check_for_parens(cx, start, true); } diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 7052e1d0fbe5..3d2285efbe18 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -364,7 +364,6 @@ impl MutablyUsedVariablesCtxt<'_> { } impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { - #[allow(clippy::if_same_then_else)] fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) { if let euv::Place { base: @@ -398,7 +397,6 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { fn use_cloned(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {} - #[allow(clippy::if_same_then_else)] fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId, borrow: ty::BorrowKind) { self.prev_bind = None; if let euv::Place { diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 32ded96c1236..fb5f21acf2af 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -1,16 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ptr::get_spans; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::{ - implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item, -}; -use clippy_utils::{is_self, peel_hir_ty_options}; +use clippy_utils::ty::{implements_trait, implements_trait_with_env_from_iter, is_copy}; +use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{is_self, peel_hir_ty_options, strip_pat_refs, sym}; use rustc_abi::ExternAbi; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Attribute, BindingMode, Body, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability, Node, - PatKind, QPath, TyKind, + Attribute, BindingMode, Body, ExprKind, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability, + Node, PatKind, QPath, TyKind, }; use rustc_hir_typeck::expr_use_visitor as euv; use rustc_lint::{LateContext, LateLintPass}; @@ -19,10 +18,13 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol}; use rustc_trait_selection::traits; use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy; +use std::borrow::Cow; +use std::ops::ControlFlow; + declare_clippy_lint! { /// ### What it does /// Checks for functions taking arguments by value, but not @@ -216,8 +218,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { diag.span_help(span, "or consider marking this type as `Copy`"); } - if is_type_diagnostic_item(cx, ty, sym::Vec) - && let Some(clone_spans) = get_spans(cx, Some(body.id()), idx, &[(sym::clone, ".to_owned()")]) + if ty.is_diag_item(cx, sym::Vec) + && let Some(clone_spans) = get_spans(cx, body, idx, &[(sym::clone, ".to_owned()")]) && let TyKind::Path(QPath::Resolved(_, path)) = input.kind && let Some(elem_ty) = path .segments @@ -259,13 +261,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { return; } - if is_type_lang_item(cx, ty, LangItem::String) - && let Some(clone_spans) = get_spans( - cx, - Some(body.id()), - idx, - &[(sym::clone, ".to_string()"), (sym::as_str, "")], - ) + if ty.is_lang_item(cx, LangItem::String) + && let Some(clone_spans) = + get_spans(cx, body, idx, &[(sym::clone, ".to_string()"), (sym::as_str, "")]) { diag.span_suggestion( input.span, @@ -340,3 +338,43 @@ impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} } + +fn get_spans<'tcx>( + cx: &LateContext<'tcx>, + body: &'tcx Body<'_>, + idx: usize, + replacements: &[(Symbol, &'static str)], +) -> Option)>> { + if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind { + extract_clone_suggestions(cx, binding_id, replacements, body) + } else { + Some(vec![]) + } +} + +fn extract_clone_suggestions<'tcx>( + cx: &LateContext<'tcx>, + id: HirId, + replace: &[(Symbol, &'static str)], + body: &'tcx Body<'_>, +) -> Option)>> { + let mut spans = Vec::new(); + for_each_expr_without_closures(body, |e| { + if let ExprKind::MethodCall(seg, recv, [], _) = e.kind + && recv.res_local_id() == Some(id) + { + if seg.ident.name == sym::capacity { + return ControlFlow::Break(()); + } + for &(fn_name, suffix) in replace { + if seg.ident.name == fn_name { + spans.push((e.span, snippet(cx, recv.span, "_") + suffix)); + return ControlFlow::Continue(Descend::No); + } + } + } + ControlFlow::Continue(Descend::Yes) + }) + .is_none() + .then_some(spans) +} diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 2a2160c3be2d..29c45168b61e 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::path_res; +use clippy_utils::res::MaybeQPath; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Block, Body, Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_hir::{Block, Body, Expr, ExprKind, LangItem, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -94,7 +94,7 @@ impl LateLintPass<'_> for NeedlessQuestionMark { fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Call(path, [arg]) = expr.kind - && let Res::Def(DefKind::Ctor(..), ctor_id) = path_res(cx, path) + && let Res::Def(DefKind::Ctor(..), ctor_id) = path.res(cx) && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) && let variant = if cx.tcx.lang_items().option_some_variant() == Some(variant_id) { "Some" @@ -105,7 +105,8 @@ fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { } && let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar(_)) = &arg.kind && let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind - && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind + && let ExprKind::Path(qpath) = called.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::TryTraitBranch) && expr.span.eq_ctxt(inner_expr.span) && let expr_ty = cx.typeck_results().expr_ty(expr) && let inner_ty = cx.typeck_results().expr_ty(inner_expr) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index b598a390005b..6fc034b6fc5d 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::return_ty; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::DiagExt; use rustc_errors::Applicability; use rustc_hir as hir; @@ -58,116 +58,132 @@ impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { - if let hir::ItemKind::Impl(hir::Impl { + let hir::ItemKind::Impl(hir::Impl { of_trait: None, generics, self_ty: impl_self_ty, .. }) = item.kind + else { + return; + }; + + for assoc_item in cx + .tcx + .associated_items(item.owner_id.def_id) + .filter_by_name_unhygienic(sym::new) { - for assoc_item in cx - .tcx - .associated_items(item.owner_id.def_id) - .filter_by_name_unhygienic(sym::new) + if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind + && let assoc_item_hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local()) + && let impl_item = cx.tcx.hir_node(assoc_item_hir_id).expect_impl_item() + && !impl_item.span.in_external_macro(cx.sess().source_map()) + && let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind + && let id = impl_item.owner_id + // can't be implemented for unsafe new + && !sig.header.is_unsafe() + // shouldn't be implemented when it is hidden in docs + && !cx.tcx.is_doc_hidden(impl_item.owner_id.def_id) + // when the result of `new()` depends on a parameter we should not require + // an impl of `Default` + && impl_item.generics.params.is_empty() + && sig.decl.inputs.is_empty() + && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) + && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && self_ty == return_ty(cx, impl_item.owner_id) + && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) { - if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind { - let impl_item = cx - .tcx - .hir_node_by_def_id(assoc_item.def_id.expect_local()) - .expect_impl_item(); - if impl_item.span.in_external_macro(cx.sess().source_map()) { - return; - } - if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { - let id = impl_item.owner_id; - if sig.header.is_unsafe() { - // can't be implemented for unsafe new - return; - } - if cx.tcx.is_doc_hidden(impl_item.owner_id.def_id) { - // shouldn't be implemented when it is hidden in docs - return; - } - if !impl_item.generics.params.is_empty() { - // when the result of `new()` depends on a parameter we should not require - // an impl of `Default` - return; - } - if sig.decl.inputs.is_empty() - && cx.effective_visibilities.is_reachable(impl_item.owner_id.def_id) - && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() - && self_ty == return_ty(cx, impl_item.owner_id) - && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) + if self.impling_types.is_none() { + let mut impls = HirIdSet::default(); + for &d in cx.tcx.local_trait_impls(default_trait_id) { + let ty = cx.tcx.type_of(d).instantiate_identity(); + if let Some(ty_def) = ty.ty_adt_def() + && let Some(local_def_id) = ty_def.did().as_local() { - if self.impling_types.is_none() { - let mut impls = HirIdSet::default(); - for &d in cx.tcx.local_trait_impls(default_trait_id) { - let ty = cx.tcx.type_of(d).instantiate_identity(); - if let Some(ty_def) = ty.ty_adt_def() - && let Some(local_def_id) = ty_def.did().as_local() - { - impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); - } - } - self.impling_types = Some(impls); - } + impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); + } + } + self.impling_types = Some(impls); + } - // Check if a Default implementation exists for the Self type, regardless of - // generics - if let Some(ref impling_types) = self.impling_types - && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity() - && let Some(self_def) = self_def.ty_adt_def() - && let Some(self_local_did) = self_def.did().as_local() - && let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did) - && impling_types.contains(&self_id) - { - return; - } + // Check if a Default implementation exists for the Self type, regardless of + // generics + if let Some(ref impling_types) = self.impling_types + && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let Some(self_def) = self_def.ty_adt_def() + && let Some(self_local_did) = self_def.did().as_local() + && let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did) + && impling_types.contains(&self_id) + { + return; + } - let generics_sugg = snippet(cx, generics.span, ""); - let where_clause_sugg = if generics.has_where_clause_predicates { - format!("\n{}\n", snippet(cx, generics.where_clause_span, "")) - } else { - String::new() - }; - let self_ty_fmt = self_ty.to_string(); - let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt); - span_lint_hir_and_then( - cx, - NEW_WITHOUT_DEFAULT, - id.into(), - impl_item.span, - format!("you should consider adding a `Default` implementation for `{self_type_snip}`"), - |diag| { - diag.suggest_prepend_item( - cx, - item.span, - "try adding this", - &create_new_without_default_suggest_msg( - &self_type_snip, - &generics_sugg, - &where_clause_sugg, - ), - Applicability::MachineApplicable, - ); - }, - ); + let mut app = Applicability::MachineApplicable; + let attrs_sugg = { + let mut sugg = String::new(); + for attr in cx.tcx.hir_attrs(assoc_item_hir_id) { + if !attr.has_name(sym::cfg_trace) { + // This might be some other attribute that the `impl Default` ought to inherit. + // But it could also be one of the many attributes that: + // - can't be put on an impl block -- like `#[inline]` + // - we can't even build a suggestion for, since `Attribute::span` may panic. + // + // Because of all that, remain on the safer side -- don't inherit this attr, and just + // reduce the applicability + app = Applicability::MaybeIncorrect; + continue; } + + sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app)); + sugg.push('\n'); } - } + sugg + }; + let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app); + let where_clause_sugg = if generics.has_where_clause_predicates { + format!( + "\n{}\n", + snippet_with_applicability(cx, generics.where_clause_span, "", &mut app) + ) + } else { + String::new() + }; + let self_ty_fmt = self_ty.to_string(); + let self_type_snip = snippet_with_applicability(cx, impl_self_ty.span, &self_ty_fmt, &mut app); + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id.into(), + impl_item.span, + format!("you should consider adding a `Default` implementation for `{self_type_snip}`"), + |diag| { + diag.suggest_prepend_item( + cx, + item.span, + "try adding this", + &create_new_without_default_suggest_msg( + &attrs_sugg, + &self_type_snip, + &generics_sugg, + &where_clause_sugg, + ), + app, + ); + }, + ); } } } } fn create_new_without_default_suggest_msg( + attrs_sugg: &str, self_type_snip: &str, generics_sugg: &str, where_clause_sugg: &str, ) -> String { #[rustfmt::skip] format!( -"impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{ +"{attrs_sugg}impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{ fn default() -> Self {{ Self::new() }} diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 0d6666eed455..fdb8e1b475c1 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -1,9 +1,8 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::{expr_type_is_certain, has_drop}; -use clippy_utils::{ - in_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks, -}; +use clippy_utils::{in_automatically_derived, is_inside_always_const_context, is_lint_allowed, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ @@ -109,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect { } fn check_expr(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let Some(def_id) = path_to_local(expr) { + if let Some(def_id) = expr.res_local_id() { self.underscore_bindings.swap_remove(&def_id); } } @@ -123,7 +122,7 @@ impl NoEffect { return true; } - if expr.span.from_expansion() { + if expr.range_span().unwrap_or(expr.span).from_expansion() { return false; } let expr = peel_blocks(expr); @@ -274,7 +273,7 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { if let StmtKind::Semi(expr) = stmt.kind && !stmt.span.in_external_macro(cx.sess().source_map()) && let ctxt = stmt.span.ctxt() - && expr.span.ctxt() == ctxt + && expr.range_span().unwrap_or(expr.span).ctxt() == ctxt && let Some(reduced) = reduce_expression(cx, expr) && reduced.iter().all(|e| e.span.ctxt() == ctxt) { @@ -331,7 +330,7 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { } fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option>> { - if expr.span.from_expansion() { + if expr.range_span().unwrap_or(expr.span).from_expansion() { return None; } match expr.kind { diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index ba67dc62abbd..e66c088617cb 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::ty::implements_trait; -use clippy_utils::{ - is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core, -}; +use clippy_utils::{is_from_proc_macro, last_path_segment, std_or_core}; use rustc_errors::Applicability; -use rustc_hir::def_id::LocalDefId; -use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp}; +use rustc_hir::def_id::DefId; +use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::EarlyBinder; -use rustc_session::declare_lint_pass; +use rustc_middle::ty::{TyCtxt, TypeckResults}; +use rustc_session::impl_lint_pass; use rustc_span::sym; use rustc_span::symbol::kw; @@ -109,143 +108,210 @@ declare_clippy_lint! { suspicious, "non-canonical implementation of `PartialOrd` on an `Ord` type" } -declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]); +impl_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]); + +#[expect( + clippy::struct_field_names, + reason = "`_trait` suffix is meaningful on its own, \ + and creating an inner `StoredTraits` struct would just add a level of indirection" +)] +pub(crate) struct NonCanonicalImpls { + partial_ord_trait: Option, + ord_trait: Option, + clone_trait: Option, + copy_trait: Option, +} + +impl NonCanonicalImpls { + pub(crate) fn new(tcx: TyCtxt<'_>) -> Self { + let lang_items = tcx.lang_items(); + Self { + partial_ord_trait: lang_items.partial_ord_trait(), + ord_trait: tcx.get_diagnostic_item(sym::Ord), + clone_trait: lang_items.clone_trait(), + copy_trait: lang_items.copy_trait(), + } + } +} + +/// The traits that this lint looks at +enum Trait { + Clone, + PartialOrd, +} impl LateLintPass<'_> for NonCanonicalImpls { - #[expect(clippy::too_many_lines)] - fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) { - let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else { - return; - }; - let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else { - return; - }; - if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) { - return; + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if let ItemKind::Impl(impl_) = item.kind + // Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total + && (1..=5).contains(&impl_.items.len()) + && let Some(of_trait) = impl_.of_trait + && let Some(trait_did) = of_trait.trait_ref.trait_def_id() + // Check this early to hopefully bail out as soon as possible + && let trait_ = if Some(trait_did) == self.clone_trait { + Trait::Clone + } else if Some(trait_did) == self.partial_ord_trait { + Trait::PartialOrd + } else { + return; + } + && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) + { + let mut assoc_fns = impl_ + .items + .iter() + .map(|id| cx.tcx.hir_impl_item(*id)) + .filter_map(|assoc| { + if let ImplItemKind::Fn(_, body_id) = assoc.kind + && let body = cx.tcx.hir_body(body_id) + && let ExprKind::Block(block, ..) = body.value.kind + && !block.span.in_external_macro(cx.sess().source_map()) + { + Some((assoc, body, block)) + } else { + None + } + }); + + let trait_impl = cx.tcx.impl_trait_ref(item.owner_id).skip_binder(); + + match trait_ { + Trait::Clone => { + if let Some(copy_trait) = self.copy_trait + && implements_trait(cx, trait_impl.self_ty(), copy_trait, &[]) + { + for (assoc, _, block) in assoc_fns { + check_clone_on_copy(cx, assoc, block); + } + } + }, + Trait::PartialOrd => { + // If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible, + // since it doesn't have an `Rhs` + if let [lhs, rhs] = trait_impl.args.as_slice() + && lhs == rhs + && let Some(ord_trait) = self.ord_trait + && implements_trait(cx, trait_impl.self_ty(), ord_trait, &[]) + && let Some((assoc, body, block)) = + assoc_fns.find(|(assoc, _, _)| assoc.ident.name == sym::partial_cmp) + { + check_partial_ord_on_ord(cx, assoc, item, body, block); + } + }, + } } - let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind else { - return; - }; - let body = cx.tcx.hir_body(impl_item_id); - let ExprKind::Block(block, ..) = body.value.kind else { + } +} + +fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) { + if impl_item.ident.name == sym::clone { + if block.stmts.is_empty() + && let Some(expr) = block.expr + && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind + && let ExprKind::Path(qpath) = deref.kind + && last_path_segment(&qpath).ident.name == kw::SelfLower + { + // this is the canonical implementation, `fn clone(&self) -> Self { *self }` return; - }; - if block.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, impl_item) { + } + + if is_from_proc_macro(cx, impl_item) { return; } - let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id); - if trait_name == Some(sym::Clone) - && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) - && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) - { - if impl_item.ident.name == sym::clone { - if block.stmts.is_empty() - && let Some(expr) = block.expr - && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind - && let ExprKind::Path(qpath) = deref.kind - && last_path_segment(&qpath).ident.name == kw::SelfLower - { - } else { - span_lint_and_sugg( - cx, - NON_CANONICAL_CLONE_IMPL, - block.span, - "non-canonical implementation of `clone` on a `Copy` type", - "change this to", - "{ *self }".to_owned(), - Applicability::MaybeIncorrect, - ); + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + block.span, + "non-canonical implementation of `clone` on a `Copy` type", + "change this to", + "{ *self }".to_owned(), + Applicability::MaybeIncorrect, + ); + } - return; - } - } + if impl_item.ident.name == sym::clone_from && !is_from_proc_macro(cx, impl_item) { + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + impl_item.span, + "unnecessary implementation of `clone_from` on a `Copy` type", + "remove it", + String::new(), + Applicability::MaybeIncorrect, + ); + } +} - if impl_item.ident.name == sym::clone_from { - span_lint_and_sugg( - cx, - NON_CANONICAL_CLONE_IMPL, - impl_item.span, - "unnecessary implementation of `clone_from` on a `Copy` type", - "remove it", - String::new(), - Applicability::MaybeIncorrect, - ); - } - } else if trait_name == Some(sym::PartialOrd) - && impl_item.ident.name == sym::partial_cmp - && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) - && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) - { - // If the `cmp` call likely needs to be fully qualified in the suggestion - // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't - // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. - let mut needs_fully_qualified = false; +fn check_partial_ord_on_ord<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &ImplItem<'_>, + item: &Item<'_>, + body: &Body<'_>, + block: &Block<'tcx>, +) { + // If the `cmp` call likely needs to be fully qualified in the suggestion + // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't + // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. - if block.stmts.is_empty() - && let Some(expr) = block.expr - && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) - { - return; - } - // Fix #12683, allow [`needless_return`] here - else if block.expr.is_none() - && let Some(stmt) = block.stmts.first() - && let rustc_hir::StmtKind::Semi(Expr { - kind: ExprKind::Ret(Some(ret)), - .. - }) = stmt.kind - && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) - { + let mut needs_fully_qualified = false; + if block.stmts.is_empty() + && let Some(expr) = block.expr + && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) + { + return; + } + // Fix #12683, allow [`needless_return`] here + else if block.expr.is_none() + && let Some(stmt) = block.stmts.first() + && let rustc_hir::StmtKind::Semi(Expr { + kind: ExprKind::Ret(Some(ret)), + .. + }) = stmt.kind + && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) + { + return; + } else if is_from_proc_macro(cx, impl_item) { + return; + } + + span_lint_and_then( + cx, + NON_CANONICAL_PARTIAL_ORD_IMPL, + item.span, + "non-canonical implementation of `partial_cmp` on an `Ord` type", + |diag| { + let [_, other] = body.params else { return; - } - // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid - // suggestion tons more complex. - else if let [lhs, rhs, ..] = trait_impl.args.as_slice() - && lhs != rhs - { + }; + let Some(std_or_core) = std_or_core(cx) else { return; - } - - span_lint_and_then( - cx, - NON_CANONICAL_PARTIAL_ORD_IMPL, - item.span, - "non-canonical implementation of `partial_cmp` on an `Ord` type", - |diag| { - let [_, other] = body.params else { - return; - }; - let Some(std_or_core) = std_or_core(cx) else { - return; - }; - - let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { - (Some(other_ident), true) => vec![( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), - )], - (Some(other_ident), false) => { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - }, - (None, true) => vec![ - ( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), - ), - (other.pat.span, "other".to_owned()), - ], - (None, false) => vec![ - (block.span, "{ Some(self.cmp(other)) }".to_owned()), - (other.pat.span, "other".to_owned()), - ], - }; + }; - diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] }, - ); - } - } + (None, true) => vec![ + ( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ + (block.span, "{ Some(self.cmp(other)) }".to_owned()), + (other.pat.span, "other".to_owned()), + ], + }; + + diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); + }, + ); } /// Return true if `expr_kind` is a `cmp` call. @@ -255,41 +321,40 @@ fn expr_is_cmp<'tcx>( impl_item: &ImplItem<'_>, needs_fully_qualified: &mut bool, ) -> bool { - let impl_item_did = impl_item.owner_id.def_id; - if let ExprKind::Call( - Expr { - kind: ExprKind::Path(some_path), - hir_id: some_hir_id, - .. + let typeck = cx.tcx.typeck(impl_item.owner_id.def_id); + match expr.kind { + ExprKind::Call( + Expr { + kind: ExprKind::Path(some_path), + hir_id: some_hir_id, + .. + }, + [cmp_expr], + ) => { + typeck.qpath_res(some_path, *some_hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + // Fix #11178, allow `Self::cmp(self, ..)` + && self_cmp_call(cx, typeck, cmp_expr, needs_fully_qualified) }, - [cmp_expr], - ) = expr.kind - { - is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) - // Fix #11178, allow `Self::cmp(self, ..)` too - && self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified) - } else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind { - cx.tcx - .typeck(impl_item_did) - .type_dependent_def_id(expr.hir_id) - .is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into)) - && self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified) - } else { - false + ExprKind::MethodCall(_, recv, [], _) => { + typeck + .type_dependent_def(expr.hir_id) + .assoc_parent(cx) + .is_diag_item(cx, sym::Into) + && self_cmp_call(cx, typeck, recv, needs_fully_qualified) + }, + _ => false, } } /// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`. fn self_cmp_call<'tcx>( cx: &LateContext<'tcx>, + typeck: &TypeckResults<'tcx>, cmp_expr: &'tcx Expr<'tcx>, - def_id: LocalDefId, needs_fully_qualified: &mut bool, ) -> bool { match cmp_expr.kind { - ExprKind::Call(path, [_, _]) => path_res(cx, path) - .opt_def_id() - .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), + ExprKind::Call(path, [_, _]) => path.res(typeck).is_diag_item(cx, sym::ord_cmp_method), ExprKind::MethodCall(_, recv, [_], ..) => { let ExprKind::Path(path) = recv.kind else { return false; @@ -302,11 +367,7 @@ fn self_cmp_call<'tcx>( // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp` *needs_fully_qualified = true; - // It's a bit annoying but `typeck_results` only gives us the CURRENT body, which we - // have none, not of any `LocalDefId` we want, so we must call the query itself to avoid - // an immediate ICE - cx.tcx - .typeck(def_id) + typeck .type_dependent_def_id(cmp_expr.hir_id) .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)) }, 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 b810bc01fbdc..fd5562f310e4 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { && let Some(trait_id) = of_trait.trait_ref.trait_def_id() && send_trait == trait_id && of_trait.polarity == ImplPolarity::Positive - && let Some(ty_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id) + && let ty_trait_ref = cx.tcx.impl_trait_ref(item.owner_id) && let self_ty = ty_trait_ref.instantiate_identity().self_ty() && let ty::Adt(adt_def, impl_trait_args) = self_ty.kind() { diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs index 7ecde40aee01..61e4d419fdf0 100644 --- a/clippy_lints/src/non_std_lazy_statics.rs +++ b/clippy_lints/src/non_std_lazy_statics.rs @@ -2,8 +2,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::paths::{self, PathNS, find_crates, lookup_path_str}; +use clippy_utils::res::MaybeResPath; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{fn_def_id, is_no_std_crate, path_def_id, sym}; +use clippy_utils::{fn_def_id, is_no_std_crate, sym}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -188,7 +189,7 @@ impl LazyInfo { fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Option { // Check if item is a `once_cell:sync::Lazy` static. if let ItemKind::Static(_, _, ty, body_id) = item.kind - && let Some(path_def_id) = path_def_id(cx, ty) + && let Some(path_def_id) = ty.basic_res().opt_def_id() && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind && paths::ONCE_CELL_SYNC_LAZY.matches(cx, path_def_id) { @@ -219,7 +220,7 @@ impl LazyInfo { fn lint(&self, cx: &LateContext<'_>, sugg_map: &FxIndexMap>) { // Applicability might get adjusted to `Unspecified` later if any calls // in `calls_span_and_id` are not replaceable judging by the `sugg_map`. - let mut appl = Applicability::MachineApplicable; + let mut app = Applicability::MachineApplicable; let mut suggs = vec![(self.ty_span_no_args, "std::sync::LazyLock".to_string())]; for (span, def_id) in &self.calls_span_and_id { @@ -228,7 +229,7 @@ impl LazyInfo { suggs.push((*span, sugg)); } else { // If NO suggested replacement, not machine applicable - appl = Applicability::Unspecified; + app = Applicability::Unspecified; } } @@ -239,7 +240,7 @@ impl LazyInfo { self.ty_span_no_args, "this type has been superseded by `LazyLock` in the standard library", |diag| { - diag.multipart_suggestion("use `std::sync::LazyLock` instead", suggs, appl); + diag.multipart_suggestion("use `std::sync::LazyLock` instead", suggs, app); }, ); } diff --git a/clippy_lints/src/nonstandard_macro_braces.rs b/clippy_lints/src/nonstandard_macro_braces.rs index 83f7d9319697..3a8a4dd0c713 100644 --- a/clippy_lints/src/nonstandard_macro_braces.rs +++ b/clippy_lints/src/nonstandard_macro_braces.rs @@ -16,8 +16,8 @@ declare_clippy_lint! { /// Checks that common macros are used with consistent bracing. /// /// ### Why is this bad? - /// This is mostly a consistency lint although using () or [] - /// doesn't give you a semicolon in item position, which can be unexpected. + /// Having non-conventional braces on well-stablished macros can be confusing + /// when debugging, and they bring incosistencies with the rest of the ecosystem. /// /// ### Example /// ```no_run @@ -33,8 +33,12 @@ declare_clippy_lint! { "check consistent use of braces in macro" } -/// The (callsite span, (open brace, close brace), source snippet) -type MacroInfo = (Span, (char, char), SourceText); +struct MacroInfo { + callsite_span: Span, + callsite_snippet: SourceText, + old_open_brace: char, + braces: (char, char), +} pub struct MacroBraces { macro_braces: FxHashMap, @@ -54,30 +58,58 @@ impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]); impl EarlyLintPass for MacroBraces { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { - if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + .. + }) = is_offending_macro(cx, item.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { - if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + old_open_brace, + }) = is_offending_macro(cx, stmt.span, self) + { + // if we turn `macro!{}` into `macro!()`/`macro![]`, we'll no longer get the implicit + // trailing semicolon, see #9913 + // NOTE: `stmt.kind != StmtKind::MacCall` because `EarlyLintPass` happens after macro expansion + let add_semi = matches!(stmt.kind, ast::StmtKind::Expr(..)) && old_open_brace == '{'; + emit_help(cx, &callsite_snippet, braces, callsite_span, add_semi); + self.done.insert(callsite_span); } } fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + .. + }) = is_offending_macro(cx, expr.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { - if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + braces, + callsite_snippet, + .. + }) = is_offending_macro(cx, ty.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } } @@ -90,39 +122,44 @@ fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBrace .last() .is_some_and(|e| e.macro_def_id.is_some_and(DefId::is_local)) }; - let span_call_site = span.ctxt().outer_expn_data().call_site; + let callsite_span = span.ctxt().outer_expn_data().call_site; if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind && let name = mac_name.as_str() && let Some(&braces) = mac_braces.macro_braces.get(name) - && let Some(snip) = span_call_site.get_source_text(cx) + && let Some(snip) = callsite_span.get_source_text(cx) // we must check only invocation sites // https://github.com/rust-lang/rust-clippy/issues/7422 - && snip.starts_with(&format!("{name}!")) + && let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!')) + && let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next() + && old_open_brace != braces.0 && unnested_or_local() - // make formatting consistent - && let c = snip.replace(' ', "") - && !c.starts_with(&format!("{name}!{}", braces.0)) - && !mac_braces.done.contains(&span_call_site) + && !mac_braces.done.contains(&callsite_span) { - Some((span_call_site, braces, snip)) + Some(MacroInfo { + callsite_span, + callsite_snippet: snip, + old_open_brace, + braces, + }) } else { None } } -fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span) { +fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) { + let semi = if add_semi { ";" } else { "" }; if let Some((macro_name, macro_args_str)) = snip.split_once('!') { let mut macro_args = macro_args_str.trim().to_string(); // now remove the wrong braces - macro_args.remove(0); macro_args.pop(); + macro_args.remove(0); span_lint_and_sugg( cx, NONSTANDARD_MACRO_BRACES, span, format!("use of irregular braces for `{macro_name}!` macro"), "consider writing", - format!("{macro_name}!{open}{macro_args}{close}"), + format!("{macro_name}!{open}{macro_args}{close}{semi}"), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index a42763172f56..f756f94d97b3 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -1,13 +1,16 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{get_expr_use_or_unification_node, path_def_id, path_to_local, path_to_local_id}; +use clippy_utils::get_expr_use_or_unification_node; +use clippy_utils::res::{MaybeQPath, MaybeResPath}; use core::cell::Cell; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdMap; -use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind}; +use rustc_hir::{ + Body, Expr, ExprKind, HirId, ImplItem, ImplItemImplKind, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, ConstKind, EarlyBinder, GenericArgKind, GenericArgsRef}; +use rustc_middle::ty::{self, ConstKind, GenericArgKind, GenericArgsRef}; use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::symbol::{Ident, kw}; @@ -24,6 +27,33 @@ declare_clippy_lint! { /// the calculations have no side-effects (function calls or mutating dereference) /// and the assigned variables are also only in recursion, it is useless. /// + /// ### Example + /// ```no_run + /// 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: + /// ```no_run + /// fn f(a: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1)); + /// # } + /// ``` + /// /// ### 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. @@ -51,39 +81,90 @@ declare_clippy_lint! { /// - struct pattern binding /// /// Also, when you recurse the function name with path segments, it is not possible to detect. + #[clippy::version = "1.61.0"] + pub ONLY_USED_IN_RECURSION, + complexity, + "arguments that is only used in recursion can be removed" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `self` receiver that is only used in recursion with no side-effects. + /// + /// ### Why is this bad? + /// + /// It may be possible to remove the `self` argument, allowing the function to be + /// used without an object of type `Self`. /// /// ### Example /// ```no_run - /// fn f(a: usize, b: usize) -> usize { - /// if a == 0 { - /// 1 - /// } else { - /// f(a - 1, b + 1) + /// struct Foo; + /// impl Foo { + /// fn f(&self, n: u32) -> u32 { + /// if n == 0 { + /// 1 + /// } else { + /// n * self.f(n - 1) + /// } /// } /// } /// # fn main() { - /// # print!("{}", f(1, 1)); + /// # print!("{}", Foo.f(10)); /// # } /// ``` /// Use instead: /// ```no_run - /// fn f(a: usize) -> usize { - /// if a == 0 { - /// 1 - /// } else { - /// f(a - 1) + /// struct Foo; + /// impl Foo { + /// fn f(n: u32) -> u32 { + /// if n == 0 { + /// 1 + /// } else { + /// n * Self::f(n - 1) + /// } /// } /// } /// # fn main() { - /// # print!("{}", f(1)); + /// # print!("{}", Foo::f(10)); /// # } /// ``` - #[clippy::version = "1.61.0"] - pub ONLY_USED_IN_RECURSION, - complexity, - "arguments that is only used in recursion can be removed" + /// + /// ### 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. + /// + /// ```no_run + /// struct Foo; + /// impl Foo { + /// fn foo(&self, a: usize) -> usize { + /// let f = |x| x; + /// + /// if a == 0 { + /// 1 + /// } else { + /// f(self).foo(a) + /// } + /// } + /// } + /// ``` + /// + /// For example, here `self` 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. + #[clippy::version = "1.92.0"] + pub SELF_ONLY_USED_IN_RECURSION, + pedantic, + "self receiver only used to recursively call method can be removed" } -impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); +impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION, SELF_ONLY_USED_IN_RECURSION]); #[derive(Clone, Copy)] enum FnKind { @@ -134,7 +215,7 @@ impl Usage { /// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the /// `DefId` of the function paired with the parameter's index. #[derive(Default)] -#[allow(clippy::struct_field_names)] +#[expect(clippy::struct_field_names)] struct Params { params: Vec, by_id: HirIdMap, @@ -241,18 +322,19 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { Node::ImplItem(&ImplItem { kind: ImplItemKind::Fn(ref sig, _), owner_id, + impl_kind, .. }) => { - if let Node::Item(item) = cx.tcx.parent_hir_node(owner_id.into()) - && let Some(trait_ref) = cx - .tcx - .impl_trait_ref(item.owner_id) - .map(EarlyBinder::instantiate_identity) - && let Some(trait_item_id) = cx.tcx.associated_item(owner_id).trait_item_def_id + if let ImplItemImplKind::Trait { trait_item_def_id, .. } = impl_kind + && let Ok(trait_item_id) = trait_item_def_id { + let impl_id = cx.tcx.parent(owner_id.into()); + let trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity(); ( trait_item_id, - FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_regions(trait_ref.args)) as usize), + FnKind::ImplTraitFn( + std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize + ), usize::from(sig.decl.implicit_self.has_implicit_self()), ) } else { @@ -278,7 +360,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { } fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) { - if let Some(id) = path_to_local(e) + if let Some(id) = e.res_local_id() && let Some(param) = self.params.get_by_id_mut(id) { let typeck = cx.typeck_results(); @@ -290,7 +372,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { Some((Node::Expr(parent), child_id)) => match parent.kind { // Recursive call. Track which index the parameter is used in. ExprKind::Call(callee, args) - if path_def_id(cx, callee).is_some_and(|id| { + if callee.res(cx).opt_def_id().is_some_and(|id| { id == param.fn_id && has_matching_args(param.fn_kind, typeck.node_args(callee.hir_id)) }) => { @@ -315,7 +397,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { }, // Parameter update e.g. `x = x + 1` ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs) - if rhs.hir_id == child_id && path_to_local_id(lhs, id) => + if rhs.hir_id == child_id && lhs.res_local_id() == Some(id) => { return; }, @@ -355,26 +437,39 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { self.params.flag_for_linting(); for param in &self.params.params { if param.apply_lint.get() { - span_lint_and_then( - cx, - ONLY_USED_IN_RECURSION, - param.ident.span, - "parameter is only used in recursion", - |diag| { - if param.ident.name != kw::SelfLower { + if param.ident.name == kw::SelfLower { + span_lint_and_then( + cx, + SELF_ONLY_USED_IN_RECURSION, + param.ident.span, + "`self` is only used in recursion", + |diag| { + diag.span_note( + param.uses.iter().map(|x| x.span).collect::>(), + "`self` used here", + ); + }, + ); + } else { + span_lint_and_then( + cx, + ONLY_USED_IN_RECURSION, + param.ident.span, + "parameter is only used in recursion", + |diag| { diag.span_suggestion( param.ident.span, "if this is intentional, prefix it with an underscore", format!("_{}", param.ident.name), Applicability::MaybeIncorrect, ); - } - diag.span_note( - param.uses.iter().map(|x| x.span).collect::>(), - "parameter used here", - ); - }, - ); + diag.span_note( + param.uses.iter().map(|x| x.span).collect::>(), + "parameter used here", + ); + }, + ); + } } } self.params.clear(); diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index ea5b81aec31e..0a6499e09583 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -2,7 +2,7 @@ use super::ARITHMETIC_SIDE_EFFECTS; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; @@ -108,9 +108,7 @@ impl ArithmeticSideEffects { rhs_ty: Ty<'tcx>, ) -> bool { let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_sat_or_wrap = |ty: Ty<'_>| { - is_type_diagnostic_item(cx, ty, sym::Saturating) || is_type_diagnostic_item(cx, ty, sym::Wrapping) - }; + let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); // If the RHS is `NonZero`, then division or module by zero will never occur. if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { @@ -190,7 +188,7 @@ impl ArithmeticSideEffects { lhs: &'tcx hir::Expr<'_>, rhs: &'tcx hir::Expr<'_>, ) { - if ConstEvalCtxt::new(cx).eval_simple(expr).is_some() { + if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_some() { return; } if !matches!( @@ -283,7 +281,7 @@ impl ArithmeticSideEffects { let Some(arg) = args.first() else { return; }; - if ConstEvalCtxt::new(cx).eval_simple(receiver).is_some() { + if ConstEvalCtxt::new(cx).eval_local(receiver, expr.span.ctxt()).is_some() { return; } let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); diff --git a/clippy_lints/src/operators/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs index e87cfd103c30..d6af0234f010 100644 --- a/clippy_lints/src/operators/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -47,7 +47,6 @@ fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, } } -#[allow(clippy::too_many_lines)] fn check_bit_mask( cx: &LateContext<'_>, bit_op: BinOpKind, diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 604f8f5da0b8..05358de5b348 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::path_def_id; +use clippy_utils::res::MaybeQPath; use clippy_utils::source::snippet; use clippy_utils::ty::{implements_trait, is_copy}; use rustc_errors::Applicability; @@ -47,11 +47,14 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { - Some(sym::from_str_method) => true, - Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), - _ => false, - }) => + if path + .res(cx) + .opt_def_id() + .is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { + Some(sym::from_str_method) => true, + Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, + }) => { (arg, arg.span) }, diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 10455d3b93a0..56001a185771 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -22,7 +22,7 @@ fn comparison_to_const<'tcx>( cx: &LateContext<'tcx>, typeck: &'tcx TypeckResults<'tcx>, expr: &'tcx Expr<'tcx>, -) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> { +) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant, Ty<'tcx>)> { if let ExprKind::Binary(operator, left, right) = expr.kind && let Ok(cmp_op) = CmpOp::try_from(operator.node) { diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs index 54f50f11e034..71982023779e 100644 --- a/clippy_lints/src/operators/double_comparison.rs +++ b/clippy_lints/src/operators/double_comparison.rs @@ -8,46 +8,52 @@ use rustc_span::Span; use super::DOUBLE_COMPARISONS; -pub(super) fn check<'tcx>(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)) => { - (lb.node, llhs, lrhs, rb.node, rlhs, rrhs) - }, - _ => return, - }; - if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) { - return; - } - macro_rules! lint_double_comparison { - ($op:tt) => {{ - let mut applicability = Applicability::MachineApplicable; - let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); - let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); - let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str); - span_lint_and_sugg( - cx, - DOUBLE_COMPARISONS, - span, - "this binary expression can be simplified", - "try", - sugg, - applicability, - ); - }}; - } - match (op, lkind, rkind) { - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { - lint_double_comparison!(<=); - }, - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { - lint_double_comparison!(>=); - }, - (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { - lint_double_comparison!(!=); - }, - (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { - lint_double_comparison!(==); - }, - _ => (), +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) { + if let ExprKind::Binary(lop, llhs, lrhs) = lhs.kind + && let ExprKind::Binary(rop, rlhs, rrhs) = rhs.kind + && eq_expr_value(cx, llhs, rlhs) + && eq_expr_value(cx, lrhs, rrhs) + { + let op = match (op, lop.node, rop.node) { + // x == y || x < y => x <= y + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) + // x < y || x == y => x <= y + | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { + "<=" + }, + // x == y || x > y => x >= y + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) + // x > y || x == y => x >= y + | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { + ">=" + }, + // x < y || x > y => x != y + (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) + // x > y || x < y => x != y + | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { + "!=" + }, + // x <= y && x >= y => x == y + (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) + // x >= y && x <= y => x == y + | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { + "==" + }, + _ => return, + }; + + let mut applicability = Applicability::MachineApplicable; + let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); + let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); + let sugg = format!("{lhs_str} {op} {rhs_str}"); + span_lint_and_sugg( + cx, + DOUBLE_COMPARISONS, + span, + "this binary expression can be simplified", + "try", + sugg, + applicability, + ); } } diff --git a/clippy_lints/src/operators/duration_subsec.rs b/clippy_lints/src/operators/duration_subsec.rs index 6c9be7c5e90b..4a1da7e07a88 100644 --- a/clippy_lints/src/operators/duration_subsec.rs +++ b/clippy_lints/src/operators/duration_subsec.rs @@ -1,8 +1,8 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sym; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -18,8 +18,12 @@ pub(crate) fn check<'tcx>( ) { if op == BinOpKind::Div && let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration) - && let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval(right) + && cx + .typeck_results() + .expr_ty(self_arg) + .peel_refs() + .is_diag_item(cx, sym::Duration) + && let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval_local(right, expr.span.ctxt()) { let suggested_fn = match (method_path.ident.name, divisor) { (sym::subsec_micros, 1_000) | (sym::subsec_nanos, 1_000_000) => "subsec_millis", diff --git a/clippy_lints/src/operators/erasing_op.rs b/clippy_lints/src/operators/erasing_op.rs index e3fc8d8fea7d..ae1a94a3e23f 100644 --- a/clippy_lints/src/operators/erasing_op.rs +++ b/clippy_lints/src/operators/erasing_op.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::same_type_and_consts; +use clippy_utils::ty::same_type_modulo_regions; use rustc_hir::{BinOpKind, Expr}; use rustc_lint::LateContext; @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool { let input_ty = tck.expr_ty(input).peel_refs(); let output_ty = tck.expr_ty(output).peel_refs(); - !same_type_and_consts(input_ty, output_ty) + !same_type_modulo_regions(input_ty, output_ty) } fn check_op<'tcx>( @@ -39,7 +39,9 @@ fn check_op<'tcx>( other: &Expr<'tcx>, parent: &Expr<'tcx>, ) { - if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_simple(op) == Some(Constant::Int(0)) { + if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_local(op, parent.span.ctxt()) + == Some(Constant::Int(0)) + { if different_types(tck, other, parent) { return; } diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index ded161c8576a..eb2353cfd90b 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -18,12 +18,13 @@ pub(crate) fn check<'tcx>( ) { if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) { let ecx = ConstEvalCtxt::new(cx); - let left_is_local = match ecx.eval_with_source(left) { + let ctxt = expr.span.ctxt(); + let left_is_local = match ecx.eval_with_source(left, ctxt) { Some((c, s)) if !is_allowed(&c) => s.is_local(), Some(_) => return, None => true, }; - let right_is_local = match ecx.eval_with_source(right) { + let right_is_local = match ecx.eval_with_source(right, ctxt) { Some((c, s)) if !is_allowed(&c) => s.is_local(), Some(_) => return, None => true, @@ -84,7 +85,7 @@ fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static } } -fn is_allowed(val: &Constant<'_>) -> bool { +fn is_allowed(val: &Constant) -> bool { match val { // FIXME(f16_f128): add when equality check is available on all platforms &Constant::F32(f) => f == 0.0 || f.is_infinite(), diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 3efbb8963587..43c62e1e131a 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -7,7 +7,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::{Span, kw}; +use rustc_span::{Span, SyntaxContext, kw}; use super::IDENTITY_OP; @@ -41,42 +41,43 @@ pub(crate) fn check<'tcx>( (span, is_coerced) }; + let ctxt = expr.span.ctxt(); match op { BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - if is_redundant_op(cx, left, 0) { + if is_redundant_op(cx, left, 0, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, 0) { + } else if is_redundant_op(cx, right, 0, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - if is_redundant_op(cx, right, 0) { + if is_redundant_op(cx, right, 0, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Mul => { - if is_redundant_op(cx, left, 1) { + if is_redundant_op(cx, left, 1, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, 1) { + } else if is_redundant_op(cx, right, 1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Div => { - if is_redundant_op(cx, right, 1) { + if is_redundant_op(cx, right, 1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::BitAnd => { - if is_redundant_op(cx, left, -1) { + if is_redundant_op(cx, left, -1, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, -1) { + } else if is_redundant_op(cx, right, -1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } @@ -184,14 +185,17 @@ fn is_allowed<'tcx>( // This lint applies to integers and their references cx.typeck_results().expr_ty(left).peel_refs().is_integral() - && cx.typeck_results().expr_ty(right).peel_refs().is_integral() + && cx.typeck_results().expr_ty(right).peel_refs().is_integral() // `1 << 0` is a common pattern in bit manipulation code - && !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1)) + && !(cmp == BinOpKind::Shl + && is_zero_integer_const(cx, right, expr.span.ctxt()) + && integer_const(cx, left, expr.span.ctxt()) == Some(1)) } fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { let ecx = ConstEvalCtxt::new(cx); - if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) { + let ctxt = span.ctxt(); + if match (ecx.eval_full_int(left, ctxt), ecx.eval_full_int(right, ctxt)) { (Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(), (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv, _ => return, @@ -200,8 +204,8 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span } } -fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool { - if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) { +fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, ctxt: SyntaxContext) -> bool { + if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_local(e, ctxt).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), ty::Uint(uty) => clip(cx.tcx, !0, uty), diff --git a/clippy_lints/src/operators/integer_division.rs b/clippy_lints/src/operators/integer_division.rs index 7b98afa9b40b..1620312474e9 100644 --- a/clippy_lints/src/operators/integer_division.rs +++ b/clippy_lints/src/operators/integer_division.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -16,7 +16,7 @@ pub(crate) fn check<'tcx>( if op == hir::BinOpKind::Div && cx.typeck_results().expr_ty(left).is_integral() && let right_ty = cx.typeck_results().expr_ty(right) - && (right_ty.is_integral() || is_type_diagnostic_item(cx, right_ty, sym::NonZero)) + && (right_ty.is_integral() || right_ty.is_diag_item(cx, sym::NonZero)) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then(cx, INTEGER_DIVISION, expr.span, "integer division", |diag| { diff --git a/clippy_lints/src/operators/integer_division_remainder_used.rs b/clippy_lints/src/operators/integer_division_remainder_used.rs new file mode 100644 index 000000000000..976b2d8b0c63 --- /dev/null +++ b/clippy_lints/src/operators/integer_division_remainder_used.rs @@ -0,0 +1,24 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::BinOpKind; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Span; + +use super::INTEGER_DIVISION_REMAINDER_USED; + +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) { + if let BinOpKind::Div | BinOpKind::Rem = op + && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let rhs_ty = cx.typeck_results().expr_ty(rhs) + && let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind() + && let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind() + { + span_lint( + cx, + INTEGER_DIVISION_REMAINDER_USED, + span.source_callsite(), + format!("use of `{}` has been disallowed in this context", op.as_str()), + ); + } +} diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/operators/invalid_upcast_comparisons.rs similarity index 52% rename from clippy_lints/src/invalid_upcast_comparisons.rs rename to clippy_lints/src/operators/invalid_upcast_comparisons.rs index b0ecc5d52ddb..b32848a32337 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/operators/invalid_upcast_comparisons.rs @@ -1,39 +1,36 @@ -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, IntTy, UintTy}; -use rustc_session::declare_lint_pass; use rustc_span::Span; use clippy_utils::comparisons; use clippy_utils::comparisons::Rel; use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; -declare_clippy_lint! { - /// ### What it does - /// Checks for comparisons where the relation is always either - /// true or false, but where one side has been upcast so that the comparison is - /// necessary. Only integer types are checked. - /// - /// ### Why is this bad? - /// An expression like `let x : u8 = ...; (x as u32) > 300` - /// will mistakenly imply that it is possible for `x` to be outside the range of - /// `u8`. - /// - /// ### Example - /// ```no_run - /// let x: u8 = 1; - /// (x as u32) > 300; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INVALID_UPCAST_COMPARISONS, - pedantic, - "a comparison involving an upcast which is always true or false" -} +use super::INVALID_UPCAST_COMPARISONS; -declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + cmp: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, + span: Span, +) { + let normalized = comparisons::normalize_comparison(cmp, lhs, rhs); + let Some((rel, normalized_lhs, normalized_rhs)) = normalized else { + return; + }; + + let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); + let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); + + upcast_comparison_bounds_err(cx, span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); + upcast_comparison_bounds_err(cx, span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); +} fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(FullInt, FullInt)> { if let ExprKind::Cast(cast_exp, _) = expr.kind { @@ -67,21 +64,6 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option< } } -fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { - if let ExprKind::Cast(cast_val, _) = expr.kind { - span_lint( - cx, - INVALID_UPCAST_COMPARISONS, - span, - format!( - "because of the numeric bounds on `{}` prior to casting, this expression is always {}", - snippet(cx, cast_val.span, "the expression"), - if always { "true" } else { "false" }, - ), - ); - } -} - fn upcast_comparison_bounds_err<'tcx>( cx: &LateContext<'tcx>, span: Span, @@ -92,65 +74,56 @@ fn upcast_comparison_bounds_err<'tcx>( invert: bool, ) { if let Some((lb, ub)) = lhs_bounds - && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) + && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt()) { - if rel == Rel::Eq || rel == Rel::Ne { - if norm_rhs_val < lb || norm_rhs_val > ub { - err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); - } - } else if match rel { - Rel::Lt => { - if invert { - norm_rhs_val < lb - } else { - ub < norm_rhs_val + match rel { + Rel::Eq => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, false); } }, - Rel::Le => { - if invert { - norm_rhs_val <= lb - } else { - ub <= norm_rhs_val + Rel::Ne => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, true); } }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, true); - } else if match rel { Rel::Lt => { - if invert { - norm_rhs_val >= ub - } else { - lb >= norm_rhs_val + if (invert && norm_rhs_val < lb) || (!invert && ub < norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, true); + } else if (!invert && norm_rhs_val <= lb) || (invert && ub <= norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, false); } }, Rel::Le => { - if invert { - norm_rhs_val > ub - } else { - lb > norm_rhs_val + if (invert && norm_rhs_val <= lb) || (!invert && ub <= norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, true); + } else if (!invert && norm_rhs_val < lb) || (invert && ub < norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, false); } }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, false); } } } -impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { - let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs); - let Some((rel, normalized_lhs, normalized_rhs)) = normalized else { - return; - }; - - let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); - let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); - - upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); - upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); - } +fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { + if let ExprKind::Cast(cast_val, _) = expr.kind { + let mut applicability = Applicability::MachineApplicable; + let (cast_val_snip, _) = snippet_with_context( + cx, + cast_val.span, + expr.span.ctxt(), + "the expression", + &mut applicability, + ); + span_lint( + cx, + INVALID_UPCAST_COMPARISONS, + span, + format!( + "because of the numeric bounds on `{}` prior to casting, this expression is always {}", + cast_val_snip, + if always { "true" } else { "false" }, + ), + ); } } diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs new file mode 100644 index 000000000000..98aa47421537 --- /dev/null +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -0,0 +1,175 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::{Sugg, has_enclosing_paren}; +use clippy_utils::{SpanlessEq, sym}; +use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp}; +use rustc_data_structures::packed::Pu128; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self}; +use rustc_span::source_map::Spanned; + +use super::MANUAL_DIV_CEIL; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, msrv: Msrv) { + let mut applicability = Applicability::MachineApplicable; + + if op == BinOpKind::Div + && check_int_ty_and_feature(cx, lhs) + && check_int_ty_and_feature(cx, rhs) + && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = lhs.kind + && msrv.meets(cx, msrvs::DIV_CEIL) + { + // (x + (y - 1)) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) + { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + return; + } + + // ((y - 1) + x) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) + { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + return; + } + + // (x + y - 1) / y + if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Sub + && add_op.node == BinOpKind::Add + && check_literal(inner_rhs) + && check_eq_expr(cx, add_rhs, rhs) + { + build_suggestion(cx, expr, add_lhs, rhs, &mut applicability); + } + + // (x + (Y - 1)) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, rhs) { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + } + + // ((Y - 1) + x) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, rhs) { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + } + + // (x - (-Y - 1)) / Y + if inner_op.node == BinOpKind::Sub + && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = rhs.kind + && differ_by_one(abs_div_rhs, inner_rhs) + { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + } + } +} + +/// Checks if two expressions represent non-zero integer literals such that `small_expr + 1 == +/// large_expr`. +fn differ_by_one(small_expr: &Expr<'_>, large_expr: &Expr<'_>) -> bool { + if let ExprKind::Lit(small) = small_expr.kind + && let ExprKind::Lit(large) = large_expr.kind + && let LitKind::Int(s, _) = small.node + && let LitKind::Int(l, _) = large.node + { + Some(l.get()) == s.get().checked_add(1) + } else if let ExprKind::Unary(UnOp::Neg, small_inner_expr) = small_expr.kind + && let ExprKind::Unary(UnOp::Neg, large_inner_expr) = large_expr.kind + { + differ_by_one(large_inner_expr, small_inner_expr) + } else { + false + } +} + +fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let expr_ty = cx.typeck_results().expr_ty(expr); + match expr_ty.peel_refs().kind() { + ty::Uint(_) => true, + ty::Int(_) => cx.tcx.features().enabled(sym::int_roundings), + _ => false, + } +} + +fn check_literal(expr: &Expr<'_>) -> bool { + if let ExprKind::Lit(lit) = expr.kind + && let LitKind::Int(Pu128(1), _) = lit.node + { + return true; + } + false +} + +fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool { + SpanlessEq::new(cx).eq_expr(lhs, rhs) +} + +fn build_suggestion( + cx: &LateContext<'_>, + expr: &Expr<'_>, + lhs: &Expr<'_>, + rhs: &Expr<'_>, + applicability: &mut Applicability, +) { + let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren(); + let rhs_ty = cx.typeck_results().expr_ty(rhs); + let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric() + && matches!( + lhs.kind, + ExprKind::Lit(Spanned { + node: LitKind::Int(_, LitIntType::Unsuffixed), + .. + }) | ExprKind::Unary( + UnOp::Neg, + Expr { + kind: ExprKind::Lit(Spanned { + node: LitKind::Int(_, LitIntType::Unsuffixed), + .. + }), + .. + } + ) + ) { + format!("_{rhs_ty}") + } else { + String::new() + }; + let dividend_sugg_str = dividend_sugg.into_string(); + // If `dividend_sugg` has enclosing paren like `(-2048)` and we need to add type suffix in the + // suggestion message, we want to make a suggestion string before `div_ceil` like + // `(-2048_{type_suffix})`. + let suggestion_before_div_ceil = if has_enclosing_paren(÷nd_sugg_str) { + format!( + "{}{})", + ÷nd_sugg_str[..dividend_sugg_str.len() - 1].to_string(), + type_suffix + ) + } else { + format!("{dividend_sugg_str}{type_suffix}") + }; + + // Dereference the RHS if it is a reference type + let divisor_snippet = match Sugg::hir_with_context(cx, rhs, expr.span.ctxt(), "_", applicability) { + sugg if rhs_ty.is_ref() => sugg.deref(), + sugg => sugg, + }; + + span_lint_and_sugg( + cx, + MANUAL_DIV_CEIL, + expr.span, + "manually reimplementing `div_ceil`", + "consider using `.div_ceil()`", + format!("{suggestion_before_div_ceil}.div_ceil({divisor_snippet})"), + *applicability, + ); +} diff --git a/clippy_lints/src/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs index 55bb78cfce5f..0b9bd4fb6d32 100644 --- a/clippy_lints/src/operators/manual_is_multiple_of.rs +++ b/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -13,14 +13,14 @@ use super::MANUAL_IS_MULTIPLE_OF; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &Expr<'_>, + expr: &'tcx Expr<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>, msrv: Msrv, ) { if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF) - && let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs) + && let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs) && let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind && operand_op.node == BinOpKind::Rem && matches!( @@ -57,18 +57,19 @@ pub(super) fn check<'tcx>( // If we have a `x == 0`, `x != 0` or `x > 0` (or the reverted ones), return the non-zero operand fn uint_compare_to_zero<'tcx>( cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>, ) -> Option<&'tcx Expr<'tcx>> { let operand = if matches!(lhs.kind, ExprKind::Binary(..)) && matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Gt) - && is_zero_integer_const(cx, rhs) + && is_zero_integer_const(cx, rhs, e.span.ctxt()) { lhs } else if matches!(rhs.kind, ExprKind::Binary(..)) && matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt) - && is_zero_integer_const(cx, lhs) + && is_zero_integer_const(cx, lhs, e.span.ctxt()) { rhs } else { diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index bdbbb3475cd5..8db2cc1d3f57 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -11,6 +11,9 @@ mod float_cmp; mod float_equality_without_abs; mod identity_op; mod integer_division; +mod integer_division_remainder_used; +mod invalid_upcast_comparisons; +mod manual_div_ceil; mod manual_is_multiple_of; mod manual_midpoint; mod misrefactored_assign_op; @@ -463,7 +466,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for statements of the form `(a - b) < f32::EPSILON` or - /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. + /// `(a - b) < f64::EPSILON`. Note the missing `.abs()`. /// /// ### Why is this bad? /// The code without `.abs()` is more likely to have a bug. @@ -616,7 +619,7 @@ declare_clippy_lint! { /// println!("{within_tolerance}"); // true /// ``` /// - /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is /// a different use of the term that is not suitable for floating point equality comparison. /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. /// @@ -679,7 +682,7 @@ declare_clippy_lint! { /// println!("{within_tolerance}"); // true /// ``` /// - /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is /// a different use of the term that is not suitable for floating point equality comparison. /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. /// @@ -854,12 +857,84 @@ declare_clippy_lint! { /// println!("{a} is divisible by {b}"); /// } /// ``` - #[clippy::version = "1.89.0"] + #[clippy::version = "1.90.0"] pub MANUAL_IS_MULTIPLE_OF, complexity, "manual implementation of `.is_multiple_of()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation + /// of `x.div_ceil(y)`. + /// + /// ### Why is this bad? + /// It's simpler, clearer and more readable. + /// + /// ### Example + /// ```no_run + /// let x: i32 = 7; + /// let y: i32 = 4; + /// let div = (x + (y - 1)) / y; + /// ``` + /// Use instead: + /// ```no_run + /// #![feature(int_roundings)] + /// let x: i32 = 7; + /// let y: i32 = 4; + /// let div = x.div_ceil(y); + /// ``` + #[clippy::version = "1.83.0"] + pub MANUAL_DIV_CEIL, + complexity, + "manually reimplementing `div_ceil`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for comparisons where the relation is always either + /// true or false, but where one side has been upcast so that the comparison is + /// necessary. Only integer types are checked. + /// + /// ### Why is this bad? + /// An expression like `let x : u8 = ...; (x as u32) > 300` + /// will mistakenly imply that it is possible for `x` to be outside the range of + /// `u8`. + /// + /// ### Example + /// ```no_run + /// let x: u8 = 1; + /// (x as u32) > 300; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INVALID_UPCAST_COMPARISONS, + pedantic, + "a comparison involving an upcast which is always true or false" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of division (`/`) and remainder (`%`) operations + /// when performed on any integer types using the default `Div` and `Rem` trait implementations. + /// + /// ### Why restrict this? + /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, + /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). + /// + /// ### Example + /// ```no_run + /// let my_div = 10 / 2; + /// ``` + /// Use instead: + /// ```no_run + /// let my_div = 10 >> 1; + /// ``` + #[clippy::version = "1.79.0"] + pub INTEGER_DIVISION_REMAINDER_USED, + restriction, + "use of disallowed default division and remainder operations" +} + pub struct Operators { arithmetic_context: numeric_arithmetic::Context, verbose_bit_mask_threshold: u64, @@ -897,6 +972,7 @@ impl_lint_pass!(Operators => [ FLOAT_EQUALITY_WITHOUT_ABS, IDENTITY_OP, INTEGER_DIVISION, + INTEGER_DIVISION_REMAINDER_USED, CMP_OWNED, FLOAT_CMP, FLOAT_CMP_CONST, @@ -906,6 +982,8 @@ impl_lint_pass!(Operators => [ SELF_ASSIGNMENT, MANUAL_MIDPOINT, MANUAL_IS_MULTIPLE_OF, + MANUAL_DIV_CEIL, + INVALID_UPCAST_COMPARISONS, ]); impl<'tcx> LateLintPass<'tcx> for Operators { @@ -921,6 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { } erasing_op::check(cx, e, op.node, lhs, rhs); identity_op::check(cx, e, op.node, lhs, rhs); + invalid_upcast_comparisons::check(cx, op.node, lhs, rhs, e.span); needless_bitwise_bool::check(cx, e, op.node, lhs, rhs); manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv); manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv); @@ -933,6 +1012,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { duration_subsec::check(cx, e, op.node, lhs, rhs); float_equality_without_abs::check(cx, e, op.node, lhs, rhs); integer_division::check(cx, e, op.node, lhs, rhs); + integer_division_remainder_used::check(cx, op.node, lhs, rhs, e.span); cmp_owned::check(cx, op.node, lhs, rhs); float_cmp::check(cx, e, op.node, lhs, rhs); modulo_one::check(cx, e, op.node, rhs); @@ -944,6 +1024,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { rhs, self.modulo_arithmetic_allow_comparison_to_zero, ); + manual_div_ceil::check(cx, e, op.node, lhs, rhs, self.msrv); }, ExprKind::AssignOp(op, lhs, rhs) => { let bin_op = op.node.into(); diff --git a/clippy_lints/src/operators/modulo_arithmetic.rs b/clippy_lints/src/operators/modulo_arithmetic.rs index b79461663d7b..ffe91fc2cef6 100644 --- a/clippy_lints/src/operators/modulo_arithmetic.rs +++ b/clippy_lints/src/operators/modulo_arithmetic.rs @@ -39,7 +39,9 @@ fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { && let BinOpKind::Eq | BinOpKind::Ne = op.node { let ecx = ConstEvalCtxt::new(cx); - matches!(ecx.eval(lhs), Some(Constant::Int(0))) || matches!(ecx.eval(rhs), Some(Constant::Int(0))) + let ctxt = expr.span.ctxt(); + matches!(ecx.eval_local(lhs, ctxt), Some(Constant::Int(0))) + || matches!(ecx.eval_local(rhs, ctxt), Some(Constant::Int(0))) } else { false } @@ -55,7 +57,7 @@ fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> match ConstEvalCtxt::new(cx).eval(operand)? { Constant::Int(v) => match *cx.typeck_results().expr_ty(expr).kind() { ty::Int(ity) => { - let value = sext(cx.tcx, v, ity); + let value: i128 = sext(cx.tcx, v, ity); Some(OperandInfo { string_representation: Some(value.to_string()), is_negative: value < 0, diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs index 9b1b063c4737..622f328f369a 100644 --- a/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -55,7 +55,7 @@ impl Context { return; } let ty = cx.typeck_results().expr_ty(arg); - if ConstEvalCtxt::new(cx).eval_simple(expr).is_none() && ty.is_floating_point() { + if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_none() && ty.is_floating_point() { span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); self.expr_id = Some(expr.hir_id); } diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 3483f3081a58..85cf483fce90 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -1,20 +1,20 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, eager_or_lazy, expr_requires_coercion, higher, is_else_clause, - is_in_const_context, is_res_lang_ctor, peel_blocks, peel_hir_expr_while, + is_in_const_context, is_none_pattern, peel_blocks, peel_hir_expr_while, }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; use rustc_hir::{ - Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path, - QPath, UnOp, + Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -312,11 +312,14 @@ impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { } fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> { - if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { - let res = cx.qpath_res(qpath, pat.hir_id); - if is_res_lang_ctor(cx, res, OptionSome) { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind + && let res = cx.qpath_res(qpath, pat.hir_id) + && let Some(did) = res.ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { return Some((inner_pat, false)); - } else if is_res_lang_ctor(cx, res, ResultOk) { + } else if Some(did) == lang_items.result_ok_variant() { return Some((inner_pat, true)); } } @@ -375,13 +378,11 @@ fn try_convert_match<'tcx>( fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { match arm.pat.kind { - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), + _ if is_none_pattern(cx, arm.pat) => true, PatKind::TupleStruct(ref qpath, [first_pat], _) => { - is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr) + cx.qpath_res(qpath, arm.pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, ResultErr) && matches!(first_pat.kind, PatKind::Wild) }, PatKind::Wild => true, diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs index ee1d59490ce9..57127e9d2298 100644 --- a/clippy_lints/src/panic_in_result_fn.rs +++ b/clippy_lints/src/panic_in_result_fn.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use clippy_utils::visitors::{Descend, for_each_expr}; use clippy_utils::{is_inside_always_const_context, return_ty}; use core::ops::ControlFlow; @@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn { return; } let owner = cx.tcx.local_def_id_to_hir_id(def_id).expect_owner(); - if is_type_diagnostic_item(cx, return_ty(cx, owner), sym::Result) { + if return_ty(cx, owner).is_diag_item(cx, sym::Result) { lint_impl_body(cx, span, body); } } diff --git a/clippy_lints/src/partialeq_to_none.rs b/clippy_lints/src/partialeq_to_none.rs index 9b9024c81057..44217ac2c4fc 100644 --- a/clippy_lints/src/partialeq_to_none.rs +++ b/clippy_lints/src/partialeq_to_none.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_res_lang_ctor, path_res, peel_hir_expr_refs, peel_ref_operators, sugg}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::{peel_hir_expr_refs, peel_ref_operators, sugg}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem}; use rustc_lint::{LateContext, LateLintPass}; @@ -47,13 +47,21 @@ impl<'tcx> LateLintPass<'tcx> for PartialeqToNone { } // If the expression is of type `Option` - let is_ty_option = - |expr: &Expr<'_>| is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr).peel_refs(), sym::Option); + let is_ty_option = |expr: &Expr<'_>| { + cx.typeck_results() + .expr_ty(expr) + .peel_refs() + .is_diag_item(cx, sym::Option) + }; // If the expression is a literal `Option::None` let is_none_ctor = |expr: &Expr<'_>| { !expr.span.from_expansion() - && is_res_lang_ctor(cx, path_res(cx, peel_hir_expr_refs(expr).0), LangItem::OptionNone) + && peel_hir_expr_refs(expr) + .0 + .res(cx) + .ctor_parent(cx) + .is_lang_item(cx, LangItem::OptionNone) }; let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 1b1e77bbea8f..6e9142b22e0e 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -14,7 +14,7 @@ use rustc_hir::{BindingMode, Body, FnDecl, Impl, ItemKind, MutTy, Mutability, No use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, RegionKind, TyCtxt}; +use rustc_middle::ty::{self, BoundVarIndexKind, RegionKind, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; @@ -151,7 +151,7 @@ impl PassByRefOrValue { match *ty.skip_binder().kind() { ty::Ref(lt, ty, Mutability::Not) => { match lt.kind() { - RegionKind::ReBound(index, region) + RegionKind::ReBound(BoundVarIndexKind::Bound(index), region) if index.as_u32() == 0 && output_regions.contains(®ion) => { continue; diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs index 4ce6827cac9b..a5e57d97301e 100644 --- a/clippy_lints/src/pathbuf_init_then_push.rs +++ b/clippy_lints/src/pathbuf_init_then_push.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{path_to_local_id, sym}; +use clippy_utils::sym; use rustc_ast::{LitKind, StrStyle}; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -137,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { && let PatKind::Binding(BindingMode::MUT, id, name, None) = local.pat.kind && !local.span.in_external_macro(cx.sess().source_map()) && let ty = cx.typeck_results().pat_ty(local.pat) - && is_type_diagnostic_item(cx, ty, sym::PathBuf) + && ty.is_diag_item(cx, sym::PathBuf) { self.searcher = Some(PathbufPushSearcher { local_id: id, @@ -158,7 +158,7 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { && let Res::Local(id) = path.res && !expr.span.in_external_macro(cx.sess().source_map()) && let ty = cx.typeck_results().expr_ty(left) - && is_type_diagnostic_item(cx, ty, sym::PathBuf) + && ty.is_diag_item(cx, sym::PathBuf) { self.searcher = Some(PathbufPushSearcher { local_id: id, @@ -176,7 +176,7 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { if let Some(mut searcher) = self.searcher.take() && let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [arg_expr], _) = expr.kind - && path_to_local_id(self_arg, searcher.local_id) + && self_arg.res_local_id() == Some(searcher.local_id) && name.ident.name == sym::push { searcher.err_span = searcher.err_span.to(stmt.span); diff --git a/clippy_lints/src/permissions_set_readonly_false.rs b/clippy_lints/src/permissions_set_readonly_false.rs index da56a785007c..68a34d459e0d 100644 --- a/clippy_lints/src/permissions_set_readonly_false.rs +++ b/clippy_lints/src/permissions_set_readonly_false.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeDef; use clippy_utils::sym; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -34,7 +34,10 @@ impl<'tcx> LateLintPass<'tcx> for PermissionsSetReadonlyFalse { && let ExprKind::Lit(lit) = &arg.kind && LitKind::Bool(false) == lit.node && path.ident.name == sym::set_readonly - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::FsPermissions) + && cx + .typeck_results() + .expr_ty(receiver) + .is_diag_item(cx, sym::FsPermissions) { span_lint_and_then( cx, diff --git a/clippy_lints/src/precedence.rs b/clippy_lints/src/precedence.rs index ec6835db897e..034fe8edc715 100644 --- a/clippy_lints/src/precedence.rs +++ b/clippy_lints/src/precedence.rs @@ -1,4 +1,4 @@ -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_applicability; use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; use rustc_ast::ast::{BinOpKind, Expr, ExprKind}; @@ -10,7 +10,8 @@ use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does /// Checks for operations where precedence may be unclear and suggests to add parentheses. - /// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses + /// It catches a mixed usage of arithmetic and bit shifting/combining operators, + /// as well as method calls applied to closures. /// /// ### Why is this bad? /// Not everyone knows the precedence of those operators by @@ -109,6 +110,19 @@ impl EarlyLintPass for Precedence { }, _ => (), } + } else if let ExprKind::MethodCall(method_call) = &expr.kind + && let ExprKind::Closure(closure) = &method_call.receiver.kind + { + span_lint_and_then(cx, PRECEDENCE, expr.span, "precedence might not be obvious", |diag| { + diag.multipart_suggestion( + "consider parenthesizing the closure", + vec![ + (closure.fn_decl_span.shrink_to_lo(), String::from("(")), + (closure.body.span.shrink_to_hi(), String::from(")")), + ], + Applicability::MachineApplicable, + ); + }); } } } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs deleted file mode 100644 index 9eed46460a61..000000000000 --- a/clippy_lints/src/ptr.rs +++ /dev/null @@ -1,827 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::sugg::Sugg; -use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core, sym}; -use hir::LifetimeKind; -use rustc_abi::ExternAbi; -use rustc_errors::{Applicability, MultiSpan}; -use rustc_hir::hir_id::{HirId, HirIdMap}; -use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{ - self as hir, AnonConst, BinOpKind, BindingMode, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg, ImplItemKind, - ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, TyKind, -}; -use rustc_infer::infer::TyCtxtInferExt; -use rustc_infer::traits::{Obligation, ObligationCause}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::symbol::Symbol; -use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; -use std::{fmt, iter}; - -use crate::vec::is_allowed_vec_method; - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for function arguments of type `&String`, `&Vec`, - /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls - /// with the appropriate `.to_owned()`/`to_string()` calls. - /// - /// ### Why is this bad? - /// Requiring the argument to be of the specific type - /// makes the function less useful for no benefit; slices in the form of `&[T]` - /// or `&str` usually suffice and can be obtained from other types, too. - /// - /// ### Known problems - /// There may be `fn(&Vec)`-typed references pointing to your function. - /// If you have them, you will get a compiler error after applying this lint's - /// suggestions. You then have the choice to undo your changes or change the - /// type of the reference. - /// - /// Note that if the function is part of your public interface, there may be - /// other crates referencing it, of which you may not be aware. Carefully - /// deprecate the function before applying the lint suggestions in this case. - /// - /// ### Example - /// ```ignore - /// fn foo(&Vec) { .. } - /// ``` - /// - /// Use instead: - /// ```ignore - /// fn foo(&[u32]) { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PTR_ARG, - style, - "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for equality comparisons with `ptr::null` - /// - /// ### Why is this bad? - /// It's easier and more readable to use the inherent - /// `.is_null()` - /// method instead - /// - /// ### Example - /// ```rust,ignore - /// use std::ptr; - /// - /// if x == ptr::null { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// if x.is_null() { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_NULL, - style, - "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for functions that take immutable references and return - /// mutable ones. This will not trigger if no unsafe code exists as there - /// are multiple safe functions which will do this transformation - /// - /// To be on the conservative side, if there's at least one mutable - /// reference with the output lifetime, this lint will not trigger. - /// - /// ### Why is this bad? - /// Creating a mutable reference which can be repeatably derived from an - /// immutable reference is unsound as it allows creating multiple live - /// mutable references to the same object. - /// - /// This [error](https://github.com/rust-lang/rust/issues/39465) actually - /// lead to an interim Rust release 1.15.1. - /// - /// ### Known problems - /// This pattern is used by memory allocators to allow allocating multiple - /// objects while returning mutable references to each one. So long as - /// different mutable references are returned each time such a function may - /// be safe. - /// - /// ### Example - /// ```ignore - /// fn foo(&Foo) -> &mut Bar { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MUT_FROM_REF, - correctness, - "fns that create mutable refs from immutable ref args" -} - -declare_clippy_lint! { - /// ### What it does - /// Use `std::ptr::eq` when applicable - /// - /// ### Why is this bad? - /// `ptr::eq` can be used to compare `&T` references - /// (which coerce to `*const T` implicitly) by their address rather than - /// comparing the values they point to. - /// - /// ### Example - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(a as *const _ as usize == b as *const _ as usize); - /// ``` - /// Use instead: - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(std::ptr::eq(a, b)); - /// ``` - #[clippy::version = "1.49.0"] - pub PTR_EQ, - style, - "use `std::ptr::eq` when comparing raw pointers" -} - -declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); - -impl<'tcx> LateLintPass<'tcx> for Ptr { - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(sig, trait_method) = &item.kind { - if matches!(trait_method, TraitFn::Provided(_)) { - // Handled by check body. - return; - } - - check_mut_from_ref(cx, sig, None); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - for arg in check_fn_args( - cx, - cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(), - sig.decl.inputs, - &[], - ) - .filter(|arg| arg.mutability() == Mutability::Not) - { - span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { - diag.span_suggestion( - arg.span, - "change this to", - format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), - Applicability::Unspecified, - ); - }); - } - } - } - - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { - let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); - let (item_id, sig, is_trait_item) = match parents.next() { - Some((_, Node::Item(i))) => { - if let ItemKind::Fn { sig, .. } = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::ImplItem(i))) => { - if !matches!(parents.next(), - Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) - ) { - return; - } - if let ImplItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::TraitItem(i))) => { - if let TraitItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, true) - } else { - return; - } - }, - _ => return, - }; - - check_mut_from_ref(cx, sig, Some(body)); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); - let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) - .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) - .collect(); - let results = check_ptr_arg_usage(cx, body, &lint_args); - - for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { - span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { - diag.multipart_suggestion( - "change this to", - iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) - .chain(result.replacements.iter().map(|r| { - ( - r.expr_span, - format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), - ) - })) - .collect(), - Applicability::Unspecified, - ); - }); - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(op, l, r) = expr.kind - && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) - { - let non_null_path_snippet = match ( - is_lint_allowed(cx, CMP_NULL, expr.hir_id), - is_null_path(cx, l), - is_null_path(cx, r), - ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), - _ => return check_ptr_eq(cx, expr, op.node, l, r), - }; - let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; - - span_lint_and_sugg( - cx, - CMP_NULL, - expr.span, - "comparing with null is better expressed by the `.is_null()` method", - "try", - format!("{invert}{non_null_path_snippet}.is_null()",), - Applicability::MachineApplicable, - ); - } - } -} - -#[derive(Default)] -struct PtrArgResult { - skip: bool, - replacements: Vec, -} - -struct PtrArgReplacement { - expr_span: Span, - self_span: Span, - replacement: &'static str, -} - -struct PtrArg<'tcx> { - idx: usize, - emission_id: HirId, - span: Span, - ty_name: Symbol, - method_renames: &'static [(Symbol, &'static str)], - ref_prefix: RefPrefix, - deref_ty: DerefTy<'tcx>, -} -impl PtrArg<'_> { - fn build_msg(&self) -> String { - format!( - "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do", - self.ref_prefix.mutability.prefix_str(), - self.ty_name, - self.ref_prefix.mutability.prefix_str(), - self.deref_ty.argless_str(), - ) - } - - fn mutability(&self) -> Mutability { - self.ref_prefix.mutability - } -} - -struct RefPrefix { - lt: Lifetime, - mutability: Mutability, -} -impl fmt::Display for RefPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; - f.write_char('&')?; - if !self.lt.is_anonymous() { - self.lt.ident.fmt(f)?; - f.write_char(' ')?; - } - f.write_str(self.mutability.prefix_str()) - } -} - -struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>); -impl fmt::Display for DerefTyDisplay<'_, '_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::fmt::Write; - match self.1 { - DerefTy::Str => f.write_str("str"), - DerefTy::Path => f.write_str("Path"), - DerefTy::Slice(hir_ty, ty) => { - f.write_char('[')?; - match hir_ty.and_then(|s| s.get_source_text(self.0)) { - Some(s) => f.write_str(&s)?, - None => ty.fmt(f)?, - } - f.write_char(']') - }, - } - } -} - -enum DerefTy<'tcx> { - Str, - Path, - Slice(Option, Ty<'tcx>), -} -impl<'tcx> DerefTy<'tcx> { - fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> { - match *self { - Self::Str => cx.tcx.types.str_, - Self::Path => Ty::new_adt( - cx.tcx, - cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()), - List::empty(), - ), - Self::Slice(_, ty) => Ty::new_slice(cx.tcx, ty), - } - } - - fn argless_str(&self) -> &'static str { - match *self { - Self::Str => "str", - Self::Path => "Path", - Self::Slice(..) => "[_]", - } - } - - fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> { - DerefTyDisplay(cx, self) - } -} - -fn check_fn_args<'cx, 'tcx: 'cx>( - cx: &'cx LateContext<'tcx>, - fn_sig: ty::FnSig<'tcx>, - hir_tys: &'tcx [hir::Ty<'tcx>], - params: &'tcx [Param<'tcx>], -) -> impl Iterator> + 'cx { - fn_sig - .inputs() - .iter() - .zip(hir_tys.iter()) - .enumerate() - .filter_map(move |(i, (ty, hir_ty))| { - if let ty::Ref(_, ty, mutability) = *ty.kind() - && let ty::Adt(adt, args) = *ty.kind() - && let TyKind::Ref(lt, ref ty) = hir_ty.kind - && let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind - // Check that the name as typed matches the actual name of the type. - // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec` - && let [.., name] = path.segments - && cx.tcx.item_name(adt.did()) == name.ident.name - { - let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); - let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Vec) => ( - [(sym::clone, ".to_owned()")].as_slice(), - DerefTy::Slice( - if let Some(name_args) = name.args - && let [GenericArg::Type(ty), ..] = name_args.args - { - Some(ty.span) - } else { - None - }, - args.type_at(0), - ), - ), - _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( - [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), - DerefTy::Str, - ), - Some(sym::PathBuf) => ( - [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), - DerefTy::Path, - ), - Some(sym::Cow) if mutability == Mutability::Not => { - if let Some(name_args) = name.args - && let [GenericArg::Lifetime(lifetime), ty] = name_args.args - { - if let LifetimeKind::Param(param_def_id) = lifetime.kind - && !lifetime.is_anonymous() - && fn_sig - .output() - .walk() - .filter_map(ty::GenericArg::as_region) - .filter_map(|lifetime| match lifetime.kind() { - ty::ReEarlyParam(r) => Some( - cx.tcx - .generics_of(cx.tcx.parent(param_def_id.to_def_id())) - .region_param(r, cx.tcx) - .def_id, - ), - ty::ReBound(_, r) => r.kind.get_id(), - ty::ReLateParam(r) => r.kind.get_id(), - ty::ReStatic - | ty::ReVar(_) - | ty::RePlaceholder(_) - | ty::ReErased - | ty::ReError(_) => None, - }) - .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) - { - // `&Cow<'a, T>` when the return type uses 'a is okay - return None; - } - - span_lint_hir_and_then( - cx, - PTR_ARG, - emission_id, - hir_ty.span, - "using a reference to `Cow` is not recommended", - |diag| { - diag.span_suggestion( - hir_ty.span, - "change this to", - match ty.span().get_source_text(cx) { - Some(s) => format!("&{}{s}", mutability.prefix_str()), - None => format!("&{}{}", mutability.prefix_str(), args.type_at(1)), - }, - Applicability::Unspecified, - ); - }, - ); - } - return None; - }, - _ => return None, - }; - return Some(PtrArg { - idx: i, - emission_id, - span: hir_ty.span, - ty_name: name.ident.name, - method_renames, - ref_prefix: RefPrefix { lt: *lt, mutability }, - deref_ty, - }); - } - None - }) -} - -fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { - let FnRetTy::Return(ty) = sig.decl.output else { return }; - for (out, mutability, out_span) in get_lifetimes(ty) { - if mutability != Some(Mutability::Mut) { - continue; - } - let out_region = cx.tcx.named_bound_var(out.hir_id); - // `None` if one of the types contains `&'a mut T` or `T<'a>`. - // Else, contains all the locations of `&'a T` types. - let args_immut_refs: Option> = sig - .decl - .inputs - .iter() - .flat_map(get_lifetimes) - .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) - .collect(); - if let Some(args_immut_refs) = args_immut_refs - && !args_immut_refs.is_empty() - && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) - { - span_lint_and_then( - cx, - MUT_FROM_REF, - out_span, - "mutable borrow from immutable input(s)", - |diag| { - let ms = MultiSpan::from_spans(args_immut_refs); - diag.span_note(ms, "immutable borrow here"); - }, - ); - } - } -} - -#[expect(clippy::too_many_lines)] -fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { - struct V<'cx, 'tcx> { - cx: &'cx LateContext<'tcx>, - /// Map from a local id to which argument it came from (index into `Self::args` and - /// `Self::results`) - bindings: HirIdMap, - /// The arguments being checked. - args: &'cx [PtrArg<'tcx>], - /// The results for each argument (len should match args.len) - results: Vec, - /// The number of arguments which can't be linted. Used to return early. - skip_count: usize, - } - impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } - - fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} - - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.skip_count == self.args.len() { - return; - } - - // Check if this is local we care about - let Some(&args_idx) = path_to_local(e).and_then(|id| self.bindings.get(&id)) else { - return walk_expr(self, e); - }; - let args = &self.args[args_idx]; - let result = &mut self.results[args_idx]; - - // Helper function to handle early returns. - let mut set_skip_flag = || { - if !result.skip { - self.skip_count += 1; - } - result.skip = true; - }; - - match get_expr_use_or_unification_node(self.cx.tcx, e) { - Some((Node::Stmt(_), _)) => (), - Some((Node::LetStmt(l), _)) => { - // Only trace simple bindings. e.g `let x = y;` - if let PatKind::Binding(BindingMode::NONE, id, ident, None) = l.pat.kind - // Let's not lint for the current parameter. The user may still intend to mutate - // (or, if not mutate, then perhaps call a method that's not otherwise available - // for) the referenced value behind the parameter through this local let binding - // with the underscore being only temporary. - && !ident.name.as_str().starts_with('_') - { - self.bindings.insert(id, args_idx); - } else { - set_skip_flag(); - } - }, - Some((Node::Expr(use_expr), child_id)) => { - if let ExprKind::Index(e, ..) = use_expr.kind - && e.hir_id == child_id - { - // Indexing works with both owned and its dereferenced type - return; - } - - if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind - && receiver.hir_id == child_id - { - let name = name.ident.name; - - // Check if the method can be renamed. - if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { - result.replacements.push(PtrArgReplacement { - expr_span: use_expr.span, - self_span: receiver.span, - replacement, - }); - return; - } - - // Some methods exist on both `[T]` and `Vec`, such as `len`, where the receiver type - // doesn't coerce to a slice and our adjusted type check below isn't enough, - // but it would still be valid to call with a slice - if is_allowed_vec_method(self.cx, use_expr) { - return; - } - } - - // If the expression's type gets adjusted down to the deref type, we might as - // well have started with that deref type -- the lint should fire - let deref_ty = args.deref_ty.ty(self.cx); - let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs(); - if adjusted_ty == deref_ty { - return; - } - - // If the expression's type is constrained by `dyn Trait`, see if the deref - // type implements the trait(s) as well, and if so, the lint should fire - if let ty::Dynamic(preds, ..) = adjusted_ty.kind() - && matches_preds(self.cx, deref_ty, preds) - { - return; - } - - set_skip_flag(); - }, - _ => set_skip_flag(), - } - } - } - - let mut skip_count = 0; - let mut results = args.iter().map(|_| PtrArgResult::default()).collect::>(); - let mut v = V { - cx, - bindings: args - .iter() - .enumerate() - .filter_map(|(i, arg)| { - let param = &body.params[arg.idx]; - match param.pat.kind { - PatKind::Binding(BindingMode::NONE, id, ident, None) - if !is_lint_allowed(cx, PTR_ARG, param.hir_id) - // Let's not lint for the current parameter. The user may still intend to mutate - // (or, if not mutate, then perhaps call a method that's not otherwise available - // for) the referenced value behind the parameter with the underscore being only - // temporary. - && !ident.name.as_str().starts_with('_') => - { - Some((id, i)) - }, - _ => { - skip_count += 1; - results[i].skip = true; - None - }, - } - }) - .collect(), - args, - results, - skip_count, - }; - v.visit_expr(body.value); - v.results -} - -fn matches_preds<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - preds: &'tcx [ty::PolyExistentialPredicate<'tcx>], -) -> bool { - let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); - preds - .iter() - .all(|&p| match cx.tcx.instantiate_bound_regions_with_erased(p) { - ExistentialPredicate::Trait(p) => infcx - .type_implements_trait(p.def_id, [ty.into()].into_iter().chain(p.args.iter()), cx.param_env) - .must_apply_modulo_regions(), - ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new( - cx.tcx, - ObligationCause::dummy(), - cx.param_env, - cx.tcx - .mk_predicate(Binder::dummy(PredicateKind::Clause(ClauseKind::Projection( - p.with_self_ty(cx.tcx, ty), - )))), - )), - ExistentialPredicate::AutoTrait(p) => infcx - .type_implements_trait(p, [ty], cx.param_env) - .must_apply_modulo_regions(), - }) -} - -struct LifetimeVisitor<'tcx> { - result: Vec<(&'tcx Lifetime, Option, Span)>, -} - -impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { - fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { - if let TyKind::Ref(lt, ref m) = ty.kind { - self.result.push((lt, Some(m.mutbl), ty.span)); - } - hir::intravisit::walk_ty(self, ty); - } - - fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { - if let GenericArg::Lifetime(lt) = generic_arg { - self.result.push((lt, None, generic_arg.span())); - } - hir::intravisit::walk_generic_arg(self, generic_arg); - } -} - -/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. -/// -/// The second field of the vector's elements indicate if the lifetime is attached to a -/// shared reference, a mutable reference, or neither. -fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { - use hir::intravisit::VisitorExt as _; - - let mut visitor = LifetimeVisitor { result: Vec::new() }; - visitor.visit_ty_unambig(ty); - visitor.result -} - -fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(pathexp, []) = expr.kind { - path_def_id(cx, pathexp) - .is_some_and(|id| matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))) - } else { - false - } -} - -fn check_ptr_eq<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - op: BinOpKind, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, -) { - if expr.span.from_expansion() { - return; - } - - // Remove one level of usize conversion if any - let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { - (Some(lhs), Some(rhs)) => (lhs, rhs, true), - _ => (left, right, false), - }; - - // This lint concerns raw pointers - let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); - if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { - return; - } - - let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = - (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); - - if !(usize_peeled || left_casts_peeled || right_casts_peeled) { - return; - } - - let mut app = Applicability::MachineApplicable; - let left_snip = Sugg::hir_with_context(cx, left_var, expr.span.ctxt(), "_", &mut app); - let right_snip = Sugg::hir_with_context(cx, right_var, expr.span.ctxt(), "_", &mut app); - { - let Some(top_crate) = std_or_core(cx) else { return }; - let invert = if op == BinOpKind::Eq { "" } else { "!" }; - span_lint_and_sugg( - cx, - PTR_EQ, - expr.span, - format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), - "try", - format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), - app, - ); - } -} - -// If the given expression is a cast to a usize, return the lhs of the cast -// E.g., `foo as *const _ as usize` returns `foo as *const _`. -fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if !cast_expr.span.from_expansion() - && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize - && let ExprKind::Cast(expr, _) = cast_expr.kind - { - Some(expr) - } else { - None - } -} - -// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been -// peeled or not. -fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { - if !expr.span.from_expansion() - && let ExprKind::Cast(inner, _) = expr.kind - && let ty::RawPtr(target_ty, _) = expr_ty.kind() - && let inner_ty = cx.typeck_results().expr_ty(inner) - && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() - && target_ty == inner_target_ty - { - (peel_raw_casts(cx, inner, inner_ty).0, true) - } else { - (expr, false) - } -} diff --git a/clippy_lints/src/ptr/cmp_null.rs b/clippy_lints/src/ptr/cmp_null.rs new file mode 100644 index 000000000000..905b48e6d1d4 --- /dev/null +++ b/clippy_lints/src/ptr/cmp_null.rs @@ -0,0 +1,49 @@ +use super::CMP_NULL; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + l: &Expr<'_>, + r: &Expr<'_>, +) -> bool { + let non_null_path_snippet = match ( + is_lint_allowed(cx, CMP_NULL, expr.hir_id), + is_null_path(cx, l), + is_null_path(cx, r), + ) { + (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), + (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), + _ => return false, + }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + + span_lint_and_sugg( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + "try", + format!("{invert}{non_null_path_snippet}.is_null()",), + Applicability::MachineApplicable, + ); + true +} + +fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(pathexp, []) = expr.kind { + matches!( + pathexp.basic_res().opt_diag_name(cx), + Some(sym::ptr_null | sym::ptr_null_mut) + ) + } else { + false + } +} diff --git a/clippy_lints/src/ptr/mod.rs b/clippy_lints/src/ptr/mod.rs new file mode 100644 index 000000000000..6b2647e7b0a2 --- /dev/null +++ b/clippy_lints/src/ptr/mod.rs @@ -0,0 +1,202 @@ +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, ImplItemKind, ItemKind, Node, TraitFn, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +mod cmp_null; +mod mut_from_ref; +mod ptr_arg; +mod ptr_eq; + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for function arguments of type `&String`, `&Vec`, + /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls + /// with the appropriate `.to_owned()`/`to_string()` calls. + /// + /// ### Why is this bad? + /// Requiring the argument to be of the specific type + /// makes the function less useful for no benefit; slices in the form of `&[T]` + /// or `&str` usually suffice and can be obtained from other types, too. + /// + /// ### Known problems + /// There may be `fn(&Vec)`-typed references pointing to your function. + /// If you have them, you will get a compiler error after applying this lint's + /// suggestions. You then have the choice to undo your changes or change the + /// type of the reference. + /// + /// Note that if the function is part of your public interface, there may be + /// other crates referencing it, of which you may not be aware. Carefully + /// deprecate the function before applying the lint suggestions in this case. + /// + /// ### Example + /// ```ignore + /// fn foo(&Vec) { .. } + /// ``` + /// + /// Use instead: + /// ```ignore + /// fn foo(&[u32]) { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PTR_ARG, + style, + "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for equality comparisons with `ptr::null` + /// + /// ### Why is this bad? + /// It's easier and more readable to use the inherent + /// `.is_null()` + /// method instead + /// + /// ### Example + /// ```rust,ignore + /// use std::ptr; + /// + /// if x == ptr::null { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// if x.is_null() { + /// // .. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_NULL, + style, + "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for functions that take immutable references and return + /// mutable ones. This will not trigger if no unsafe code exists as there + /// are multiple safe functions which will do this transformation + /// + /// To be on the conservative side, if there's at least one mutable + /// reference with the output lifetime, this lint will not trigger. + /// + /// ### Why is this bad? + /// Creating a mutable reference which can be repeatably derived from an + /// immutable reference is unsound as it allows creating multiple live + /// mutable references to the same object. + /// + /// This [error](https://github.com/rust-lang/rust/issues/39465) actually + /// lead to an interim Rust release 1.15.1. + /// + /// ### Known problems + /// This pattern is used by memory allocators to allow allocating multiple + /// objects while returning mutable references to each one. So long as + /// different mutable references are returned each time such a function may + /// be safe. + /// + /// ### Example + /// ```ignore + /// fn foo(&Foo) -> &mut Bar { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MUT_FROM_REF, + correctness, + "fns that create mutable refs from immutable ref args" +} + +declare_clippy_lint! { + /// ### What it does + /// Use `std::ptr::eq` when applicable + /// + /// ### Why is this bad? + /// `ptr::eq` can be used to compare `&T` references + /// (which coerce to `*const T` implicitly) by their address rather than + /// comparing the values they point to. + /// + /// ### Example + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(a as *const _ as usize == b as *const _ as usize); + /// ``` + /// Use instead: + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(std::ptr::eq(a, b)); + /// ``` + #[clippy::version = "1.49.0"] + pub PTR_EQ, + style, + "use `std::ptr::eq` when comparing raw pointers" +} + +declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); + +impl<'tcx> LateLintPass<'tcx> for Ptr { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(sig, trait_method) = &item.kind { + if matches!(trait_method, TraitFn::Provided(_)) { + // Handled by `check_body`. + return; + } + + mut_from_ref::check(cx, sig, None); + ptr_arg::check_trait_item(cx, item.owner_id, sig); + } + } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); + let (item_id, sig, is_trait_item) = match parents.next() { + Some((_, Node::Item(i))) => { + if let ItemKind::Fn { sig, .. } = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::ImplItem(i))) => { + if !matches!(parents.next(), + Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) + ) { + return; + } + if let ImplItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::TraitItem(i))) => { + if let TraitItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, true) + } else { + return; + } + }, + _ => return, + }; + + mut_from_ref::check(cx, sig, Some(body)); + ptr_arg::check_body(cx, body, item_id, sig, is_trait_item); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, l, r) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + { + #[expect( + clippy::collapsible_if, + reason = "the outer `if`s check the HIR, the inner ones run lints" + )] + if !cmp_null::check(cx, expr, op.node, l, r) { + ptr_eq::check(cx, op.node, l, r, expr.span); + } + } + } +} diff --git a/clippy_lints/src/ptr/mut_from_ref.rs b/clippy_lints/src/ptr/mut_from_ref.rs new file mode 100644 index 000000000000..30d708f436b4 --- /dev/null +++ b/clippy_lints/src/ptr/mut_from_ref.rs @@ -0,0 +1,75 @@ +use super::MUT_FROM_REF; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::visitors::contains_unsafe_block; +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Body, FnRetTy, FnSig, GenericArg, Lifetime, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { + let FnRetTy::Return(ty) = sig.decl.output else { return }; + for (out, mutability, out_span) in get_lifetimes(ty) { + if mutability != Some(Mutability::Mut) { + continue; + } + let out_region = cx.tcx.named_bound_var(out.hir_id); + // `None` if one of the types contains `&'a mut T` or `T<'a>`. + // Else, contains all the locations of `&'a T` types. + let args_immut_refs: Option> = sig + .decl + .inputs + .iter() + .flat_map(get_lifetimes) + .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) + .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) + .collect(); + if let Some(args_immut_refs) = args_immut_refs + && !args_immut_refs.is_empty() + && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) + { + span_lint_and_then( + cx, + MUT_FROM_REF, + out_span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(args_immut_refs); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +struct LifetimeVisitor<'tcx> { + result: Vec<(&'tcx Lifetime, Option, Span)>, +} + +impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let TyKind::Ref(lt, ref m) = ty.kind { + self.result.push((lt, Some(m.mutbl), ty.span)); + } + hir::intravisit::walk_ty(self, ty); + } + + fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { + if let GenericArg::Lifetime(lt) = generic_arg { + self.result.push((lt, None, generic_arg.span())); + } + hir::intravisit::walk_generic_arg(self, generic_arg); + } +} + +/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. +/// +/// The second field of the vector's elements indicate if the lifetime is attached to a +/// shared reference, a mutable reference, or neither. +fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { + use hir::intravisit::VisitorExt as _; + + let mut visitor = LifetimeVisitor { result: Vec::new() }; + visitor.visit_ty_unambig(ty); + visitor.result +} diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs new file mode 100644 index 000000000000..fd9230f00a8b --- /dev/null +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -0,0 +1,475 @@ +use super::PTR_ARG; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::MaybeResPath; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, sym}; +use hir::LifetimeKind; +use rustc_abi::ExternAbi; +use rustc_errors::Applicability; +use rustc_hir::hir_id::{HirId, HirIdMap}; +use rustc_hir::intravisit::{Visitor, walk_expr}; +use rustc_hir::{ + self as hir, AnonConst, BindingMode, Body, Expr, ExprKind, FnSig, GenericArg, Lifetime, Mutability, Node, OwnerId, + Param, PatKind, QPath, TyKind, +}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; +use rustc_span::Span; +use rustc_span::symbol::Symbol; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; +use std::{fmt, iter}; + +use crate::vec::is_allowed_vec_method; + +pub(super) fn check_body<'tcx>( + cx: &LateContext<'tcx>, + body: &Body<'tcx>, + item_id: OwnerId, + sig: &FnSig<'tcx>, + is_trait_item: bool, +) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + + let decl = sig.decl; + let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); + let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) + .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) + .collect(); + let results = check_ptr_arg_usage(cx, body, &lint_args); + + for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { + span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { + diag.multipart_suggestion( + "change this to", + iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) + .chain(result.replacements.iter().map(|r| { + ( + r.expr_span, + format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), + ) + })) + .collect(), + Applicability::Unspecified, + ); + }); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, sig: &FnSig<'tcx>) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + + for arg in check_fn_args( + cx, + cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(), + sig.decl.inputs, + &[], + ) + .filter(|arg| arg.mutability() == Mutability::Not) + { + span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { + diag.span_suggestion( + arg.span, + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, + ); + }); + } +} + +#[derive(Default)] +struct PtrArgResult { + skip: bool, + replacements: Vec, +} + +struct PtrArgReplacement { + expr_span: Span, + self_span: Span, + replacement: &'static str, +} + +struct PtrArg<'tcx> { + idx: usize, + emission_id: HirId, + span: Span, + ty_name: Symbol, + method_renames: &'static [(Symbol, &'static str)], + ref_prefix: RefPrefix, + deref_ty: DerefTy<'tcx>, +} +impl PtrArg<'_> { + fn build_msg(&self) -> String { + format!( + "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do", + self.ref_prefix.mutability.prefix_str(), + self.ty_name, + self.ref_prefix.mutability.prefix_str(), + self.deref_ty.argless_str(), + ) + } + + fn mutability(&self) -> Mutability { + self.ref_prefix.mutability + } +} + +struct RefPrefix { + lt: Lifetime, + mutability: Mutability, +} +impl fmt::Display for RefPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; + f.write_char('&')?; + if !self.lt.is_anonymous() { + self.lt.ident.fmt(f)?; + f.write_char(' ')?; + } + f.write_str(self.mutability.prefix_str()) + } +} + +struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>); +impl fmt::Display for DerefTyDisplay<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write; + match self.1 { + DerefTy::Str => f.write_str("str"), + DerefTy::Path => f.write_str("Path"), + DerefTy::Slice(hir_ty, ty) => { + f.write_char('[')?; + match hir_ty.and_then(|s| s.get_source_text(self.0)) { + Some(s) => f.write_str(&s)?, + None => ty.fmt(f)?, + } + f.write_char(']') + }, + } + } +} + +enum DerefTy<'tcx> { + Str, + Path, + Slice(Option, Ty<'tcx>), +} +impl<'tcx> DerefTy<'tcx> { + fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> { + match *self { + Self::Str => cx.tcx.types.str_, + Self::Path => Ty::new_adt( + cx.tcx, + cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()), + List::empty(), + ), + Self::Slice(_, ty) => Ty::new_slice(cx.tcx, ty), + } + } + + fn argless_str(&self) -> &'static str { + match *self { + Self::Str => "str", + Self::Path => "Path", + Self::Slice(..) => "[_]", + } + } + + fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> { + DerefTyDisplay(cx, self) + } +} + +fn check_fn_args<'cx, 'tcx: 'cx>( + cx: &'cx LateContext<'tcx>, + fn_sig: ty::FnSig<'tcx>, + hir_tys: &'tcx [hir::Ty<'tcx>], + params: &'tcx [Param<'tcx>], +) -> impl Iterator> + 'cx { + iter::zip(fn_sig.inputs(), hir_tys) + .enumerate() + .filter_map(move |(i, (ty, hir_ty))| { + if let ty::Ref(_, ty, mutability) = *ty.kind() + && let ty::Adt(adt, args) = *ty.kind() + && let TyKind::Ref(lt, ref ty) = hir_ty.kind + && let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind + // Check that the name as typed matches the actual name of the type. + // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec` + && let [.., name] = path.segments + && cx.tcx.item_name(adt.did()) == name.ident.name + { + let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); + let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Vec) => ( + [(sym::clone, ".to_owned()")].as_slice(), + DerefTy::Slice( + if let Some(name_args) = name.args + && let [GenericArg::Type(ty), ..] = name_args.args + { + Some(ty.span) + } else { + None + }, + args.type_at(0), + ), + ), + _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( + [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), + DerefTy::Str, + ), + Some(sym::PathBuf) => ( + [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), + DerefTy::Path, + ), + Some(sym::Cow) if mutability == Mutability::Not => { + if let Some(name_args) = name.args + && let [GenericArg::Lifetime(lifetime), ty] = name_args.args + { + if let LifetimeKind::Param(param_def_id) = lifetime.kind + && !lifetime.is_anonymous() + && fn_sig + .output() + .walk() + .filter_map(ty::GenericArg::as_region) + .filter_map(|lifetime| match lifetime.kind() { + ty::ReEarlyParam(r) => Some( + cx.tcx + .generics_of(cx.tcx.parent(param_def_id.to_def_id())) + .region_param(r, cx.tcx) + .def_id, + ), + ty::ReBound(_, r) => r.kind.get_id(), + ty::ReLateParam(r) => r.kind.get_id(), + ty::ReStatic + | ty::ReVar(_) + | ty::RePlaceholder(_) + | ty::ReErased + | ty::ReError(_) => None, + }) + .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) + { + // `&Cow<'a, T>` when the return type uses 'a is okay + return None; + } + + span_lint_hir_and_then( + cx, + PTR_ARG, + emission_id, + hir_ty.span, + "using a reference to `Cow` is not recommended", + |diag| { + diag.span_suggestion( + hir_ty.span, + "change this to", + match ty.span().get_source_text(cx) { + Some(s) => format!("&{}{s}", mutability.prefix_str()), + None => format!("&{}{}", mutability.prefix_str(), args.type_at(1)), + }, + Applicability::Unspecified, + ); + }, + ); + } + return None; + }, + _ => return None, + }; + return Some(PtrArg { + idx: i, + emission_id, + span: hir_ty.span, + ty_name: name.ident.name, + method_renames, + ref_prefix: RefPrefix { lt: *lt, mutability }, + deref_ty, + }); + } + None + }) +} + +#[expect(clippy::too_many_lines)] +fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + /// Map from a local id to which argument it came from (index into `Self::args` and + /// `Self::results`) + bindings: HirIdMap, + /// The arguments being checked. + args: &'cx [PtrArg<'tcx>], + /// The results for each argument (len should match args.len) + results: Vec, + /// The number of arguments which can't be linted. Used to return early. + skip_count: usize, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } + + fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.skip_count == self.args.len() { + return; + } + + // Check if this is local we care about + let Some(&args_idx) = e.res_local_id().and_then(|id| self.bindings.get(&id)) else { + return walk_expr(self, e); + }; + let args = &self.args[args_idx]; + let result = &mut self.results[args_idx]; + + // Helper function to handle early returns. + let mut set_skip_flag = || { + if !result.skip { + self.skip_count += 1; + } + result.skip = true; + }; + + match get_expr_use_or_unification_node(self.cx.tcx, e) { + Some((Node::Stmt(_), _)) => (), + Some((Node::LetStmt(l), _)) => { + // Only trace simple bindings. e.g `let x = y;` + if let PatKind::Binding(BindingMode::NONE, id, ident, None) = l.pat.kind + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter through this local let binding + // with the underscore being only temporary. + && !ident.name.as_str().starts_with('_') + { + self.bindings.insert(id, args_idx); + } else { + set_skip_flag(); + } + }, + Some((Node::Expr(use_expr), child_id)) => { + if let ExprKind::Index(e, ..) = use_expr.kind + && e.hir_id == child_id + { + // Indexing works with both owned and its dereferenced type + return; + } + + if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind + && receiver.hir_id == child_id + { + let name = name.ident.name; + + // Check if the method can be renamed. + if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { + result.replacements.push(PtrArgReplacement { + expr_span: use_expr.span, + self_span: receiver.span, + replacement, + }); + return; + } + + // Some methods exist on both `[T]` and `Vec`, such as `len`, where the receiver type + // doesn't coerce to a slice and our adjusted type check below isn't enough, + // but it would still be valid to call with a slice + if is_allowed_vec_method(use_expr) { + return; + } + } + + // If the expression's type gets adjusted down to the deref type, we might as + // well have started with that deref type -- the lint should fire + let deref_ty = args.deref_ty.ty(self.cx); + let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs(); + if adjusted_ty == deref_ty { + return; + } + + // If the expression's type is constrained by `dyn Trait`, see if the deref + // type implements the trait(s) as well, and if so, the lint should fire + if let ty::Dynamic(preds, ..) = adjusted_ty.kind() + && matches_preds(self.cx, deref_ty, preds) + { + return; + } + + set_skip_flag(); + }, + _ => set_skip_flag(), + } + } + } + + let mut skip_count = 0; + let mut results = args.iter().map(|_| PtrArgResult::default()).collect::>(); + let mut v = V { + cx, + bindings: args + .iter() + .enumerate() + .filter_map(|(i, arg)| { + let param = &body.params[arg.idx]; + match param.pat.kind { + PatKind::Binding(BindingMode::NONE, id, ident, None) + if !is_lint_allowed(cx, PTR_ARG, param.hir_id) + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter with the underscore being only + // temporary. + && !ident.name.as_str().starts_with('_') => + { + Some((id, i)) + }, + _ => { + skip_count += 1; + results[i].skip = true; + None + }, + } + }) + .collect(), + args, + results, + skip_count, + }; + v.visit_expr(body.value); + v.results +} + +fn matches_preds<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + preds: &'tcx [ty::PolyExistentialPredicate<'tcx>], +) -> bool { + let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); + preds + .iter() + .all(|&p| match cx.tcx.instantiate_bound_regions_with_erased(p) { + ExistentialPredicate::Trait(p) => infcx + .type_implements_trait(p.def_id, [ty.into()].into_iter().chain(p.args.iter()), cx.param_env) + .must_apply_modulo_regions(), + ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new( + cx.tcx, + ObligationCause::dummy(), + cx.param_env, + cx.tcx + .mk_predicate(Binder::dummy(PredicateKind::Clause(ClauseKind::Projection( + p.with_self_ty(cx.tcx, ty), + )))), + )), + ExistentialPredicate::AutoTrait(p) => infcx + .type_implements_trait(p, [ty], cx.param_env) + .must_apply_modulo_regions(), + }) +} diff --git a/clippy_lints/src/ptr/ptr_eq.rs b/clippy_lints/src/ptr/ptr_eq.rs new file mode 100644 index 000000000000..c982bb1ffbc5 --- /dev/null +++ b/clippy_lints/src/ptr/ptr_eq.rs @@ -0,0 +1,87 @@ +use super::PTR_EQ; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::std_or_core; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + span: Span, +) { + if span.from_expansion() { + return; + } + + // Remove one level of usize conversion if any + let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs, true), + _ => (left, right, false), + }; + + // This lint concerns raw pointers + let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); + if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { + return; + } + + let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = + (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); + + if !(usize_peeled || left_casts_peeled || right_casts_peeled) { + return; + } + + let mut app = Applicability::MachineApplicable; + let ctxt = span.ctxt(); + let left_snip = Sugg::hir_with_context(cx, left_var, ctxt, "_", &mut app); + let right_snip = Sugg::hir_with_context(cx, right_var, ctxt, "_", &mut app); + { + let Some(top_crate) = std_or_core(cx) else { return }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + span_lint_and_sugg( + cx, + PTR_EQ, + span, + format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), + "try", + format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), + app, + ); + } +} + +// If the given expression is a cast to a usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if !cast_expr.span.from_expansion() + && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize + && let ExprKind::Cast(expr, _) = cast_expr.kind + { + Some(expr) + } else { + None + } +} + +// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been +// peeled or not. +fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { + if !expr.span.from_expansion() + && let ExprKind::Cast(inner, _) = expr.kind + && let ty::RawPtr(target_ty, _) = expr_ty.kind() + && let inner_ty = cx.typeck_results().expr_ty(inner) + && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() + && target_ty == inner_target_ty + { + (peel_raw_casts(cx, inner, inner_ty).0, true) + } else { + (expr, false) + } +} diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs deleted file mode 100644 index d8d813f9846d..000000000000 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ /dev/null @@ -1,151 +0,0 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::sym; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use std::fmt; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `offset` pointer method with a `usize` casted to an - /// `isize`. - /// - /// ### Why is this bad? - /// If we’re always increasing the pointer address, we can avoid the numeric - /// cast by using the `add` method instead. - /// - /// ### Example - /// ```no_run - /// let vec = vec![b'a', b'b', b'c']; - /// let ptr = vec.as_ptr(); - /// let offset = 1_usize; - /// - /// unsafe { - /// ptr.offset(offset as isize); - /// } - /// ``` - /// - /// Could be written: - /// - /// ```no_run - /// let vec = vec![b'a', b'b', b'c']; - /// let ptr = vec.as_ptr(); - /// let offset = 1_usize; - /// - /// unsafe { - /// ptr.add(offset); - /// } - /// ``` - #[clippy::version = "1.30.0"] - pub PTR_OFFSET_WITH_CAST, - complexity, - "unneeded pointer offset cast" -} - -declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]); - -impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call - let Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else { - return; - }; - - // Check if the argument to the method call is a cast from usize - let Some(cast_lhs_expr) = expr_as_cast_from_usize(cx, arg_expr) else { - return; - }; - - let msg = format!("use of `{method}` with a `usize` casted to an `isize`"); - if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) { - span_lint_and_sugg( - cx, - PTR_OFFSET_WITH_CAST, - expr.span, - msg, - "try", - sugg, - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, msg); - } - } -} - -// If the given expression is a cast from a usize, return the lhs of the cast -fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind - && is_expr_ty_usize(cx, cast_lhs_expr) - { - return Some(cast_lhs_expr); - } - None -} - -// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the -// receiver, the arg of the method call, and the method. -fn expr_as_ptr_offset_call<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, -) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { - if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind - && is_expr_ty_raw_ptr(cx, arg_0) - { - if path_segment.ident.name == sym::offset { - return Some((arg_0, arg_1, Method::Offset)); - } - if path_segment.ident.name == sym::wrapping_offset { - return Some((arg_0, arg_1, Method::WrappingOffset)); - } - } - None -} - -// Is the type of the expression a usize? -fn is_expr_ty_usize(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize -} - -// Is the type of the expression a raw pointer? -fn is_expr_ty_raw_ptr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_raw_ptr() -} - -fn build_suggestion( - cx: &LateContext<'_>, - method: Method, - receiver_expr: &Expr<'_>, - cast_lhs_expr: &Expr<'_>, -) -> Option { - let receiver = receiver_expr.span.get_source_text(cx)?; - let cast_lhs = cast_lhs_expr.span.get_source_text(cx)?; - Some(format!("{receiver}.{}({cast_lhs})", method.suggestion())) -} - -#[derive(Copy, Clone)] -enum Method { - Offset, - WrappingOffset, -} - -impl Method { - #[must_use] - fn suggestion(self) -> &'static str { - match self { - Self::Offset => "add", - Self::WrappingOffset => "wrapping_add", - } - } -} - -impl fmt::Display for Method { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Offset => write!(f, "offset"), - Self::WrappingOffset => write!(f, "wrapping_offset"), - } - } -} diff --git a/clippy_lints/src/pub_underscore_fields.rs b/clippy_lints/src/pub_underscore_fields.rs index 66c59cb70d36..694d44d70856 100644 --- a/clippy_lints/src/pub_underscore_fields.rs +++ b/clippy_lints/src/pub_underscore_fields.rs @@ -2,7 +2,7 @@ use clippy_config::Conf; use clippy_config::types::PubUnderscoreFieldsBehaviour; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::is_path_lang_item; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::{FieldDef, Item, ItemKind, LangItem}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -76,7 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields { // We ignore fields that have `#[doc(hidden)]`. && !is_doc_hidden(cx.tcx.hir_attrs(field.hir_id)) // We ignore fields that are `PhantomData`. - && !is_path_lang_item(cx, field.ty, LangItem::PhantomData) + && !field.ty.basic_res().is_lang_item(cx, LangItem::PhantomData) { span_lint_hir_and_then( cx, diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index de12a25b03df..1a6165d0af83 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -4,13 +4,15 @@ use clippy_config::Conf; use clippy_config::types::MatchLintBehaviour; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::{implements_trait, is_copy}; +use clippy_utils::usage::local_used_after_expr; use clippy_utils::{ - eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, - pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, - span_contains_cfg, span_contains_comment, sym, + eq_expr_value, fn_def_id_with_node_args, higher, is_else_clause, is_in_const_context, is_lint_allowed, + pat_and_expr_can_be_question_mark, peel_blocks, peel_blocks_with_stmt, span_contains_cfg, span_contains_comment, + sym, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; @@ -204,7 +206,7 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ IfBlockType::IfIs(caller, caller_ty, call_sym, if_then) => { // If the block could be identified as `if x.is_none()/is_err()`, // we then only need to check the if_then return to see if it is none/err. - is_type_diagnostic_item(cx, caller_ty, smbl) + caller_ty.is_diag_item(cx, smbl) && expr_return_none_or_err(smbl, cx, if_then, caller, None) && match smbl { sym::Option => call_sym == sym::is_none, @@ -213,20 +215,20 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ } }, IfBlockType::IfLet(res, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => { - is_type_diagnostic_item(cx, let_expr_ty, smbl) + let_expr_ty.is_diag_item(cx, smbl) && match smbl { sym::Option => { // We only need to check `if let Some(x) = option` not `if let None = option`, // because the later one will be suggested as `if option.is_none()` thus causing conflict. - is_res_lang_ctor(cx, res, OptionSome) + res.ctor_parent(cx).is_lang_item(cx, OptionSome) && if_else.is_some() && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None) }, sym::Result => { - (is_res_lang_ctor(cx, res, ResultOk) + (res.ctor_parent(cx).is_lang_item(cx, ResultOk) && if_else.is_some() && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym))) - || is_res_lang_ctor(cx, res, ResultErr) + || res.ctor_parent(cx).is_lang_item(cx, ResultErr) && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym)) && if_else.is_none() }, @@ -246,8 +248,11 @@ fn expr_return_none_or_err( match peel_blocks_with_stmt(expr).kind { ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym), ExprKind::Path(ref qpath) => match smbl { - sym::Option => is_res_lang_ctor(cx, cx.qpath_res(qpath, expr.hir_id), OptionNone), - sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), + sym::Option => cx + .qpath_res(qpath, expr.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone), + sym::Result => expr.res_local_id().is_some() && expr.res_local_id() == cond_expr.res_local_id(), _ => false, }, ExprKind::Call(call_expr, [arg]) => { @@ -341,7 +346,10 @@ fn extract_ctor_call<'a, 'tcx>( pat: &'a Pat<'tcx>, ) -> Option<&'a Pat<'tcx>> { if let PatKind::TupleStruct(variant_path, [val_binding], _) = &pat.kind - && is_res_lang_ctor(cx, cx.qpath_res(variant_path, pat.hir_id), expected_ctor) + && cx + .qpath_res(variant_path, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, expected_ctor) { Some(val_binding) } else { @@ -370,7 +378,7 @@ fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Ar // Extract out `val` && let Some(binding) = extract_binding_pat(val_binding) // Check body is just `=> val` - && path_to_local_id(peel_blocks(arm.body), binding) + && peel_blocks(arm.body).res_local_id() == Some(binding) { true } else { @@ -392,9 +400,9 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A // check `=> return Err(...)` && let ExprKind::Ret(Some(wrapped_ret_expr)) = arm_body.kind && let ExprKind::Call(ok_ctor, [ret_expr]) = wrapped_ret_expr.kind - && is_res_lang_ctor(cx, path_res(cx, ok_ctor), ResultErr) - // check `...` is `val` from binding - && path_to_local_id(ret_expr, ok_val) + && ok_ctor.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) + // check if `...` is `val` from binding or `val.into()` + && is_local_or_local_into(cx, ret_expr, ok_val) { true } else { @@ -403,10 +411,10 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A }, TryMode::Option => { // Check the pat is `None` - if is_res_lang_ctor(cx, path_res(cx, arm.pat), OptionNone) + if arm.pat.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) // Check `=> return None` && let ExprKind::Ret(Some(ret_expr)) = arm_body.kind - && is_res_lang_ctor(cx, path_res(cx, ret_expr), OptionNone) + && ret_expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) && !ret_expr.span.from_expansion() { true @@ -417,6 +425,19 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A } } +/// Check if `expr` is `val` or `val.into()` +fn is_local_or_local_into(cx: &LateContext<'_>, expr: &Expr<'_>, val: HirId) -> bool { + let is_into_call = fn_def_id_with_node_args(cx, expr) + .and_then(|(fn_def_id, _)| cx.tcx.trait_of_assoc(fn_def_id)) + .is_some_and(|trait_def_id| cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)); + match expr.kind { + ExprKind::MethodCall(_, recv, [], _) | ExprKind::Call(_, [recv]) => { + is_into_call && recv.res_local_id() == Some(val) + }, + _ => expr.res_local_id() == Some(val), + } +} + fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool { (check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2)) || (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1)) @@ -465,13 +486,20 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: if_then, if_else, ) - && ((is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id)) + && ((is_early_return(sym::Option, cx, &if_block) && peel_blocks(if_then).res_local_id() == Some(bind_id)) || is_early_return(sym::Result, cx, &if_block)) && if_else .map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))) .filter(|e| *e) .is_none() { + if !is_copy(cx, caller_ty) + && let Some(hir_id) = let_expr.res_local_id() + && local_used_after_expr(cx, hir_id, expr) + { + return; + } + let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_)); @@ -505,8 +533,10 @@ impl QuestionMark { fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool { if let Some(expr) = bl.expr && let ExprKind::Call(callee, [_]) = expr.kind + && let ExprKind::Path(qpath) = callee.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::TryTraitFromOutput) { - is_path_lang_item(cx, callee, LangItem::TryTraitFromOutput) + true } else { false } diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 03d00ba849f3..ca0d41fca524 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -2,13 +2,11 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; -use clippy_utils::{ - expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item, - path_to_local, -}; +use clippy_utils::{expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const}; use rustc_ast::Mutability; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; @@ -17,7 +15,7 @@ use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty}; use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; +use rustc_span::{DesugaringKind, Span, sym}; use std::cmp::Ordering; declare_clippy_lint! { @@ -299,8 +297,8 @@ fn check_possible_range_contains( } } -struct RangeBounds<'a, 'tcx> { - val: Constant<'tcx>, +struct RangeBounds<'a> { + val: Constant, expr: &'a Expr<'a>, id: HirId, name_span: Span, @@ -312,7 +310,7 @@ struct RangeBounds<'a, 'tcx> { // Takes a binary expression such as x <= 2 as input // Breaks apart into various pieces, such as the value of the number, // hir id of the variable, and direction/inclusiveness of the operator -fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option> { +fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option> { if let ExprKind::Binary(ref op, l, r) = ex.kind { let (inclusive, ordering) = match op.node { BinOpKind::Gt => (false, Ordering::Greater), @@ -321,7 +319,7 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> BinOpKind::Le => (true, Ordering::Less), _ => return None, }; - if let Some(id) = path_to_local(l) { + if let Some(id) = l.res_local_id() { if let Some(c) = ConstEvalCtxt::new(cx).eval(r) { return Some(RangeBounds { val: c, @@ -333,7 +331,7 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> inc: inclusive, }); } - } else if let Some(id) = path_to_local(r) + } else if let Some(id) = r.res_local_id() && let Some(c) = ConstEvalCtxt::new(cx).eval(l) { return Some(RangeBounds { @@ -370,7 +368,9 @@ fn can_switch_ranges<'tcx>( // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)` if let ExprKind::Call(func, [arg]) = parent_expr.kind && arg.hir_id == use_ctxt.child_id - && is_path_lang_item(cx, func, LangItem::IntoIterIntoIter) + && let ExprKind::Path(qpath) = func.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) + && parent_expr.span.is_desugaring(DesugaringKind::ForLoop) { return true; } @@ -503,17 +503,18 @@ fn check_range_switch<'tcx>( msg: &'static str, operator: &str, ) { - if expr.span.can_be_used_for_suggestions() - && let Some(higher::Range { + if let Some(range) = higher::Range::hir(cx, expr) + && let higher::Range { start, end: Some(end), limits, - }) = higher::Range::hir(expr) + span, + } = range + && span.can_be_used_for_suggestions() && limits == kind && let Some(y) = predicate(cx, end) && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) { - let span = expr.span; span_lint_and_then(cx, lint, span, msg, |diag| { let mut app = Applicability::MachineApplicable; let start = start.map_or(String::new(), |x| { @@ -569,7 +570,8 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { start: Some(start), end: Some(end), limits, - }) = higher::Range::hir(expr) + span, + }) = higher::Range::hir(cx, expr) && let ty = cx.typeck_results().expr_ty(start) && let ty::Int(_) | ty::Uint(_) = ty.kind() && let ecx = ConstEvalCtxt::new(cx) @@ -584,7 +586,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { span_lint( cx, REVERSED_EMPTY_RANGES, - expr.span, + span, "this range is reversed and using it to index a slice will panic at run-time", ); } @@ -593,7 +595,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { span_lint_and_then( cx, REVERSED_EMPTY_RANGES, - expr.span, + span, "this range is empty so it will yield no values", |diag| { if ordering != Ordering::Equal { @@ -605,7 +607,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { }; diag.span_suggestion( - expr.span, + span, "consider using the following if you are attempting to iterate over this \ range in reverse", format!("({end_snippet}{dots}{start_snippet}).rev()"), diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index acd840401c6b..b8d4e7c4651d 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; -use clippy_utils::source::snippet; +use clippy_utils::source::{indent_of, snippet}; use clippy_utils::{get_enclosing_block, sym}; use rustc_errors::Applicability; @@ -83,10 +83,12 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { expr.span, "reading zero byte data to `Vec`", |diag| { + let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); + let indent = indent_of(cx, span).unwrap_or(0); diag.span_suggestion( - expr.span, + span.shrink_to_lo(), "try", - format!("{}.resize({len}, 0); {}", ident, snippet(cx, expr.span, "..")), + format!("{ident}.resize({len}, 0);\n{}", " ".repeat(indent)), applicability, ); }, @@ -100,14 +102,15 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { expr.span, "reading zero byte data to `Vec`", |diag| { + let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); + let indent = indent_of(cx, span).unwrap_or(0); diag.span_suggestion( - expr.span, + span.shrink_to_lo(), "try", format!( - "{}.resize({}, 0); {}", - ident, + "{ident}.resize({}, 0);\n{}", snippet(cx, e.span, ".."), - snippet(cx, expr.span, "..") + " ".repeat(indent) ), applicability, ); @@ -130,6 +133,16 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { } } +fn first_stmt_containing_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx hir::Stmt<'tcx>> { + cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { + if let hir::Node::Stmt(stmt) = node { + Some(stmt) + } else { + None + } + }) +} + struct ReadVecVisitor<'tcx> { local_id: HirId, read_zero_expr: Option<&'tcx Expr<'tcx>>, diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 1d58cdd26d88..13c1b10bf2e8 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::fn_has_unsatisfiable_preds; use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, peel_and_count_ty_refs}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -100,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) || fn_name == Some(sym::to_owned_method) - || (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String)); + || (fn_name == Some(sym::to_string_method) && arg_ty.is_lang_item(cx, LangItem::String)); let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string)); @@ -263,7 +264,7 @@ fn is_call_with_ref_arg<'tcx>( && args.len() == 1 && let mir::Operand::Move(mir::Place { local, .. }) = &args[0].node && let ty::FnDef(def_id, _) = *func.ty(mir, cx.tcx).kind() - && let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].node.ty(mir, cx.tcx)) + && let (inner_ty, 1, _) = peel_and_count_ty_refs(args[0].node.ty(mir, cx.tcx)) && !is_copy(cx, inner_ty) { Some((def_id, *local, inner_ty, destination.as_local()?)) diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 324a05cdcc0c..f2cf809d6012 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_parent_expr; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{get_parent_expr, peel_middle_ty_refs}; +use clippy_utils::ty::peel_and_count_ty_refs; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; @@ -80,10 +81,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind && addressee.span.ctxt() == ctxt && let ExprKind::Index(indexed, range, _) = addressee.kind - && is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull) + && cx + .typeck_results() + .expr_ty_adjusted(range) + .is_lang_item(cx, LangItem::RangeFull) { - let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr)); - let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed)); + let (expr_ty, expr_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(expr)); + let (indexed_ty, indexed_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(indexed)); let parent_expr = get_parent_expr(cx, expr); let needs_parens_for_prefix = parent_expr.is_some_and(|parent| cx.precedence(parent) > ExprPrecedence::Prefix); diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 7bd4d6e993b4..e298fa55a2b6 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -97,7 +97,6 @@ fn extract_fn_ty<'tcx>( // let a: String = String::new(); // let a: String = String::get_string(); hir::QPath::TypeRelative(..) => func_hir_id_to_func_ty(cx, call.hir_id), - hir::QPath::LangItem(..) => None, } } diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 89d945161f62..d1fc228f4b35 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -2,9 +2,10 @@ use std::fmt::Display; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::paths; use clippy_utils::paths::PathLookup; +use clippy_utils::res::MaybeQPath; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{path_def_id, paths}; use rustc_ast::ast::{LitKind, StrStyle}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId}; @@ -138,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for Regex { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Call(fun, [arg]) = expr.kind - && let Some(def_id) = path_def_id(cx, fun) + && let Some(def_id) = fun.res(cx).opt_def_id() && let Some(regex_kind) = self.definitions.get(&def_id) { if let Some(&(loop_item_id, loop_span)) = self.loop_stack.last() diff --git a/clippy_lints/src/replace_box.rs b/clippy_lints/src/replace_box.rs new file mode 100644 index 000000000000..638f6dc1532b --- /dev/null +++ b/clippy_lints/src/replace_box.rs @@ -0,0 +1,200 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::implements_trait; +use clippy_utils::{is_default_equivalent_call, local_is_initialized}; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::smallvec::SmallVec; +use rustc_errors::Applicability; +use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, LangItem, QPath}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::place::ProjectionKind; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Symbol, sym}; + +declare_clippy_lint! { + /// ### What it does + /// Detects assignments of `Default::default()` or `Box::new(value)` + /// to a place of type `Box`. + /// + /// ### Why is this bad? + /// This incurs an extra heap allocation compared to assigning the boxed + /// storage. + /// + /// ### Example + /// ```no_run + /// let mut b = Box::new(1u32); + /// b = Default::default(); + /// ``` + /// Use instead: + /// ```no_run + /// let mut b = Box::new(1u32); + /// *b = Default::default(); + /// ``` + #[clippy::version = "1.92.0"] + pub REPLACE_BOX, + perf, + "assigning a newly created box to `Box` is inefficient" +} + +#[derive(Default)] +pub struct ReplaceBox { + consumed_locals: FxHashSet, + loaded_bodies: SmallVec<[BodyId; 2]>, +} + +impl ReplaceBox { + fn get_consumed_locals(&mut self, cx: &LateContext<'_>) -> &FxHashSet { + if let Some(body_id) = cx.enclosing_body + && !self.loaded_bodies.contains(&body_id) + { + self.loaded_bodies.push(body_id); + ExprUseVisitor::for_clippy( + cx, + cx.tcx.hir_body_owner_def_id(body_id), + MovedVariablesCtxt { + consumed_locals: &mut self.consumed_locals, + }, + ) + .consume_body(cx.tcx.hir_body(body_id)) + .into_ok(); + } + + &self.consumed_locals + } +} + +impl_lint_pass!(ReplaceBox => [REPLACE_BOX]); + +impl LateLintPass<'_> for ReplaceBox { + fn check_body_post(&mut self, _: &LateContext<'_>, body: &Body<'_>) { + if self.loaded_bodies.first().is_some_and(|&x| x == body.id()) { + self.consumed_locals.clear(); + self.loaded_bodies.clear(); + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if let ExprKind::Assign(lhs, rhs, _) = &expr.kind + && !lhs.span.from_expansion() + && !rhs.span.from_expansion() + && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let Some(inner_ty) = lhs_ty.boxed_ty() + // No diagnostic for late-initialized locals + && lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local)) + // No diagnostic if this is a local that has been moved, or the field + // of a local that has been moved, or several chained field accesses of a local + && local_base(lhs).is_none_or(|(base_id, _)| { + !self.get_consumed_locals(cx).contains(&base_id) + }) + { + if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) + && implements_trait(cx, inner_ty, default_trait_id, &[]) + && is_default_call(cx, rhs) + { + span_lint_and_then( + cx, + REPLACE_BOX, + expr.span, + "creating a new box with default content", + |diag| { + let mut app = Applicability::MachineApplicable; + let suggestion = format!( + "{} = Default::default()", + Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref() + ); + + diag.note("this creates a needless allocation").span_suggestion( + expr.span, + "replace existing content with default instead", + suggestion, + app, + ); + }, + ); + } + + if inner_ty.is_sized(cx.tcx, cx.typing_env()) + && let Some(rhs_inner) = get_box_new_payload(cx, rhs) + { + span_lint_and_then(cx, REPLACE_BOX, expr.span, "creating a new box", |diag| { + let mut app = Applicability::MachineApplicable; + let suggestion = format!( + "{} = {}", + Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref(), + Sugg::hir_with_context(cx, rhs_inner, expr.span.ctxt(), "_", &mut app), + ); + + diag.note("this creates a needless allocation").span_suggestion( + expr.span, + "replace existing content with inner value instead", + suggestion, + app, + ); + }); + } + } + } +} + +fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Call(func, _args) if is_default_equivalent_call(cx, func, Some(expr))) +} + +fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call(box_new, [arg]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind + && seg.ident.name == sym::new + && ty.basic_res().is_lang_item(cx, LangItem::OwnedBox) + { + Some(arg) + } else { + None + } +} + +struct MovedVariablesCtxt<'a> { + consumed_locals: &'a mut FxHashSet, +} + +impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_> { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(id) = cmt.place.base + && let mut projections = cmt + .place + .projections + .iter() + .filter(|x| matches!(x.kind, ProjectionKind::Deref)) + // Either no deref or multiple derefs + && (projections.next().is_none() || projections.next().is_some()) + { + self.consumed_locals.insert(id); + } + } + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +/// A local place followed by optional fields +type IdFields = (HirId, Vec); + +/// If `expr` is a local variable with optional field accesses, return it. +fn local_base(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Path(qpath) => qpath.res_local_id().map(|id| (id, Vec::new())), + ExprKind::Field(expr, field) => local_base(expr).map(|(id, mut fields)| { + fields.push(field.name); + (id, fields) + }), + _ => None, + } +} diff --git a/clippy_lints/src/reserve_after_initialization.rs b/clippy_lints/src/reserve_after_initialization.rs index 51adbbcd58bd..ce86a9caac75 100644 --- a/clippy_lints/src/reserve_after_initialization.rs +++ b/clippy_lints/src/reserve_after_initialization.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; -use clippy_utils::{is_from_proc_macro, path_to_local_id, sym}; +use clippy_utils::{is_from_proc_macro, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, LetStmt, PatKind, QPath, Stmt, StmtKind}; @@ -125,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for ReserveAfterInitialization { if let Some(searcher) = self.searcher.take() { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [space_hint], _) = expr.kind - && path_to_local_id(self_arg, searcher.local_id) + && self_arg.res_local_id() == Some(searcher.local_id) && name.ident.name == sym::reserve && !is_from_proc_macro(cx, expr) { diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index b057396034c0..83a226b29e75 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -113,10 +113,9 @@ impl<'tcx> LateLintPass<'tcx> for ReturnSelfNotMustUse { ) { if matches!(kind, FnKind::Method(_, _)) // We are only interested in methods, not in functions or associated functions. - && let Some(impl_def) = cx.tcx.impl_of_assoc(fn_def.to_def_id()) // We don't want this method to be te implementation of a trait because the // `#[must_use]` should be put on the trait definition directly. - && cx.tcx.trait_id_of_impl(impl_def).is_none() + && cx.tcx.inherent_impl_of_assoc(fn_def.to_def_id()).is_some() { let hir_id = cx.tcx.local_def_id_to_hir_id(fn_def); check_method(cx, decl, fn_def, span, hir_id.expect_owner()); diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs deleted file mode 100644 index e0c93153a77a..000000000000 --- a/clippy_lints/src/returns.rs +++ /dev/null @@ -1,513 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; -use clippy_utils::source::{SpanRangeExt, snippet_with_context}; -use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::visitors::for_each_expr; -use clippy_utils::{ - binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, - leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg, - span_find_starting_semi, sym, -}; -use core::ops::ControlFlow; -use rustc_ast::MetaItemInner; -use rustc_errors::Applicability; -use rustc_hir::LangItem::ResultErr; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{ - Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt, - StmtKind, -}; -use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; -use rustc_middle::ty::adjustment::Adjust; -use rustc_middle::ty::{self, GenericArgKind, Ty}; -use rustc_session::declare_lint_pass; -use rustc_span::def_id::LocalDefId; -use rustc_span::edition::Edition; -use rustc_span::{BytePos, Pos, Span}; -use std::borrow::Cow; -use std::fmt::Display; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `let`-bindings, which are subsequently - /// returned. - /// - /// ### Why is this bad? - /// It is just extraneous code. Remove it to make your code - /// more rusty. - /// - /// ### Known problems - /// In the case of some temporaries, e.g. locks, eliding the variable binding could lead - /// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612). - /// This could become relevant if the code is later changed to use the code that would have been - /// bound without first assigning it to a let-binding. - /// - /// ### Example - /// ```no_run - /// fn foo() -> String { - /// let x = String::new(); - /// x - /// } - /// ``` - /// instead, use - /// ```no_run - /// fn foo() -> String { - /// String::new() - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub LET_AND_RETURN, - style, - "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for return statements at the end of a block. - /// - /// ### Why is this bad? - /// Removing the `return` and semicolon will make the code - /// more rusty. - /// - /// ### Example - /// ```no_run - /// fn foo(x: usize) -> usize { - /// return x; - /// } - /// ``` - /// simplify to - /// ```no_run - /// fn foo(x: usize) -> usize { - /// x - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEEDLESS_RETURN, - // This lint requires some special handling in `check_final_expr` for `#[expect]`. - // This handling needs to be updated if the group gets changed. This should also - // be caught by tests. - style, - "using a return statement like `return expr;` where an expression would suffice" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for return statements on `Err` paired with the `?` operator. - /// - /// ### Why is this bad? - /// The `return` is unnecessary. - /// - /// Returns may be used to add attributes to the return expression. Return - /// statements with attributes are therefore be accepted by this lint. - /// - /// ### Example - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// return Err(...)?; - /// } - /// Ok(()) - /// } - /// ``` - /// simplify to - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// Err(...)?; - /// } - /// Ok(()) - /// } - /// ``` - /// if paired with `try_err`, use instead: - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// return Err(...); - /// } - /// Ok(()) - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub NEEDLESS_RETURN_WITH_QUESTION_MARK, - style, - "using a return statement like `return Err(expr)?;` where removing it would suffice" -} - -#[derive(PartialEq, Eq)] -enum RetReplacement<'tcx> { - Empty, - Block, - Unit, - NeedsPar(Cow<'tcx, str>, Applicability), - Expr(Cow<'tcx, str>, Applicability), -} - -impl RetReplacement<'_> { - fn sugg_help(&self) -> &'static str { - match self { - Self::Empty | Self::Expr(..) => "remove `return`", - Self::Block => "replace `return` with an empty block", - Self::Unit => "replace `return` with a unit value", - Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", - } - } - - fn applicability(&self) -> Applicability { - match self { - Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, - _ => Applicability::MachineApplicable, - } - } -} - -impl Display for RetReplacement<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Empty => write!(f, ""), - Self::Block => write!(f, "{{}}"), - Self::Unit => write!(f, "()"), - Self::NeedsPar(inner, _) => write!(f, "({inner})"), - Self::Expr(inner, _) => write!(f, "{inner}"), - } - } -} - -declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); - -/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. This -/// is the case when the enclosing block expression is coerced to some other type, which only works -/// because of the never-ness of `return` expressions -fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool { - cx.tcx - .hir_parent_iter(stmt_hir_id) - .find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None }) - .is_some_and(|e| { - cx.typeck_results() - .expr_adjustments(e) - .iter() - .any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny)) - }) -} - -impl<'tcx> LateLintPass<'tcx> for Return { - fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if !stmt.span.in_external_macro(cx.sess().source_map()) - && let StmtKind::Semi(expr) = stmt.kind - && let ExprKind::Ret(Some(ret)) = expr.kind - // return Err(...)? desugars to a match - // over a Err(...).branch() - // which breaks down to a branch call, with the callee being - // the constructor of the Err variant - && let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind - && let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind - && let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind - && is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr) - - // Ensure this is not the final stmt, otherwise removing it would cause a compile error - && let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) - && let ItemKind::Fn { body, .. } = item.kind - && let block = cx.tcx.hir_body(body).value - && let ExprKind::Block(block, _) = block.kind - && !is_inside_let_else(cx.tcx, expr) - && let [.., final_stmt] = block.stmts - && final_stmt.hir_id != stmt.hir_id - && !is_from_proc_macro(cx, expr) - && !stmt_needs_never_type(cx, stmt.hir_id) - { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN_WITH_QUESTION_MARK, - expr.span.until(ret.span), - "unneeded `return` statement with `?` operator", - "remove it", - String::new(), - Applicability::MachineApplicable, - ); - } - } - - fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { - // we need both a let-binding stmt and an expr - if let Some(retexpr) = block.expr - && let Some(stmt) = block.stmts.iter().last() - && let StmtKind::Let(local) = &stmt.kind - && local.ty.is_none() - && cx.tcx.hir_attrs(local.hir_id).is_empty() - && let Some(initexpr) = &local.init - && let PatKind::Binding(_, local_id, _, _) = local.pat.kind - && path_to_local_id(retexpr, local_id) - && (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr)) - && !initexpr.span.in_external_macro(cx.sess().source_map()) - && !retexpr.span.in_external_macro(cx.sess().source_map()) - && !local.span.from_expansion() - && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) - { - span_lint_hir_and_then( - cx, - LET_AND_RETURN, - retexpr.hir_id, - retexpr.span, - "returning the result of a `let` binding from a block", - |err| { - err.span_label(local.span, "unnecessary `let` binding"); - - if let Some(src) = initexpr.span.get_source_text(cx) { - let sugg = if binary_expr_needs_parentheses(initexpr) { - if has_enclosing_paren(&src) { - src.to_owned() - } else { - format!("({src})") - } - } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { - if has_enclosing_paren(&src) { - format!("{src} as _") - } else { - format!("({src}) as _") - } - } else { - src.to_owned() - }; - err.multipart_suggestion( - "return the expression directly", - vec![(local.span, String::new()), (retexpr.span, sugg)], - Applicability::MachineApplicable, - ); - } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } - }, - ); - } - } - - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - _: &'tcx FnDecl<'tcx>, - body: &'tcx Body<'tcx>, - sp: Span, - _: LocalDefId, - ) { - if sp.from_expansion() { - return; - } - - match kind { - FnKind::Closure => { - // when returning without value in closure, replace this `return` - // with an empty block to prevent invalid suggestion (see #6501) - let replacement = if let ExprKind::Ret(None) = &body.value.kind { - RetReplacement::Block - } else { - RetReplacement::Empty - }; - check_final_expr(cx, body.value, vec![], replacement, None); - }, - FnKind::ItemFn(..) | FnKind::Method(..) => { - check_block_return(cx, &body.value.kind, sp, vec![]); - }, - } - } -} - -// if `expr` is a block, check if there are needless returns in it -fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec) { - if let ExprKind::Block(block, _) = expr_kind { - if let Some(block_expr) = block.expr { - check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None); - } else if let Some(stmt) = block.stmts.iter().last() { - match stmt.kind { - StmtKind::Expr(expr) => { - check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None); - }, - StmtKind::Semi(semi_expr) => { - // Remove ending semicolons and any whitespace ' ' in between. - // Without `return`, the suggestion might not compile if the semicolon is retained - if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) { - let semi_span_to_remove = - span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); - semi_spans.push(semi_span_to_remove); - } - check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None); - }, - _ => (), - } - } - } -} - -fn check_final_expr<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, - semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an - * needless return */ - replacement: RetReplacement<'tcx>, - match_ty_opt: Option>, -) { - let peeled_drop_expr = expr.peel_drop_temps(); - match &peeled_drop_expr.kind { - // simple return is always "bad" - ExprKind::Ret(inner) => { - // check if expr return nothing - let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { - extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) - } else { - peeled_drop_expr.span - }; - - let replacement = if let Some(inner_expr) = inner { - // if desugar of `do yeet`, don't lint - if let ExprKind::Call(path_expr, [_]) = inner_expr.kind - && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind - { - return; - } - - let mut applicability = Applicability::MachineApplicable; - let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); - if binary_expr_needs_parentheses(inner_expr) { - RetReplacement::NeedsPar(snippet, applicability) - } else { - RetReplacement::Expr(snippet, applicability) - } - } else { - match match_ty_opt { - Some(match_ty) => { - match match_ty.kind() { - // If the code got till here with - // tuple not getting detected before it, - // then we are sure it's going to be Unit - // type - ty::Tuple(_) => RetReplacement::Unit, - // We don't want to anything in this case - // cause we can't predict what the user would - // want here - _ => return, - } - }, - None => replacement, - } - }; - - if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) { - return; - } - - if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { - return; - } - - // Returns may be used to turn an expression into a statement in rustc's AST. - // This allows the addition of attributes, like `#[allow]` (See: clippy#9361) - // `#[expect(clippy::needless_return)]` needs to be handled separately to - // actually fulfill the expectation (clippy::#12998) - match cx.tcx.hir_attrs(expr.hir_id) { - [] => {}, - [attr] => { - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) - && let metas = attr.meta_item_list() - && let Some(lst) = metas - && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy - && matches!( - lint_name.ident.name, - sym::needless_return | sym::style | sym::all | sym::warnings - ) - { - // This is an expectation of the `needless_return` lint - } else { - return; - } - }, - _ => return, - } - - emit_return_lint( - cx, - peeled_drop_expr.span, - ret_span, - semi_spans, - &replacement, - expr.hir_id, - ); - }, - ExprKind::If(_, then, else_clause_opt) => { - check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); - if let Some(else_clause) = else_clause_opt { - // The `RetReplacement` won't be used there as `else_clause` will be either a block or - // a `if` expression. - check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt); - } - }, - // a match expr, check all arms - // an if/if let expr, check both exprs - // note, if without else is going to be a type checking error anyways - // (except for unit type functions) so we don't match it - ExprKind::Match(_, arms, MatchSource::Normal) => { - let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); - for arm in *arms { - check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty)); - } - }, - // if it's a whole block, check it - other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans), - } -} - -fn emit_return_lint( - cx: &LateContext<'_>, - lint_span: Span, - ret_span: Span, - semi_spans: Vec, - replacement: &RetReplacement<'_>, - at: HirId, -) { - span_lint_hir_and_then( - cx, - NEEDLESS_RETURN, - at, - lint_span, - "unneeded `return` statement", - |diag| { - let suggestions = std::iter::once((ret_span, replacement.to_string())) - .chain(semi_spans.into_iter().map(|span| (span, String::new()))) - .collect(); - - diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability()); - }, - ); -} - -fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - for_each_expr(cx, expr, |e| { - if let Some(def_id) = fn_def_id(cx, e) - && cx - .tcx - .fn_sig(def_id) - .instantiate_identity() - .skip_binder() - .output() - .walk() - .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static())) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() -} - -// Go backwards while encountering whitespace and extend the given Span to that point. -fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span { - if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) { - let ws = [b' ', b'\t', b'\n']; - if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) { - let len = prev_source.len() - non_ws_pos - 1; - return sp.with_lo(sp.lo() - BytePos::from_usize(len)); - } - } - - sp -} diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs new file mode 100644 index 000000000000..0a00981e15be --- /dev/null +++ b/clippy_lints/src/returns/let_and_return.rs @@ -0,0 +1,87 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::MaybeResPath; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sugg::has_enclosing_paren; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, PatKind, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::GenericArgKind; +use rustc_span::edition::Edition; + +use super::LET_AND_RETURN; + +pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr + if let Some(retexpr) = block.expr + && let Some(stmt) = block.stmts.last() + && let StmtKind::Let(local) = &stmt.kind + && local.ty.is_none() + && cx.tcx.hir_attrs(local.hir_id).is_empty() + && let Some(initexpr) = &local.init + && let PatKind::Binding(_, local_id, _, _) = local.pat.kind + && retexpr.res_local_id() == Some(local_id) + && (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr)) + && !initexpr.span.in_external_macro(cx.sess().source_map()) + && !retexpr.span.in_external_macro(cx.sess().source_map()) + && !local.span.from_expansion() + && !span_contains_non_whitespace(cx, stmt.span.between(retexpr.span), true) + { + span_lint_hir_and_then( + cx, + LET_AND_RETURN, + retexpr.hir_id, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(src) = initexpr.span.get_source_text(cx) { + let sugg = if binary_expr_needs_parentheses(initexpr) { + if has_enclosing_paren(&src) { + src.to_owned() + } else { + format!("({src})") + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if has_enclosing_paren(&src) { + format!("{src} as _") + } else { + format!("({src}) as _") + } + } else { + src.to_owned() + }; + err.multipart_suggestion( + "return the expression directly", + vec![(local.span, String::new()), (retexpr.span, sugg)], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } +} +fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + for_each_expr(cx, expr, |e| { + if let Some(def_id) = fn_def_id(cx, e) + && cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_binder() + .output() + .walk() + .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static())) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} diff --git a/clippy_lints/src/returns/mod.rs b/clippy_lints/src/returns/mod.rs new file mode 100644 index 000000000000..47c6332b9b81 --- /dev/null +++ b/clippy_lints/src/returns/mod.rs @@ -0,0 +1,140 @@ +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Block, Body, FnDecl, Stmt}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; + +mod let_and_return; +mod needless_return; +mod needless_return_with_question_mark; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// ### Why is this bad? + /// It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// ### Known problems + /// In the case of some temporaries, e.g. locks, eliding the variable binding could lead + /// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612). + /// This could become relevant if the code is later changed to use the code that would have been + /// bound without first assigning it to a let-binding. + /// + /// ### Example + /// ```no_run + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ```no_run + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements at the end of a block. + /// + /// ### Why is this bad? + /// Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// ### Example + /// ```no_run + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```no_run + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NEEDLESS_RETURN, + // This lint requires some special handling in `check_final_expr` for `#[expect]`. + // This handling needs to be updated if the group gets changed. This should also + // be caught by tests. + style, + "using a return statement like `return expr;` where an expression would suffice" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements on `Err` paired with the `?` operator. + /// + /// ### Why is this bad? + /// The `return` is unnecessary. + /// + /// Returns may be used to add attributes to the return expression. Return + /// statements with attributes are therefore be accepted by this lint. + /// + /// ### Example + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// simplify to + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// if paired with `try_err`, use instead: + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...); + /// } + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub NEEDLESS_RETURN_WITH_QUESTION_MARK, + style, + "using a return statement like `return Err(expr)?;` where removing it would suffice" +} + +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); + +impl<'tcx> LateLintPass<'tcx> for Return { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + needless_return_with_question_mark::check_stmt(cx, stmt); + } + + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + let_and_return::check_block(cx, block); + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + sp: Span, + _: LocalDefId, + ) { + needless_return::check_fn(cx, kind, body, sp); + } +} diff --git a/clippy_lints/src/returns/needless_return.rs b/clippy_lints/src/returns/needless_return.rs new file mode 100644 index 000000000000..7d836b610e5f --- /dev/null +++ b/clippy_lints/src/returns/needless_return.rs @@ -0,0 +1,270 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::source::snippet_with_context; +use clippy_utils::{ + binary_expr_needs_parentheses, is_from_proc_macro, leaks_droppable_temporary_with_limited_lifetime, + span_contains_cfg, span_find_starting_semi, sym, +}; +use rustc_ast::MetaItemInner; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Expr, ExprKind, HirId, LangItem, MatchSource, StmtKind}; +use rustc_lint::{LateContext, Level, LintContext}; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{BytePos, Pos, Span}; +use std::borrow::Cow; +use std::fmt::Display; + +use super::NEEDLESS_RETURN; + +#[derive(PartialEq, Eq)] +enum RetReplacement<'tcx> { + Empty, + Block, + Unit, + NeedsPar(Cow<'tcx, str>, Applicability), + Expr(Cow<'tcx, str>, Applicability), +} + +impl RetReplacement<'_> { + fn sugg_help(&self) -> &'static str { + match self { + Self::Empty | Self::Expr(..) => "remove `return`", + Self::Block => "replace `return` with an empty block", + Self::Unit => "replace `return` with a unit value", + Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", + } + } + + fn applicability(&self) -> Applicability { + match self { + Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, + _ => Applicability::MachineApplicable, + } + } +} + +impl Display for RetReplacement<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Block => f.write_str("{}"), + Self::Unit => f.write_str("()"), + Self::NeedsPar(inner, _) => write!(f, "({inner})"), + Self::Expr(inner, _) => write!(f, "{inner}"), + } + } +} + +pub(super) fn check_fn<'tcx>(cx: &LateContext<'tcx>, kind: FnKind<'tcx>, body: &'tcx Body<'tcx>, sp: Span) { + if sp.from_expansion() { + return; + } + + match kind { + FnKind::Closure => { + // when returning without value in closure, replace this `return` + // with an empty block to prevent invalid suggestion (see #6501) + let replacement = if let ExprKind::Ret(None) = &body.value.kind { + RetReplacement::Block + } else { + RetReplacement::Empty + }; + check_final_expr(cx, body.value, vec![], replacement, None); + }, + FnKind::ItemFn(..) | FnKind::Method(..) => { + check_block_return(cx, &body.value.kind, sp, vec![]); + }, + } +} + +// if `expr` is a block, check if there are needless returns in it +fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec) { + if let ExprKind::Block(block, _) = expr_kind { + if let Some(block_expr) = block.expr { + check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None); + } else if let Some(stmt) = block.stmts.last() { + if span_contains_cfg( + cx, + Span::between( + stmt.span, + cx.sess().source_map().end_point(block.span), // the closing brace of the block + ), + ) { + return; + } + match stmt.kind { + StmtKind::Expr(expr) => { + check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None); + }, + StmtKind::Semi(semi_expr) => { + // Remove ending semicolons and any whitespace ' ' in between. + // Without `return`, the suggestion might not compile if the semicolon is retained + if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) { + let semi_span_to_remove = + span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); + semi_spans.push(semi_span_to_remove); + } + check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None); + }, + _ => (), + } + } + } +} + +fn check_final_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an + * needless return */ + replacement: RetReplacement<'tcx>, + match_ty_opt: Option>, +) { + let peeled_drop_expr = expr.peel_drop_temps(); + match &peeled_drop_expr.kind { + // simple return is always "bad" + ExprKind::Ret(inner) => { + // check if expr return nothing + let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { + extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) + } else { + peeled_drop_expr.span + }; + + let replacement = if let Some(inner_expr) = inner { + // if desugar of `do yeet`, don't lint + if let ExprKind::Call(path_expr, [_]) = inner_expr.kind + && let ExprKind::Path(qpath) = path_expr.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::TryTraitFromYeet) + { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); + if binary_expr_needs_parentheses(inner_expr) { + RetReplacement::NeedsPar(snippet, applicability) + } else { + RetReplacement::Expr(snippet, applicability) + } + } else { + match match_ty_opt { + Some(match_ty) => { + match match_ty.kind() { + // If the code got till here with + // tuple not getting detected before it, + // then we are sure it's going to be Unit + // type + ty::Tuple(_) => RetReplacement::Unit, + // We don't want to anything in this case + // cause we can't predict what the user would + // want here + _ => return, + } + }, + None => replacement, + } + }; + + if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) { + return; + } + + if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { + return; + } + + // Returns may be used to turn an expression into a statement in rustc's AST. + // This allows the addition of attributes, like `#[allow]` (See: clippy#9361) + // `#[expect(clippy::needless_return)]` needs to be handled separately to + // actually fulfill the expectation (clippy::#12998) + match cx.tcx.hir_attrs(expr.hir_id) { + [] => {}, + [attr] => { + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let metas = attr.meta_item_list() + && let Some(lst) = metas + && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && matches!( + lint_name.ident.name, + sym::needless_return | sym::style | sym::all | sym::warnings + ) + { + // This is an expectation of the `needless_return` lint + } else { + return; + } + }, + _ => return, + } + + emit_return_lint( + cx, + peeled_drop_expr.span, + ret_span, + semi_spans, + &replacement, + expr.hir_id, + ); + }, + ExprKind::If(_, then, else_clause_opt) => { + check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); + if let Some(else_clause) = else_clause_opt { + // The `RetReplacement` won't be used there as `else_clause` will be either a block or + // a `if` expression. + check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt); + } + }, + // a match expr, check all arms + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + ExprKind::Match(_, arms, MatchSource::Normal) => { + let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); + for arm in *arms { + check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty)); + } + }, + // if it's a whole block, check it + other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans), + } +} + +fn emit_return_lint( + cx: &LateContext<'_>, + lint_span: Span, + ret_span: Span, + semi_spans: Vec, + replacement: &RetReplacement<'_>, + at: HirId, +) { + span_lint_hir_and_then( + cx, + NEEDLESS_RETURN, + at, + lint_span, + "unneeded `return` statement", + |diag| { + let suggestions = std::iter::once((ret_span, replacement.to_string())) + .chain(semi_spans.into_iter().map(|span| (span, String::new()))) + .collect(); + + diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability()); + }, + ); +} + +// Go backwards while encountering whitespace and extend the given Span to that point. +fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span { + if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) { + let ws = [b' ', b'\t', b'\n']; + if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) { + let len = prev_source.len() - non_ws_pos - 1; + return sp.with_lo(sp.lo() - BytePos::from_usize(len)); + } + } + + sp +} diff --git a/clippy_lints/src/returns/needless_return_with_question_mark.rs b/clippy_lints/src/returns/needless_return_with_question_mark.rs new file mode 100644 index 000000000000..c47a3ef21e86 --- /dev/null +++ b/clippy_lints/src/returns/needless_return_with_question_mark.rs @@ -0,0 +1,61 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::{is_from_proc_macro, is_inside_let_else}; +use rustc_errors::Applicability; +use rustc_hir::LangItem::ResultErr; +use rustc_hir::{ExprKind, HirId, ItemKind, MatchSource, Node, OwnerNode, Stmt, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::adjustment::Adjust; + +use super::NEEDLESS_RETURN_WITH_QUESTION_MARK; + +pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if !stmt.span.in_external_macro(cx.sess().source_map()) + && let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::Ret(Some(ret)) = expr.kind + // return Err(...)? desugars to a match + // over a Err(...).branch() + // which breaks down to a branch call, with the callee being + // the constructor of the Err variant + && let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind + && let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind + && let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind + && maybe_constr.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) + + // Ensure this is not the final stmt, otherwise removing it would cause a compile error + && let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) + && let ItemKind::Fn { body, .. } = item.kind + && let block = cx.tcx.hir_body(body).value + && let ExprKind::Block(block, _) = block.kind + && !is_inside_let_else(cx.tcx, expr) + && let [.., final_stmt] = block.stmts + && final_stmt.hir_id != stmt.hir_id + && !is_from_proc_macro(cx, expr) + && !stmt_needs_never_type(cx, stmt.hir_id) + { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN_WITH_QUESTION_MARK, + expr.span.until(ret.span), + "unneeded `return` statement with `?` operator", + "remove it", + String::new(), + Applicability::MachineApplicable, + ); + } +} + +/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. +/// This is the case when the enclosing block expression is coerced to some other type, +/// which only works because of the never-ness of `return` expressions +fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool { + cx.tcx + .hir_parent_iter(stmt_hir_id) + .find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None }) + .is_some_and(|e| { + cx.typeck_results() + .expr_adjustments(e) + .iter() + .any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny)) + }) +} diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 1dea8f17c34b..371d62a06849 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -82,6 +83,19 @@ impl SemicolonBlock { let insert_span = tail.span.source_callsite().shrink_to_hi(); let remove_span = semi_span.with_lo(block.span.hi()); + // If the block is surrounded by parens (`({ 0 });`), the author probably knows what + // they're doing and why, so don't get in their way. + // + // This has the additional benefit of stopping the block being parsed as a function call: + // ``` + // fn foo() { + // ({ 0 }); // if we remove this `;`, this will parse as a `({ 0 })(5);` function call + // (5); + // } + if remove_span.check_source_text(cx, |src| src.contains(')')) { + return; + } + if self.semicolon_inside_block_ignore_singleline && get_line(cx, remove_span) == get_line(cx, insert_span) { return; } diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index ff6e6ef214b5..688da33a1777 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::res::MaybeDef; use clippy_utils::visitors::for_each_expr; use clippy_utils::{SpanlessEq, higher, peel_hir_expr_while, sym}; use rustc_hir::{Expr, ExprKind, UnOp}; @@ -103,7 +103,7 @@ fn try_parse_op_call<'tcx>( let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); if value.span.eq_ctxt(expr.span) && path.ident.name == symbol { for sym in &[sym::HashSet, sym::BTreeSet] { - if is_type_diagnostic_item(cx, receiver_ty, *sym) { + if receiver_ty.is_diag_item(cx, *sym) { return Some((OpExpr { receiver, value, span }, *sym)); } } diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 14399867f318..f6083394fea5 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::path_to_local_id; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::visitors::{Descend, Visitable, for_each_expr}; use rustc_data_structures::fx::FxHashMap; @@ -133,7 +133,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { .tcx .hir_parent_iter(pat.hir_id) .find(|(_, node)| !matches!(node, Node::Pat(_) | Node::PatField(_))) - && let LocalSource::AssignDesugar(_) = let_stmt.source + && let LocalSource::AssignDesugar = let_stmt.source { return; } @@ -202,7 +202,7 @@ pub fn is_local_used_except<'tcx>( for_each_expr(cx, visitable, |e| { if except.is_some_and(|it| it == e.hir_id) { ControlFlow::Continue(Descend::No) - } else if path_to_local_id(e, id) { + } else if e.res_local_id() == Some(id) { ControlFlow::Break(()) } else { ControlFlow::Continue(Descend::Yes) diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 9110f684bd10..fabb21f78b9e 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, path_to_local, peel_hir_expr_unary, sym}; +use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -166,7 +167,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { if let Some(adt) = ty.ty_adt_def() { - let mut iter = get_attr( + let mut iter = get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, @@ -276,7 +277,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { && let hir::PatKind::Binding(_, hir_id, ident, _) = local.pat.kind && !self.ap.apas.contains_key(&hir_id) && { - if let Some(local_hir_id) = path_to_local(expr) { + if let Some(local_hir_id) = expr.res_local_id() { local_hir_id == hir_id } else { true @@ -301,7 +302,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { modify_apa_params(&mut apa); let _ = self.ap.apas.insert(hir_id, apa); } else { - let Some(hir_id) = path_to_local(expr) else { + let Some(hir_id) = expr.res_local_id() else { return; }; let Some(apa) = self.ap.apas.get_mut(&hir_id) else { @@ -319,7 +320,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { } }, hir::StmtKind::Semi(semi_expr) => { - if has_drop(semi_expr, apa.first_bind_ident, self.cx) { + if has_drop(self.cx, semi_expr, apa.first_bind_ident) { apa.has_expensive_expr_after_last_attr = false; apa.last_stmt_span = DUMMY_SP; return; @@ -416,11 +417,11 @@ fn dummy_stmt_expr<'any>(expr: &'any hir::Expr<'any>) -> hir::Stmt<'any> { } } -fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: Option, lcx: &LateContext<'_>) -> bool { +fn has_drop(cx: &LateContext<'_>, expr: &hir::Expr<'_>, first_bind_ident: Option) -> bool { if let hir::ExprKind::Call(fun, [first_arg]) = expr.kind && let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind && let Res::Def(DefKind::Fn, did) = fun_path.res - && lcx.tcx.is_diagnostic_item(sym::mem_drop, did) + && cx.tcx.is_diagnostic_item(sym::mem_drop, did) { let has_ident = |local_expr: &hir::Expr<'_>| { if let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &local_expr.kind diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index 8c34da0d14a4..595c75acf031 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -40,8 +40,8 @@ declare_clippy_lint! { declare_lint_pass!(SingleCharLifetimeNames => [SINGLE_CHAR_LIFETIME_NAMES]); impl EarlyLintPass for SingleCharLifetimeNames { - fn check_generic_param(&mut self, ctx: &EarlyContext<'_>, param: &GenericParam) { - if param.ident.span.in_external_macro(ctx.sess().source_map()) { + fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &GenericParam) { + if param.ident.span.in_external_macro(cx.sess().source_map()) { return; } @@ -51,7 +51,7 @@ impl EarlyLintPass for SingleCharLifetimeNames { { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( - ctx, + cx, SINGLE_CHAR_LIFETIME_NAMES, param.ident.span, "single-character lifetime names are likely uninformative", diff --git a/clippy_lints/src/single_option_map.rs b/clippy_lints/src/single_option_map.rs index cc497c97a472..4556d287711f 100644 --- a/clippy_lints/src/single_option_map.rs +++ b/clippy_lints/src/single_option_map.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{path_res, peel_blocks}; +use clippy_utils::peel_blocks; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; @@ -55,10 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for SingleOptionMap { if let ExprKind::MethodCall(method_name, callee, args, _span) = func_body.kind && method_name.ident.name == sym::map && let callee_type = cx.typeck_results().expr_ty(callee) - && is_type_diagnostic_item(cx, callee_type, sym::Option) + && callee_type.is_diag_item(cx, sym::Option) && let ExprKind::Path(_path) = callee.kind - && let Res::Local(_id) = path_res(cx, callee) - && matches!(path_res(cx, callee), Res::Local(_id)) + && matches!(callee.basic_res(), Res::Local(_)) && !matches!(args[0].kind, ExprKind::Path(_)) { if let ExprKind::Closure(closure) = args[0].kind { @@ -71,7 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for SingleOptionMap { } else if let ExprKind::MethodCall(_segment, receiver, method_args, _span) = value.kind && matches!(receiver.kind, ExprKind::Path(_)) && method_args.iter().all(|arg| matches!(arg.kind, ExprKind::Path(_))) - && method_args.iter().all(|arg| matches!(path_res(cx, arg), Res::Local(_))) + && method_args.iter().all(|arg| matches!(arg.basic_res(), Res::Local(_))) { return; } diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index dda2f8cc1d00..412ca2fa4ed9 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -6,7 +6,7 @@ use clippy_utils::ty::implements_trait; use clippy_utils::{is_no_std_crate, sym}; use rustc_ast::{LitIntType, LitKind, UintTy}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr}; +use rustc_hir::{Expr, ExprKind, LangItem, StructTailExpr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use std::fmt::{self, Display, Formatter}; @@ -86,12 +86,11 @@ impl LateLintPass<'_> for SingleRangeInVecInit { return; }; - let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind - else { + let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind else { return; }; - if matches!(lang_item, LangItem::Range) + if cx.tcx.qpath_is_lang_item(qpath, LangItem::Range) && let ty = cx.typeck_results().expr_ty(start.expr) && let Some(snippet) = span.get_source_text(cx) // `is_from_proc_macro` will skip any `vec![]`. Let's not! diff --git a/clippy_lints/src/size_of_ref.rs b/clippy_lints/src/size_of_ref.rs index 60d923bcd77e..bf304ebcfdc0 100644 --- a/clippy_lints/src/size_of_ref.rs +++ b/clippy_lints/src/size_of_ref.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{path_def_id, peel_middle_ty_refs}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::ty::peel_and_count_ty_refs; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -56,10 +57,9 @@ declare_lint_pass!(SizeOfRef => [SIZE_OF_REF]); impl LateLintPass<'_> for SizeOfRef { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Call(path, [arg]) = expr.kind - && let Some(def_id) = path_def_id(cx, path) - && cx.tcx.is_diagnostic_item(sym::mem_size_of_val, def_id) + && path.basic_res().is_diag_item(cx, sym::mem_size_of_val) && let arg_ty = cx.typeck_results().expr_ty(arg) - && peel_middle_ty_refs(arg_ty).1 > 1 + && peel_and_count_ty_refs(arg_ty).1 > 1 { span_lint_and_help( cx, diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index f497d0700b8e..b25fa0905feb 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -1,10 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::matching_root_macro_call; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - SpanlessEq, get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, - span_contains_comment, sym, -}; +use clippy_utils::{SpanlessEq, get_enclosing_block, is_integer_literal, span_contains_comment, sym}; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind}; @@ -102,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)` if let ExprKind::Assign(left, right, _) = expr.kind - && let Some(local_id) = path_to_local(left) + && let Some(local_id) = left.res_local_id() && let Some(size_expr) = Self::as_vec_initializer(cx, right) { let vi = VecAllocation { @@ -149,10 +147,10 @@ impl SlowVectorInit { } if let ExprKind::Call(func, [len_expr]) = expr.kind - && is_path_diagnostic_item(cx, func, sym::vec_with_capacity) + && func.ty_rel_def(cx).is_diag_item(cx, sym::vec_with_capacity) { Some(InitializedSize::Initialized(len_expr)) - } else if matches!(expr.kind, ExprKind::Call(func, []) if is_path_diagnostic_item(cx, func, sym::vec_new)) { + } else if matches!(expr.kind, ExprKind::Call(func, []) if func.ty_rel_def(cx).is_diag_item(cx, sym::vec_new)) { Some(InitializedSize::Uninitialized) } else { None @@ -246,7 +244,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind - && path_to_local_id(self_arg, self.vec_alloc.local_id) + && self_arg.res_local_id() == Some(self.vec_alloc.local_id) && path.ident.name == sym::extend && self.is_repeat_take(extend_arg) { @@ -258,7 +256,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind - && path_to_local_id(self_arg, self.vec_alloc.local_id) + && self_arg.res_local_id() == Some(self.vec_alloc.local_id) && path.ident.name == sym::resize // Check that is filled with 0 && is_integer_literal(fill_arg, 0) @@ -301,7 +299,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { /// Returns `true` if given expression is `repeat(0)` fn is_repeat_zero(&self, expr: &Expr<'_>) -> bool { if let ExprKind::Call(fn_expr, [repeat_arg]) = expr.kind - && is_path_diagnostic_item(self.cx, fn_expr, sym::iter_repeat) + && fn_expr.basic_res().is_diag_item(self.cx, sym::iter_repeat) && is_integer_literal(repeat_arg, 0) { true diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs index f63e6b3087b9..e5347bf3e8f0 100644 --- a/clippy_lints/src/string_patterns.rs +++ b/clippy_lints/src/string_patterns.rs @@ -5,9 +5,10 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::macros::matching_root_macro_call; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{snippet, str_literal_to_char_literal}; +use clippy_utils::sym; use clippy_utils::visitors::{Descend, for_each_expr}; -use clippy_utils::{path_to_local_id, sym}; use itertools::Itertools; use rustc_ast::{BinOpKind, LitKind}; use rustc_errors::Applicability; @@ -146,12 +147,12 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr< if for_each_expr(cx, body.value, |sub_expr| -> ControlFlow<(), Descend> { match sub_expr.kind { ExprKind::Binary(op, left, right) if op.node == BinOpKind::Eq => { - if path_to_local_id(left, binding) + if left.res_local_id() == Some(binding) && let Some(span) = get_char_span(cx, right) { set_char_spans.push(span); ControlFlow::Continue(Descend::No) - } else if path_to_local_id(right, binding) + } else if right.res_local_id() == Some(binding) && let Some(span) = get_char_span(cx, left) { set_char_spans.push(span); @@ -164,7 +165,7 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr< ExprKind::Match(match_value, [arm, _], _) => { if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none() || arm.guard.is_some() - || !path_to_local_id(match_value, binding) + || match_value.res_local_id() != Some(binding) { return ControlFlow::Break(()); } diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 57d5900b045e..1d0efa46a14c 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::ty::is_type_lang_item; use clippy_utils::{ - SpanlessEq, get_expr_use_or_unification_node, get_parent_expr, is_lint_allowed, method_calls, path_def_id, - peel_blocks, sym, + SpanlessEq, get_expr_use_or_unification_node, get_parent_expr, is_lint_allowed, method_calls, peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, Node, QPath}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, Node}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::declare_lint_pass; @@ -188,7 +187,7 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { }, ExprKind::Index(target, _idx, _) => { let e_ty = cx.typeck_results().expr_ty_adjusted(target).peel_refs(); - if e_ty.is_str() || is_type_lang_item(cx, e_ty, LangItem::String) { + if e_ty.is_str() || e_ty.is_lang_item(cx, LangItem::String) { span_lint( cx, STRING_SLICE, @@ -203,7 +202,10 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { } fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - is_type_lang_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), LangItem::String) + cx.typeck_results() + .expr_ty(e) + .peel_refs() + .is_lang_item(cx, LangItem::String) } fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { @@ -253,7 +255,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { if let ExprKind::Call(fun, [bytes_arg]) = e.kind // Find `std::str::converts::from_utf8` or `std::primitive::str::from_utf8` && let Some(sym::str_from_utf8 | sym::str_inherent_from_utf8) = - path_def_id(cx, fun).and_then(|id| cx.tcx.get_diagnostic_name(id)) + fun.res(cx).opt_diag_name(cx) // Find string::as_bytes && let ExprKind::AddrOf(BorrowKind::Ref, _, args) = bytes_arg.kind @@ -264,7 +266,8 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { && expressions[0].1.is_empty() // Check for slicer - && let ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), _, _) = right.kind + && let ExprKind::Struct(&qpath, _, _) = right.kind + && cx.tcx.qpath_is_lang_item(qpath, LangItem::Range) { let mut applicability = Applicability::MachineApplicable; let string_expression = &expressions[0].0; diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 33856c750d7e..58d692db5029 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::visitors::is_expr_unsafe; use clippy_utils::{match_libc_symbol, sym}; use rustc_errors::Applicability; @@ -61,9 +61,9 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); let mut app = Applicability::MachineApplicable; let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; - let method_name = if is_type_diagnostic_item(cx, ty, sym::cstring_type) { + let method_name = if ty.is_diag_item(cx, sym::cstring_type) { "as_bytes" - } else if is_type_lang_item(cx, ty, LangItem::CStr) { + } else if ty.is_lang_item(cx, LangItem::CStr) { "to_bytes" } else { return; diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 76ab3cdae22e..c3cb2c09752f 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_indent, snippet_with_context}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, path_to_local, std_or_core}; +use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, std_or_core}; use itertools::Itertools; use rustc_data_structures::fx::FxIndexSet; @@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for Swap { } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn generate_swap_warning<'tcx>( block: &'tcx Block<'tcx>, cx: &LateContext<'tcx>, @@ -110,8 +110,8 @@ fn generate_swap_warning<'tcx>( if matches!(ty.kind(), ty::Slice(_)) || matches!(ty.kind(), ty::Array(_, _)) - || is_type_diagnostic_item(cx, ty, sym::Vec) - || is_type_diagnostic_item(cx, ty, sym::VecDeque) + || ty.is_diag_item(cx, sym::Vec) + || ty.is_diag_item(cx, sym::VecDeque) { let slice = Sugg::hir_with_applicability(cx, lhs1, "", &mut applicability); @@ -361,7 +361,8 @@ impl<'tcx> IndexBinding<'_, 'tcx> { // - Variable declaration is outside the suggestion span // - Variable is not used as an index or elsewhere later if !self.suggest_span.contains(init.span) - || path_to_local(expr) + || expr + .res_local_id() .is_some_and(|hir_id| !self.suggest_span.contains(self.cx.tcx.hir_span(hir_id))) || !self.is_used_other_than_swapping(first_segment.ident) { diff --git a/clippy_lints/src/swap_ptr_to_ref.rs b/clippy_lints/src/swap_ptr_to_ref.rs index ff196355a2e3..339c97d575ae 100644 --- a/clippy_lints/src/swap_ptr_to_ref.rs +++ b/clippy_lints/src/swap_ptr_to_ref.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::path_def_id; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp}; @@ -41,8 +41,7 @@ declare_lint_pass!(SwapPtrToRef => [SWAP_PTR_TO_REF]); impl LateLintPass<'_> for SwapPtrToRef { fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { if let ExprKind::Call(fn_expr, [arg1, arg2]) = e.kind - && let Some(fn_id) = path_def_id(cx, fn_expr) - && cx.tcx.is_diagnostic_item(sym::mem_swap, fn_id) + && fn_expr.basic_res().is_diag_item(cx, sym::mem_swap) && let ctxt = e.span.ctxt() && let (from_ptr1, arg1_span) = is_ptr_to_ref(cx, arg1, ctxt) && let (from_ptr2, arg2_span) = is_ptr_to_ref(cx, arg2, ctxt) diff --git a/clippy_lints/src/time_subtraction.rs b/clippy_lints/src/time_subtraction.rs new file mode 100644 index 000000000000..dbd4ec77fd5f --- /dev/null +++ b/clippy_lints/src/time_subtraction.rs @@ -0,0 +1,216 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::impl_lint_pass; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Lints subtraction between `Instant::now()` and another `Instant`. + /// + /// ### Why is this bad? + /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns + /// as `Instant` subtraction saturates. + /// + /// `prev_instant.elapsed()` also more clearly signals intention. + /// + /// ### Example + /// ```no_run + /// use std::time::Instant; + /// let prev_instant = Instant::now(); + /// let duration = Instant::now() - prev_instant; + /// ``` + /// Use instead: + /// ```no_run + /// use std::time::Instant; + /// let prev_instant = Instant::now(); + /// let duration = prev_instant.elapsed(); + /// ``` + #[clippy::version = "1.65.0"] + pub MANUAL_INSTANT_ELAPSED, + pedantic, + "subtraction between `Instant::now()` and previous `Instant`" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints subtraction between an `Instant` and a `Duration`, or between two `Duration` values. + /// + /// ### Why is this bad? + /// Unchecked subtraction could cause underflow on certain platforms, leading to + /// unintentional panics. + /// + /// ### Example + /// ```no_run + /// # use std::time::{Instant, Duration}; + /// let time_passed = Instant::now() - Duration::from_secs(5); + /// let dur1 = Duration::from_secs(3); + /// let dur2 = Duration::from_secs(5); + /// let diff = dur1 - dur2; + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::time::{Instant, Duration}; + /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); + /// let dur1 = Duration::from_secs(3); + /// let dur2 = Duration::from_secs(5); + /// let diff = dur1.checked_sub(dur2); + /// ``` + #[clippy::version = "1.67.0"] + pub UNCHECKED_TIME_SUBTRACTION, + pedantic, + "finds unchecked subtraction involving 'Duration' or 'Instant'" +} + +pub struct UncheckedTimeSubtraction { + msrv: Msrv, +} + +impl UncheckedTimeSubtraction { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + +impl_lint_pass!(UncheckedTimeSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_TIME_SUBTRACTION]); + +impl LateLintPass<'_> for UncheckedTimeSubtraction { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + lhs, + rhs, + ) = expr.kind + { + let typeck = cx.typeck_results(); + let lhs_ty = typeck.expr_ty(lhs); + let rhs_ty = typeck.expr_ty(rhs); + + if lhs_ty.is_diag_item(cx, sym::Instant) { + // Instant::now() - instant + if is_instant_now_call(cx, lhs) + && rhs_ty.is_diag_item(cx, sym::Instant) + && let Some(sugg) = Sugg::hir_opt(cx, rhs) + { + print_manual_instant_elapsed_sugg(cx, expr, sugg); + } + // instant - duration + else if rhs_ty.is_diag_item(cx, sym::Duration) + && !expr.span.from_expansion() + && self.msrv.meets(cx, msrvs::TRY_FROM) + { + // For chained subtraction like (instant - dur1) - dur2, avoid suggestions + if is_chained_time_subtraction(cx, lhs) { + span_lint( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + "unchecked subtraction of a 'Duration' from an 'Instant'", + ); + } else { + // instant - duration + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + } + } + } else if lhs_ty.is_diag_item(cx, sym::Duration) + && rhs_ty.is_diag_item(cx, sym::Duration) + && !expr.span.from_expansion() + && self.msrv.meets(cx, msrvs::TRY_FROM) + { + // For chained subtraction like (dur1 - dur2) - dur3, avoid suggestions + if is_chained_time_subtraction(cx, lhs) { + span_lint( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + "unchecked subtraction between 'Duration' values", + ); + } else { + // duration - duration + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + } + } + } + } +} + +fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { + if let ExprKind::Call(fn_expr, []) = expr_block.kind + && cx.ty_based_def(fn_expr).is_diag_item(cx, sym::instant_now) + { + true + } else { + false + } +} + +/// Returns true if this subtraction is part of a chain like `(a - b) - c` +fn is_chained_time_subtraction(cx: &LateContext<'_>, lhs: &Expr<'_>) -> bool { + if let ExprKind::Binary(op, inner_lhs, inner_rhs) = &lhs.kind + && matches!(op.node, BinOpKind::Sub) + { + let typeck = cx.typeck_results(); + let left_ty = typeck.expr_ty(inner_lhs); + let right_ty = typeck.expr_ty(inner_rhs); + is_time_type(cx, left_ty) && is_time_type(cx, right_ty) + } else { + false + } +} + +/// Returns true if the type is Duration or Instant +fn is_time_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + ty.is_diag_item(cx, sym::Duration) || ty.is_diag_item(cx, sym::Instant) +} + +fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { + span_lint_and_sugg( + cx, + MANUAL_INSTANT_ELAPSED, + expr.span, + "manual implementation of `Instant::elapsed`", + "try", + format!("{}.elapsed()", sugg.maybe_paren()), + Applicability::MachineApplicable, + ); +} + +fn print_unchecked_duration_subtraction_sugg( + cx: &LateContext<'_>, + left_expr: &Expr<'_>, + right_expr: &Expr<'_>, + expr: &Expr<'_>, +) { + let typeck = cx.typeck_results(); + let left_ty = typeck.expr_ty(left_expr); + + let lint_msg = if left_ty.is_diag_item(cx, sym::Instant) { + "unchecked subtraction of a 'Duration' from an 'Instant'" + } else { + "unchecked subtraction between 'Duration' values" + }; + + let mut applicability = Applicability::MachineApplicable; + let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); + let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); + + span_lint_and_sugg( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + lint_msg, + "try", + format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), + applicability, + ); +} diff --git a/clippy_lints/src/to_digit_is_some.rs b/clippy_lints/src/to_digit_is_some.rs index 3e847543e1c1..71c4172c9852 100644 --- a/clippy_lints/src/to_digit_is_some.rs +++ b/clippy_lints/src/to_digit_is_some.rs @@ -1,8 +1,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_in_const_context, is_path_diagnostic_item, sym}; +use clippy_utils::{is_in_const_context, sym}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -62,7 +63,7 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome { } }, hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => { - if is_path_diagnostic_item(cx, to_digits_call, sym::char_to_digit) { + if to_digits_call.res(cx).is_diag_item(cx, sym::char_to_digit) { Some((false, char_arg, radix_arg)) } else { None diff --git a/clippy_lints/src/toplevel_ref_arg.rs b/clippy_lints/src/toplevel_ref_arg.rs new file mode 100644 index 000000000000..074b79263d37 --- /dev/null +++ b/clippy_lints/src/toplevel_ref_arg.rs @@ -0,0 +1,119 @@ +use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; +use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, iter_input_pats}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingMode, Body, ByRef, FnDecl, Mutability, PatKind, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; + +use crate::ref_patterns::REF_PATTERNS; + +declare_clippy_lint! { + /// ### What it does + /// Checks for function arguments and let bindings denoted as + /// `ref`. + /// + /// ### Why is this bad? + /// The `ref` declaration makes the function take an owned + /// value, but turns the argument into a reference (which means that the value + /// is destroyed when exiting the function). This adds not much value: either + /// take a reference type, or take an owned value and create references in the + /// body. + /// + /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The + /// type of `x` is more obvious with the former. + /// + /// ### Known problems + /// If the argument is dereferenced within the function, + /// removing the `ref` will lead to errors. This can be fixed by removing the + /// dereferences, e.g., changing `*x` to `x` within the function. + /// + /// ### Example + /// ```no_run + /// fn foo(ref _x: u8) {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn foo(_x: &u8) {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub TOPLEVEL_REF_ARG, + style, + "an entire binding declared as `ref`, in a function argument or a `let` statement" +} + +declare_lint_pass!(ToplevelRefArg => [TOPLEVEL_REF_ARG]); + +impl<'tcx> LateLintPass<'tcx> for ToplevelRefArg { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + k: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: LocalDefId, + ) { + if !matches!(k, FnKind::Closure) { + for arg in iter_input_pats(decl, body) { + if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind + && is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) + && !arg.span.in_external_macro(cx.tcx.sess.source_map()) + { + span_lint_hir( + cx, + TOPLEVEL_REF_ARG, + arg.hir_id, + arg.pat.span, + "`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \ + Consider using a reference type instead", + ); + } + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Let(local) = stmt.kind + && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind + && let Some(init) = local.init + // Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue. + && is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id) + && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) + { + let ctxt = local.span.ctxt(); + let mut app = Applicability::MachineApplicable; + let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app); + let (mutopt, initref) = match mutabl { + Mutability::Mut => ("mut ", sugg_init.mut_addr()), + Mutability::Not => ("", sugg_init.addr()), + }; + let tyopt = if let Some(ty) = local.ty { + let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0; + format!(": &{mutopt}{ty_snip}") + } else { + String::new() + }; + span_lint_hir_and_then( + cx, + TOPLEVEL_REF_ARG, + init.hir_id, + local.pat.span, + "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", + |diag| { + diag.span_suggestion( + stmt.span, + "try", + format!("let {name}{tyopt} = {initref};", name = snippet(cx, name.span, ".."),), + app, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 9182a55081f4..352b8526b021 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -238,7 +238,7 @@ impl TraitBounds { } } - #[allow(clippy::mutable_key_type)] + #[expect(clippy::mutable_key_type)] fn check_type_repetition<'tcx>(&self, cx: &LateContext<'tcx>, generics: &'tcx Generics<'_>) { struct SpanlessTy<'cx, 'tcx> { ty: &'tcx Ty<'tcx>, diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 5fda388259a6..d643f7aea497 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -435,10 +435,11 @@ declare_clippy_lint! { /// to infer a technically correct yet unexpected type. /// /// ### Example - /// ```no_run + /// ``` /// # unsafe { + /// let mut x: i32 = 0; /// // Avoid "naked" calls to `transmute()`! - /// let x: i32 = std::mem::transmute([1u16, 2u16]); + /// x = std::mem::transmute([1u16, 2u16]); /// /// // `first_answers` is intended to transmute a slice of bool to a slice of u8. /// // But the programmer forgot to index the first element of the outer slice, @@ -449,7 +450,7 @@ declare_clippy_lint! { /// # } /// ``` /// Use instead: - /// ```no_run + /// ``` /// # unsafe { /// let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); /// diff --git a/clippy_lints/src/transmute/transmute_null_to_fn.rs b/clippy_lints/src/transmute/transmute_null_to_fn.rs index 7acf3be51fb7..e109b1c50e22 100644 --- a/clippy_lints/src/transmute/transmute_null_to_fn.rs +++ b/clippy_lints/src/transmute/transmute_null_to_fn.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_integer_literal, is_path_diagnostic_item}; +use clippy_utils::is_integer_literal; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -40,7 +41,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t }, // Catching: // `std::mem::transmute(std::ptr::null::())` - ExprKind::Call(func1, []) if is_path_diagnostic_item(cx, func1, sym::ptr_null) => { + ExprKind::Call(func1, []) if func1.basic_res().is_diag_item(cx, sym::ptr_null) => { lint_expr(cx, expr); true }, diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index e58212fae15c..e67ab6a73d26 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -28,16 +28,27 @@ pub(super) fn check<'tcx>( format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"), |diag| { let arg = sugg::Sugg::hir(cx, arg, ".."); - let (deref, cast) = if *mutbl == Mutability::Mut { - ("&mut *", "*mut") - } else { - ("&*", "*const") + let (deref, cast) = match mutbl { + Mutability::Mut => ("&mut *", "*mut"), + Mutability::Not => ("&*", "*const"), }; let mut app = Applicability::MachineApplicable; let sugg = if let Some(ty) = get_explicit_type(path) { let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app); - if msrv.meets(cx, msrvs::POINTER_CAST) { + if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) { + // We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized. + if from_ptr_ty.has_erased_regions() { + // We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a + // thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`, + // being !Sized. + // + // The only remaining option is be to skip `*mut/const ()`, but that might not be safe + // to do because of the erased regions in `from_ptr_ty`, so reduce the applicability. + app = Applicability::MaybeIncorrect; + } + sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string() + } else if msrv.meets(cx, msrvs::POINTER_CAST) { format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_paren()) } else if from_ptr_ty.has_erased_regions() { sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string() @@ -45,15 +56,22 @@ pub(super) fn check<'tcx>( sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string() } } else if *from_ptr_ty == *to_ref_ty { - if from_ptr_ty.has_erased_regions() { - if msrv.meets(cx, msrvs::POINTER_CAST) { - format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren()) - } else { - sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))) - .to_string() - } - } else { + if !from_ptr_ty.has_erased_regions() { sugg::make_unop(deref, arg).to_string() + } else if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) { + // 1. We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized. + // 2. We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a + // thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`, + // being !Sized. + // + // The only remaining option is be to skip `*mut/const ()`, but that might not be safe to do + // because of the erased regions in `from_ptr_ty`, so reduce the applicability. + app = Applicability::MaybeIncorrect; + sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string() + } else if msrv.meets(cx, msrvs::POINTER_CAST) { + format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren()) + } else { + sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))).to_string() } } else { sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string() diff --git a/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/clippy_lints/src/transmute/transmute_ref_to_ref.rs index 3842c4eb60e8..70c2a73ce6ef 100644 --- a/clippy_lints/src/transmute/transmute_ref_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -45,7 +45,9 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect, ); triggered = true; - } else if (cx.tcx.erase_regions(from_ty) != cx.tcx.erase_regions(to_ty)) && !const_context { + } else if (cx.tcx.erase_and_anonymize_regions(from_ty) != cx.tcx.erase_and_anonymize_regions(to_ty)) + && !const_context + { span_lint_and_then( cx, TRANSMUTE_PTR_TO_PTR, diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 26323af31228..3e6aae475ecc 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -12,8 +12,8 @@ pub(super) fn check<'tcx>( from_ty_orig: Ty<'tcx>, to_ty_orig: Ty<'tcx>, ) -> bool { - let mut from_ty = cx.tcx.erase_regions(from_ty_orig); - let mut to_ty = cx.tcx.erase_regions(to_ty_orig); + let mut from_ty = cx.tcx.erase_and_anonymize_regions(from_ty_orig); + let mut to_ty = cx.tcx.erase_and_anonymize_regions(to_ty_orig); while from_ty != to_ty { let reduced_tys = reduce_refs(cx, from_ty, to_ty); diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs index 544014bd32b3..1a6262f2ff76 100644 --- a/clippy_lints/src/transmute/transmuting_null.rs +++ b/clippy_lints/src/transmute/transmuting_null.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_integer_literal, is_path_diagnostic_item}; +use clippy_utils::is_integer_literal; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -35,7 +36,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t // Catching: // `std::mem::transmute(std::ptr::null::())` if let ExprKind::Call(func1, []) = arg.kind - && is_path_diagnostic_item(cx, func1, sym::ptr_null) + && func1.basic_res().is_diag_item(cx, sym::ptr_null) { span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); return true; diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs index 3c21d194b81d..5d0945bece55 100644 --- a/clippy_lints/src/tuple_array_conversions.rs +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -1,8 +1,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeResPath; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{is_from_proc_macro, path_to_local}; use itertools::Itertools; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind, Node, PatKind}; @@ -152,7 +153,7 @@ fn all_bindings_are_for_conv<'tcx>( locals: &[&Expr<'_>], kind: ToType, ) -> bool { - let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::>>() else { + let Some(locals) = locals.iter().map(|e| e.res_local_id()).collect::>>() else { return false; }; let local_parents = locals.iter().map(|l| cx.tcx.parent_hir_node(*l)).collect::>(); diff --git a/clippy_lints/src/types/box_collection.rs b/clippy_lints/src/types/box_collection.rs index 24fe4e08a5b4..985057cd6734 100644 --- a/clippy_lints/src/types/box_collection.rs +++ b/clippy_lints/src/types/box_collection.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{path_def_id, qpath_generic_tys}; +use clippy_utils::qpath_generic_tys; +use clippy_utils::res::MaybeResPath; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath}; use rustc_lint::LateContext; @@ -33,7 +34,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option { let param = qpath_generic_tys(qpath).next()?; - let id = path_def_id(cx, param)?; + let id = param.basic_res().opt_def_id()?; cx.tcx .get_diagnostic_name(id) .filter(|&name| { diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 515be5adeed0..7018146f184b 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -655,7 +655,6 @@ impl Types { } } }, - QPath::LangItem(..) => {}, } }, TyKind::Path(ref qpath) => { @@ -693,7 +692,7 @@ impl Types { } } -#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)] +#[expect(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Default)] struct CheckTyContext { is_in_trait_impl: bool, diff --git a/clippy_lints/src/types/option_option.rs b/clippy_lints/src/types/option_option.rs index d12d14f2b141..6d57bb6ef3a0 100644 --- a/clippy_lints/src/types/option_option.rs +++ b/clippy_lints/src/types/option_option.rs @@ -1,5 +1,7 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::{path_def_id, qpath_generic_tys}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::qpath_generic_tys; +use clippy_utils::res::MaybeResPath; +use clippy_utils::source::snippet; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath}; use rustc_lint::LateContext; @@ -10,14 +12,23 @@ use super::OPTION_OPTION; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { if cx.tcx.is_diagnostic_item(sym::Option, def_id) && let Some(arg) = qpath_generic_tys(qpath).next() - && path_def_id(cx, arg) == Some(def_id) + && arg.basic_res().opt_def_id() == Some(def_id) { - span_lint( + span_lint_and_then( cx, OPTION_OPTION, hir_ty.span, - "consider using `Option` instead of `Option>` or a custom \ - enum if you need to distinguish all 3 cases", + // use just `T` here, as the inner type is not what's problematic + "use of `Option>`", + |diag| { + // but use the specific type here, as: + // - this is kind of a suggestion + // - it's printed right after the linted type + let inner_opt = snippet(cx, arg.span, "_"); + diag.help(format!( + "consider using `{inner_opt}`, or a custom enum if you need to distinguish all 3 cases" + )); + }, ); true } else { diff --git a/clippy_lints/src/types/owned_cow.rs b/clippy_lints/src/types/owned_cow.rs index 8933994d1855..0eef373be04e 100644 --- a/clippy_lints/src/types/owned_cow.rs +++ b/clippy_lints/src/types/owned_cow.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; @@ -30,10 +31,10 @@ pub(super) fn check(cx: &LateContext<'_>, qpath: &hir::QPath<'_>, def_id: DefId) } fn replacement(cx: &LateContext<'_>, cty: &hir::Ty<'_>) -> Option<(Span, String)> { - if clippy_utils::is_path_lang_item(cx, cty, hir::LangItem::String) { + if cty.basic_res().is_lang_item(cx, hir::LangItem::String) { return Some((cty.span, "str".into())); } - if clippy_utils::is_path_diagnostic_item(cx, cty, sym::Vec) { + if cty.basic_res().is_diag_item(cx, sym::Vec) { return if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = cty.kind && let [.., last_seg] = path.segments && let Some(args) = last_seg.args @@ -45,7 +46,7 @@ fn replacement(cx: &LateContext<'_>, cty: &hir::Ty<'_>) -> Option<(Span, String) None }; } - if clippy_utils::is_path_diagnostic_item(cx, cty, sym::cstring_type) { + if cty.basic_res().is_diag_item(cx, sym::cstring_type) { return Some(( cty.span, (if clippy_utils::is_no_std_crate(cx) { @@ -58,7 +59,7 @@ fn replacement(cx: &LateContext<'_>, cty: &hir::Ty<'_>) -> Option<(Span, String) } // Neither OsString nor PathBuf are available outside std for (diag, repl) in [(sym::OsString, "std::ffi::OsStr"), (sym::PathBuf, "std::path::Path")] { - if clippy_utils::is_path_diagnostic_item(cx, cty, diag) { + if cty.basic_res().is_diag_item(cx, diag) { return Some((cty.span, repl.into())); } } diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index c4fd0fbf87a9..46d9febb187f 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::qpath_generic_tys; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath, TyKind}; @@ -28,8 +29,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ let Some(ty) = qpath_generic_tys(qpath).next() else { return false; }; - let Some(id) = path_def_id(cx, ty) else { return false }; - if !cx.tcx.is_diagnostic_item(sym::Vec, id) { + if !ty.basic_res().is_diag_item(cx, sym::Vec) { return false; } let TyKind::Path(qpath) = &ty.kind else { return false }; @@ -70,8 +70,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ }, ); } else if let Some(ty) = qpath_generic_tys(qpath).next() { - let Some(id) = path_def_id(cx, ty) else { return false }; - if !cx.tcx.is_diagnostic_item(sym::Vec, id) { + if !ty.basic_res().is_diag_item(cx, sym::Vec) { return false; } let TyKind::Path(qpath) = &ty.kind else { return false }; @@ -106,7 +105,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { let ty = qpath_generic_tys(qpath).next()?; - let id = path_def_id(cx, ty)?; + let id = ty.basic_res().opt_def_id()?; let path = match cx.tcx.get_diagnostic_name(id) { Some(sym::OsString) => "std::ffi::OsStr", Some(sym::PathBuf) => "std::path::Path", diff --git a/clippy_lints/src/types/rc_mutex.rs b/clippy_lints/src/types/rc_mutex.rs index 7b13debc01ef..e3d536822d5f 100644 --- a/clippy_lints/src/types/rc_mutex.rs +++ b/clippy_lints/src/types/rc_mutex.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{path_def_id, qpath_generic_tys}; +use clippy_utils::qpath_generic_tys; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath}; use rustc_lint::LateContext; @@ -10,8 +11,7 @@ use super::RC_MUTEX; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { if cx.tcx.is_diagnostic_item(sym::Rc, def_id) && let Some(arg) = qpath_generic_tys(qpath).next() - && let Some(id) = path_def_id(cx, arg) - && cx.tcx.is_diagnostic_item(sym::Mutex, id) + && arg.basic_res().is_diag_item(cx, sym::Mutex) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then(cx, RC_MUTEX, hir_ty.span, "usage of `Rc>`", |diag| { diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs index 0ba51daf027d..dbae2cc33516 100644 --- a/clippy_lints/src/types/redundant_allocation.rs +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::qpath_generic_tys; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath, TyKind}; @@ -40,7 +41,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: let Some(ty) = qpath_generic_tys(qpath).next() else { return false; }; - let Some(id) = path_def_id(cx, ty) else { return false }; + let Some(id) = ty.basic_res().opt_def_id() else { + return false; + }; let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) { Some(sym::Arc) => ("Arc", ty), Some(sym::Rc) => ("Rc", ty), diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index e843e169113e..48f1cda9ee7b 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id}; +use clippy_utils::res::MaybeQPath; +use clippy_utils::{expr_or_init, fn_def_id_with_node_args}; use rustc_ast::BinOpKind; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; @@ -105,7 +106,6 @@ fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Op _ => None, } }, - QPath::LangItem(..) => None, } } @@ -290,7 +290,6 @@ fn is_default_method_on_current_ty<'tcx>(tcx: TyCtxt<'tcx>, qpath: QPath<'tcx>, } get_hir_ty_def_id(tcx, *ty) == Some(implemented_ty_id) }, - QPath::LangItem(..) => false, } } @@ -317,7 +316,7 @@ where if let ExprKind::Call(f, _) = expr.kind && let ExprKind::Path(qpath) = f.kind && is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id) - && let Some(method_def_id) = path_def_id(self.cx, f) + && let Some(method_def_id) = f.res(self.cx).opt_def_id() && let Some(trait_def_id) = self.cx.tcx.trait_of_assoc(method_def_id) && self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id) { diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index ba0d4de5f3b3..9935dc309611 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -7,8 +7,8 @@ use clippy_utils::is_lint_allowed; use clippy_utils::source::walk_span_to_context; use clippy_utils::visitors::{Descend, for_each_expr}; use hir::HirId; -use rustc_hir as hir; -use rustc_hir::{Block, BlockCheckMode, Impl, ItemKind, Node, UnsafeSource}; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, Block, BlockCheckMode, FnSig, Impl, ItemKind, Node, UnsafeSource}; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; @@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { && !block.span.in_external_macro(cx.tcx.sess.source_map()) && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id) && !is_unsafe_from_proc_macro(cx, block.span) - && !block_has_safety_comment(cx, block.span) + && !block_has_safety_comment(cx, block.span, self.accept_comment_above_attributes) && !block_parents_have_safety_comment( self.accept_comment_above_statement, self.accept_comment_above_attributes, @@ -143,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { if let Some(tail) = block.expr && !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id) && !tail.span.in_external_macro(cx.tcx.sess.source_map()) - && let HasSafetyComment::Yes(pos) = + && let HasSafetyComment::Yes(pos, _) = stmt_has_safety_comment(cx, tail.span, tail.hir_id, self.accept_comment_above_attributes) && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos) { @@ -168,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { }; if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id) && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) - && let HasSafetyComment::Yes(pos) = + && let HasSafetyComment::Yes(pos, _) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id, self.accept_comment_above_attributes) && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos) { @@ -191,8 +191,12 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { let mk_spans = |pos: BytePos| { let source_map = cx.tcx.sess.source_map(); - let span = Span::new(pos, pos, SyntaxContext::root(), None); - let help_span = source_map.span_extend_to_next_char(span, '\n', true); + let help_span = Span::new( + pos, + pos + BytePos(u32::try_from("SAFETY:".len()).unwrap()), + SyntaxContext::root(), + None, + ); let span = if source_map.is_multiline(item.span) { source_map.span_until_char(item.span, '\n') } else { @@ -201,16 +205,16 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { (span, help_span) }; - let item_has_safety_comment = item_has_safety_comment(cx, item); + let item_has_safety_comment = item_has_safety_comment(cx, item, self.accept_comment_above_attributes); match item_has_safety_comment { - HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)), + HasSafetyComment::Yes(pos, is_doc) => check_has_safety_comment(cx, item, mk_spans(pos), is_doc), HasSafetyComment::No => check_has_no_safety_comment(cx, item), HasSafetyComment::Maybe => {}, } } } -fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) { +fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span), is_doc: bool) { match &item.kind { ItemKind::Impl(Impl { of_trait: Some(of_trait), @@ -252,6 +256,40 @@ fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, h } } }, + // Unsafe functions with a SAFETY comment are suggested to change it to a `# Safety` comment + ItemKind::Fn { + sig: FnSig { header, .. }, + .. + } if header.is_unsafe() => { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { + span_lint_and_then( + cx, + UNNECESSARY_SAFETY_COMMENT, + span, + format!( + "{} has unnecessary safety comment", + cx.tcx.def_descr(item.owner_id.to_def_id()), + ), + |diag| { + if is_doc { + // If it's already within a doc comment, we try to suggest the change + + diag.span_suggestion( + help_span, + "consider changing it to a `# Safety` section", + "# Safety", + Applicability::MachineApplicable, + ); + } else { + diag.span_help( + help_span, + "consider changing the `safety` comment for a `# Safety` doc comment", + ); + } + }, + ); + } + }, // Aside from unsafe impls and consts/statics with an unsafe block, items in general // do not have safety invariants that need to be documented, so lint those. _ => { @@ -272,6 +310,7 @@ fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, h }, } } + fn check_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) { if let ItemKind::Impl(Impl { of_trait: Some(of_trait), @@ -335,7 +374,7 @@ fn expr_has_unnecessary_safety_comment<'tcx>( hir::Stmt { kind: hir::StmtKind::Let(hir::LetStmt { - source: hir::LocalSource::AssignDesugar(_), + source: hir::LocalSource::AssignDesugar, .. }), .. @@ -407,21 +446,21 @@ fn block_parents_have_safety_comment( cx: &LateContext<'_>, id: HirId, ) -> bool { - let (span, hir_id) = match cx.tcx.parent_hir_node(id) { - Node::Expr(expr) if let Some(inner) = find_unsafe_block_parent_in_expr(cx, expr) => inner, + let span = match cx.tcx.parent_hir_node(id) { + Node::Expr(expr) if let Some((span, _)) = find_unsafe_block_parent_in_expr(cx, expr) => span, Node::Stmt(hir::Stmt { kind: - hir::StmtKind::Let(hir::LetStmt { span, hir_id, .. }) - | hir::StmtKind::Expr(hir::Expr { span, hir_id, .. }) - | hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }), + hir::StmtKind::Let(hir::LetStmt { span, .. }) + | hir::StmtKind::Expr(hir::Expr { span, .. }) + | hir::StmtKind::Semi(hir::Expr { span, .. }), .. }) - | Node::LetStmt(hir::LetStmt { span, hir_id, .. }) => (*span, *hir_id), + | Node::LetStmt(hir::LetStmt { span, .. }) => *span, - node if let Some((span, hir_id)) = span_and_hid_of_item_alike_node(&node) + node if let Some((span, _)) = span_and_hid_of_item_alike_node(&node) && is_const_or_static(&node) => { - (span, hir_id) + span }, _ => return false, @@ -429,24 +468,7 @@ fn block_parents_have_safety_comment( // if unsafe block is part of a let/const/static statement, // and accept_comment_above_statement is set to true // we accept the safety comment in the line the precedes this statement. - accept_comment_above_statement - && span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes) -} - -/// Extends `span` to also include its attributes, then checks if that span has a safety comment. -fn span_with_attrs_has_safety_comment( - cx: &LateContext<'_>, - span: Span, - hir_id: HirId, - accept_comment_above_attributes: bool, -) -> bool { - let span = if accept_comment_above_attributes { - include_attrs_in_span(cx, hir_id, span) - } else { - span - }; - - span_has_safety_comment(cx, span) + accept_comment_above_statement && span_has_safety_comment(cx, span, accept_comment_above_attributes) } /// Checks if an expression is "branchy", e.g. loop, match/if/etc. @@ -458,7 +480,7 @@ fn is_branchy(expr: &hir::Expr<'_>) -> bool { } /// Checks if the lines immediately preceding the block contain a safety comment. -fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { +fn block_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_above_attributes: bool) -> bool { // This intentionally ignores text before the start of a function so something like: // ``` // // SAFETY: reason @@ -468,30 +490,25 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { // attributes and doc comments. matches!( - span_from_macro_expansion_has_safety_comment(cx, span), - HasSafetyComment::Yes(_) - ) || span_has_safety_comment(cx, span) -} - -fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span { - span.to(cx.tcx.hir_attrs(hir_id).iter().fold(span, |acc, attr| { - if attr.is_doc_comment() { - return acc; - } - acc.to(attr.span()) - })) + span_from_macro_expansion_has_safety_comment(cx, span, accept_comment_above_attributes), + HasSafetyComment::Yes(_, _) + ) || span_has_safety_comment(cx, span, accept_comment_above_attributes) } +#[derive(Debug)] enum HasSafetyComment { - Yes(BytePos), + Yes(BytePos, bool), No, Maybe, } /// Checks if the lines immediately preceding the item contain a safety comment. -#[allow(clippy::collapsible_match)] -fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSafetyComment { - match span_from_macro_expansion_has_safety_comment(cx, item.span) { +fn item_has_safety_comment( + cx: &LateContext<'_>, + item: &hir::Item<'_>, + accept_comment_above_attributes: bool, +) -> HasSafetyComment { + match span_from_macro_expansion_has_safety_comment(cx, item.span, accept_comment_above_attributes) { HasSafetyComment::Maybe => (), has_safety_comment => return has_safety_comment, } @@ -536,29 +553,26 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf return if comment_start_line.line >= unsafe_line.line { HasSafetyComment::No } else { - match text_has_safety_comment( + text_has_safety_comment( src, &unsafe_line.sf.lines() [(comment_start_line.line + usize::from(!include_first_line_of_file))..=unsafe_line.line], unsafe_line.sf.start_pos, - ) { - Some(b) => HasSafetyComment::Yes(b), - None => HasSafetyComment::No, - } + accept_comment_above_attributes, + ) }; } HasSafetyComment::Maybe } /// Checks if the lines immediately preceding the item contain a safety comment. -#[allow(clippy::collapsible_match)] fn stmt_has_safety_comment( cx: &LateContext<'_>, span: Span, hir_id: HirId, accept_comment_above_attributes: bool, ) -> HasSafetyComment { - match span_from_macro_expansion_has_safety_comment(cx, span) { + match span_from_macro_expansion_has_safety_comment(cx, span, accept_comment_above_attributes) { HasSafetyComment::Maybe => (), has_safety_comment => return has_safety_comment, } @@ -572,13 +586,6 @@ fn stmt_has_safety_comment( _ => return HasSafetyComment::Maybe, }; - // if span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attrib - // } - let mut span = span; - if accept_comment_above_attributes { - span = include_attrs_in_span(cx, hir_id, span); - } - let source_map = cx.sess().source_map(); if let Some(comment_start) = comment_start && let Ok(unsafe_line) = source_map.lookup_line(span.lo()) @@ -589,14 +596,12 @@ fn stmt_has_safety_comment( return if comment_start_line.line >= unsafe_line.line { HasSafetyComment::No } else { - match text_has_safety_comment( + text_has_safety_comment( src, &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line], unsafe_line.sf.start_pos, - ) { - Some(b) => HasSafetyComment::Yes(b), - None => HasSafetyComment::No, - } + accept_comment_above_attributes, + ) }; } HasSafetyComment::Maybe @@ -649,7 +654,11 @@ fn comment_start_before_item_in_mod( }) } -fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> HasSafetyComment { +fn span_from_macro_expansion_has_safety_comment( + cx: &LateContext<'_>, + span: Span, + accept_comment_above_attributes: bool, +) -> HasSafetyComment { let source_map = cx.sess().source_map(); let ctxt = span.ctxt(); if ctxt == SyntaxContext::root() { @@ -665,14 +674,12 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span && let Some(src) = unsafe_line.sf.src.as_deref() { if macro_line.line < unsafe_line.line { - match text_has_safety_comment( + text_has_safety_comment( src, &unsafe_line.sf.lines()[macro_line.line + 1..=unsafe_line.line], unsafe_line.sf.start_pos, - ) { - Some(b) => HasSafetyComment::Yes(b), - None => HasSafetyComment::No, - } + accept_comment_above_attributes, + ) } else { HasSafetyComment::No } @@ -715,7 +722,7 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option { None } -fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { +fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_above_attributes: bool) -> bool { let source_map = cx.sess().source_map(); let ctxt = span.ctxt(); if ctxt.is_root() @@ -731,12 +738,15 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { // fn foo() { some_stuff; unsafe { stuff }; other_stuff; } // ^-------------^ body_line.line < unsafe_line.line - && text_has_safety_comment( - src, - &unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line], - unsafe_line.sf.start_pos, + && matches!( + text_has_safety_comment( + src, + &unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line], + unsafe_line.sf.start_pos, + accept_comment_above_attributes, + ), + HasSafetyComment::Yes(..) ) - .is_some() } else { // Problem getting source text. Pretend a comment was found. true @@ -747,7 +757,15 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { } /// Checks if the given text has a safety comment for the immediately proceeding line. -fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos: BytePos) -> Option { +/// +/// If `accept_comment_above_attributes` is true, it will ignore attributes inbetween blocks of +/// comments +fn text_has_safety_comment( + src: &str, + line_starts: &[RelativeBytePos], + start_pos: BytePos, + accept_comment_above_attributes: bool, +) -> HasSafetyComment { let mut lines = line_starts .array_windows::<2>() .rev() @@ -758,9 +776,12 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos let trimmed = text.trim_start(); Some((start + (text.len() - trimmed.len()), trimmed)) }) - .filter(|(_, text)| !text.is_empty()); + .filter(|(_, text)| !(text.is_empty() || (accept_comment_above_attributes && is_attribute(text)))); + + let Some((line_start, line)) = lines.next() else { + return HasSafetyComment::No; + }; - let (line_start, line) = lines.next()?; let mut in_codeblock = false; // Check for a sequence of line comments. if line.starts_with("//") { @@ -773,12 +794,17 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos in_codeblock = !in_codeblock; } - if line.to_ascii_uppercase().contains("SAFETY:") && !in_codeblock { - return Some(start_pos + BytePos(u32::try_from(line_start).unwrap())); + if !in_codeblock && let Some(safety_pos) = line.to_ascii_uppercase().find("SAFETY:") { + return HasSafetyComment::Yes( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(safety_pos).unwrap()), + line.starts_with("///"), + ); } match lines.next() { Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s), - _ => return None, + _ => return HasSafetyComment::No, } } } @@ -789,19 +815,30 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos if line.starts_with("/*") { let src = &src[line_start..line_starts.last().unwrap().to_usize()]; let mut tokens = tokenize(src, FrontmatterAllowed::No); - return (src[..tokens.next().unwrap().len as usize] - .to_ascii_uppercase() - .contains("SAFETY:") - && tokens.all(|t| t.kind == TokenKind::Whitespace)) - .then_some(start_pos + BytePos(u32::try_from(line_start).unwrap())); + let a = tokens.next(); + if let Some(safety_pos) = src[..a.unwrap().len as usize].to_ascii_uppercase().find("SAFETY:") + && tokens.all(|t| t.kind == TokenKind::Whitespace) + { + return HasSafetyComment::Yes( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(safety_pos).unwrap()), + line.starts_with("/**"), + ); + } + return HasSafetyComment::No; } match lines.next() { Some(x) => (line_start, line) = x, - None => return None, + None => return HasSafetyComment::No, } } } +fn is_attribute(text: &str) -> bool { + (text.starts_with("#[") || text.starts_with("#![")) && text.trim_end().ends_with(']') +} + fn span_and_hid_of_item_alike_node(node: &Node<'_>) -> Option<(Span, HirId)> { match node { Node::Item(item) => Some((item.span, item.owner_id.into())), diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index cee4a53f03cb..df06982904b3 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -1,7 +1,8 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; -use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty}; -use clippy_utils::{SpanlessEq, is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, sym}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::ty::is_uninit_value_valid_for_ty; +use clippy_utils::{SpanlessEq, is_integer_literal, is_lint_allowed, peel_hir_expr_while, sym}; use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -95,16 +96,13 @@ fn handle_uninit_vec_pair<'tcx>( // Check T of Vec if !is_uninit_value_valid_for_ty(cx, args.type_at(0)) { - // FIXME: #7698, false positive of the internal lints - #[expect(clippy::collapsible_span_lint_calls)] - span_lint_and_then( + span_lint_and_help( cx, UNINIT_VEC, vec![call_span, maybe_init_or_reserve.span], "calling `set_len()` immediately after reserving a buffer creates uninitialized values", - |diag| { - diag.help("initialize the buffer or wrap the content in `MaybeUninit`"); - }, + None, + "initialize the buffer or wrap the content in `MaybeUninit`", ); } } else { @@ -142,7 +140,7 @@ enum VecLocation<'tcx> { impl<'tcx> VecLocation<'tcx> { pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { match self { - VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id), + VecLocation::Local(hir_id) => expr.res_local_id() == Some(hir_id), VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr), } } @@ -186,7 +184,10 @@ fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt } fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool { - is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec) + cx.typeck_results() + .expr_ty(self_expr) + .peel_refs() + .is_diag_item(cx, sym::Vec) && path.ident.name == sym::reserve } @@ -208,10 +209,7 @@ fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Opt match expr.kind { ExprKind::MethodCall(path, self_expr, [arg], _) => { let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs(); - if is_type_diagnostic_item(cx, self_type, sym::Vec) - && path.ident.name == sym::set_len - && !is_integer_literal(arg, 0) - { + if self_type.is_diag_item(cx, sym::Vec) && path.ident.name == sym::set_len && !is_integer_literal(arg, 0) { Some((self_expr, expr.span)) } else { None diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index d5b6c1758549..2645e94358e1 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; -use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_context}; +use clippy_utils::source::{snippet_indent, walk_span_to_context}; use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; use rustc_ast::{FormatArgs, FormatArgumentKind}; @@ -74,10 +74,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag "this let-binding has unit value", |diag| { let mut suggestions = Vec::new(); + let init_new_span = walk_span_to_context(init.span, local.span.ctxt()).unwrap(); // Suggest omitting the `let` binding - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0; + let app = Applicability::MachineApplicable; // If this is a binding pattern, we need to add suggestions to remove any usages // of the variable @@ -89,35 +89,49 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag walk_body(&mut visitor, body); let mut has_in_format_capture = false; - suggestions.extend(visitor.spans.iter().filter_map(|span| match span { - MaybeInFormatCapture::Yes => { + suggestions.extend(visitor.spans.into_iter().filter_map(|span| match span { + VariableUsage::FormatCapture => { has_in_format_capture = true; None }, - MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())), + VariableUsage::Normal(span) => Some((span, "()".to_string())), + VariableUsage::FieldShorthand(span) => Some((span.shrink_to_hi(), ": ()".to_string())), })); if has_in_format_capture { + // In a case like this: + // ``` + // let unit = returns_unit(); + // eprintln!("{unit}"); + // ``` + // we can't remove the `unit` binding and replace its uses with a `()`, + // because the `eprintln!` would break. + // + // So do the following instead: + // ``` + // let unit = (); + // returns_unit(); + // eprintln!("{unit}"); + // ``` + // TODO: find a less awkward way to do this suggestions.push(( - init.span, - format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), + init_new_span.shrink_to_lo(), + format!("();\n{}", snippet_indent(cx, local.span).as_deref().unwrap_or("")), )); - diag.multipart_suggestion( - "replace variable usages with `()`", - suggestions, - Applicability::MachineApplicable, - ); + diag.multipart_suggestion_verbose("replace variable usages with `()`", suggestions, app); return; } } - suggestions.push((local.span, format!("{snip};"))); + // let local = returns_unit(); + // ^^^^^^^^^^^^ remove this + suggestions.push((local.span.until(init_new_span), String::new())); let message = if suggestions.len() == 1 { "omit the `let` binding" } else { "omit the `let` binding and replace variable usages with `()`" }; - diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable); + diag.multipart_suggestion_verbose(message, suggestions, app); }, ); } @@ -128,13 +142,30 @@ struct UnitVariableCollector<'a, 'tcx> { cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId, - spans: Vec, + spans: Vec, macro_call: Option<&'a FormatArgs>, } -enum MaybeInFormatCapture { - Yes, - No(Span), +/// How the unit variable is used +enum VariableUsage { + Normal(Span), + /// Captured in a `format!`: + /// + /// ```ignore + /// let unit = (); + /// eprintln!("{unit}"); + /// ``` + FormatCapture, + /// In a field shorthand init: + /// + /// ```ignore + /// struct Foo { + /// unit: (), + /// } + /// let unit = (); + /// Foo { unit }; + /// ``` + FieldShorthand(Span), } impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> { @@ -174,9 +205,17 @@ impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> { matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some() }) { - self.spans.push(MaybeInFormatCapture::Yes); + self.spans.push(VariableUsage::FormatCapture); } else { - self.spans.push(MaybeInFormatCapture::No(path.span)); + let parent = self.cx.tcx.parent_hir_node(ex.hir_id); + match parent { + Node::ExprField(expr_field) if expr_field.is_shorthand => { + self.spans.push(VariableUsage::FieldShorthand(ex.span)); + }, + _ => { + self.spans.push(VariableUsage::Normal(path.span)); + }, + } } } diff --git a/clippy_lints/src/unnecessary_literal_bound.rs b/clippy_lints/src/unnecessary_literal_bound.rs index 9f107fbeec03..2603884440d1 100644 --- a/clippy_lints/src/unnecessary_literal_bound.rs +++ b/clippy_lints/src/unnecessary_literal_bound.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::path_res; +use clippy_utils::res::MaybeResPath; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -138,7 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound { return; }; - if path_res(cx, inner_hir_ty) != Res::PrimTy(PrimTy::Str) { + if !matches!(inner_hir_ty.basic_res(), Res::PrimTy(PrimTy::Str)) { return; } diff --git a/clippy_lints/src/unnecessary_map_on_constructor.rs b/clippy_lints/src/unnecessary_map_on_constructor.rs index d3700d05b014..af9f291f5deb 100644 --- a/clippy_lints/src/unnecessary_map_on_constructor.rs +++ b/clippy_lints/src/unnecessary_map_on_constructor.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::get_type_diagnostic_name; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { return; } if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind - && let Some(sym::Option | sym::Result) = get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv)) + && let Some(sym::Option | sym::Result) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx) { let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind && let hir::ExprKind::Path(constructor_path) = constructor.kind @@ -60,7 +60,6 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { } }, hir::QPath::TypeRelative(_, path) => path.ident.name, - hir::QPath::LangItem(..) => return, }; match constructor_symbol { sym::Some | sym::Ok if path.ident.name == sym::map => (), diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/unnecessary_mut_passed.rs similarity index 72% rename from clippy_lints/src/mut_reference.rs rename to clippy_lints/src/unnecessary_mut_passed.rs index ec93ef97cfaf..eb2d7639e91f 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/unnecessary_mut_passed.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::sugg::Sugg; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; @@ -87,16 +87,33 @@ fn check_arguments<'tcx>( if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind { - let mut applicability = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr(); - span_lint_and_sugg( + let applicability = Applicability::MachineApplicable; + + let span_to_remove = { + let span_until_arg = argument.span.until(arg.span); + if let Some(Some(ref_pos)) = span_until_arg.with_source_text(cx, |src| { + src + // we don't use `strip_prefix` here, because `argument` might be enclosed in parens, in + // which case `&` is no longer the prefix + .find('&') + // just a sanity check, in case some proc-macro messes up the spans + .filter(|ref_pos| src[*ref_pos..].contains("mut")) + }) && let Ok(lo) = u32::try_from(ref_pos + '&'.len_utf8()) + { + span_until_arg.split_at(lo).1 + } else { + return; + } + }; + + span_lint_and_then( cx, UNNECESSARY_MUT_PASSED, argument.span, format!("the {fn_kind} `{name}` doesn't need a mutable reference"), - "remove this `mut`", - sugg.to_string(), - applicability, + |diag| { + diag.span_suggestion_verbose(span_to_remove, "remove this `mut`", String::new(), applicability); + }, ); } } diff --git a/clippy_lints/src/unnecessary_owned_empty_strings.rs b/clippy_lints/src/unnecessary_owned_empty_strings.rs index 28f4884fa311..0388450c9f7e 100644 --- a/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::res::MaybeDef; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { && let LitKind::Str(symbol, _) = spanned.node && symbol.is_empty() && let inner_expr_type = cx.typeck_results().expr_ty(inner_expr) - && is_type_lang_item(cx, inner_expr_type, LangItem::String) + && inner_expr_type.is_lang_item(cx, LangItem::String) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs index 76e24b6bf805..e1e450a52fdf 100644 --- a/clippy_lints/src/unnecessary_semicolon.rs +++ b/clippy_lints/src/unnecessary_semicolon.rs @@ -88,7 +88,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { ) && cx.typeck_results().expr_ty(expr).is_unit() // if a stmt has attrs, then turning it into an expr will break the code, since attrs aren't allowed on exprs - && cx.tcx.hir_attrs(stmt.hir_id).is_empty() + // -- unless the corresponding feature is enabled + && (cx.tcx.hir_attrs(stmt.hir_id).is_empty() || cx.tcx.features().stmt_expr_attributes()) { if let Some(block_is_unit) = self.is_last_in_block(stmt) { if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { diff --git a/clippy_lints/src/unnecessary_struct_initialization.rs b/clippy_lints/src/unnecessary_struct_initialization.rs index 5792b6b3178d..51397accefea 100644 --- a/clippy_lints/src/unnecessary_struct_initialization.rs +++ b/clippy_lints/src/unnecessary_struct_initialization.rs @@ -1,10 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::ty::is_copy; -use clippy_utils::{get_parent_expr, is_mutable, path_to_local}; +use clippy_utils::{get_parent_expr, is_mutable}; use rustc_hir::{Expr, ExprField, ExprKind, Path, QPath, StructTailExpr, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::{DesugaringKind, Ident}; declare_clippy_lint! { /// ### What it does @@ -51,7 +53,8 @@ impl LateLintPass<'_> for UnnecessaryStruct { return; }; - if expr.span.from_expansion() { + let expr_span = expr.range_span().unwrap_or(expr.span); + if expr_span.from_expansion() { // Prevent lint from hitting inside macro code return; } @@ -79,7 +82,7 @@ impl LateLintPass<'_> for UnnecessaryStruct { span_lint_and_sugg( cx, UNNECESSARY_STRUCT_INITIALIZATION, - expr.span, + expr_span, "unnecessary struct building", "replace with", snippet(cx, sugg, "..").into_owned(), @@ -129,7 +132,7 @@ fn same_path_in_all_fields<'tcx>( // expression type matches && ty == cx.typeck_results().expr_ty(src_expr) // field name matches - && f.ident == ident + && ident_without_range_desugaring(f.ident) == ident // assigned from a path expression && let ExprKind::Path(QPath::Resolved(None, src_path)) = src_expr.kind { @@ -162,7 +165,7 @@ fn check_references(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) && let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) && parent_ty.is_any_ptr() { - if is_copy(cx, cx.typeck_results().expr_ty(expr_a)) && path_to_local(expr_b).is_some() { + if is_copy(cx, cx.typeck_results().expr_ty(expr_a)) && expr_b.res_local_id().is_some() { // When the type implements `Copy`, a reference to the new struct works on the // copy. Using the original would borrow it. return false; @@ -196,3 +199,14 @@ fn path_matches_base(path: &Path<'_>, base: &Expr<'_>) -> bool { }; path.res == base_path.res } + +fn ident_without_range_desugaring(ident: Ident) -> Ident { + if ident.span.desugaring_kind() == Some(DesugaringKind::RangeExpr) { + Ident { + span: ident.span.parent_callsite().unwrap(), + ..ident + } + } else { + ident + } +} diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 849c0b438a5a..29747cf0e447 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -2,9 +2,10 @@ use std::borrow::Cow; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use clippy_utils::visitors::find_all_ret_expressions; -use clippy_utils::{contains_return, is_res_lang_ctor, path_res, return_ty}; +use clippy_utils::{contains_return, return_ty}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; use rustc_hir::intravisit::FnKind; @@ -122,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { if !ret_expr.span.from_expansion() // Check if a function call. && let ExprKind::Call(func, [arg]) = ret_expr.kind - && is_res_lang_ctor(cx, path_res(cx, func), lang_item) + && func.res(cx).ctor_parent(cx).is_lang_item(cx, lang_item) // Make sure the function argument does not contain a return expression. && !contains_return(arg) { diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index f3410c98973f..66a1dfc1f486 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -9,6 +9,8 @@ use rustc_ast::PatKind::*; use rustc_ast::mut_visit::*; use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind}; use rustc_ast_pretty::pprust; +use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_data_structures::thinvec::ExtractIf; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::impl_lint_pass; @@ -17,7 +19,6 @@ use rustc_span::DUMMY_SP; use std::boxed::Box; use std::cell::Cell; use std::mem; -use thin_vec::{ThinVec, thin_vec}; declare_clippy_lint! { /// ### What it does @@ -98,7 +99,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { return; } - let mut pat = Box::new(pat.clone()); + let mut pat = pat.clone(); // Nix all the paren patterns everywhere so that they aren't in our way. remove_all_parens(&mut pat); @@ -120,7 +121,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { } /// Remove all `(p)` patterns in `pat`. -fn remove_all_parens(pat: &mut Box) { +fn remove_all_parens(pat: &mut Pat) { #[derive(Default)] struct Visitor { /// If is not in the outer most pattern. This is needed to avoid removing the outermost @@ -143,7 +144,7 @@ fn remove_all_parens(pat: &mut Box) { } /// Insert parens where necessary according to Rust's precedence rules for patterns. -fn insert_necessary_parens(pat: &mut Box) { +fn insert_necessary_parens(pat: &mut Pat) { struct Visitor; impl MutVisitor for Visitor { fn visit_pat(&mut self, pat: &mut Pat) { @@ -163,7 +164,7 @@ fn insert_necessary_parens(pat: &mut Box) { /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. -fn unnest_or_patterns(pat: &mut Box) -> bool { +fn unnest_or_patterns(pat: &mut Pat) -> bool { struct Visitor { changed: bool, } @@ -223,7 +224,7 @@ macro_rules! always_pat { /// Focus on `focus_idx` in `alternatives`, /// attempting to extend it with elements of the same constructor `C` /// in `alternatives[focus_idx + 1..]`. -fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: usize) -> bool { +fn transform_with_focus_on_idx(alternatives: &mut ThinVec, focus_idx: usize) -> bool { // Extract the kind; we'll need to make some changes in it. let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, Wild); // We'll focus on `alternatives[focus_idx]`, @@ -251,20 +252,20 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: Box(target) => extend_with_matching( target, start, alternatives, |k| matches!(k, Box(_)), - |k| always_pat!(k, Box(p) => p), + |k| always_pat!(k, Box(p) => *p), ), // Transform `&mut x | ... | &mut y` into `&mut (x | y)`. Ref(target, Mutability::Mut) => extend_with_matching( target, start, alternatives, |k| matches!(k, Ref(_, Mutability::Mut)), - |k| always_pat!(k, Ref(p, _) => p), + |k| always_pat!(k, Ref(p, _) => *p), ), // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`. Ident(b1, i1, Some(target)) => extend_with_matching( target, start, alternatives, // Binding names must match. |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)), - |k| always_pat!(k, Ident(_, _, Some(p)) => p), + |k| always_pat!(k, Ident(_, _, Some(p)) => *p), ), // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`. Slice(ps1) => extend_with_matching_product( @@ -309,7 +310,7 @@ fn extend_with_struct_pat( fps1: &mut [ast::PatField], rest1: ast::PatFieldsRest, start: usize, - alternatives: &mut ThinVec>, + alternatives: &mut ThinVec, ) -> bool { (0..fps1.len()).any(|idx| { let pos_in_2 = Cell::new(None); // The element `k`. @@ -339,7 +340,7 @@ fn extend_with_struct_pat( })) }, // Extract `p2_k`. - |k| always_pat!(k, Struct(_, _, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat), + |k| always_pat!(k, Struct(_, _, mut fps, _) => *fps.swap_remove(pos_in_2.take().unwrap()).pat), ); extend_with_tail_or(&mut fps1[idx].pat, tail_or) }) @@ -351,11 +352,11 @@ fn extend_with_struct_pat( /// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post), /// where `~` denotes semantic equality. fn extend_with_matching_product( - targets: &mut [Box], + targets: &mut [Pat], start: usize, - alternatives: &mut ThinVec>, - predicate: impl Fn(&PatKind, &[Box], usize) -> bool, - extract: impl Fn(PatKind) -> ThinVec>, + alternatives: &mut ThinVec, + predicate: impl Fn(&PatKind, &[Pat], usize) -> bool, + extract: impl Fn(PatKind) -> ThinVec, ) -> bool { (0..targets.len()).any(|idx| { let tail_or = drain_matching( @@ -382,17 +383,16 @@ fn take_pat(from: &mut Pat) -> Pat { /// Extend `target` as an or-pattern with the alternatives /// in `tail_or` if there are any and return if there were. -fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec>) -> bool { - fn extend(target: &mut Pat, mut tail_or: ThinVec>) { - match target { - // On an existing or-pattern in the target, append to it. - Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), - // Otherwise convert the target to an or-pattern. - target => { - let mut init_or = thin_vec![Box::new(take_pat(target))]; - init_or.append(&mut tail_or); - target.kind = Or(init_or); - }, +fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec) -> bool { + fn extend(target: &mut Pat, mut tail_or: ThinVec) { + // On an existing or-pattern in the target, append to it, + // otherwise convert the target to an or-pattern. + if let Or(ps) = &mut target.kind { + ps.append(&mut tail_or); + } else { + let mut init_or = thin_vec![take_pat(target)]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); } } @@ -408,33 +408,21 @@ fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec>) -> bool { // Only elements beginning with `start` are considered for extraction. fn drain_matching( start: usize, - alternatives: &mut ThinVec>, + alternatives: &mut ThinVec, predicate: impl Fn(&PatKind) -> bool, - extract: impl Fn(PatKind) -> Box, -) -> ThinVec> { + extract: impl Fn(PatKind) -> Pat, +) -> ThinVec { let mut tail_or = ThinVec::new(); let mut idx = 0; - // If `ThinVec` had the `drain_filter` method, this loop could be rewritten - // like so: - // - // for pat in alternatives.drain_filter(|p| { - // // Check if we should extract, but only if `idx >= start`. - // idx += 1; - // idx > start && predicate(&p.kind) - // }) { - // tail_or.push(extract(pat.into_inner().kind)); - // } - let mut i = 0; - while i < alternatives.len() { - idx += 1; + // FIXME: once `thin-vec` releases a new version, change this to `alternatives.extract_if()` + // See https://github.com/mozilla/thin-vec/issues/77 + for pat in ExtractIf::new(alternatives, |p| { // Check if we should extract, but only if `idx >= start`. - if idx > start && predicate(&alternatives[i].kind) { - let pat = alternatives.remove(i); - tail_or.push(extract(pat.kind)); - } else { - i += 1; - } + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.kind)); } tail_or @@ -443,17 +431,17 @@ fn drain_matching( fn extend_with_matching( target: &mut Pat, start: usize, - alternatives: &mut ThinVec>, + alternatives: &mut ThinVec, predicate: impl Fn(&PatKind) -> bool, - extract: impl Fn(PatKind) -> Box, + extract: impl Fn(PatKind) -> Pat, ) -> bool { extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract)) } /// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`? -fn eq_pre_post(ps1: &[Box], ps2: &[Box], idx: usize) -> bool { +fn eq_pre_post(ps1: &[Pat], ps2: &[Pat], idx: usize) -> bool { ps1.len() == ps2.len() && ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`. - && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r)) - && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r)) + && over(&ps1[..idx], &ps2[..idx], eq_pat) + && over(&ps1[idx + 1..], &ps2[idx + 1..], eq_pat) } diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index af3ad4566c46..ffce03cb5dbe 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, paths, peel_blocks, sym}; +use clippy_utils::res::MaybeDef; +use clippy_utils::{paths, peel_blocks, sym}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -84,9 +85,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { /// get desugared to match. fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { let fn_def_id = block.hir_id.owner.to_def_id(); - if let Some(impl_id) = cx.tcx.impl_of_assoc(fn_def_id) - && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id) - { + if let Some(impl_id) = cx.tcx.trait_impl_of_assoc(fn_def_id) { + let trait_id = cx.tcx.impl_trait_id(impl_id); // We don't want to lint inside io::Read or io::Write implementations, as the author has more // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 if let Some(trait_name) = cx.tcx.get_diagnostic_name(trait_id) @@ -136,7 +136,10 @@ fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool { } if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind { - return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr); + return cx + .qpath_res(path, inner_pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, hir::LangItem::ResultErr); } false @@ -189,9 +192,9 @@ fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) { fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option { inner = unpack_match(inner); - inner = unpack_try(inner); + inner = unpack_try(cx, inner); inner = unpack_call_chain(inner); - inner = unpack_await(inner); + inner = unpack_await(cx, inner); // we type-check it to get whether it's a read/write or their vectorized forms // and keep only the ones that are produce io amount check_io_mode(cx, inner) @@ -203,7 +206,7 @@ fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind // we check against Result::Ok to avoid linting on Err(_) or something else. - && is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk) + && cx.qpath_res(path, pat.hir_id).ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultOk) { if matches!(inner_pat, []) { return true; @@ -253,12 +256,10 @@ fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { expr } -fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { +fn unpack_try<'a>(cx: &LateContext<'_>, mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { while let ExprKind::Call(func, [arg_0]) = expr.kind - && matches!( - func.kind, - ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..)) - ) + && let ExprKind::Path(qpath) = func.kind + && cx.tcx.qpath_is_lang_item(qpath, hir::LangItem::TryTraitBranch) { expr = arg_0; } @@ -274,13 +275,11 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { /// If `expr` is an (e).await, return the inner expression "e" that's being /// waited on. Otherwise return None. -fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { +fn unpack_await<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind && let ExprKind::Call(func, [arg_0]) = expr.kind - && matches!( - func.kind, - ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) - ) + && let ExprKind::Path(qpath) = func.kind + && cx.tcx.qpath_is_lang_item(qpath, hir::LangItem::IntoFutureIntoFuture) { return arg_0; } diff --git a/clippy_lints/src/unused_peekable.rs b/clippy_lints/src/unused_peekable.rs index 1f5351e32aac..668663e4cf79 100644 --- a/clippy_lints/src/unused_peekable.rs +++ b/clippy_lints/src/unused_peekable.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; -use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, peel_ref_operators, sym}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; +use clippy_utils::ty::peel_and_count_ty_refs; +use clippy_utils::{fn_def_id, peel_ref_operators, sym}; use rustc_ast::Mutability; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{Block, Expr, ExprKind, HirId, LetStmt, Node, PatKind, PathSegment, StmtKind}; @@ -49,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable { // Don't lint `Peekable`s returned from a block if let Some(expr) = block.expr && let Some(ty) = cx.typeck_results().expr_ty_opt(peel_ref_operators(cx, expr)) - && is_type_diagnostic_item(cx, ty, sym::IterPeekable) + && ty.is_diag_item(cx, sym::IterPeekable) { return; } @@ -61,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable { && let Some(init) = local.init && !init.span.from_expansion() && let Some(ty) = cx.typeck_results().expr_ty_opt(init) - && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) - && is_type_diagnostic_item(cx, ty, sym::IterPeekable) + && let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty) + && ty.is_diag_item(cx, sym::IterPeekable) { let mut vis = PeekableVisitor::new(cx, binding); @@ -116,7 +117,7 @@ impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> { } fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> ControlFlow<()> { - if path_to_local_id(ex, self.expected_hir_id) { + if ex.res_local_id() == Some(self.expected_hir_id) { for (_, node) in self.cx.tcx.hir_parent_iter(ex.hir_id) { match node { Node::Expr(expr) => { @@ -160,7 +161,11 @@ impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> { // foo.some_method() excluding Iterator methods if remaining_args.iter().any(|arg| arg_is_mut_peekable(self.cx, arg)) - && !is_trait_method(self.cx, expr, sym::Iterator) + && !self + .cx + .ty_based_def(expr) + .opt_parent(self.cx) + .is_diag_item(self.cx, sym::Iterator) { return ControlFlow::Break(()); } @@ -211,8 +216,8 @@ impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> { fn arg_is_mut_peekable(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool { if let Some(ty) = cx.typeck_results().expr_ty_opt(arg) - && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) - && is_type_diagnostic_item(cx, ty, sym::IterPeekable) + && let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty) + && ty.is_diag_item(cx, sym::IterPeekable) { true } else { diff --git a/clippy_lints/src/unused_result_ok.rs b/clippy_lints/src/unused_result_ok.rs index f5ed10fb7609..fe323a0b7b0c 100644 --- a/clippy_lints/src/unused_result_ok.rs +++ b/clippy_lints/src/unused_result_ok.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::sym; -use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -37,7 +37,7 @@ impl LateLintPass<'_> for UnusedResultOk { if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(ok_path, recv, [], ..) = expr.kind //check is expr.ok() has type Result.ok(, _) && ok_path.ident.name == sym::ok - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) && !stmt.span.in_external_macro(cx.sess().source_map()) { let ctxt = expr.span.ctxt(); diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 490da4f1e037..99201a1ca215 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,7 +1,9 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::msrvs::Msrv; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::usage::is_potentially_local_place; -use clippy_utils::{higher, path_to_local, sym}; +use clippy_utils::{can_use_if_let_chains, higher, sym}; use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp}; @@ -10,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, Symbol}; @@ -72,10 +74,21 @@ declare_clippy_lint! { "checks for calls of `unwrap[_err]()` that will always fail" } +pub(crate) struct Unwrap { + msrv: Msrv, +} + +impl Unwrap { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + /// Visitor that keeps track of which variables are unwrappable. struct UnwrappableVariablesVisitor<'a, 'tcx> { unwrappables: Vec>, cx: &'a LateContext<'tcx>, + msrv: Msrv, } /// What kind of unwrappable this is. @@ -133,12 +146,14 @@ fn collect_unwrap_info<'tcx>( invert: bool, is_entire_condition: bool, ) -> Vec> { - fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool { - is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some) - } - - fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool { - is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok) + fn option_or_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<(UnwrappableKind, bool)> { + match (ty.opt_diag_name(cx)?, method_name) { + (sym::Option, sym::is_some) => Some((UnwrappableKind::Option, true)), + (sym::Option, sym::is_none) => Some((UnwrappableKind::Option, false)), + (sym::Result, sym::is_ok) => Some((UnwrappableKind::Result, true)), + (sym::Result, sym::is_err) => Some((UnwrappableKind::Result, false)), + _ => None, + } } match expr.kind { @@ -154,18 +169,12 @@ fn collect_unwrap_info<'tcx>( }, ExprKind::Unary(UnOp::Not, expr) => collect_unwrap_info(cx, if_expr, expr, branch, !invert, false), ExprKind::MethodCall(method_name, receiver, [], _) - if let Some(local_id) = path_to_local(receiver) + if let Some(local_id) = receiver.res_local_id() && let ty = cx.typeck_results().expr_ty(receiver) && let name = method_name.ident.name - && (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) => + && let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) => { - let unwrappable = matches!(name, sym::is_some | sym::is_ok); let safe_to_unwrap = unwrappable != invert; - let kind = if is_type_diagnostic_item(cx, ty, sym::Option) { - UnwrappableKind::Option - } else { - UnwrappableKind::Result - }; vec![UnwrapInfo { local_id, @@ -292,6 +301,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { // Shouldn't lint when `expr` is in macro. if expr.span.in_external_macro(self.cx.tcx.sess.source_map()) { + walk_expr(self, expr); return; } // Skip checking inside closures since they are visited through `Unwrap::check_fn()` already. @@ -308,7 +318,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { // find `unwrap[_err]()` or `expect("...")` calls: if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind && let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg) - && let Some(id) = path_to_local(self_arg) + && let Some(id) = self_arg.res_local_id() && matches!(method_name.ident.name, sym::unwrap | sym::expect | sym::unwrap_err) && let call_to_unwrap = matches!(method_name.ident.name, sym::unwrap | sym::expect) && let Some(unwrappable) = self.unwrappables.iter() @@ -356,7 +366,11 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { ); } else { diag.span_label(unwrappable.check.span, "the check is happening here"); - diag.help("try using `if let` or `match`"); + if can_use_if_let_chains(self.cx, self.msrv) { + diag.help("try using `if let` or `match`"); + } else { + diag.help("try using `match`"); + } } }, ); @@ -382,7 +396,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { } } -declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); +impl_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); impl<'tcx> LateLintPass<'tcx> for Unwrap { fn check_fn( @@ -401,6 +415,7 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap { let mut v = UnwrappableVariablesVisitor { unwrappables: Vec::new(), cx, + msrv: self.msrv, }; walk_fn(&mut v, kind, decl, body.id(), fn_id); diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index aeda864b7eb5..eba60501ae21 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -2,20 +2,21 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{same_type_and_consts, ty_from_hir_ty}; +use clippy_utils::ty::{same_type_modulo_regions, ty_from_hir_ty}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; use rustc_hir::{ - self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind, - HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, + self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParamKind, HirId, Impl, + ImplItemImplKind, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty as MiddleTy; use rustc_session::impl_lint_pass; use rustc_span::Span; +use std::iter; declare_clippy_lint! { /// ### What it does @@ -57,6 +58,7 @@ declare_clippy_lint! { pub struct UseSelf { msrv: Msrv, stack: Vec, + recursive_self_in_type_definitions: bool, } impl UseSelf { @@ -64,6 +66,7 @@ impl UseSelf { Self { msrv: conf.msrv, stack: Vec::new(), + recursive_self_in_type_definitions: conf.recursive_self_in_type_definitions, } } } @@ -83,10 +86,10 @@ const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element"; impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { - // We push the self types of `impl`s on a stack here. Only the top type on the stack is - // relevant for linting, since this is the self type of the `impl` we're currently in. To - // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that - // we're in an `impl` or nested item, that we don't want to lint + // We push the self types of items on a stack here. Only the top type on the stack is + // relevant for linting, since this is the self type of the item we're currently in. To + // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal that + // we're in an item or nested item that we don't want to lint let stack_item = if let ItemKind::Impl(Impl { self_ty, generics, .. }) = item.kind && let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind && let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args @@ -101,22 +104,25 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { let types_to_skip = generics .params .iter() - .filter_map(|param| match param { - GenericParam { - kind: - GenericParamKind::Const { - ty: Ty { hir_id, .. }, .. - }, - .. - } => Some(*hir_id), + .filter_map(|param| match param.kind { + GenericParamKind::Const { ty, .. } => Some(ty.hir_id), _ => None, }) - .chain(std::iter::once(self_ty.hir_id)) + .chain([self_ty.hir_id]) .collect(); StackItem::Check { impl_id: item.owner_id.def_id, types_to_skip, } + } else if let ItemKind::Struct(..) | ItemKind::Enum(..) = item.kind + && self.recursive_self_in_type_definitions + && !item.span.from_expansion() + && !is_from_proc_macro(cx, item) + { + StackItem::Check { + impl_id: item.owner_id.def_id, + types_to_skip: FxHashSet::default(), + } } else { StackItem::NoCheck }; @@ -136,13 +142,14 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { // We want to skip types in trait `impl`s that aren't declared as `Self` in the trait // declaration. The collection of those types is all this method implementation does. if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind + && let ImplItemImplKind::Trait { .. } = impl_item.impl_kind && let Some(&mut StackItem::Check { impl_id, ref mut types_to_skip, .. }) = self.stack.last_mut() - && let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_id) { + let impl_trait_ref = cx.tcx.impl_trait_ref(impl_id); // `self_ty` is the semantic self type of `impl for `. This cannot be // `Self`. let self_ty = impl_trait_ref.instantiate_identity().self_ty(); @@ -151,8 +158,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { // trait, not in the impl of the trait. let trait_method = cx .tcx - .associated_item(impl_item.owner_id) - .trait_item_def_id + .trait_item_of(impl_item.owner_id) .expect("impl method matches a trait method"); let trait_method_sig = cx.tcx.fn_sig(trait_method).instantiate_identity(); let trait_method_sig = cx.tcx.instantiate_bound_regions_with_erased(trait_method_sig); @@ -210,11 +216,11 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && !types_to_skip.contains(&hir_ty.hir_id) && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) && let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity() - && same_type_and_consts(ty, impl_ty) + && same_type_modulo_regions(ty, impl_ty) // Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that - // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in + // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`), in // which case we must still trigger the lint. - && (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty)) + && same_lifetimes(ty, impl_ty) && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { span_lint(cx, hir_ty.span); @@ -227,18 +233,16 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity() && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { - } else { - return; - } - match expr.kind { - ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path), - ExprKind::Call(fun, _) => { - if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind { - check_path(cx, path); - } - }, - ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path), - _ => (), + match expr.kind { + ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path), + ExprKind::Call(fun, _) => { + if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind { + check_path(cx, path); + } + }, + ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path), + _ => (), + } } } @@ -308,36 +312,20 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) { } } -/// Returns `true` if types `a` and `b` have the same lifetime parameters, otherwise returns -/// `false`. +/// Checks whether types `a` and `b` have the same lifetime parameters. /// /// This function does not check that types `a` and `b` are the same types. fn same_lifetimes<'tcx>(a: MiddleTy<'tcx>, b: MiddleTy<'tcx>) -> bool { use rustc_middle::ty::{Adt, GenericArgKind}; - match (&a.kind(), &b.kind()) { - (&Adt(_, args_a), &Adt(_, args_b)) => { - args_a - .iter() - .zip(args_b.iter()) - .all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { - // TODO: Handle inferred lifetimes - (GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b, - (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b), - _ => true, - }) + match (a.kind(), b.kind()) { + (Adt(_, args_a), Adt(_, args_b)) => { + iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { + // TODO: Handle inferred lifetimes + (GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b), + _ => true, + }) }, _ => a == b, } } - -/// Returns `true` if `ty` has no lifetime parameter, otherwise returns `false`. -fn has_no_lifetime(ty: MiddleTy<'_>) -> bool { - use rustc_middle::ty::{Adt, GenericArgKind}; - match ty.kind() { - &Adt(_, args) => !args - .iter() - // TODO: Handle inferred lifetimes - .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(..))), - _ => true, - } -} diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 70ae982a4458..0cf5b9431a34 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::{DiagExt as _, Sugg}; -use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_and_consts}; -use clippy_utils::{ - get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local, sym, -}; +use clippy_utils::ty::{is_copy, same_type_modulo_regions}; +use clippy_utils::{get_parent_expr, is_ty_alias, sym}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, PatKind}; @@ -98,7 +97,7 @@ fn into_iter_bound<'tcx>( if tr.def_id() == into_iter_did { into_iter_span = Some(*span); } else { - let tr = cx.tcx.erase_regions(tr); + let tr = cx.tcx.erase_and_anonymize_regions(tr); if tr.has_escaping_bound_vars() { return None; } @@ -133,7 +132,7 @@ fn into_iter_bound<'tcx>( /// Extracts the receiver of a `.into_iter()` method call. fn into_iter_call<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Option<&'hir Expr<'hir>> { if let ExprKind::MethodCall(name, recv, [], _) = expr.kind - && is_trait_method(cx, expr, sym::IntoIterator) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::IntoIterator) && name.ident.name == sym::into_iter { Some(recv) @@ -181,10 +180,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { path.ident.name, sym::map | sym::map_err | sym::map_break | sym::map_continue ) && has_eligible_receiver(cx, recv, e) - && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) + && matches!( + arg.res(cx).assoc_parent(cx).opt_diag_name(cx), + Some(sym::Into | sym::From) + ) && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() - && same_type_and_consts(from_ty, to_ty) + && same_type_modulo_regions(from_ty, to_ty) { span_lint_and_then( cx, @@ -204,10 +206,10 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { }, ExprKind::MethodCall(name, recv, [], _) => { - if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { + if cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(recv); - if same_type_and_consts(a, b) { + if same_type_modulo_regions(a, b) { let mut app = Applicability::MachineApplicable; let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "", &mut app).0; span_lint_and_sugg( @@ -308,7 +310,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } } - if let Some(id) = path_to_local(recv) + if let Some(id) = recv.res_local_id() && let Node::Pat(pat) = cx.tcx.hir_node(id) && let PatKind::Binding(ann, ..) = pat.kind && ann != BindingMode::MUT @@ -324,7 +326,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { // If the types are identical then .into_iter() can be removed, unless the type // implements Copy, in which case .into_iter() returns a copy of the receiver and // cannot be safely omitted. - if same_type_and_consts(a, b) && !is_copy(cx, b) { + if same_type_modulo_regions(a, b) && !is_copy(cx, b) { // Below we check if the parent method call meets the following conditions: // 1. First parameter is `&mut self` (requires mutable reference) // 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any) @@ -364,14 +366,14 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { ); } } - if is_trait_method(cx, e, sym::TryInto) + if cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::TryInto) && name.ident.name == sym::try_into && let a = cx.typeck_results().expr_ty(e) && let b = cx.typeck_results().expr_ty(recv) - && is_type_diagnostic_item(cx, a, sym::Result) + && a.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() - && same_type_and_consts(a_type, b) + && same_type_modulo_regions(a_type, b) { span_lint_and_help( cx, @@ -393,10 +395,10 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(arg); if name == sym::try_from_fn - && is_type_diagnostic_item(cx, a, sym::Result) + && a.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() - && same_type_and_consts(a_type, b) + && same_type_modulo_regions(a_type, b) { let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); span_lint_and_help( @@ -407,7 +409,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { None, hint, ); - } else if name == sym::from_fn && same_type_and_consts(a, b) { + } else if name == sym::from_fn && same_type_modulo_regions(a, b) { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); @@ -439,13 +441,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { - if is_inherent_method_call(cx, expr) { + if cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) { matches!( - get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv)), + cx.typeck_results().expr_ty(recv).opt_diag_name(cx), Some(sym::Option | sym::Result | sym::ControlFlow) ) } else { - is_trait_method(cx, expr, sym::Iterator) + cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) } } diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index ece29362a39f..ac80c9b39006 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -1,4 +1,5 @@ -use clippy_utils::{MaybePath, get_attr, higher, path_def_id, sym}; +use clippy_utils::res::MaybeQPath; +use clippy_utils::{get_builtin_attr, higher, sym}; use itertools::Itertools; use rustc_ast::LitIntType; use rustc_ast::ast::{LitFloatType, LitKind}; @@ -205,7 +206,6 @@ struct PrintVisitor<'a, 'tcx> { first: Cell, } -#[allow(clippy::unused_self)] impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> Self { Self { @@ -269,16 +269,14 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str()); } - fn qpath<'p>(&self, qpath: &Binding<&QPath<'_>>, has_hir_id: &Binding<&impl MaybePath<'p>>) { - if let QPath::LangItem(lang_item, ..) = *qpath.value { - chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))"); - } else if let Some(def_id) = self.cx.qpath_res(qpath.value, has_hir_id.value.hir_id()).opt_def_id() + fn qpath(&self, qpath: &Binding<&QPath<'_>>, hir_id_binding: &str, hir_id: HirId) { + if let Some(def_id) = self.cx.qpath_res(qpath.value, hir_id).opt_def_id() && !def_id.is_local() { bind!(self, def_id); chain!( self, - "let Some({def_id}) = cx.qpath_res({qpath}, {has_hir_id}.hir_id).opt_def_id()" + "let Some({def_id}) = cx.qpath_res({qpath}, {hir_id_binding}.hir_id).opt_def_id()" ); if let Some(name) = self.cx.tcx.get_diagnostic_name(def_id.value) { chain!(self, "cx.tcx.is_diagnostic_item(sym::{name}, {def_id})"); @@ -292,14 +290,14 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { } } - fn maybe_path<'p>(&self, path: &Binding<&impl MaybePath<'p>>) { - if let Some(id) = path_def_id(self.cx, path.value) + fn maybe_path<'p>(&self, path: &Binding>) { + if let Some(id) = path.value.res(self.cx).opt_def_id() && !id.is_local() { if let Some(lang) = self.cx.tcx.lang_items().from_def_id(id) { - chain!(self, "is_path_lang_item(cx, {path}, LangItem::{}", lang.name()); + chain!(self, "{path}.res(cx).is_lang_item(cx, LangItem::{}", lang.name()); } else if let Some(name) = self.cx.tcx.get_diagnostic_name(id) { - chain!(self, "is_path_diagnostic_item(cx, {path}, sym::{name})"); + chain!(self, "{path}.res(cx).is_diag_item(cx, sym::{name})"); } else { chain!( self, @@ -410,7 +408,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.expr(field!(arm.body)); } - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn expr(&self, expr: &Binding<&hir::Expr<'_>>) { if let Some(higher::While { condition, body, .. }) = higher::While::hir(expr.value) { bind!(self, condition, body); @@ -672,7 +670,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { StructTailExpr::None | StructTailExpr::DefaultFields(_) => None, }); kind!("Struct({qpath}, {fields}, {base})"); - self.qpath(qpath, expr); + self.qpath(qpath, &expr.name, expr.value.hir_id); self.slice(fields, |field| { self.ident(field!(field.ident)); self.expr(field!(field.expr)); @@ -758,7 +756,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { let ignore = etc.is_some(); bind!(self, qpath, fields); kind!("Struct(ref {qpath}, {fields}, {ignore})"); - self.qpath(qpath, pat); + self.qpath(qpath, &pat.name, pat.value.hir_id); self.slice(fields, |field| { self.ident(field!(field.ident)); self.pat(field!(field.pat)); @@ -772,7 +770,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { PatKind::TupleStruct(ref qpath, fields, skip_pos) => { bind!(self, qpath, fields); kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})"); - self.qpath(qpath, pat); + self.qpath(qpath, &pat.name, pat.value.hir_id); self.slice(fields, |pat| self.pat(pat)); }, PatKind::Tuple(fields, skip_pos) => { @@ -856,5 +854,5 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::author).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0 } diff --git a/clippy_lints/src/utils/dump_hir.rs b/clippy_lints/src/utils/dump_hir.rs index d6cf07fdaf3f..b490866f0a11 100644 --- a/clippy_lints/src/utils/dump_hir.rs +++ b/clippy_lints/src/utils/dump_hir.rs @@ -1,4 +1,4 @@ -use clippy_utils::{get_attr, sym}; +use clippy_utils::{get_builtin_attr, sym}; use hir::TraitItem; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -60,5 +60,5 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir { fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::dump).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0 } diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 52b30ddce12b..b87db836869d 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -10,7 +10,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{get_parent_expr, higher, is_in_test, is_trait_method, span_contains_comment, sym}; +use clippy_utils::{get_parent_expr, higher, is_in_test, span_contains_comment, sym}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, LetStmt, Mutability, Node, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -124,7 +124,7 @@ impl UselessVec { if let Some(parent) = get_parent_expr(cx, expr) && (adjusts_to_slice(cx, expr) || matches!(parent.kind, ExprKind::Index(..)) - || is_allowed_vec_method(cx, parent)) + || is_allowed_vec_method(parent)) { ControlFlow::Continue(()) } else { @@ -304,11 +304,11 @@ fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { /// Checks if the given expression is a method call to a `Vec` method /// that also exists on slices. If this returns true, it means that /// this expression does not actually require a `Vec` and could just work with an array. -pub fn is_allowed_vec_method(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { +pub fn is_allowed_vec_method(e: &Expr<'_>) -> bool { if let ExprKind::MethodCall(path, _, [], _) = e.kind { matches!(path.ident.name, sym::as_ptr | sym::is_empty | sym::len) } else { - is_trait_method(cx, e, sym::IntoIterator) + false } } diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index 8d873536295d..5d074208c029 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{get_parent_expr, path_to_local_id, sym}; +use clippy_utils::{get_parent_expr, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -201,7 +202,7 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush { if let Some(searcher) = self.searcher.take() { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [_], _) = expr.kind - && path_to_local_id(self_arg, searcher.local_id) + && self_arg.res_local_id() == Some(searcher.local_id) && name.ident.name == sym::push { self.searcher = Some(VecPushSearcher { diff --git a/clippy_lints/src/volatile_composites.rs b/clippy_lints/src/volatile_composites.rs new file mode 100644 index 000000000000..6402c3ef72c4 --- /dev/null +++ b/clippy_lints/src/volatile_composites.rs @@ -0,0 +1,180 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::res::MaybeDef; +use clippy_utils::sym; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// This lint warns when volatile load/store operations + /// (`write_volatile`/`read_volatile`) are applied to composite types. + /// + /// ### Why is this bad? + /// + /// Volatile operations are typically used with memory mapped IO devices, + /// where the precise number and ordering of load and store instructions is + /// important because they can have side effects. This is well defined for + /// primitive types like `u32`, but less well defined for structures and + /// other composite types. In practice it's implementation defined, and the + /// behavior can be rustc-version dependent. + /// + /// As a result, code should only apply `write_volatile`/`read_volatile` to + /// primitive types to be fully well-defined. + /// + /// ### Example + /// ```no_run + /// struct MyDevice { + /// addr: usize, + /// count: usize + /// } + /// + /// fn start_device(device: *mut MyDevice, addr: usize, count: usize) { + /// unsafe { + /// device.write_volatile(MyDevice { addr, count }); + /// } + /// } + /// ``` + /// Instead, operate on each primtive field individually: + /// ```no_run + /// struct MyDevice { + /// addr: usize, + /// count: usize + /// } + /// + /// fn start_device(device: *mut MyDevice, addr: usize, count: usize) { + /// unsafe { + /// (&raw mut (*device).addr).write_volatile(addr); + /// (&raw mut (*device).count).write_volatile(count); + /// } + /// } + /// ``` + #[clippy::version = "1.92.0"] + pub VOLATILE_COMPOSITES, + nursery, + "warn about volatile read/write applied to composite types" +} +declare_lint_pass!(VolatileComposites => [VOLATILE_COMPOSITES]); + +/// Zero-sized types are intrinsically safe to use volatile on since they won't +/// actually generate *any* loads or stores. But this is also used to skip zero-sized +/// fields of `#[repr(transparent)]` structures. +fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + cx.layout_of(ty).is_ok_and(|layout| layout.is_zst()) +} + +/// A thin raw pointer or reference. +fn is_narrow_ptr<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::RawPtr(inner, _) | ty::Ref(_, inner, _) => inner.has_trivial_sizedness(cx.tcx, ty::SizedTraitKind::Sized), + _ => false, + } +} + +/// Enum with some fixed representation and no data-carrying variants. +fn is_enum_repr_c<'tcx>(_cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + ty.ty_adt_def().is_some_and(|adt_def| { + adt_def.is_enum() && adt_def.repr().inhibit_struct_field_reordering() && adt_def.is_payloadfree() + }) +} + +/// `#[repr(transparent)]` structures are also OK if the only non-zero +/// sized field contains a volatile-safe type. +fn is_struct_repr_transparent<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if let ty::Adt(adt_def, args) = ty.kind() + && adt_def.is_struct() + && adt_def.repr().transparent() + && let [fieldty] = adt_def + .all_fields() + .filter_map(|field| { + let fty = field.ty(cx.tcx, args); + if is_zero_sized_ty(cx, fty) { None } else { Some(fty) } + }) + .collect::>() + .as_slice() + { + is_volatile_safe_ty(cx, *fieldty) + } else { + false + } +} + +/// SIMD can be useful to get larger single loads/stores, though this is still +/// pretty machine-dependent. +fn is_simd_repr<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if let ty::Adt(adt_def, _args) = ty.kind() + && adt_def.is_struct() + && adt_def.repr().simd() + { + let (_size, simdty) = ty.simd_size_and_type(cx.tcx); + is_volatile_safe_ty(cx, simdty) + } else { + false + } +} + +/// Top-level predicate for whether a type is volatile-safe or not. +fn is_volatile_safe_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + ty.is_primitive() + || is_narrow_ptr(cx, ty) + || is_zero_sized_ty(cx, ty) + || is_enum_repr_c(cx, ty) + || is_simd_repr(cx, ty) + || is_struct_repr_transparent(cx, ty) + // We can't know about a generic type, so just let it pass to avoid noise + || ty.has_non_region_param() +} + +/// Print diagnostic for volatile read/write on non-volatile-safe types. +fn report_volatile_safe<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty: Ty<'tcx>) { + if !is_volatile_safe_ty(cx, ty) { + span_lint( + cx, + VOLATILE_COMPOSITES, + expr.span, + format!("type `{ty}` is not volatile-compatible"), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for VolatileComposites { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + // Check our expr is calling a method with pattern matching + match expr.kind { + // Look for method calls to `write_volatile`/`read_volatile`, which + // apply to both raw pointers and std::ptr::NonNull. + ExprKind::MethodCall(name, self_arg, _, _) + if matches!(name.ident.name, sym::read_volatile | sym::write_volatile) => + { + let self_ty = cx.typeck_results().expr_ty(self_arg); + match self_ty.kind() { + // Raw pointers + ty::RawPtr(innerty, _) => report_volatile_safe(cx, expr, *innerty), + // std::ptr::NonNull + ty::Adt(_, args) if self_ty.is_diag_item(cx, sym::NonNull) => { + report_volatile_safe(cx, expr, args.type_at(0)); + }, + _ => (), + } + }, + + // Also plain function calls to std::ptr::{read,write}_volatile + ExprKind::Call(func, [arg_ptr, ..]) => { + if let ExprKind::Path(ref qpath) = func.kind + && let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_read_volatile | sym::ptr_write_volatile) + ) + && let ty::RawPtr(ptrty, _) = cx.typeck_results().expr_ty_adjusted(arg_ptr).kind() + { + report_volatile_safe(cx, expr, *ptrty); + } + }, + _ => {}, + } + } +} diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs deleted file mode 100644 index c55c5ec2f51a..000000000000 --- a/clippy_lints/src/write.rs +++ /dev/null @@ -1,727 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{FormatArgsStorage, MacroCall, format_arg_removal_span, root_macro_call_first_node}; -use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; -use clippy_utils::{is_in_test, sym}; -use rustc_ast::token::LitKind; -use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, -}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, Impl, Item, ItemKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, Span}; - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `println!("")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `println!()`, which is simpler. - /// - /// ### Example - /// ```no_run - /// println!(""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// println!(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINTLN_EMPTY_STRING, - style, - "using `println!(\"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `print!()` with a format - /// string that ends in a newline. - /// - /// ### Why is this bad? - /// You should use `println!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # let name = "World"; - /// print!("Hello {}!\n", name); - /// ``` - /// use println!() instead - /// ```no_run - /// # let name = "World"; - /// println!("Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_WITH_NEWLINE, - style, - "using `print!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stdout*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stdout* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `print!` and `println!` calls. - /// - /// ### Example - /// ```no_run - /// println!("Hello world!"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_STDOUT, - restriction, - "printing on stdout" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stderr*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stderr* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `eprint!` and `eprintln!` calls. - /// - /// ### Example - /// ```no_run - /// eprintln!("Hello world!"); - /// ``` - #[clippy::version = "1.50.0"] - pub PRINT_STDERR, - restriction, - "printing on stderr" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Debug` formatting. The purpose of this - /// lint is to catch debugging remnants. - /// - /// ### Why restrict this? - /// The purpose of the `Debug` trait is to facilitate debugging Rust code, - /// and [no guarantees are made about its output][stability]. - /// It should not be used in user-facing output. - /// - /// ### Example - /// ```no_run - /// # let foo = "bar"; - /// println!("{:?}", foo); - /// ``` - /// - /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability - #[clippy::version = "pre 1.29.0"] - pub USE_DEBUG, - restriction, - "use of `Debug`-based formatting" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `print!`/`println!` args. - /// - /// ### Why is this bad? - /// Using literals as `println!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// println!("{}", "foo"); - /// ``` - /// use the literal without formatting: - /// ```no_run - /// println!("foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_LITERAL, - style, - "printing a literal with a format string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `writeln!(buf, "")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!(buf)`, which is simpler. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, ""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITELN_EMPTY_STRING, - style, - "using `writeln!(buf, \"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `write!()` with a format - /// string that - /// ends in a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// write!(buf, "Hello {}!\n", name); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// writeln!(buf, "Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_WITH_NEWLINE, - style, - "using `write!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `write!`/`writeln!` args. - /// - /// ### Why is this bad? - /// Using literals as `writeln!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "{}", "foo"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_LITERAL, - style, - "writing a literal with a format string" -} - -pub struct Write { - format_args: FormatArgsStorage, - in_debug_impl: bool, - allow_print_in_tests: bool, -} - -impl Write { - pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { - Self { - format_args, - in_debug_impl: false, - allow_print_in_tests: conf.allow_print_in_tests, - } - } -} - -impl_lint_pass!(Write => [ - PRINT_WITH_NEWLINE, - PRINTLN_EMPTY_STRING, - PRINT_STDOUT, - PRINT_STDERR, - USE_DEBUG, - PRINT_LITERAL, - WRITE_WITH_NEWLINE, - WRITELN_EMPTY_STRING, - WRITE_LITERAL, -]); - -impl<'tcx> LateLintPass<'tcx> for Write { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_debug_impl(cx, item) { - self.in_debug_impl = true; - } - } - - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_debug_impl(cx, item) { - self.in_debug_impl = false; - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(macro_call) = root_macro_call_first_node(cx, expr) else { - return; - }; - let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { - return; - }; - let Some(name) = diag_name.as_str().strip_suffix("_macro") else { - return; - }; - - let is_build_script = cx - .sess() - .opts - .crate_name - .as_ref() - .is_some_and(|crate_name| crate_name == "build_script_build"); - - let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); - match diag_name { - sym::print_macro | sym::println_macro if !allowed_in_tests => { - if !is_build_script { - span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); - } - }, - sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { - span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); - }, - sym::write_macro | sym::writeln_macro => {}, - _ => return, - } - - if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { - // ignore `writeln!(w)` and `write!(v, some_macro!())` - if format_args.span.from_expansion() { - return; - } - - match diag_name { - sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, format_args, ¯o_call, name); - }, - sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, format_args, ¯o_call, name); - }, - _ => {}, - } - - check_literal(cx, format_args, name); - - if !self.in_debug_impl { - for piece in &format_args.template { - if let &FormatArgsPiece::Placeholder(FormatPlaceholder { - span: Some(span), - format_trait: FormatTrait::Debug, - .. - }) = piece - { - span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); - } - } - } - } - } -} - -fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { - if let ItemKind::Impl(Impl { - of_trait: Some(of_trait), - .. - }) = &item.kind - && let Some(trait_id) = of_trait.trait_ref.trait_def_id() - { - cx.tcx.is_diagnostic_item(sym::Debug, trait_id) - } else { - false - } -} - -fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { - return; - }; - - let count_vertical_whitespace = || { - format_args - .template - .iter() - .filter_map(|piece| match piece { - FormatArgsPiece::Literal(literal) => Some(literal), - FormatArgsPiece::Placeholder(_) => None, - }) - .flat_map(|literal| literal.as_str().chars()) - .filter(|ch| matches!(ch, '\r' | '\n')) - .count() - }; - - if last.as_str().ends_with('\n') - // ignore format strings with other internal vertical whitespace - && count_vertical_whitespace() == 1 - { - let mut format_string_span = format_args.span; - - let lint = if name == "write" { - format_string_span = expand_past_previous_comma(cx, format_string_span); - - WRITE_WITH_NEWLINE - } else { - PRINT_WITH_NEWLINE - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("using `{name}!()` with a format string that ends in a single newline"), - |diag| { - let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); - let Some(format_snippet) = format_string_span.get_source_text(cx) else { - return; - }; - - if format_args.template.len() == 1 && last == sym::LF { - // print!("\n"), write!(f, "\n") - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], - Applicability::MachineApplicable, - ); - } else if format_snippet.ends_with("\\n\"") { - // print!("...\n"), write!(f, "...\n") - - let hi = format_string_span.hi(); - let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (newline_span, String::new())], - Applicability::MachineApplicable, - ); - } - }, - ); - } -} - -fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { - let mut span = format_args.span; - - let lint = if name == "writeln" { - span = expand_past_previous_comma(cx, span); - - WRITELN_EMPTY_STRING - } else { - PRINTLN_EMPTY_STRING - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("empty string literal in `{name}!`"), - |diag| { - diag.span_suggestion( - span, - "remove the empty string", - String::new(), - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { - let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); - - let lint_name = if name.starts_with("write") { - WRITE_LITERAL - } else { - PRINT_LITERAL - }; - - let mut counts = vec![0u32; format_args.arguments.all_args().len()]; - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece { - counts[arg_index(&placeholder.argument)] += 1; - } - } - - let mut suggestion: Vec<(Span, String)> = vec![]; - // holds index of replaced positional arguments; used to decrement the index of the remaining - // positional arguments. - let mut replaced_position: Vec = vec![]; - let mut sug_span: Option = None; - - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument, - span: Some(placeholder_span), - format_trait: FormatTrait::Display, - format_options, - }) = piece - && *format_options == FormatOptions::default() - && let index = arg_index(argument) - && counts[index] == 1 - && let Some(arg) = format_args.arguments.by_index(index) - && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind - && !arg.expr.span.from_expansion() - && let Some(value_string) = arg.expr.span.get_source_text(cx) - { - let (replacement, replace_raw) = match lit.kind { - LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { - Some(extracted) => extracted, - None => return, - }, - LitKind::Char => ( - match lit.symbol { - sym::DOUBLE_QUOTE => "\\\"", - sym::BACKSLASH_SINGLE_QUOTE => "'", - _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { - Some(stripped) => stripped, - None => return, - }, - } - .to_string(), - false, - ), - LitKind::Bool => (lit.symbol.to_string(), false), - _ => continue, - }; - - let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { - continue; - }; - let format_string_is_raw = format_string_snippet.starts_with('r'); - - let replacement = match (format_string_is_raw, replace_raw) { - (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), - (true, false) => match conservative_unescape(&replacement) { - Ok(unescaped) => Some(unescaped), - Err(UnescapeErr::Lint) => None, - Err(UnescapeErr::Ignore) => continue, - }, - (true, true) => { - if replacement.contains(['#', '"']) { - None - } else { - Some(replacement) - } - }, - }; - - sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - - if let Some((_, index)) = format_arg_piece_span(piece) { - replaced_position.push(index); - } - - if let Some(replacement) = replacement - // `format!("{}", "a")`, `format!("{named}", named = "b") - // ~~~~~ ~~~~~~~~~~~~~ - && let Some(removal_span) = format_arg_removal_span(format_args, index) - { - let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); - suggestion.push((*placeholder_span, replacement)); - suggestion.push((removal_span, String::new())); - } - } - } - - // Decrement the index of the remaining by the number of replaced positional arguments - if !suggestion.is_empty() { - for piece in &format_args.template { - relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); - } - } - - if let Some(span) = sug_span { - span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { - if !suggestion.is_empty() { - diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); - } - }); - } -} - -/// Extract Span and its index from the given `piece` -fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { - match piece { - FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: FormatArgPosition { index: Ok(index), .. }, - span: Some(span), - .. - }) => Some((*span, *index)), - _ => None, - } -} - -/// Relocalizes the indexes of positional arguments in the format string -fn relocalize_format_args_indexes( - piece: &FormatArgsPiece, - suggestion: &mut Vec<(Span, String)>, - replaced_position: &[usize], -) { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - // Only consider positional arguments - kind: FormatArgPositionKind::Number, - span: Some(span), - }, - format_options, - .. - }) = piece - { - if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { - // If the span is already in the suggestion, we don't need to process it again - return; - } - - // lambda to get the decremented index based on the replaced positions - let decremented_index = |index: usize| -> usize { - let decrement = replaced_position.iter().filter(|&&i| i < index).count(); - index - decrement - }; - - suggestion.push((*span, decremented_index(*index).to_string())); - - // If there are format options, we need to handle them as well - if *format_options != FormatOptions::default() { - // lambda to process width and precision format counts and add them to the suggestion - let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { - if let Some(FormatCount::Argument(FormatArgPosition { - index: Ok(format_arg_index), - kind: FormatArgPositionKind::Number, - span: Some(format_arg_span), - })) = count - { - suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); - } - }; - - process_format_count(&format_options.width, &|index: usize| format!("{index}$")); - process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); - } - } -} - -/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw -/// -/// `r#"a"#` -> (`a`, true) -/// -/// `"b"` -> (`b`, false) -fn extract_str_literal(literal: &str) -> Option<(String, bool)> { - let (literal, raw) = match literal.strip_prefix('r') { - Some(stripped) => (stripped.trim_matches('#'), true), - None => (literal, false), - }; - - Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) -} - -enum UnescapeErr { - /// Should still be linted, can be manually resolved by author, e.g. - /// - /// ```ignore - /// print!(r"{}", '"'); - /// ``` - Lint, - /// Should not be linted, e.g. - /// - /// ```ignore - /// print!(r"{}", '\r'); - /// ``` - Ignore, -} - -/// Unescape a normal string into a raw string -fn conservative_unescape(literal: &str) -> Result { - let mut unescaped = String::with_capacity(literal.len()); - let mut chars = literal.chars(); - let mut err = false; - - while let Some(ch) = chars.next() { - match ch { - '#' => err = true, - '\\' => match chars.next() { - Some('\\') => unescaped.push('\\'), - Some('"') => err = true, - _ => return Err(UnescapeErr::Ignore), - }, - _ => unescaped.push(ch), - } - } - - if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } -} - -/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in -/// `\u{xxxx}` are left unmodified -#[expect(clippy::match_same_arms)] -fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { - #[derive(Clone, Copy)] - enum State { - Normal, - Backslash, - UnicodeEscape, - } - - let mut escaped = String::with_capacity(literal.len()); - let mut state = State::Normal; - - for ch in literal.chars() { - state = match (ch, state) { - // Escape braces outside of unicode escapes by doubling them up - ('{' | '}', State::Normal) => { - escaped.push(ch); - State::Normal - }, - // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: - // - // \u{aaaa} \\ \x01 - // ^ ^ ^ - ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, - // \u{aaaa} - // ^ - ('u', State::Backslash) => State::UnicodeEscape, - // \xAA \\ - // ^ ^ - (_, State::Backslash) => State::Normal, - // \u{aaaa} - // ^ - ('}', State::UnicodeEscape) => State::Normal, - _ => state, - }; - - escaped.push(ch); - } - - escaped -} diff --git a/clippy_lints/src/write/empty_string.rs b/clippy_lints/src/write/empty_string.rs new file mode 100644 index 000000000000..e7eb99eb34ec --- /dev/null +++ b/clippy_lints/src/write/empty_string.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::expand_past_previous_comma; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; + +use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { + let mut span = format_args.span; + + let lint = if name == "writeln" { + span = expand_past_previous_comma(cx, span); + + WRITELN_EMPTY_STRING + } else { + PRINTLN_EMPTY_STRING + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("empty string literal in `{name}!`"), + |diag| { + diag.span_suggestion( + span, + "remove the empty string", + String::new(), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/write/literal.rs b/clippy_lints/src/write/literal.rs new file mode 100644 index 000000000000..699ac7ea7a5c --- /dev/null +++ b/clippy_lints/src/write/literal.rs @@ -0,0 +1,285 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::format_arg_removal_span; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sym; +use rustc_ast::token::LitKind; +use rustc_ast::{ + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, +}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::{PRINT_LITERAL, WRITE_LITERAL}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { + let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); + + let lint_name = if name.starts_with("write") { + WRITE_LITERAL + } else { + PRINT_LITERAL + }; + + let mut counts = vec![0u32; format_args.arguments.all_args().len()]; + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece { + counts[arg_index(&placeholder.argument)] += 1; + } + } + + let mut suggestion: Vec<(Span, String)> = vec![]; + // holds index of replaced positional arguments; used to decrement the index of the remaining + // positional arguments. + let mut replaced_position: Vec = vec![]; + let mut sug_span: Option = None; + + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span: Some(placeholder_span), + format_trait: FormatTrait::Display, + format_options, + }) = piece + && *format_options == FormatOptions::default() + && let index = arg_index(argument) + && counts[index] == 1 + && let Some(arg) = format_args.arguments.by_index(index) + && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind + && !arg.expr.span.from_expansion() + && let Some(value_string) = arg.expr.span.get_source_text(cx) + { + let (replacement, replace_raw) = match lit.kind { + LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { + Some(extracted) => extracted, + None => return, + }, + LitKind::Char => ( + match lit.symbol { + sym::DOUBLE_QUOTE => "\\\"", + sym::BACKSLASH_SINGLE_QUOTE => "'", + _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + Some(stripped) => stripped, + None => return, + }, + } + .to_string(), + false, + ), + LitKind::Bool => (lit.symbol.to_string(), false), + _ => continue, + }; + + let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { + continue; + }; + let format_string_is_raw = format_string_snippet.starts_with('r'); + + let replacement = match (format_string_is_raw, replace_raw) { + (false, false) => Some(replacement), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), + (true, false) => match conservative_unescape(&replacement) { + Ok(unescaped) => Some(unescaped), + Err(UnescapeErr::Lint) => None, + Err(UnescapeErr::Ignore) => continue, + }, + (true, true) => { + if replacement.contains(['#', '"']) { + None + } else { + Some(replacement) + } + }, + }; + + sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); + + if let Some((_, index)) = format_arg_piece_span(piece) { + replaced_position.push(index); + } + + if let Some(replacement) = replacement + // `format!("{}", "a")`, `format!("{named}", named = "b") + // ~~~~~ ~~~~~~~~~~~~~ + && let Some(removal_span) = format_arg_removal_span(format_args, index) + { + let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); + suggestion.push((*placeholder_span, replacement)); + suggestion.push((removal_span, String::new())); + } + } + } + + // Decrement the index of the remaining by the number of replaced positional arguments + if !suggestion.is_empty() { + for piece in &format_args.template { + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); + } + } + + if let Some(span) = sug_span { + span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { + if !suggestion.is_empty() { + diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); + } + }); + } +} + +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { + match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { index: Ok(index), .. }, + span: Some(span), + .. + }) => Some((*span, *index)), + _ => None, + } +} + +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + +/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw +/// +/// `r#"a"#` -> (`a`, true) +/// +/// `"b"` -> (`b`, false) +fn extract_str_literal(literal: &str) -> Option<(String, bool)> { + let (literal, raw) = match literal.strip_prefix('r') { + Some(stripped) => (stripped.trim_matches('#'), true), + None => (literal, false), + }; + + Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) +} + +enum UnescapeErr { + /// Should still be linted, can be manually resolved by author, e.g. + /// + /// ```ignore + /// print!(r"{}", '"'); + /// ``` + Lint, + /// Should not be linted, e.g. + /// + /// ```ignore + /// print!(r"{}", '\r'); + /// ``` + Ignore, +} + +/// Unescape a normal string into a raw string +fn conservative_unescape(literal: &str) -> Result { + let mut unescaped = String::with_capacity(literal.len()); + let mut chars = literal.chars(); + let mut err = false; + + while let Some(ch) = chars.next() { + match ch { + '#' => err = true, + '\\' => match chars.next() { + Some('\\') => unescaped.push('\\'), + Some('"') => err = true, + _ => return Err(UnescapeErr::Ignore), + }, + _ => unescaped.push(ch), + } + } + + if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } +} + +/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces +/// in `\u{xxxx}` are left unmodified +#[expect(clippy::match_same_arms)] +fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { + #[derive(Clone, Copy)] + enum State { + Normal, + Backslash, + UnicodeEscape, + } + + let mut escaped = String::with_capacity(literal.len()); + let mut state = State::Normal; + + for ch in literal.chars() { + state = match (ch, state) { + // Escape braces outside of unicode escapes by doubling them up + ('{' | '}', State::Normal) => { + escaped.push(ch); + State::Normal + }, + // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: + // + // \u{aaaa} \\ \x01 + // ^ ^ ^ + ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, + // \u{aaaa} + // ^ + ('u', State::Backslash) => State::UnicodeEscape, + // \xAA \\ + // ^ ^ + (_, State::Backslash) => State::Normal, + // \u{aaaa} + // ^ + ('}', State::UnicodeEscape) => State::Normal, + _ => state, + }; + + escaped.push(ch); + } + + escaped +} diff --git a/clippy_lints/src/write/mod.rs b/clippy_lints/src/write/mod.rs new file mode 100644 index 000000000000..c42c047745bb --- /dev/null +++ b/clippy_lints/src/write/mod.rs @@ -0,0 +1,354 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node}; +use clippy_utils::{is_in_test, sym}; +use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; + +mod empty_string; +mod literal; +mod use_debug; +mod with_newline; + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `println!("")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `println!()`, which is simpler. + /// + /// ### Example + /// ```no_run + /// println!(""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// println!(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINTLN_EMPTY_STRING, + style, + "using `println!(\"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `print!()` with a format + /// string that ends in a newline. + /// + /// ### Why is this bad? + /// You should use `println!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # let name = "World"; + /// print!("Hello {}!\n", name); + /// ``` + /// use println!() instead + /// ```no_run + /// # let name = "World"; + /// println!("Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_WITH_NEWLINE, + style, + "using `print!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stdout*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stdout* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `print!` and `println!` calls. + /// + /// ### Example + /// ```no_run + /// println!("Hello world!"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_STDOUT, + restriction, + "printing on stdout" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stderr*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stderr* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `eprint!` and `eprintln!` calls. + /// + /// ### Example + /// ```no_run + /// eprintln!("Hello world!"); + /// ``` + #[clippy::version = "1.50.0"] + pub PRINT_STDERR, + restriction, + "printing on stderr" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Debug` formatting. The purpose of this + /// lint is to catch debugging remnants. + /// + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. + /// + /// ### Example + /// ```no_run + /// # let foo = "bar"; + /// println!("{:?}", foo); + /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability + #[clippy::version = "pre 1.29.0"] + pub USE_DEBUG, + restriction, + "use of `Debug`-based formatting" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `print!`/`println!` args. + /// + /// ### Why is this bad? + /// Using literals as `println!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// println!("{}", "foo"); + /// ``` + /// use the literal without formatting: + /// ```no_run + /// println!("foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_LITERAL, + style, + "printing a literal with a format string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `writeln!(buf, "")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!(buf)`, which is simpler. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, ""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITELN_EMPTY_STRING, + style, + "using `writeln!(buf, \"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `write!()` with a format + /// string that + /// ends in a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// write!(buf, "Hello {}!\n", name); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// writeln!(buf, "Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_WITH_NEWLINE, + style, + "using `write!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `write!`/`writeln!` args. + /// + /// ### Why is this bad? + /// Using literals as `writeln!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "{}", "foo"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_LITERAL, + style, + "writing a literal with a format string" +} + +pub struct Write { + format_args: FormatArgsStorage, + // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated + outermost_debug_impl: Option, + allow_print_in_tests: bool, +} + +impl Write { + pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + Self { + format_args, + outermost_debug_impl: None, + allow_print_in_tests: conf.allow_print_in_tests, + } + } + + fn in_debug_impl(&self) -> bool { + self.outermost_debug_impl.is_some() + } +} + +impl_lint_pass!(Write => [ + PRINT_WITH_NEWLINE, + PRINTLN_EMPTY_STRING, + PRINT_STDOUT, + PRINT_STDERR, + USE_DEBUG, + PRINT_LITERAL, + WRITE_WITH_NEWLINE, + WRITELN_EMPTY_STRING, + WRITE_LITERAL, +]); + +impl<'tcx> LateLintPass<'tcx> for Write { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // Only check for `impl Debug`s if we're not already in one + if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { + self.outermost_debug_impl = Some(item.owner_id); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { + // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl + if self.outermost_debug_impl == Some(item.owner_id) { + self.outermost_debug_impl = None; + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { + return; + }; + let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { + return; + }; + let Some(name) = diag_name.as_str().strip_suffix("_macro") else { + return; + }; + + let is_build_script = cx + .sess() + .opts + .crate_name + .as_ref() + .is_some_and(|crate_name| crate_name == "build_script_build"); + + let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); + match diag_name { + sym::print_macro | sym::println_macro if !allowed_in_tests => { + if !is_build_script { + span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); + } + }, + sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { + span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); + }, + sym::write_macro | sym::writeln_macro => {}, + _ => return, + } + + if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { + // ignore `writeln!(w)` and `write!(v, some_macro!())` + if format_args.span.from_expansion() { + return; + } + + match diag_name { + sym::print_macro | sym::eprint_macro | sym::write_macro => { + with_newline::check(cx, format_args, ¯o_call, name); + }, + sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { + empty_string::check(cx, format_args, ¯o_call, name); + }, + _ => {}, + } + + literal::check(cx, format_args, name); + + if !self.in_debug_impl() { + use_debug::check(cx, format_args); + } + } + } +} + +fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) = &item.kind + && let Some(trait_id) = of_trait.trait_ref.trait_def_id() + { + cx.tcx.is_diagnostic_item(sym::Debug, trait_id) + } else { + false + } +} diff --git a/clippy_lints/src/write/use_debug.rs b/clippy_lints/src/write/use_debug.rs new file mode 100644 index 000000000000..75dddeb5d2a7 --- /dev/null +++ b/clippy_lints/src/write/use_debug.rs @@ -0,0 +1,18 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::{FormatArgs, FormatArgsPiece, FormatPlaceholder, FormatTrait}; +use rustc_lint::LateContext; + +use super::USE_DEBUG; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs) { + for piece in &format_args.template { + if let &FormatArgsPiece::Placeholder(FormatPlaceholder { + span: Some(span), + format_trait: FormatTrait::Debug, + .. + }) = piece + { + span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); + } + } +} diff --git a/clippy_lints/src/write/with_newline.rs b/clippy_lints/src/write/with_newline.rs new file mode 100644 index 000000000000..e4b51da3cadc --- /dev/null +++ b/clippy_lints/src/write/with_newline.rs @@ -0,0 +1,78 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LintContext}; +use rustc_span::BytePos; + +use super::{PRINT_WITH_NEWLINE, WRITE_WITH_NEWLINE}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { + return; + }; + + let count_vertical_whitespace = || { + format_args + .template + .iter() + .filter_map(|piece| match piece { + FormatArgsPiece::Literal(literal) => Some(literal), + FormatArgsPiece::Placeholder(_) => None, + }) + .flat_map(|literal| literal.as_str().chars()) + .filter(|ch| matches!(ch, '\r' | '\n')) + .count() + }; + + if last.as_str().ends_with('\n') + // ignore format strings with other internal vertical whitespace + && count_vertical_whitespace() == 1 + { + let mut format_string_span = format_args.span; + + let lint = if name == "write" { + format_string_span = expand_past_previous_comma(cx, format_string_span); + + WRITE_WITH_NEWLINE + } else { + PRINT_WITH_NEWLINE + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("using `{name}!()` with a format string that ends in a single newline"), + |diag| { + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let Some(format_snippet) = format_string_span.get_source_text(cx) else { + return; + }; + + if format_args.template.len() == 1 && last == sym::LF { + // print!("\n"), write!(f, "\n") + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], + Applicability::MachineApplicable, + ); + } else if format_snippet.ends_with("\\n\"") { + // print!("...\n"), write!(f, "...\n") + + let hi = format_string_span.hi(); + let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (newline_span, String::new())], + Applicability::MachineApplicable, + ); + } + }, + ); + } +} diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index 5eb207a0aedb..bb0cab3a3075 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -37,8 +37,9 @@ impl<'tcx> LateLintPass<'tcx> for ZeroDiv { // That's probably fine for this lint - it's pretty unlikely that someone would // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too. && let ecx = ConstEvalCtxt::new(cx) - && let Some(lhs_value) = ecx.eval_simple(left) - && let Some(rhs_value) = ecx.eval_simple(right) + && let ctxt = expr.span.ctxt() + && let Some(lhs_value) = ecx.eval_local(left, ctxt) + && let Some(rhs_value) = ecx.eval_local(right, ctxt) // FIXME(f16_f128): add these types when eq is available on all platforms && (Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value) && (Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value) diff --git a/clippy_lints/src/zero_repeat_side_effects.rs b/clippy_lints/src/zero_repeat_side_effects.rs index 30fdf22fdbb0..a8351690068d 100644 --- a/clippy_lints/src/zero_repeat_side_effects.rs +++ b/clippy_lints/src/zero_repeat_side_effects.rs @@ -1,19 +1,17 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; -use clippy_utils::source::snippet; -use clippy_utils::visitors::for_each_expr_without_closures; +use clippy_utils::source::{snippet, snippet_indent}; use rustc_ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{ConstArgKind, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Ty; +use rustc_middle::ty::IsSuggestable; use rustc_session::declare_lint_pass; -use rustc_span::Span; declare_clippy_lint! { /// ### What it does - /// Checks for array or vec initializations which call a function or method, + /// Checks for array or vec initializations which contain an expression with side effects, /// but which have a repeat count of zero. /// /// ### Why is this bad? @@ -73,89 +71,63 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects { fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) { // check if expr is a call or has a call inside it - if for_each_expr_without_closures(inner_expr, |x| { - if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind { - std::ops::ControlFlow::Break(()) - } else { - std::ops::ControlFlow::Continue(()) - } - }) - .is_some() - { + if inner_expr.can_have_side_effects() { let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id); + let inner_expr_ty = cx.typeck_results().expr_ty(inner_expr); let return_type = cx.typeck_results().expr_ty(expr); - if let Node::LetStmt(l) = parent_hir_node { - array_span_lint( - cx, + let inner_expr = snippet(cx, inner_expr.span.source_callsite(), ".."); + let indent = snippet_indent(cx, expr.span).unwrap_or_default(); + let vec = if is_vec { "vec!" } else { "" }; + + let (span, sugg) = match parent_hir_node { + Node::LetStmt(l) => ( l.span, - inner_expr.span, - l.pat.span, - Some(return_type), - is_vec, - false, - ); - } else if let Node::Expr(x) = parent_hir_node - && let ExprKind::Assign(l, _, _) = x.kind - { - array_span_lint(cx, x.span, inner_expr.span, l.span, Some(return_type), is_vec, true); - } else { - span_lint_and_sugg( - cx, - ZERO_REPEAT_SIDE_EFFECTS, - expr.span.source_callsite(), - "function or method calls as the initial value in zero-sized array initializers may cause side effects", - "consider using", format!( - "{{ {}; {}[] as {return_type} }}", - snippet(cx, inner_expr.span.source_callsite(), ".."), - if is_vec { "vec!" } else { "" }, + "{inner_expr};\n{indent}let {var_name}: {return_type} = {vec}[];", + var_name = snippet(cx, l.pat.span.source_callsite(), "..") ), - Applicability::Unspecified, - ); - } - } -} - -fn array_span_lint( - cx: &LateContext<'_>, - expr_span: Span, - func_call_span: Span, - variable_name_span: Span, - expr_ty: Option>, - is_vec: bool, - is_assign: bool, -) { - let has_ty = expr_ty.is_some(); - - span_lint_and_sugg( - cx, - ZERO_REPEAT_SIDE_EFFECTS, - expr_span.source_callsite(), - "function or method calls as the initial value in zero-sized array initializers may cause side effects", - "consider using", - format!( - "{}; {}{}{} = {}[]{}{}", - snippet(cx, func_call_span.source_callsite(), ".."), - if has_ty && !is_assign { "let " } else { "" }, - snippet(cx, variable_name_span.source_callsite(), ".."), - if let Some(ty) = expr_ty - && !is_assign - { - format!(": {ty}") - } else { - String::new() - }, - if is_vec { "vec!" } else { "" }, - if let Some(ty) = expr_ty - && is_assign - { - format!(" as {ty}") - } else { - String::new() + ), + Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => ( + x.span, + format!( + "{inner_expr};\n{indent}{var_name} = {vec}[] as {return_type}", + var_name = snippet(cx, l.span.source_callsite(), "..") + ), + ), + // NOTE: don't use the stmt span to avoid touching the trailing semicolon + Node::Stmt(_) => (expr.span, format!("{inner_expr};\n{indent}{vec}[] as {return_type}")), + _ => ( + expr.span, + format!( + "\ +{{ +{indent} {inner_expr}; +{indent} {vec}[] as {return_type} +{indent}}}" + ), + ), + }; + let span = span.source_callsite(); + span_lint_and_then( + cx, + ZERO_REPEAT_SIDE_EFFECTS, + span, + "expression with side effects as the initial value in a zero-sized array initializer", + |diag| { + if (!inner_expr_ty.is_never() || cx.tcx.features().never_type()) + && return_type.is_suggestable(cx.tcx, true) + { + diag.span_suggestion_verbose( + span, + "consider performing the side effect separately", + sugg, + Applicability::Unspecified, + ); + } else { + diag.help("consider performing the side effect separately"); + } }, - if is_assign { "" } else { ";" } - ), - Applicability::Unspecified, - ); + ); + } } diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index f1572fd65bbf..bf133d26ed9d 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::{is_type_diagnostic_item, ty_from_hir_ty}; +use clippy_utils::res::MaybeDef; +use clippy_utils::ty::ty_from_hir_ty; use rustc_hir::{self as hir, AmbigArg, HirId, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf as _; @@ -48,7 +49,7 @@ impl LateLintPass<'_> for ZeroSizedMapValues { && !in_trait_impl(cx, hir_ty.hir_id) // We don't care about infer vars && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) - && (is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap)) + && (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap)) && let ty::Adt(_, args) = ty.kind() && let ty = args.type_at(1) // Ensure that no type information is missing, to avoid a delayed bug in the compiler if this is not the case. diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index a934d2094e00..0319f3e656e1 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -1,7 +1,7 @@ use ControlFlow::{Break, Continue}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{fn_def_id, get_enclosing_block, path_to_local_id}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::{fn_def_id, get_enclosing_block}; use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; use rustc_errors::Applicability; @@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind && let child_ty = cx.typeck_results().expr_ty(expr) - && is_type_diagnostic_item(cx, child_ty, sym::Child) + && child_ty.is_diag_item(cx, sym::Child) { match cx.tcx.parent_hir_node(expr.hir_id) { Node::LetStmt(local) @@ -168,7 +168,7 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { return walk_expr(self, ex); } - if path_to_local_id(ex, self.local_id) { + if ex.res_local_id() == Some(self.local_id) { match self.cx.tcx.parent_hir_node(ex.hir_id) { Node::Stmt(Stmt { kind: StmtKind::Semi(_), diff --git a/clippy_lints_internal/Cargo.toml b/clippy_lints_internal/Cargo.toml index a8293a1ad395..16b45322e30f 100644 --- a/clippy_lints_internal/Cargo.toml +++ b/clippy_lints_internal/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] clippy_config = { path = "../clippy_config" } clippy_utils = { path = "../clippy_utils" } +itertools = "0.12" regex = { version = "1.5" } rustc-semver = "1.1" diff --git a/clippy_lints_internal/src/internal_paths.rs b/clippy_lints_internal/src/internal_paths.rs index dc1e30ab2bdd..95bdf27b019c 100644 --- a/clippy_lints_internal/src/internal_paths.rs +++ b/clippy_lints_internal/src/internal_paths.rs @@ -2,13 +2,18 @@ use clippy_utils::paths::{PathLookup, PathNS}; use clippy_utils::{sym, type_path, value_path}; // Paths inside rustc +pub static APPLICABILITY: PathLookup = type_path!(rustc_errors::Applicability); +pub static EARLY_CONTEXT: PathLookup = type_path!(rustc_lint::EarlyContext); pub static EARLY_LINT_PASS: PathLookup = type_path!(rustc_lint::passes::EarlyLintPass); pub static KW_MODULE: PathLookup = type_path!(rustc_span::symbol::kw); +pub static LATE_CONTEXT: PathLookup = type_path!(rustc_lint::LateContext); pub static LINT: PathLookup = type_path!(rustc_lint_defs::Lint); pub static SYMBOL: PathLookup = type_path!(rustc_span::symbol::Symbol); pub static SYMBOL_AS_STR: PathLookup = value_path!(rustc_span::symbol::Symbol::as_str); pub static SYM_MODULE: PathLookup = type_path!(rustc_span::symbol::sym); pub static SYNTAX_CONTEXT: PathLookup = type_path!(rustc_span::hygiene::SyntaxContext); +#[expect(clippy::unnecessary_def_path, reason = "for uniform checking in internal lint")] +pub static TY_CTXT: PathLookup = type_path!(rustc_middle::ty::TyCtxt); // Paths in clippy itself pub static CLIPPY_SYM_MODULE: PathLookup = type_path!(clippy_utils::sym); diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 43cde86504f5..d686ba73387c 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -41,6 +41,7 @@ mod produce_ice; mod symbols; mod unnecessary_def_path; mod unsorted_clippy_utils_paths; +mod unusual_names; use rustc_lint::{Lint, LintStore}; @@ -59,6 +60,7 @@ static LINTS: &[&Lint] = &[ symbols::SYMBOL_AS_STR, unnecessary_def_path::UNNECESSARY_DEF_PATH, unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS, + unusual_names::UNUSUAL_NAMES, ]; pub fn register_lints(store: &mut LintStore) { @@ -74,4 +76,5 @@ pub fn register_lints(store: &mut LintStore) { store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass)); store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl)); store.register_late_pass(|_| Box::new(almost_standard_lint_formulation::AlmostStandardFormulation::new())); + store.register_late_pass(|_| Box::new(unusual_names::UnusualNames)); } diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs index 66aeb910891a..6d5c7b86a0ae 100644 --- a/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/clippy_lints_internal/src/msrv_attr_impl.rs @@ -5,7 +5,7 @@ use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, EarlyBinder, GenericArgKind}; +use rustc_middle::ty::{self, GenericArgKind}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_tool_lint! { @@ -26,10 +26,7 @@ impl LateLintPass<'_> for MsrvAttrImpl { items, .. }) = &item.kind - && let Some(trait_ref) = cx - .tcx - .impl_trait_ref(item.owner_id) - .map(EarlyBinder::instantiate_identity) + && let trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity() && internal_paths::EARLY_LINT_PASS.matches(cx, trait_ref.def_id) && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind() && self_ty_def.is_struct() diff --git a/clippy_lints_internal/src/produce_ice.rs b/clippy_lints_internal/src/produce_ice.rs index 3a813b4b9a22..630843049da2 100644 --- a/clippy_lints_internal/src/produce_ice.rs +++ b/clippy_lints_internal/src/produce_ice.rs @@ -25,9 +25,9 @@ declare_tool_lint! { declare_lint_pass!(ProduceIce => [PRODUCE_ICE]); impl EarlyLintPass for ProduceIce { - fn check_fn(&mut self, ctx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { if is_trigger_fn(fn_kind) { - ctx.sess() + cx.sess() .dcx() .span_delayed_bug(span, "Would you like some help with that?"); } diff --git a/clippy_lints_internal/src/unnecessary_def_path.rs b/clippy_lints_internal/src/unnecessary_def_path.rs index 8877f1faf0ee..f2e8d3579d85 100644 --- a/clippy_lints_internal/src/unnecessary_def_path.rs +++ b/clippy_lints_internal/src/unnecessary_def_path.rs @@ -1,7 +1,8 @@ use crate::internal_paths; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::paths::{PathNS, lookup_path}; -use clippy_utils::{path_def_id, peel_ref_operators}; +use clippy_utils::peel_ref_operators; +use clippy_utils::res::MaybeQPath; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -26,7 +27,7 @@ declare_tool_lint! { /// /// Use instead: /// ```rust,ignore - /// is_type_diagnostic_item(cx, ty, sym::Vec) + /// ty.is_diag_item(cx, sym::Vec) /// ``` pub clippy::UNNECESSARY_DEF_PATH, Warn, @@ -53,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath { let path: Vec = segments .iter() .map(|segment| { - if let Some(const_def_id) = path_def_id(cx, segment) + if let Some(const_def_id) = segment.res(cx).opt_def_id() && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(const_def_id) && let Some(value) = value.to_u32().discard_err() { diff --git a/clippy_lints_internal/src/unusual_names.rs b/clippy_lints_internal/src/unusual_names.rs new file mode 100644 index 000000000000..e11a2868fb69 --- /dev/null +++ b/clippy_lints_internal/src/unusual_names.rs @@ -0,0 +1,99 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::paths::PathLookup; +use clippy_utils::sym; +use itertools::Itertools; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, FnDecl, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_span::{Span, Symbol}; + +use crate::internal_paths::{APPLICABILITY, EARLY_CONTEXT, LATE_CONTEXT, TY_CTXT}; + +declare_tool_lint! { + /// ### What it does + /// Checks if variables of some types use the usual name. + /// + /// ### Why is this bad? + /// Restricting the identifiers used for common things in + /// Clippy sources increases consistency. + /// + /// ### Example + /// Check that an `rustc_errors::Applicability` variable is + /// named either `app` or `applicability`, and not + /// `a` or `appl`. + pub clippy::UNUSUAL_NAMES, + Warn, + "commonly used concepts should use usual same variable name.", + report_in_external_macro: true +} + +declare_lint_pass!(UnusualNames => [UNUSUAL_NAMES]); + +const USUAL_NAMES: [(&PathLookup, &str, &[Symbol]); 4] = [ + ( + &APPLICABILITY, + "rustc_errors::Applicability", + &[sym::app, sym::applicability], + ), + (&EARLY_CONTEXT, "rustc_lint::EarlyContext", &[sym::cx]), + (&LATE_CONTEXT, "rustc_lint::LateContext", &[sym::cx]), + (&TY_CTXT, "rustc_middle::ty::TyCtxt", &[sym::tcx]), +]; + +impl<'tcx> LateLintPass<'tcx> for UnusualNames { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Let(let_stmt) = stmt.kind + && let Some(init_expr) = let_stmt.init + { + check_pat_name_for_ty(cx, let_stmt.pat, cx.typeck_results().expr_ty(init_expr), "variable"); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _span: Span, + def_id: LocalDefId, + ) { + if matches!(kind, FnKind::Closure) { + return; + } + for (param, ty) in body + .params + .iter() + .zip(cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder().inputs()) + { + check_pat_name_for_ty(cx, param.pat, *ty, "parameter"); + } + } +} + +fn check_pat_name_for_ty(cx: &LateContext<'_>, pat: &Pat<'_>, ty: Ty<'_>, kind: &str) { + if let PatKind::Binding(_, _, ident, _) = pat.kind { + let ty = ty.peel_refs(); + for (usual_ty, ty_str, usual_names) in USUAL_NAMES { + if usual_ty.matches_ty(cx, ty) + && !usual_names.contains(&ident.name) + && ident.name != kw::SelfLower + && !ident.name.as_str().starts_with('_') + { + let usual_names = usual_names.iter().map(|name| format!("`{name}`")).join(" or "); + span_lint_and_help( + cx, + UNUSUAL_NAMES, + ident.span, + format!("unusual name for a {kind} of type `{ty_str}`"), + None, + format!("prefer using {usual_names}"), + ); + } + } + } +} diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index bdf7431f29f2..a1e1b763dccb 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.91" +version = "0.1.93" edition = "2024" description = "Helpful tools for writing lints, provided as they are used in Clippy" repository = "https://github.com/rust-lang/rust-clippy" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index e01f563c49e7..45463b4fa1db 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-09-04 +nightly-2025-10-31 ``` diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index ad69e6eb184e..31416f12c698 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -48,12 +48,10 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { (Box(l), Box(r)) | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), - (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, eq_pat), (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { - eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) - && eq_path(lp, rp) - && over(lfs, rfs, |l, r| eq_pat(l, r)) + eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) && eq_path(lp, rp) && over(lfs, rfs, eq_pat) }, (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => { lr == rr @@ -61,7 +59,7 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat) }, - (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), + (Or(ls), Or(rs)) => unordered_over(ls, rs, eq_pat), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), _ => false, } @@ -143,7 +141,7 @@ pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool { } } -#[allow(clippy::too_many_lines)] // Just a big match statement +#[expect(clippy::too_many_lines, reason = "big match statement")] pub fn eq_expr(l: &Expr, r: &Expr) -> bool { use ExprKind::*; if !over(&l.attrs, &r.attrs, eq_attr) { @@ -328,7 +326,7 @@ pub fn eq_item(l: &Item, r: &Item, mut eq_kind: impl FnMut(&K, &K) -> b over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind) } -#[expect(clippy::too_many_lines)] // Just a big match statement +#[expect(clippy::too_many_lines, reason = "big match statement")] pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { use ItemKind::*; match (l, r) { @@ -562,7 +560,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { defaultness: ld, ident: li, generics: lg, - where_clauses: _, + after_where_clause: lw, bounds: lb, ty: lt, }), @@ -570,7 +568,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { defaultness: rd, ident: ri, generics: rg, - where_clauses: _, + after_where_clause: rw, bounds: rb, ty: rt, }), @@ -578,6 +576,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { eq_defaultness(*ld, *rd) && eq_id(*li, *ri) && eq_generics(lg, rg) + && over(&lw.predicates, &rw.predicates, eq_where_predicate) && over(lb, rb, eq_generic_bound) && both(lt.as_ref(), rt.as_ref(), |l, r| eq_ty(l, r)) }, @@ -645,7 +644,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { defaultness: ld, ident: li, generics: lg, - where_clauses: _, + after_where_clause: lw, bounds: lb, ty: lt, }), @@ -653,7 +652,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { defaultness: rd, ident: ri, generics: rg, - where_clauses: _, + after_where_clause: rw, bounds: rb, ty: rt, }), @@ -661,6 +660,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { eq_defaultness(*ld, *rd) && eq_id(*li, *ri) && eq_generics(lg, rg) + && over(&lw.predicates, &rw.predicates, eq_where_predicate) && over(lb, rb, eq_generic_bound) && both(lt.as_ref(), rt.as_ref(), |l, r| eq_ty(l, r)) }, diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 2d42e76dcbc9..671b266ba008 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -1,3 +1,5 @@ +//! Utility functions for attributes, including Clippy's built-in ones + use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; use rustc_ast::attr; @@ -12,131 +14,59 @@ use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -/// Deprecation status of attributes known by Clippy. -pub enum DeprecationStatus { - /// Attribute is deprecated - Deprecated, - /// Attribute is deprecated and was replaced by the named attribute - Replaced(&'static str), - None, -} - -#[rustfmt::skip] -pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[ - (sym::author, DeprecationStatus::None), - (sym::version, DeprecationStatus::None), - (sym::cognitive_complexity, DeprecationStatus::None), - (sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")), - (sym::dump, DeprecationStatus::None), - (sym::msrv, DeprecationStatus::None), - // The following attributes are for the 3rd party crate authors. - // See book/src/attribs.md - (sym::has_significant_drop, DeprecationStatus::None), - (sym::format_args, DeprecationStatus::None), -]; - -pub struct LimitStack { - stack: Vec, -} - -impl Drop for LimitStack { - fn drop(&mut self) { - assert_eq!(self.stack.len(), 1); - } -} - -impl LimitStack { - #[must_use] - pub fn new(limit: u64) -> Self { - Self { stack: vec![limit] } - } - pub fn limit(&self) -> u64 { - *self.stack.last().expect("there should always be a value in the stack") - } - pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| stack.push(val)); - } - pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val))); - } -} - -pub fn get_attr<'a, A: AttributeExt + 'a>( +/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name` +pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( sess: &'a Session, attrs: &'a [A], name: Symbol, ) -> impl Iterator { attrs.iter().filter(move |attr| { - let Some(attr_segments) = attr.ident_path() else { - return false; - }; + if let Some([clippy, segment2]) = attr.ident_path().as_deref() + && clippy.name == sym::clippy + { + let new_name = match segment2.name { + sym::cyclomatic_complexity => Some("cognitive_complexity"), + sym::author + | sym::version + | sym::cognitive_complexity + | sym::dump + | sym::msrv + // The following attributes are for the 3rd party crate authors. + // See book/src/attribs.md + | sym::has_significant_drop + | sym::format_args => None, + _ => { + sess.dcx().span_err(segment2.span, "usage of unknown attribute"); + return false; + }, + }; - if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy { - BUILTIN_ATTRIBUTES - .iter() - .find_map(|(builtin_name, deprecation_status)| { - if attr_segments[1].name == *builtin_name { - Some(deprecation_status) - } else { - None - } - }) - .map_or_else( - || { - sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute"); - false - }, - |deprecation_status| { - let mut diag = sess - .dcx() - .struct_span_err(attr_segments[1].span, "usage of deprecated attribute"); - match *deprecation_status { - DeprecationStatus::Deprecated => { - diag.emit(); - false - }, - DeprecationStatus::Replaced(new_name) => { - diag.span_suggestion( - attr_segments[1].span, - "consider using", - new_name, - Applicability::MachineApplicable, - ); - diag.emit(); - false - }, - DeprecationStatus::None => { - diag.cancel(); - attr_segments[1].name == name - }, - } - }, - ) + match new_name { + Some(new_name) => { + sess.dcx() + .struct_span_err(segment2.span, "usage of deprecated attribute") + .with_span_suggestion( + segment2.span, + "consider using", + new_name, + Applicability::MachineApplicable, + ) + .emit(); + false + }, + None => segment2.name == name, + } } else { false } }) } -fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { - for attr in get_attr(sess, attrs, name) { - if let Some(value) = attr.value_str() { - if let Ok(value) = FromStr::from_str(value.as_str()) { - f(value); - } else { - sess.dcx().span_err(attr.span(), "not a number"); - } - } else { - sess.dcx().span_err(attr.span(), "bad clippy attribute"); - } - } -} - -pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { +/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`, +/// returns that attribute, and `None` otherwise +pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { let mut unique_attr: Option<&A> = None; - for attr in get_attr(sess, attrs, name) { + for attr in get_builtin_attr(sess, attrs, name) { if let Some(duplicate) = unique_attr { sess.dcx() .struct_span_err(attr.span(), format!("`{name}` is defined multiple times")) @@ -149,13 +79,13 @@ pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], n unique_attr } -/// Returns true if the attributes contain any of `proc_macro`, -/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise +/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or +/// `proc_macro_attribute` pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { attrs.iter().any(AttributeExt::is_proc_macro_attr) } -/// Returns true if the attributes contain `#[doc(hidden)]` +/// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { attrs .iter() @@ -164,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { .any(|l| attr::list_contains_name(&l, sym::hidden)) } +/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) @@ -176,7 +107,7 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..))) } -/// Checks if the given span contains a `#[cfg(..)]` attribute +/// Checks whether the given span contains a `#[cfg(..)]` attribute pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { s.check_source_text(cx, |src| { let mut iter = tokenize_with_text(src); @@ -198,3 +129,52 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { false }) } + +/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]` +pub struct LimitStack { + default: u64, + stack: Vec, +} + +impl Drop for LimitStack { + fn drop(&mut self) { + debug_assert_eq!(self.stack, Vec::::new()); // avoid `.is_empty()`, for a nicer error message + } +} + +#[expect(missing_docs, reason = "they're all trivial...")] +impl LimitStack { + #[must_use] + /// Initialize the stack starting with a default value, which usually comes from configuration + pub fn new(limit: u64) -> Self { + Self { + default: limit, + stack: vec![], + } + } + pub fn limit(&self) -> u64 { + self.stack.last().copied().unwrap_or(self.default) + } + pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| stack.push(val)); + } + pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val))); + } +} + +fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { + for attr in get_builtin_attr(sess, attrs, name) { + let Some(value) = attr.value_str() else { + sess.dcx().span_err(attr.span(), "bad clippy attribute"); + continue; + }; + let Ok(value) = u64::from_str(value.as_str()) else { + sess.dcx().span_err(attr.span(), "not a number"); + continue; + }; + f(value); + } +} diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index c4a759e919b1..544218e09930 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -13,20 +13,24 @@ //! if the span is not from a `macro_rules` based macro. use rustc_abi::ExternAbi; +use rustc_ast as ast; use rustc_ast::AttrStyle; -use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy}; +use rustc_ast::ast::{ + AttrKind, Attribute, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy, +}; use rustc_ast::token::CommentKind; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl, - ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, - TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, + ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, + QPath, Safety, TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, + YieldSource, }; use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::symbol::{Ident, kw}; -use rustc_span::{Span, Symbol}; +use rustc_span::{Span, Symbol, sym}; /// The search pattern to look for. Used by `span_matches_pat` #[derive(Clone)] @@ -117,7 +121,6 @@ fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) { (start, end) }, QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)), - QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")), } } @@ -280,16 +283,17 @@ fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) { } fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) { - let (start_pat, end_pat) = match &item.kind { + let (mut start_pat, end_pat) = match &item.kind { ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")), ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")), ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")), }; - if item.vis_span.is_empty() { - (start_pat, end_pat) - } else { - (Pat::Str("pub"), end_pat) + if let ImplItemImplKind::Inherent { vis_span, .. } = item.impl_kind + && !vis_span.is_empty() + { + start_pat = Pat::Str("pub"); } + (start_pat, end_pat) } fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) { @@ -313,22 +317,24 @@ fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) { } fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) { - let (start_pat, end_pat) = match kind { + let (mut start_pat, end_pat) = match kind { FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")), FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")), FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1), }; - let start_pat = match tcx.hir_node(hir_id) { - Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => { - if vis_span.is_empty() { - start_pat - } else { - Pat::Str("pub") + match tcx.hir_node(hir_id) { + Node::Item(Item { vis_span, .. }) + | Node::ImplItem(ImplItem { + impl_kind: ImplItemImplKind::Inherent { vis_span, .. }, + .. + }) => { + if !vis_span.is_empty() { + start_pat = Pat::Str("pub"); } }, - Node::TraitItem(_) => start_pat, - _ => Pat::Str(""), - }; + Node::ImplItem(_) | Node::TraitItem(_) => {}, + _ => start_pat = Pat::Str(""), + } (start_pat, end_pat) } @@ -403,6 +409,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")), TyKind::Path(qpath) => qpath_search_pat(&qpath), TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")), + TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1), TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => { (Pat::Str("dyn"), Pat::Str("")) }, @@ -411,6 +418,124 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { } } +fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { + use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind}; + + match &ty.kind { + TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")), + TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1), + TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => { + (Pat::Str("&"), ast_ty_search_pat(ty).1) + }, + TyKind::FnPtr(fn_ptr) => ( + if let Safety::Unsafe(_) = fn_ptr.safety { + Pat::Str("unsafe") + } else if let Extern::Explicit(strlit, _) = fn_ptr.ext + && strlit.symbol == sym::rust + { + Pat::MultiStr(&["fn", "extern"]) + } else { + Pat::Str("extern") + }, + match &fn_ptr.decl.output { + FnRetTy::Default(_) => { + if let [.., param] = &*fn_ptr.decl.inputs { + ast_ty_search_pat(¶m.ty).1 + } else { + Pat::Str("(") + } + }, + FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1, + }, + ), + TyKind::Never => (Pat::Str("!"), Pat::Str("!")), + // Parenthesis are trimmed from the text before the search patterns are matched. + // See: `span_matches_pat` + TyKind::Tup(tup) => match &**tup { + [] => (Pat::Str(")"), Pat::Str("(")), + [ty] => ast_ty_search_pat(ty), + [head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1), + }, + TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")), + TyKind::Path(qself_path, path) => { + let start = if qself_path.is_some() { + Pat::Str("<") + } else if let Some(first) = path.segments.first() { + ident_search_pat(first.ident).0 + } else { + // this shouldn't be possible, but sure + Pat::Str("") + }; + let end = if let Some(last) = path.segments.last() { + match last.args.as_deref() { + // last `>` in `std::foo::Bar` + Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"), + Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output { + FnRetTy::Default(_) => { + if let Some(last) = par_args.inputs.last() { + // `B` in `(A, B)` -- `)` gets stripped + ast_ty_search_pat(last).1 + } else { + // `(` in `()` -- `)` gets stripped + Pat::Str("(") + } + }, + // `C` in `(A, B) -> C` + FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1, + }, + // last `..` in `(..)` -- `)` gets stripped + Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."), + // `bar` in `std::foo::bar` + None => ident_search_pat(last.ident).1, + } + } else { + // this shouldn't be possible + Pat::Str( + if qself_path.is_some() { + ">" // last `>` in `` + } else { + "" + } + ) + }; + (start, end) + }, + TyKind::Infer => (Pat::Str("_"), Pat::Str("_")), + TyKind::Paren(ty) => ast_ty_search_pat(ty), + TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1), + TyKind::TraitObject(_, trait_obj_syntax) => { + if let TraitObjectSyntax::Dyn = trait_obj_syntax { + (Pat::Str("dyn"), Pat::Str("")) + } else { + // NOTE: `TraitObject` is incomplete. It will always return true then. + (Pat::Str(""), Pat::Str("")) + } + }, + TyKind::MacCall(mac_call) => { + let start = if let Some(first) = mac_call.path.segments.first() { + ident_search_pat(first.ident).0 + } else { + Pat::Str("") + }; + (start, Pat::Str("")) + }, + + // implicit, so has no contents to match against + TyKind::ImplicitSelf + + // experimental + |TyKind::Pat(..) + + // unused + | TyKind::CVarArgs + | TyKind::Typeof(_) + + // placeholder + | TyKind::Dummy + | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")), + } +} + fn ident_search_pat(ident: Ident) -> (Pat, Pat) { (Pat::Sym(ident.name), Pat::Sym(ident.name)) } @@ -445,6 +570,7 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&sel impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self)); impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self)); +impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self)); impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { type Context = LateContext<'cx>; diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index ecd88daa6b39..7b8c7f3f0d6b 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -2,28 +2,25 @@ //! //! This cannot use rustc's const eval, aka miri, as arbitrary HIR expressions cannot be lowered to //! executable MIR bodies, so we have to do this instead. -#![allow(clippy::float_cmp)] +#![expect(clippy::float_cmp)] +use crate::res::MaybeDef; use crate::source::{SpanRangeExt, walk_span_to_context}; -use crate::{clip, is_direct_expn_of, sext, unsext}; +use crate::{clip, is_direct_expn_of, sext, sym, unsext}; use rustc_abi::Size; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Half, Quad}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{ - BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, QPath, UnOp, -}; +use rustc_hir::{BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, PatExpr, PatExprKind, QPath, TyKind, UnOp}; use rustc_lexer::{FrontmatterAllowed, tokenize}; use rustc_lint::LateContext; use rustc_middle::mir::ConstValue; use rustc_middle::mir::interpret::{Scalar, alloc_range}; use rustc_middle::ty::{self, FloatTy, IntTy, ScalarInt, Ty, TyCtxt, TypeckResults, UintTy}; use rustc_middle::{bug, mir, span_bug}; -use rustc_span::def_id::DefId; -use rustc_span::symbol::Ident; -use rustc_span::{SyntaxContext, sym}; +use rustc_span::{Symbol, SyntaxContext}; use std::cell::Cell; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; @@ -31,8 +28,8 @@ use std::iter; /// A `LitKind`-like enum to fold constant `Expr`s into. #[derive(Debug, Clone)] -pub enum Constant<'tcx> { - Adt(mir::Const<'tcx>), +pub enum Constant { + Adt(ConstValue), /// A `String` (e.g., "abc"). Str(String), /// A binary string (e.g., `b"abc"`). @@ -54,15 +51,15 @@ pub enum Constant<'tcx> { /// `true` or `false`. Bool(bool), /// An array of constants. - Vec(Vec>), + Vec(Vec), /// Also an array, but with only one constant, repeated N times. - Repeat(Box>, u64), + Repeat(Box, u64), /// A tuple of constants. - Tuple(Vec>), + Tuple(Vec), /// A raw pointer. RawPtr(u128), /// A reference - Ref(Box>), + Ref(Box), /// A literal with syntax error. Err, } @@ -124,7 +121,7 @@ impl IntTypeBounds for IntTy { } } -impl PartialEq for Constant<'_> { +impl PartialEq for Constant { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Str(ls), Self::Str(rs)) => ls == rs, @@ -132,16 +129,12 @@ impl PartialEq for Constant<'_> { (&Self::Char(l), &Self::Char(r)) => l == r, (&Self::Int(l), &Self::Int(r)) => l == r, (&Self::F64(l), &Self::F64(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - l.to_bits() == r.to_bits() + // `to_bits` is required to catch non-matching `0.0` and `-0.0`. + l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::F32(l), &Self::F32(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - f64::from(l).to_bits() == f64::from(r).to_bits() + // `to_bits` is required to catch non-matching `0.0` and `-0.0`. + l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::Bool(l), &Self::Bool(r)) => l == r, (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, @@ -153,7 +146,7 @@ impl PartialEq for Constant<'_> { } } -impl Hash for Constant<'_> { +impl Hash for Constant { fn hash(&self, state: &mut H) where H: Hasher, @@ -209,7 +202,7 @@ impl Hash for Constant<'_> { } } -impl Constant<'_> { +impl Constant { pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { match (left, right) { (Self::Str(ls), Self::Str(rs)) => Some(ls.cmp(rs)), @@ -297,10 +290,129 @@ impl Constant<'_> { let f: Quad = s.parse().unwrap(); Self::F128(f.to_bits()) } + + pub fn new_numeric_min<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + match *ty.kind() { + ty::Uint(_) => Some(Self::Int(0)), + ty::Int(ty) => { + let val = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MIN), + IntTy::I16 => i128::from(i16::MIN), + IntTy::I32 => i128::from(i32::MIN), + IntTy::I64 => i128::from(i64::MIN), + IntTy::I128 => i128::MIN, + IntTy::Isize => return None, + }; + Some(Self::Int(val.cast_unsigned())) + }, + ty::Char => Some(Self::Char(char::MIN)), + ty::Float(FloatTy::F32) => Some(Self::F32(f32::NEG_INFINITY)), + ty::Float(FloatTy::F64) => Some(Self::F64(f64::NEG_INFINITY)), + _ => None, + } + } + + pub fn new_numeric_max<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + match *ty.kind() { + ty::Uint(ty) => Some(Self::Int(match ty.normalize(tcx.sess.target.pointer_width) { + UintTy::U8 => u128::from(u8::MAX), + UintTy::U16 => u128::from(u16::MAX), + UintTy::U32 => u128::from(u32::MAX), + UintTy::U64 => u128::from(u64::MAX), + UintTy::U128 => u128::MAX, + UintTy::Usize => return None, + })), + ty::Int(ty) => { + let val = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MAX), + IntTy::I16 => i128::from(i16::MAX), + IntTy::I32 => i128::from(i32::MAX), + IntTy::I64 => i128::from(i64::MAX), + IntTy::I128 => i128::MAX, + IntTy::Isize => return None, + }; + Some(Self::Int(val.cast_unsigned())) + }, + ty::Char => Some(Self::Char(char::MAX)), + ty::Float(FloatTy::F32) => Some(Self::F32(f32::INFINITY)), + ty::Float(FloatTy::F64) => Some(Self::F64(f64::INFINITY)), + _ => None, + } + } + + pub fn is_numeric_min<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match (self, ty.kind()) { + (&Self::Int(x), &ty::Uint(_)) => x == 0, + (&Self::Int(x), &ty::Int(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MIN), + IntTy::I16 => i128::from(i16::MIN), + IntTy::I32 => i128::from(i32::MIN), + IntTy::I64 => i128::from(i64::MIN), + IntTy::I128 => i128::MIN, + IntTy::Isize => return false, + }; + x.cast_signed() == limit + }, + (&Self::Char(x), &ty::Char) => x == char::MIN, + (&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::NEG_INFINITY, + (&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::NEG_INFINITY, + _ => false, + } + } + + pub fn is_numeric_max<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match (self, ty.kind()) { + (&Self::Int(x), &ty::Uint(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + UintTy::U8 => u128::from(u8::MAX), + UintTy::U16 => u128::from(u16::MAX), + UintTy::U32 => u128::from(u32::MAX), + UintTy::U64 => u128::from(u64::MAX), + UintTy::U128 => u128::MAX, + UintTy::Usize => return false, + }; + x == limit + }, + (&Self::Int(x), &ty::Int(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MAX), + IntTy::I16 => i128::from(i16::MAX), + IntTy::I32 => i128::from(i32::MAX), + IntTy::I64 => i128::from(i64::MAX), + IntTy::I128 => i128::MAX, + IntTy::Isize => return false, + }; + x.cast_signed() == limit + }, + (&Self::Char(x), &ty::Char) => x == char::MAX, + (&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::INFINITY, + (&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::INFINITY, + _ => false, + } + } + + pub fn is_pos_infinity(&self) -> bool { + match *self { + // FIXME(f16_f128): add f16 and f128 when constants are available + Constant::F32(x) => x == f32::INFINITY, + Constant::F64(x) => x == f64::INFINITY, + _ => false, + } + } + + pub fn is_neg_infinity(&self) -> bool { + match *self { + // FIXME(f16_f128): add f16 and f128 when constants are available + Constant::F32(x) => x == f32::NEG_INFINITY, + Constant::F64(x) => x == f64::NEG_INFINITY, + _ => false, + } + } } /// Parses a `LitKind` to a `Constant`. -pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constant<'tcx> { +pub fn lit_to_mir_constant(lit: &LitKind, ty: Option>) -> Constant { match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), @@ -331,10 +443,9 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constan pub enum ConstantSource { /// The value is determined solely from the expression. Local, - /// The value is dependent on a defined constant. - Constant, - /// The value is dependent on a constant defined in `core` crate. - CoreConstant, + /// The value is dependent on another definition that may change independently from the local + /// expression. + NonLocal, } impl ConstantSource { pub fn is_local(self) -> bool { @@ -387,6 +498,7 @@ pub struct ConstEvalCtxt<'tcx> { typing_env: ty::TypingEnv<'tcx>, typeck: &'tcx TypeckResults<'tcx>, source: Cell, + ctxt: Cell, } impl<'tcx> ConstEvalCtxt<'tcx> { @@ -398,6 +510,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { typing_env: cx.typing_env(), typeck: cx.typeck_results(), source: Cell::new(ConstantSource::Local), + ctxt: Cell::new(SyntaxContext::root()), } } @@ -408,38 +521,50 @@ impl<'tcx> ConstEvalCtxt<'tcx> { typing_env, typeck, source: Cell::new(ConstantSource::Local), + ctxt: Cell::new(SyntaxContext::root()), } } /// Attempts to evaluate the expression and returns both the value and whether it's dependant on /// other items. - pub fn eval_with_source(&self, e: &Expr<'_>) -> Option<(Constant<'tcx>, ConstantSource)> { + pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant, ConstantSource)> { self.source.set(ConstantSource::Local); + self.ctxt.set(ctxt); self.expr(e).map(|c| (c, self.source.get())) } /// Attempts to evaluate the expression. - pub fn eval(&self, e: &Expr<'_>) -> Option> { + pub fn eval(&self, e: &Expr<'_>) -> Option { self.expr(e) } /// Attempts to evaluate the expression without accessing other items. - pub fn eval_simple(&self, e: &Expr<'_>) -> Option> { - match self.eval_with_source(e) { + /// + /// The context argument is the context used to view the evaluated expression. e.g. when + /// evaluating the argument in `f(m!(1))` the context of the call expression should be used. + /// This is need so the const evaluator can see the `m` macro and marke the evaluation as + /// non-local independant of what the macro expands to. + pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option { + match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => Some(x), _ => None, } } /// Attempts to evaluate the expression as an integer without accessing other items. - pub fn eval_full_int(&self, e: &Expr<'_>) -> Option { - match self.eval_with_source(e) { + /// + /// The context argument is the context used to view the evaluated expression. e.g. when + /// evaluating the argument in `f(m!(1))` the context of the call expression should be used. + /// This is need so the const evaluator can see the `m` macro and marke the evaluation as + /// non-local independant of what the macro expands to. + pub fn eval_full_int(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option { + match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => x.int_value(self.tcx, self.typeck.expr_ty(e)), _ => None, } } - pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option> { + pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option { match &pat_expr.kind { PatExprKind::Lit { lit, negated } => { let ty = self.typeck.node_type_opt(pat_expr.hir_id); @@ -455,39 +580,31 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option> { - let is_core_crate = if let Some(def_id) = self.typeck.qpath_res(qpath, hir_id).opt_def_id() { - self.tcx.crate_name(def_id.krate) == sym::core - } else { - false - }; - self.fetch_path_and_apply(qpath, hir_id, self.typeck.node_type(hir_id), |self_, result| { - let result = mir_to_const(self_.tcx, result)?; - // If source is already Constant we wouldn't want to override it with CoreConstant - self_.source.set( - if is_core_crate && !matches!(self_.source.get(), ConstantSource::Constant) { - ConstantSource::CoreConstant - } else { - ConstantSource::Constant - }, - ); - Some(result) - }) + fn check_ctxt(&self, ctxt: SyntaxContext) { + if self.ctxt.get() != ctxt { + self.source.set(ConstantSource::NonLocal); + } + } + + fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option { + self.fetch_path(qpath, hir_id) + .and_then(|c| mir_to_const(self.tcx, c, self.typeck.node_type(hir_id))) } /// Simple constant folding: Insert an expression, get a constant or none. - fn expr(&self, e: &Expr<'_>) -> Option> { + fn expr(&self, e: &Expr<'_>) -> Option { + self.check_ctxt(e.span.ctxt()); match e.kind { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir_body(body).value), ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => self.qpath(qpath, e.hir_id), - ExprKind::Block(block, _) => self.block(block), + ExprKind::Block(block, _) => { + self.check_ctxt(block.span.ctxt()); + self.block(block) + }, ExprKind::Lit(lit) => { - if is_direct_expn_of(e.span, sym::cfg).is_some() { - None - } else { - Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e))) - } + self.check_ctxt(lit.span.ctxt()); + Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e))) }, ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec), ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple), @@ -504,7 +621,10 @@ impl<'tcx> ConstEvalCtxt<'tcx> { UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }), }), ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), - ExprKind::Binary(op, left, right) => self.binop(op.node, left, right), + ExprKind::Binary(op, left, right) => { + self.check_ctxt(e.span.ctxt()); + self.binop(op.node, left, right) + }, ExprKind::Call(callee, []) => { // We only handle a few const functions for now. if let ExprKind::Path(qpath) = &callee.kind @@ -524,17 +644,20 @@ impl<'tcx> ConstEvalCtxt<'tcx> { }, ExprKind::Index(arr, index, _) => self.index(arr, index), ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), - ExprKind::Field(local_expr, ref field) => { - let result = self.expr(local_expr); - if let Some(Constant::Adt(constant)) = &self.expr(local_expr) - && let ty::Adt(adt_def, _) = constant.ty().kind() + ExprKind::Field(base, ref field) + if let base_ty = self.typeck.expr_ty(base) + && match self.typeck.expr_adjustments(base) { + [] => true, + [.., a] => a.target == base_ty, + } + && let Some(Constant::Adt(constant)) = self.expr(base) + && let ty::Adt(adt_def, _) = *base_ty.kind() && adt_def.is_struct() - && let Some(desired_field) = field_of_struct(*adt_def, self.tcx, *constant, field) - { - mir_to_const(self.tcx, desired_field) - } else { - result - } + && let Some((desired_field, ty)) = + field_of_struct(adt_def, self.tcx, constant, base_ty, field.name) => + { + self.check_ctxt(field.span.ctxt()); + mir_to_const(self.tcx, desired_field, ty) }, _ => None, } @@ -547,19 +670,6 @@ impl<'tcx> ConstEvalCtxt<'tcx> { match e.kind { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.eval_is_empty(self.tcx.hir_body(body).value), ExprKind::DropTemps(e) => self.eval_is_empty(e), - ExprKind::Path(ref qpath) => { - if !self - .typeck - .qpath_res(qpath, e.hir_id) - .opt_def_id() - .is_some_and(DefId::is_local) - { - return None; - } - self.fetch_path_and_apply(qpath, e.hir_id, self.typeck.expr_ty(e), |self_, result| { - mir_is_empty(self_.tcx, result) - }) - }, ExprKind::Lit(lit) => { if is_direct_expn_of(e.span, sym::cfg).is_some() { None @@ -584,7 +694,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } #[expect(clippy::cast_possible_wrap)] - fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { use self::Constant::{Bool, Int}; match *o { Bool(b) => Some(Bool(!b)), @@ -600,7 +710,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn constant_negate(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { use self::Constant::{F32, F64, Int}; match *o { Int(value) => { @@ -626,48 +736,128 @@ impl<'tcx> ConstEvalCtxt<'tcx> { /// Create `Some(Vec![..])` of all constants, unless there is any /// non-constant part. - fn multi(&self, vec: &[Expr<'_>]) -> Option>> { + fn multi(&self, vec: &[Expr<'_>]) -> Option> { vec.iter().map(|elem| self.expr(elem)).collect::>() } /// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it. - fn fetch_path_and_apply(&self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>, f: F) -> Option - where - F: FnOnce(&Self, mir::Const<'tcx>) -> Option, - { - let res = self.typeck.qpath_res(qpath, id); - match res { - Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { - // Check if this constant is based on `cfg!(..)`, - // which is NOT constant for our purposes. - if let Some(node) = self.tcx.hir_get_if_local(def_id) - && let Node::Item(Item { - kind: ItemKind::Const(.., body_id), - .. - }) = node - && let Node::Expr(Expr { - kind: ExprKind::Lit(_), - span, - .. - }) = self.tcx.hir_node(body_id.hir_id) - && is_direct_expn_of(*span, sym::cfg).is_some() - { - return None; - } - - let args = self.typeck.node_args(id); - let result = self - .tcx - .const_eval_resolve(self.typing_env, mir::UnevaluatedConst::new(def_id, args), qpath.span()) - .ok() - .map(|val| mir::Const::from_value(val, ty))?; - f(self, result) + #[expect(clippy::too_many_lines)] + fn fetch_path(&self, qpath: &QPath<'_>, id: HirId) -> Option { + // Resolve the path to a constant and check if that constant is known to + // not change based on the target. + // + // This should be replaced with an attribute at some point. + let did = match *qpath { + QPath::Resolved(None, path) + if path.span.ctxt() == self.ctxt.get() + && path.segments.iter().all(|s| self.ctxt.get() == s.ident.span.ctxt()) + && let Res::Def(DefKind::Const, did) = path.res + && (matches!( + self.tcx.get_diagnostic_name(did), + Some( + sym::f32_legacy_const_digits + | sym::f32_legacy_const_epsilon + | sym::f32_legacy_const_infinity + | sym::f32_legacy_const_mantissa_dig + | sym::f32_legacy_const_max + | sym::f32_legacy_const_max_10_exp + | sym::f32_legacy_const_max_exp + | sym::f32_legacy_const_min + | sym::f32_legacy_const_min_10_exp + | sym::f32_legacy_const_min_exp + | sym::f32_legacy_const_min_positive + | sym::f32_legacy_const_nan + | sym::f32_legacy_const_neg_infinity + | sym::f32_legacy_const_radix + | sym::f64_legacy_const_digits + | sym::f64_legacy_const_epsilon + | sym::f64_legacy_const_infinity + | sym::f64_legacy_const_mantissa_dig + | sym::f64_legacy_const_max + | sym::f64_legacy_const_max_10_exp + | sym::f64_legacy_const_max_exp + | sym::f64_legacy_const_min + | sym::f64_legacy_const_min_10_exp + | sym::f64_legacy_const_min_exp + | sym::f64_legacy_const_min_positive + | sym::f64_legacy_const_nan + | sym::f64_legacy_const_neg_infinity + | sym::f64_legacy_const_radix + | sym::u8_legacy_const_min + | sym::u16_legacy_const_min + | sym::u32_legacy_const_min + | sym::u64_legacy_const_min + | sym::u128_legacy_const_min + | sym::usize_legacy_const_min + | sym::u8_legacy_const_max + | sym::u16_legacy_const_max + | sym::u32_legacy_const_max + | sym::u64_legacy_const_max + | sym::u128_legacy_const_max + | sym::i8_legacy_const_min + | sym::i16_legacy_const_min + | sym::i32_legacy_const_min + | sym::i64_legacy_const_min + | sym::i128_legacy_const_min + | sym::i8_legacy_const_max + | sym::i16_legacy_const_max + | sym::i32_legacy_const_max + | sym::i64_legacy_const_max + | sym::i128_legacy_const_max + ) + ) || self.tcx.opt_parent(did).is_some_and(|parent| { + parent.is_diag_item(&self.tcx, sym::f16_consts_mod) + || parent.is_diag_item(&self.tcx, sym::f32_consts_mod) + || parent.is_diag_item(&self.tcx, sym::f64_consts_mod) + || parent.is_diag_item(&self.tcx, sym::f128_consts_mod) + })) => + { + did }, - _ => None, - } + QPath::TypeRelative(ty, const_name) + if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind + && let [.., ty_name] = ty_path.segments + && (matches!( + ty_name.ident.name, + sym::i8 + | sym::i16 + | sym::i32 + | sym::i64 + | sym::i128 + | sym::u8 + | sym::u16 + | sym::u32 + | sym::u64 + | sym::u128 + | sym::f32 + | sym::f64 + | sym::char + ) || (ty_name.ident.name == sym::usize && const_name.ident.name == sym::MIN)) + && const_name.ident.span.ctxt() == self.ctxt.get() + && ty.span.ctxt() == self.ctxt.get() + && ty_name.ident.span.ctxt() == self.ctxt.get() + && matches!(ty_path.res, Res::PrimTy(_)) + && let Some((DefKind::AssocConst, did)) = self.typeck.type_dependent_def(id) => + { + did + }, + _ if let Res::Def(DefKind::Const | DefKind::AssocConst, did) = self.typeck.qpath_res(qpath, id) => { + self.source.set(ConstantSource::NonLocal); + did + }, + _ => return None, + }; + + self.tcx + .const_eval_resolve( + self.typing_env, + mir::UnevaluatedConst::new(did, self.typeck.node_args(id)), + qpath.span(), + ) + .ok() } - fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option> { + fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { let lhs = self.expr(lhs); let index = self.expr(index); @@ -697,7 +887,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } /// A block can only yield a constant if it has exactly one constant expression. - fn block(&self, block: &Block<'_>) -> Option> { + fn block(&self, block: &Block<'_>) -> Option { if block.stmts.is_empty() && let Some(expr) = block.expr { @@ -716,11 +906,11 @@ impl<'tcx> ConstEvalCtxt<'tcx> { .filter(|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)) .eq([OpenBrace]) { - self.source.set(ConstantSource::Constant); + self.source.set(ConstantSource::NonLocal); } } else { // Unable to access the source. Assume a non-local dependency. - self.source.set(ConstantSource::Constant); + self.source.set(ConstantSource::NonLocal); } } @@ -730,7 +920,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option> { + fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { if let Some(Constant::Bool(b)) = self.expr(cond) { if b { self.expr(then) @@ -742,7 +932,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option> { + fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option { let l = self.expr(left)?; let r = self.expr(right); match (l, r) { @@ -778,6 +968,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { BinOpKind::BitXor => Some(zext(l ^ r)), BinOpKind::BitOr => Some(zext(l | r)), BinOpKind::BitAnd => Some(zext(l & r)), + // FIXME: f32/f64 currently consider `0.0` and `-0.0` as different. BinOpKind::Eq => Some(Constant::Bool(l == r)), BinOpKind::Ne => Some(Constant::Bool(l != r)), BinOpKind::Lt => Some(Constant::Bool(l < r)), @@ -856,14 +1047,10 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } -pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option> { - let mir::Const::Val(val, _) = result else { - // We only work on evaluated consts. - return None; - }; - match (val, result.ty().kind()) { - (ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() { - ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), +pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, val: ConstValue, ty: Ty<'tcx>) -> Option { + match (val, ty.kind()) { + (_, &ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(val)), + (ConstValue::Scalar(Scalar::Int(int)), _) => match ty.kind() { ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.to_bits(int.size()))), ty::Float(FloatTy::F16) => Some(Constant::F16(int.into())), @@ -877,7 +1064,6 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option let data = val.try_get_slice_bytes_for_diagnostics(tcx)?; String::from_utf8(data.to_owned()).ok().map(Constant::Str) }, - (_, ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(result)), (ConstValue::Indirect { alloc_id, offset }, ty::Array(sub_type, len)) => { let alloc = tcx.global_alloc(alloc_id).unwrap_memory().inner(); let len = len.try_to_target_usize(tcx)?; @@ -902,64 +1088,32 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option } } -fn mir_is_empty<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option { - let mir::Const::Val(val, _) = result else { - // We only work on evaluated consts. - return None; - }; - match (val, result.ty().kind()) { - (_, ty::Ref(_, inner_ty, _)) => match inner_ty.kind() { - ty::Str | ty::Slice(_) => { - if let ConstValue::Indirect { alloc_id, offset } = val { - // Get the length from the slice, using the same formula as - // [`ConstValue::try_get_slice_bytes_for_diagnostics`]. - let a = tcx.global_alloc(alloc_id).unwrap_memory().inner(); - let ptr_size = tcx.data_layout.pointer_size(); - if a.size() < offset + 2 * ptr_size { - // (partially) dangling reference - return None; - } - let len = a - .read_scalar(&tcx, alloc_range(offset + ptr_size, ptr_size), false) - .ok()? - .to_target_usize(&tcx) - .discard_err()?; - Some(len == 0) - } else { - None - } - }, - ty::Array(_, len) => Some(len.try_to_target_usize(tcx)? == 0), - _ => None, - }, - (ConstValue::Indirect { .. }, ty::Array(_, len)) => Some(len.try_to_target_usize(tcx)? == 0), - (ConstValue::ZeroSized, _) => Some(true), - _ => None, - } -} - fn field_of_struct<'tcx>( adt_def: ty::AdtDef<'tcx>, tcx: TyCtxt<'tcx>, - result: mir::Const<'tcx>, - field: &Ident, -) -> Option> { - if let mir::Const::Val(result, ty) = result - && let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(result, ty) + value: ConstValue, + ty: Ty<'tcx>, + field: Symbol, +) -> Option<(ConstValue, Ty<'tcx>)> { + if let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(value, ty) && let Some(dc_variant) = dc.variant && let Some(variant) = adt_def.variants().get(dc_variant) - && let Some(field_idx) = variant.fields.iter().position(|el| el.name == field.name) - && let Some(&(val, ty)) = dc.fields.get(field_idx) + && let Some(field_idx) = variant.fields.iter().position(|el| el.name == field) { - Some(mir::Const::Val(val, ty)) + dc.fields.get(field_idx).copied() } else { None } } /// If `expr` evaluates to an integer constant, return its value. -pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_simple(expr) { +/// +/// The context argument is the context used to view the evaluated expression. e.g. when evaluating +/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so +/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of +/// what the macro expands to. +pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option { + if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { Some(value) } else { None @@ -967,7 +1121,12 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { } /// Check if `expr` evaluates to an integer constant of 0. +/// +/// The context argument is the context used to view the evaluated expression. e.g. when evaluating +/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so +/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of +/// what the macro expands to. #[inline] -pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - integer_const(cx, expr) == Some(0) +pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + integer_const(cx, expr, ctxt) == Some(0) } diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index eb3f442ac754..6f0e57bd3ab1 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -167,7 +167,6 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS QPath::TypeRelative(_, name) => { self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, !args.is_empty()); }, - QPath::LangItem(..) => self.eagerness = Lazy, }, _ => self.eagerness = Lazy, }, diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index bda28a663fb0..7d6787fec295 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -3,7 +3,7 @@ #![deny(clippy::missing_docs_in_private_items)] use crate::consts::{ConstEvalCtxt, Constant}; -use crate::ty::is_type_diagnostic_item; +use crate::res::MaybeDef; use crate::{is_expn_of, sym}; use rustc_ast::ast; @@ -14,6 +14,7 @@ use rustc_span::{Span, symbol}; /// The essential nodes of a desugared for loop as well as the entire span: /// `for pat in arg { body }` becomes `(pat, arg, body)`. Returns `(pat, arg, body, span)`. +#[derive(Debug)] pub struct ForLoop<'tcx> { /// `for` loop item pub pat: &'tcx Pat<'tcx>, @@ -208,39 +209,40 @@ pub struct Range<'a> { pub end: Option<&'a Expr<'a>>, /// Whether the interval is open or closed. pub limits: ast::RangeLimits, + pub span: Span, } impl<'a> Range<'a> { /// Higher a `hir` range to something similar to `ast::ExprKind::Range`. - #[allow(clippy::similar_names)] - pub fn hir(expr: &'a Expr<'_>) -> Option> { + #[expect(clippy::similar_names)] + pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option> { + let span = expr.range_span()?; match expr.kind { ExprKind::Call(path, [arg1, arg2]) - if matches!( - path.kind, - ExprKind::Path(QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..)) - ) => + if let ExprKind::Path(qpath) = path.kind + && cx.tcx.qpath_is_lang_item(qpath, hir::LangItem::RangeInclusiveNew) => { Some(Range { start: Some(arg1), end: Some(arg2), limits: ast::RangeLimits::Closed, + span, }) }, - ExprKind::Struct(path, fields, StructTailExpr::None) => match (path, fields) { - (QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range { + ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) { + (hir::LangItem::RangeFull, []) => Some(Range { start: None, end: None, limits: ast::RangeLimits::HalfOpen, + span, }), - (QPath::LangItem(hir::LangItem::RangeFrom, ..), [field]) if field.ident.name == sym::start => { - Some(Range { - start: Some(field.expr), - end: None, - limits: ast::RangeLimits::HalfOpen, - }) - }, - (QPath::LangItem(hir::LangItem::Range, ..), [field1, field2]) => { + (hir::LangItem::RangeFrom, [field]) if field.ident.name == sym::start => Some(Range { + start: Some(field.expr), + end: None, + limits: ast::RangeLimits::HalfOpen, + span, + }), + (hir::LangItem::Range, [field1, field2]) => { let (start, end) = match (field1.ident.name, field2.ident.name) { (sym::start, sym::end) => (field1.expr, field2.expr), (sym::end, sym::start) => (field2.expr, field1.expr), @@ -250,19 +252,20 @@ impl<'a> Range<'a> { start: Some(start), end: Some(end), limits: ast::RangeLimits::HalfOpen, + span, }) }, - (QPath::LangItem(hir::LangItem::RangeToInclusive, ..), [field]) if field.ident.name == sym::end => { - Some(Range { - start: None, - end: Some(field.expr), - limits: ast::RangeLimits::Closed, - }) - }, - (QPath::LangItem(hir::LangItem::RangeTo, ..), [field]) if field.ident.name == sym::end => Some(Range { + (hir::LangItem::RangeToInclusive, [field]) if field.ident.name == sym::end => Some(Range { + start: None, + end: Some(field.expr), + limits: ast::RangeLimits::Closed, + span, + }), + (hir::LangItem::RangeTo, [field]) if field.ident.name == sym::end => Some(Range { start: None, end: Some(field.expr), limits: ast::RangeLimits::HalfOpen, + span, }), _ => None, }, @@ -318,6 +321,7 @@ pub struct While<'hir> { pub body: &'hir Expr<'hir>, /// Span of the loop header pub span: Span, + pub label: Option, } impl<'hir> While<'hir> { @@ -333,13 +337,18 @@ impl<'hir> While<'hir> { }), .. }, - _, + label, LoopSource::While, span, ) = expr.kind && !has_let_expr(condition) { - return Some(Self { condition, body, span }); + return Some(Self { + condition, + body, + span, + label, + }); } None } @@ -446,7 +455,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - if let ExprKind::Call(func, args) = expr.kind { match func.kind { ExprKind::Path(QPath::TypeRelative(ty, name)) - if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) => + if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) => { if name.ident.name == sym::new { return Some(VecInitKind::New); @@ -454,7 +463,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - return Some(VecInitKind::Default); } else if name.ident.name == sym::with_capacity { let arg = args.first()?; - return match ConstEvalCtxt::new(cx).eval_simple(arg) { + return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) { Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)), _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)), }; @@ -462,7 +471,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - }, ExprKind::Path(QPath::Resolved(_, path)) if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?) - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) => + && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) => { return Some(VecInitKind::Default); }, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index b79e15cd7170..710b88e92154 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -290,8 +290,10 @@ impl HirEqInterExpr<'_, '_, '_> { if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) && let (Some(l), Some(r)) = ( - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs).eval_simple(left), - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs).eval_simple(right), + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs) + .eval_local(left, self.left_ctxt), + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs) + .eval_local(right, self.right_ctxt), ) && l == r { @@ -553,7 +555,6 @@ impl HirEqInterExpr<'_, '_, '_> { (QPath::TypeRelative(lty, lseg), QPath::TypeRelative(rty, rseg)) => { self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg) }, - (QPath::LangItem(llang_item, ..), QPath::LangItem(rlang_item, ..)) => llang_item == rlang_item, _ => false, } } @@ -842,7 +843,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { #[expect(clippy::too_many_lines)] pub fn hash_expr(&mut self, e: &Expr<'_>) { let simple_const = self.maybe_typeck_results.and_then(|typeck_results| { - ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_simple(e) + ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_local(e, e.span.ctxt()) }); // const hashing may result in the same hash as some unrelated node, so add a sort of @@ -1090,9 +1091,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { QPath::TypeRelative(_, path) => { self.hash_name(path.ident.name); }, - QPath::LangItem(lang_item, ..) => { - std::mem::discriminant(lang_item).hash(&mut self.s); - }, } // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s); } @@ -1121,7 +1119,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty_pat(variant); } }, - TyPatKind::Err(_) => {}, + TyPatKind::NotNull | TyPatKind::Err(_) => {}, } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 14b64eb4d54e..46c5af058ccc 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -25,14 +25,15 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) -extern crate indexmap; extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_attr_parsing; extern crate rustc_const_eval; extern crate rustc_data_structures; -// The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate. -#[allow(unused_extern_crates)] +#[expect( + unused_extern_crates, + reason = "The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate." +)] extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_hir; @@ -47,9 +48,9 @@ extern crate rustc_mir_dataflow; extern crate rustc_session; extern crate rustc_span; extern crate rustc_trait_selection; -extern crate smallvec; pub mod ast_utils; +#[deny(missing_docs)] pub mod attrs; mod check_proc_macro; pub mod comparisons; @@ -63,8 +64,8 @@ pub mod mir; pub mod msrvs; pub mod numeric_literal; pub mod paths; -pub mod ptr; pub mod qualify_min_const_fn; +pub mod res; pub mod source; pub mod str_utils; pub mod sugg; @@ -91,6 +92,7 @@ use rustc_abi::Integer; use rustc_ast::ast::{self, LitKind, RangeLimits}; use rustc_ast::join_path_syms; use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::indexmap; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; @@ -99,7 +101,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::definitions::{DefPath, DefPathData}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; -use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; +use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, @@ -127,8 +129,10 @@ use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; use crate::ast_utils::unordered_over; -use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; +use crate::consts::{ConstEvalCtxt, Constant}; use crate::higher::Range; +use crate::msrvs::Msrv; +use crate::res::{MaybeDef, MaybeQPath, MaybeResPath}; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; @@ -170,7 +174,8 @@ macro_rules! extract_msrv_attr { /// // ^^^ input /// ``` pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> { - while let Some(init) = path_to_local(expr) + while let Some(init) = expr + .res_local_id() .and_then(|id| find_binding_init(cx, id)) .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty()) { @@ -248,19 +253,6 @@ pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { } } -/// Checks if a `Res` refers to a constructor of a `LangItem` -/// For example, use this to check whether a function call or a pattern is `Some(..)`. -pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool { - if let Res::Def(DefKind::Ctor(..), id) = res - && let Some(lang_id) = cx.tcx.lang_items().get(lang_item) - && let Some(id) = cx.tcx.opt_parent(id) - { - id == lang_id - } else { - false - } -} - /// Checks if `{ctor_call_id}(...)` is `{enum_item}::{variant_name}(...)`. pub fn is_enum_variant_ctor( cx: &LateContext<'_>, @@ -309,6 +301,22 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks is `expr` is `None` +pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +} + +/// If `expr` is `Some(inner)`, returns `inner` +pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call(e, [arg]) = expr.kind + && e.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + { + Some(arg) + } else { + None + } +} + /// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( @@ -329,13 +337,40 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } -// Checks if arm has the form `None => None` -pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!( - arm.pat.kind, +/// If `pat` is: +/// - `Some(inner)`, returns `inner` +/// - it will _usually_ contain just one element, but could have two, given patterns like +/// `Some(inner, ..)` or `Some(.., inner)` +/// - `Some`, returns `[]` +/// - otherwise, returns `None` +pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> { + if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind + && cx + .qpath_res(qpath, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionSome) + { + Some(inner) + } else { + None + } +} + +/// Checks if the `pat` is `None`. +pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { + matches!(pat.kind, PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. }) - if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone) - ) + if cx.qpath_res(qpath, pat.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone)) +} + +/// Checks if `arm` has the form `None => None`. +pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + is_none_pattern(cx, arm.pat) + && matches!( + peel_blocks(arm.body).kind, + ExprKind::Path(qpath) + if cx.qpath_res(&qpath, arm.body.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone) + ) } /// Checks if the given `QPath` belongs to a type alias. @@ -343,44 +378,10 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { match *qpath { QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)), QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath), - _ => false, + QPath::TypeRelative(..) => false, } } -/// Checks if the given method call expression calls an inherent method. -pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - cx.tcx.trait_of_assoc(method_id).is_none() - } else { - false - } -} - -/// Checks if a method is defined in an impl of a diagnostic item -pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { - if let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) - && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() - { - return cx.tcx.is_diagnostic_item(diag_item, adt.did()); - } - false -} - -/// Checks if a method is in a diagnostic item trait -pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { - if let Some(trait_did) = cx.tcx.trait_of_assoc(def_id) { - return cx.tcx.is_diagnostic_item(diag_item, trait_did); - } - false -} - -/// Checks if the method call given in `expr` belongs to the given trait. -pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { - cx.typeck_results() - .type_dependent_def_id(expr.hir_id) - .is_some_and(|did| is_diag_trait_item(cx, did, diag_item)) -} - /// Checks if the `def_id` belongs to a function that is part of a trait impl. pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id)) @@ -392,30 +393,10 @@ pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool } } -/// Checks if the given expression is a path referring an item on the trait -/// that is marked with the given diagnostic item. -/// -/// For checking method call expressions instead of path expressions, use -/// [`is_trait_method`]. -/// -/// For example, this can be used to find if an expression like `u64::default` -/// refers to an item of the trait `Default`, which is associated with the -/// `diag_item` of `sym::Default`. -pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { - if let ExprKind::Path(ref qpath) = expr.kind { - cx.qpath_res(qpath, expr.hir_id) - .opt_def_id() - .is_some_and(|def_id| is_diag_trait_item(cx, def_id, diag_item)) - } else { - false - } -} - pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { match *path { QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"), QPath::TypeRelative(_, seg) => seg, - QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), } } @@ -430,38 +411,6 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>, lang_item: LangItem) -> bool { - path_def_id(cx, maybe_path).is_some_and(|id| cx.tcx.lang_items().get(lang_item) == Some(id)) -} - -/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if -/// it matches the given diagnostic item. -pub fn is_path_diagnostic_item<'tcx>( - cx: &LateContext<'_>, - maybe_path: &impl MaybePath<'tcx>, - diag_item: Symbol, -) -> bool { - path_def_id(cx, maybe_path).is_some_and(|id| cx.tcx.is_diagnostic_item(diag_item, id)) -} - -/// If the expression is a path to a local, returns the canonical `HirId` of the local. -pub fn path_to_local(expr: &Expr<'_>) -> Option { - if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind - && let Res::Local(id) = path.res - { - return Some(id); - } - None -} - -/// Returns true if the expression is a path to a local with the specified `HirId`. -/// Use this function to see if an expression matches a function argument or a match binding. -pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { - path_to_local(expr) == Some(id) -} - /// If the expression is a path to a local (with optional projections), /// returns the canonical `HirId` of the local. /// @@ -479,56 +428,6 @@ pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option { } } -pub trait MaybePath<'hir> { - fn hir_id(&self) -> HirId; - fn qpath_opt(&self) -> Option<&QPath<'hir>>; -} - -macro_rules! maybe_path { - ($ty:ident, $kind:ident) => { - impl<'hir> MaybePath<'hir> for hir::$ty<'hir> { - fn hir_id(&self) -> HirId { - self.hir_id - } - fn qpath_opt(&self) -> Option<&QPath<'hir>> { - match &self.kind { - hir::$kind::Path(qpath) => Some(qpath), - _ => None, - } - } - } - }; -} -maybe_path!(Expr, ExprKind); -impl<'hir> MaybePath<'hir> for Pat<'hir> { - fn hir_id(&self) -> HirId { - self.hir_id - } - fn qpath_opt(&self) -> Option<&QPath<'hir>> { - match &self.kind { - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - .. - }) => Some(qpath), - _ => None, - } - } -} -maybe_path!(Ty, TyKind); - -/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err` -pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res { - match maybe_path.qpath_opt() { - None => Res::Err, - Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()), - } -} - -/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID -pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option { - path_res(cx, maybe_path).opt_def_id() -} - /// Gets the `hir::TraitRef` of the trait the given method is implemented for. /// /// Use this if you want to find the `TraitRef` of the `Add` trait in this example: @@ -655,9 +554,9 @@ pub fn is_default_equivalent_call( whole_call_expr: Option<&Expr<'_>>, ) -> bool { if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind - && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() - && (is_diag_trait_item(cx, repl_def_id, sym::Default) - || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath)) + && let Some(repl_def) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def(cx) + && (repl_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Default) + || is_default_equivalent_ctor(cx, repl_def.1, repl_func_qpath)) { return true; } @@ -766,7 +665,10 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { }, ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func, Some(e)), ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg), - ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone), + ExprKind::Path(qpath) => cx + .qpath_res(qpath, e.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone), ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), ExprKind::Block(Block { stmts: [], expr, .. }, _) => expr.is_some_and(|e| is_default_equivalent(cx, e)), _ => false, @@ -781,14 +683,14 @@ fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: & ExprKind::Lit(hir::Lit { node: LitKind::Str(sym, _), .. - }) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String), - ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec), + }) => return sym.is_empty() && ty.basic_res().is_lang_item(cx, LangItem::String), + ExprKind::Array([]) => return ty.basic_res().is_diag_item(cx, sym::Vec), ExprKind::Repeat(_, len) => { if let ConstArgKind::Anon(anon_const) = len.kind && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind && let LitKind::Int(v, _) = const_lit.node { - return v == 0 && is_path_diagnostic_item(cx, ty, sym::Vec); + return v == 0 && ty.basic_res().is_diag_item(cx, sym::Vec); } }, _ => (), @@ -1415,15 +1317,13 @@ pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { /// If the given `Expr` is not some kind of range, the function returns `false`. pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool { let ty = cx.typeck_results().expr_ty(expr); - if let Some(Range { start, end, limits }) = Range::hir(expr) { + if let Some(Range { start, end, limits, .. }) = Range::hir(cx, expr) { let start_is_none_or_min = start.is_none_or(|start| { if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) - && let Some(min_const) = bnd_ty.numeric_min_val(cx.tcx) - && let Some(min_const) = mir_to_const(cx.tcx, min_const) && let Some(start_const) = ConstEvalCtxt::new(cx).eval(start) { - start_const == min_const + start_const.is_numeric_min(cx.tcx, bnd_ty) } else { false } @@ -1432,11 +1332,9 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti RangeLimits::Closed => { if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) - && let Some(max_const) = bnd_ty.numeric_max_val(cx.tcx) - && let Some(max_const) = mir_to_const(cx.tcx, max_const) && let Some(end_const) = ConstEvalCtxt::new(cx).eval(end) { - end_const == max_const + end_const.is_numeric_max(cx.tcx, bnd_ty) } else { false } @@ -1671,9 +1569,12 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind && ddpos.as_opt_usize().is_none() - && is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk) + && cx + .qpath_res(path, arm.pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, ResultOk) && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind - && path_to_local_id(arm.body, hir_id) + && arm.body.res_local_id() == Some(hir_id) { return true; } @@ -1682,7 +1583,9 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { - is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultErr) + cx.qpath_res(path, arm.pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, ResultErr) } else { false } @@ -1851,15 +1754,6 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, (conds, blocks) } -/// Checks if the given function kind is an async function. -pub fn is_async_fn(kind: FnKind<'_>) -> bool { - match kind { - FnKind::ItemFn(_, _, header) => header.asyncness.is_async(), - FnKind::Method(_, sig) => sig.header.asyncness.is_async(), - FnKind::Closure => false, - } -} - /// Peels away all the compiler generated code surrounding the body of an async closure. pub fn get_async_closure_expr<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { if let ExprKind::Closure(&Closure { @@ -1985,7 +1879,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< match (pat.kind, expr.kind) { (PatKind::Binding(_, id, _, _), _) if by_hir => { - path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty() + expr.res_local_id() == Some(id) && cx.typeck_results().expr_adjustments(expr).is_empty() }, (PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => { matches!(path.segments, [ segment] if segment.ident.name == ident.name) @@ -2058,7 +1952,7 @@ pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { match expr.kind { ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir_body(body)), - _ => path_def_id(cx, expr).is_some_and(|id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)), + _ => expr.basic_res().is_diag_item(cx, sym::convert_identity), } } @@ -2133,17 +2027,11 @@ pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { } pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { - cx.tcx - .hir_attrs(hir::CRATE_HIR_ID) - .iter() - .any(|attr| attr.has_name(sym::no_std)) + find_attr!(cx.tcx.hir_attrs(hir::CRATE_HIR_ID), AttributeKind::NoStd(..)) } pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { - cx.tcx - .hir_attrs(hir::CRATE_HIR_ID) - .iter() - .any(|attr| attr.has_name(sym::no_core)) + find_attr!(cx.tcx.hir_attrs(hir::CRATE_HIR_ID), AttributeKind::NoCore(..)) } /// Check if parent of a hir node is a trait implementation block. @@ -2380,15 +2268,12 @@ pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) } } -/// Peels off all references on the type. Returns the underlying type and the number of references -/// removed. -pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { - let mut count = 0; - while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() { - ty = *dest_ty; - count += 1; +/// Returns the base type for HIR references and pointers. +pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + match &ty.kind { + TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) => peel_hir_ty_refs_and_ptrs(mut_ty.ty), + _ => ty, } - (ty, count) } /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is @@ -2831,7 +2716,6 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtx moved_before_use, same_ctxt, }, - #[allow(unreachable_patterns)] Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow"), None => ExprUseCtxt { node: Node::Crate(cx.tcx.hir_root_module()), @@ -2934,13 +2818,15 @@ pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( pat: &'a Pat<'hir>, else_body: &Expr<'_>, ) -> Option<&'a Pat<'hir>> { - if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind - && is_res_lang_ctor(cx, cx.qpath_res(&pat_path, pat.hir_id), OptionSome) + if let Some([inner_pat]) = as_some_pattern(cx, pat) && !is_refutable(cx, inner_pat) && let else_body = peel_blocks(else_body) && let ExprKind::Ret(Some(ret_val)) = else_body.kind && let ExprKind::Path(ret_path) = ret_val.kind - && is_res_lang_ctor(cx, cx.qpath_res(&ret_path, ret_val.hir_id), OptionNone) + && cx + .qpath_res(&ret_path, ret_val.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone) { Some(inner_pat) } else { @@ -3250,8 +3136,8 @@ pub fn get_path_from_caller_to_method_type<'tcx>( let assoc_item = tcx.associated_item(method); let def_id = assoc_item.container_id(tcx); match assoc_item.container { - rustc_ty::AssocItemContainer::Trait => get_path_to_callee(tcx, from, def_id), - rustc_ty::AssocItemContainer::Impl => { + rustc_ty::AssocContainer::Trait => get_path_to_callee(tcx, from, def_id), + rustc_ty::AssocContainer::InherentImpl | rustc_ty::AssocContainer::TraitImpl(_) => { let ty = tcx.type_of(def_id).instantiate_identity(); get_path_to_ty(tcx, from, ty, args) }, @@ -3514,7 +3400,7 @@ pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) - /// Returns `true` if `expr` designates a mutable static, a mutable local binding, or an expression /// that can be owned. pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(hir_id) = path_to_local(expr) + if let Some(hir_id) = expr.res_local_id() && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) { matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) @@ -3664,3 +3550,8 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { }) ) } + +/// Checks if the chosen edition and `msrv` allows using `if let` chains. +pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool { + cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS) +} diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 7cd5a16f5b46..4e06f010bd59 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock}; use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{get_unique_attr, sym}; +use crate::{get_unique_builtin_attr, sym}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; @@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { } else { // Allow users to tag any macro as being format!-like // TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method - get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() + get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() } } diff --git a/clippy_utils/src/mir/mod.rs b/clippy_utils/src/mir/mod.rs index 9ba644fdd20e..a066427d6bc1 100644 --- a/clippy_utils/src/mir/mod.rs +++ b/clippy_utils/src/mir/mod.rs @@ -134,7 +134,7 @@ pub fn used_exactly_once(mir: &Body<'_>, local: Local) -> Option { } /// Returns the `mir::Body` containing the node associated with `hir_id`. -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&Body<'_>> { let body_owner_local_def_id = tcx.hir_enclosing_body_owner(hir_id); if tcx.hir_body_owner_kind(body_owner_local_def_id).is_fn_or_closure() { diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 152b4272c26c..f2bffc8156af 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -15,7 +15,6 @@ use std::ops::ControlFlow; /// Collects the possible borrowers of each local. /// For example, `b = &a; c = &a;` will make `b` and (transitively) `c` /// possible borrowers of `a`. -#[allow(clippy::module_name_repetitions)] struct PossibleBorrowerVisitor<'a, 'b, 'tcx> { possible_borrower: TransitiveRelation, body: &'b mir::Body<'tcx>, @@ -167,7 +166,6 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { } /// Result of `PossibleBorrowerVisitor`. -#[allow(clippy::module_name_repetitions)] pub struct PossibleBorrowerMap<'b, 'tcx> { /// Mapping `Local -> its possible borrowers` pub map: FxHashMap>, diff --git a/clippy_utils/src/mir/possible_origin.rs b/clippy_utils/src/mir/possible_origin.rs index 3d253fd2bb14..fee22c436b0f 100644 --- a/clippy_utils/src/mir/possible_origin.rs +++ b/clippy_utils/src/mir/possible_origin.rs @@ -8,7 +8,6 @@ use rustc_middle::mir; /// Collect possible borrowed for every `&mut` local. /// For example, `_1 = &mut _2` generate _1: {_2,...} /// Known Problems: not sure all borrowed are tracked -#[allow(clippy::module_name_repetitions)] pub(super) struct PossibleOriginVisitor<'a, 'tcx> { possible_origin: TransitiveRelation, body: &'a mir::Body<'tcx>, diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 896d607fbcdd..86d17a8231d5 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -2,12 +2,12 @@ use crate::sym; use rustc_ast::Attribute; use rustc_ast::attr::AttributeExt; use rustc_attr_parsing::parse_version; +use rustc_data_structures::smallvec::SmallVec; use rustc_hir::RustcVersion; use rustc_lint::LateContext; use rustc_session::Session; use rustc_span::Symbol; use serde::Deserialize; -use smallvec::SmallVec; use std::iter::once; use std::sync::atomic::{AtomicBool, Ordering}; @@ -24,13 +24,14 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,88,0 { LET_CHAINS } - 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF } + 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST } 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL } 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } - 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } + 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } + 1,79,0 { CONST_BLOCKS } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,75,0 { OPTION_AS_SLICE } @@ -46,7 +47,7 @@ msrv_aliases! { 1,60,0 { ABS_DIFF } 1,59,0 { THREAD_LOCAL_CONST_INIT } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } - 1,57,0 { MAP_WHILE } + 1,57,0 { MAP_WHILE, CONST_PANIC } 1,56,0 { CONST_FN_UNION } 1,55,0 { SEEK_REWIND } 1,54,0 { INTO_KEYS } @@ -72,12 +73,12 @@ msrv_aliases! { 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } - 1,27,0 { ITERATOR_TRY_FOLD } - 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND } + 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS } 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } - 1,16,0 { STR_REPEAT } + 1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT } 1,15,0 { MAYBE_BOUND_IN_WHERE } 1,13,0 { QUESTION_MARK_OPERATOR } } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index ea8cfc59356a..8aa663163caf 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -4,7 +4,8 @@ //! Whenever possible, please consider diagnostic items over hardcoded paths. //! See for more information. -use crate::{MaybePath, path_def_id, sym}; +use crate::res::MaybeQPath; +use crate::sym; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::Namespace::{MacroNS, TypeNS, ValueNS}; @@ -13,6 +14,7 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{ItemKind, Node, UseKind}; use rustc_lint::LateContext; use rustc_middle::ty::fast_reject::SimplifiedType; +use rustc_middle::ty::layout::HasTyCtxt; use rustc_middle::ty::{FloatTy, IntTy, Ty, TyCtxt, UintTy}; use rustc_span::{Ident, STDLIB_STABLE_CRATES, Symbol}; use std::sync::OnceLock; @@ -74,8 +76,8 @@ impl PathLookup { } /// Returns the list of [`DefId`]s that the path resolves to - pub fn get(&self, cx: &LateContext<'_>) -> &[DefId] { - self.once.get_or_init(|| lookup_path(cx.tcx, self.ns, self.path)) + pub fn get<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>) -> &[DefId] { + self.once.get_or_init(|| lookup_path(tcx.tcx(), self.ns, self.path)) } /// Returns the single [`DefId`] that the path resolves to, this can only be used for paths into @@ -90,18 +92,21 @@ impl PathLookup { } /// Checks if the path resolves to the given `def_id` - pub fn matches(&self, cx: &LateContext<'_>, def_id: DefId) -> bool { - self.get(cx).contains(&def_id) + pub fn matches<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, def_id: DefId) -> bool { + self.get(&tcx.tcx()).contains(&def_id) } /// Resolves `maybe_path` to a [`DefId`] and checks if the [`PathLookup`] matches it - pub fn matches_path<'tcx>(&self, cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> bool { - path_def_id(cx, maybe_path).is_some_and(|def_id| self.matches(cx, def_id)) + pub fn matches_path<'tcx>(&self, cx: &LateContext<'_>, maybe_path: impl MaybeQPath<'tcx>) -> bool { + maybe_path + .res(cx) + .opt_def_id() + .is_some_and(|def_id| self.matches(cx, def_id)) } /// Checks if the path resolves to `ty`'s definition, must be an `Adt` - pub fn matches_ty(&self, cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - ty.ty_adt_def().is_some_and(|adt| self.matches(cx, adt.did())) + pub fn matches_ty<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, ty: Ty<'_>) -> bool { + ty.ty_adt_def().is_some_and(|adt| self.matches(&tcx.tcx(), adt.did())) } } diff --git a/clippy_utils/src/ptr.rs b/clippy_utils/src/ptr.rs deleted file mode 100644 index 5847e916e340..000000000000 --- a/clippy_utils/src/ptr.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::source::snippet; -use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{path_to_local_id, strip_pat_refs, sym}; -use core::ops::ControlFlow; -use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, Symbol}; -use std::borrow::Cow; - -pub fn get_spans( - cx: &LateContext<'_>, - opt_body_id: Option, - idx: usize, - replacements: &[(Symbol, &'static str)], -) -> Option)>> { - if let Some(body) = opt_body_id.map(|id| cx.tcx.hir_body(id)) { - if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind { - extract_clone_suggestions(cx, binding_id, replacements, body) - } else { - Some(vec![]) - } - } else { - Some(vec![]) - } -} - -fn extract_clone_suggestions<'tcx>( - cx: &LateContext<'tcx>, - id: HirId, - replace: &[(Symbol, &'static str)], - body: &'tcx Body<'_>, -) -> Option)>> { - let mut spans = Vec::new(); - for_each_expr_without_closures(body, |e| { - if let ExprKind::MethodCall(seg, recv, [], _) = e.kind - && path_to_local_id(recv, id) - { - if seg.ident.name == sym::capacity { - return ControlFlow::Break(()); - } - for &(fn_name, suffix) in replace { - if seg.ident.name == fn_name { - spans.push((e.span, snippet(cx, recv.span, "_") + suffix)); - return ControlFlow::Continue(Descend::No); - } - } - } - ControlFlow::Continue(Descend::Yes) - }) - .is_none() - .then_some(spans) -} diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 68f0b5ea2558..90ea2616890a 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -86,7 +86,7 @@ fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, msrv: Msrv) ty::FnPtr(..) => { return Err((span, "function pointers in const fn are unstable".into())); }, - ty::Dynamic(preds, _, _) => { + ty::Dynamic(preds, _) => { for pred in *preds { match pred.skip_binder() { ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => { @@ -126,7 +126,7 @@ fn check_rvalue<'tcx>( ) -> McfResult { match rvalue { Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), - Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => { + Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => { check_place(cx, *place, span, body, msrv) }, Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), @@ -141,7 +141,8 @@ fn check_rvalue<'tcx>( | CastKind::FloatToFloat | CastKind::FnPtrToPtr | CastKind::PtrToPtr - | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _), + | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _) + | CastKind::Subtype, operand, _, ) => check_operand(cx, operand, span, body, msrv), @@ -193,10 +194,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, - _, - ) + Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, cx.tcx); @@ -231,9 +229,7 @@ fn check_statement<'tcx>( StatementKind::FakeRead(box (_, place)) => check_place(cx, *place, span, body, msrv), // just an assignment - StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { - check_place(cx, **place, span, body, msrv) - }, + StatementKind::SetDiscriminant { place, .. } => check_place(cx, **place, span, body, msrv), StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(cx, op, span, body, msrv), @@ -312,7 +308,6 @@ fn check_place<'tcx>( | ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } - | ProjectionElem::Subtype(_) | ProjectionElem::Index(_) | ProjectionElem::UnwrapUnsafeBinder(_) => {}, } @@ -475,7 +470,7 @@ fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx> let ocx = ObligationCtxt::new(&infcx); ocx.register_obligations(impl_src.nested_obligations()); - ocx.select_all_or_error().is_empty() + ocx.evaluate_obligations_error_on_ambiguity().is_empty() } !ty.needs_drop(tcx, ConstCx::new(tcx, body).typing_env) diff --git a/clippy_utils/src/res.rs b/clippy_utils/src/res.rs new file mode 100644 index 000000000000..a3efece7d224 --- /dev/null +++ b/clippy_utils/src/res.rs @@ -0,0 +1,632 @@ +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{ + self as hir, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, TyKind, +}; +use rustc_lint::LateContext; +use rustc_middle::ty::layout::HasTyCtxt; +use rustc_middle::ty::{AdtDef, AdtKind, Binder, EarlyBinder, Ty, TypeckResults}; +use rustc_span::{Ident, Symbol}; + +/// Either a `HirId` or a type which can be identified by one. +pub trait HasHirId: Copy { + fn hir_id(self) -> HirId; +} +impl HasHirId for HirId { + #[inline] + fn hir_id(self) -> HirId { + self + } +} +impl HasHirId for &Expr<'_> { + #[inline] + fn hir_id(self) -> HirId { + self.hir_id + } +} + +type DefRes = (DefKind, DefId); + +pub trait MaybeTypeckRes<'tcx> { + /// Gets the contained `TypeckResults`. + /// + /// With debug assertions enabled this will always return `Some`. `None` is + /// only returned so logic errors can be handled by not emitting a lint on + /// release builds. + fn typeck_res(&self) -> Option<&TypeckResults<'tcx>>; + + /// Gets the type-dependent resolution of the specified node. + /// + /// With debug assertions enabled this will always return `Some`. `None` is + /// only returned so logic errors can be handled by not emitting a lint on + /// release builds. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn ty_based_def(&self, node: impl HasHirId) -> Option { + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn f(typeck: &TypeckResults<'_>, id: HirId) -> Option { + if typeck.hir_owner == id.owner { + let def = typeck.type_dependent_def(id); + debug_assert!( + def.is_some(), + "attempted type-dependent lookup for a node with no definition\ + \n node `{id:?}`", + ); + def + } else { + debug_assert!( + false, + "attempted type-dependent lookup for a node in the wrong body\ + \n in body `{:?}`\ + \n expected body `{:?}`", + typeck.hir_owner, id.owner, + ); + None + } + } + self.typeck_res().and_then(|typeck| f(typeck, node.hir_id())) + } +} +impl<'tcx> MaybeTypeckRes<'tcx> for LateContext<'tcx> { + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn typeck_res(&self) -> Option<&TypeckResults<'tcx>> { + if let Some(typeck) = self.maybe_typeck_results() { + Some(typeck) + } else { + // It's possible to get the `TypeckResults` for any other body, but + // attempting to lookup the type of something across bodies like this + // is a good indication of a bug. + debug_assert!(false, "attempted type-dependent lookup in a non-body context"); + None + } + } +} +impl<'tcx> MaybeTypeckRes<'tcx> for TypeckResults<'tcx> { + #[inline] + fn typeck_res(&self) -> Option<&TypeckResults<'tcx>> { + Some(self) + } +} + +/// A `QPath` with the `HirId` of the node containing it. +type QPathId<'tcx> = (&'tcx QPath<'tcx>, HirId); + +/// A HIR node which might be a `QPath`. +pub trait MaybeQPath<'a>: Copy { + /// If this node is a path gets both the contained path and the `HirId` to + /// use for type dependant lookup. + fn opt_qpath(self) -> Option>; + + /// If this is a path gets its resolution. Returns `Res::Err` otherwise. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn res<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>) -> Res { + #[cfg_attr(debug_assertions, track_caller)] + fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>) -> Res { + match *qpath { + QPath::Resolved(_, p) => p.res, + QPath::TypeRelative(..) if let Some((kind, id)) = typeck.ty_based_def(id) => Res::Def(kind, id), + QPath::TypeRelative(..) => Res::Err, + } + } + match self.opt_qpath() { + Some((qpath, id)) if let Some(typeck) = typeck.typeck_res() => f(qpath, id, typeck), + _ => Res::Err, + } + } + + /// If this is a path with the specified name as its final segment gets its + /// resolution. Returns `Res::Err` otherwise. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn res_if_named<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>, name: Symbol) -> Res { + #[cfg_attr(debug_assertions, track_caller)] + fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>, name: Symbol) -> Res { + match *qpath { + QPath::Resolved(_, p) + if let [.., seg] = p.segments + && seg.ident.name == name => + { + p.res + }, + QPath::TypeRelative(_, seg) + if seg.ident.name == name + && let Some((kind, id)) = typeck.ty_based_def(id) => + { + Res::Def(kind, id) + }, + QPath::Resolved(..) | QPath::TypeRelative(..) => Res::Err, + } + } + match self.opt_qpath() { + Some((qpath, id)) if let Some(typeck) = typeck.typeck_res() => f(qpath, id, typeck, name), + _ => Res::Err, + } + } + + /// If this is a path gets both its resolution and final segment. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn res_with_seg<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>) -> (Res, Option<&'a PathSegment<'a>>) { + #[cfg_attr(debug_assertions, track_caller)] + fn f<'a>(qpath: &QPath<'a>, id: HirId, typeck: &TypeckResults<'_>) -> (Res, Option<&'a PathSegment<'a>>) { + match *qpath { + QPath::Resolved(_, p) if let [.., seg] = p.segments => (p.res, Some(seg)), + QPath::TypeRelative(_, seg) if let Some((kind, id)) = typeck.ty_based_def(id) => { + (Res::Def(kind, id), Some(seg)) + }, + QPath::Resolved(..) | QPath::TypeRelative(..) => (Res::Err, None), + } + } + match self.opt_qpath() { + Some((qpath, id)) if let Some(typeck) = typeck.typeck_res() => f(qpath, id, typeck), + _ => (Res::Err, None), + } + } + + /// If this is a path without an explicit `Self` type gets its resolution. + /// Returns `Res::Err` otherwise. + /// + /// Only paths to trait items can optionally contain a `Self` type. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn typeless_res<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>) -> Res { + #[cfg_attr(debug_assertions, track_caller)] + fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>) -> Res { + match *qpath { + QPath::Resolved( + None + | Some(&hir::Ty { + kind: TyKind::Infer(()), + .. + }), + p, + ) => p.res, + QPath::TypeRelative( + &hir::Ty { + kind: TyKind::Infer(()), + .. + }, + _, + ) if let Some((kind, id)) = typeck.ty_based_def(id) => Res::Def(kind, id), + QPath::Resolved(..) | QPath::TypeRelative(..) => Res::Err, + } + } + match self.opt_qpath() { + Some((qpath, id)) if let Some(typeck) = typeck.typeck_res() => f(qpath, id, typeck), + _ => Res::Err, + } + } + + /// If this is a path without an explicit `Self` type to an item with the + /// specified name gets its resolution. Returns `Res::Err` otherwise. + /// + /// Only paths to trait items can optionally contain a `Self` type. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn typeless_res_if_named<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>, name: Symbol) -> Res { + #[cfg_attr(debug_assertions, track_caller)] + fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>, name: Symbol) -> Res { + match *qpath { + QPath::Resolved( + None + | Some(&hir::Ty { + kind: TyKind::Infer(()), + .. + }), + p, + ) if let [.., seg] = p.segments + && seg.ident.name == name => + { + p.res + }, + QPath::TypeRelative( + &hir::Ty { + kind: TyKind::Infer(()), + .. + }, + seg, + ) if seg.ident.name == name + && let Some((kind, id)) = typeck.ty_based_def(id) => + { + Res::Def(kind, id) + }, + QPath::Resolved(..) | QPath::TypeRelative(..) => Res::Err, + } + } + match self.opt_qpath() { + Some((qpath, id)) if let Some(typeck) = typeck.typeck_res() => f(qpath, id, typeck, name), + _ => Res::Err, + } + } + + /// If this is a type-relative path gets the definition it resolves to. + /// + /// Only inherent associated items require a type-relative path. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn ty_rel_def<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>) -> Option { + match self.opt_qpath() { + Some((QPath::TypeRelative(..), id)) => typeck.ty_based_def(id), + _ => None, + } + } + + /// If this is a type-relative path to an item with the specified name gets + /// the definition it resolves to. + /// + /// Only inherent associated items require a type-relative path. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn ty_rel_def_if_named<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>, name: Symbol) -> Option { + match self.opt_qpath() { + Some((&QPath::TypeRelative(_, seg), id)) if seg.ident.name == name => typeck.ty_based_def(id), + _ => None, + } + } + + /// If this is a type-relative path gets the definition it resolves to and + /// its final segment. + /// + /// Only inherent associated items require a type-relative path. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + fn ty_rel_def_with_seg<'tcx>(self, typeck: &impl MaybeTypeckRes<'tcx>) -> Option<(DefRes, &'a PathSegment<'a>)> { + match self.opt_qpath() { + Some((QPath::TypeRelative(_, seg), id)) if let Some(def) = typeck.ty_based_def(id) => Some((def, seg)), + _ => None, + } + } +} + +impl<'tcx> MaybeQPath<'tcx> for QPathId<'tcx> { + #[inline] + fn opt_qpath(self) -> Option> { + Some((self.0, self.1)) + } +} +impl<'tcx> MaybeQPath<'tcx> for &'tcx Expr<'_> { + #[inline] + fn opt_qpath(self) -> Option> { + match &self.kind { + ExprKind::Path(qpath) => Some((qpath, self.hir_id)), + _ => None, + } + } +} +impl<'tcx> MaybeQPath<'tcx> for &'tcx PatExpr<'_> { + #[inline] + fn opt_qpath(self) -> Option> { + match &self.kind { + PatExprKind::Path(qpath) => Some((qpath, self.hir_id)), + _ => None, + } + } +} +impl<'tcx, AmbigArg> MaybeQPath<'tcx> for &'tcx hir::Ty<'_, AmbigArg> { + #[inline] + fn opt_qpath(self) -> Option> { + match &self.kind { + TyKind::Path(qpath) => Some((qpath, self.hir_id)), + _ => None, + } + } +} +impl<'tcx> MaybeQPath<'tcx> for &'_ Pat<'tcx> { + #[inline] + fn opt_qpath(self) -> Option> { + match self.kind { + PatKind::Expr(e) => e.opt_qpath(), + _ => None, + } + } +} +impl<'tcx, T: MaybeQPath<'tcx>> MaybeQPath<'tcx> for Option { + #[inline] + fn opt_qpath(self) -> Option> { + self.and_then(T::opt_qpath) + } +} +impl<'tcx, T: Copy + MaybeQPath<'tcx>> MaybeQPath<'tcx> for &Option { + #[inline] + fn opt_qpath(self) -> Option> { + self.and_then(T::opt_qpath) + } +} + +/// A resolved path and the explicit `Self` type if there is one. +type OptResPath<'tcx> = (Option<&'tcx hir::Ty<'tcx>>, Option<&'tcx Path<'tcx>>); + +/// A HIR node which might be a `QPath::Resolved`. +/// +/// The following are resolved paths: +/// * A path to a module or crate item. +/// * A path to a trait item via the trait's name. +/// * A path to a struct or variant constructor via the original type's path. +/// * A local. +/// +/// All other paths are `TypeRelative` and require using `PathRes` to lookup the +/// resolution. +pub trait MaybeResPath<'a>: Copy { + /// If this node is a resolved path gets both the contained path and the + /// type associated with it. + fn opt_res_path(self) -> OptResPath<'a>; + + /// If this node is a resolved path gets it's resolution. Returns `Res::Err` + /// otherwise. + #[inline] + fn basic_res(self) -> &'a Res { + self.opt_res_path().1.map_or(&Res::Err, |p| &p.res) + } + + /// If this node is a path to a local gets the local's `HirId`. + #[inline] + fn res_local_id(self) -> Option { + if let (_, Some(p)) = self.opt_res_path() + && let Res::Local(id) = p.res + { + Some(id) + } else { + None + } + } + + /// If this node is a path to a local gets the local's `HirId` and identifier. + fn res_local_id_and_ident(self) -> Option<(HirId, &'a Ident)> { + if let (_, Some(p)) = self.opt_res_path() + && let Res::Local(id) = p.res + && let [seg] = p.segments + { + Some((id, &seg.ident)) + } else { + None + } + } +} +impl<'a> MaybeResPath<'a> for &'a Path<'a> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + (None, Some(self)) + } + + #[inline] + fn basic_res(self) -> &'a Res { + &self.res + } +} +impl<'a> MaybeResPath<'a> for &QPath<'a> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match *self { + QPath::Resolved(ty, path) => (ty, Some(path)), + QPath::TypeRelative(..) => (None, None), + } + } +} +impl<'a> MaybeResPath<'a> for &Expr<'a> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match &self.kind { + ExprKind::Path(qpath) => qpath.opt_res_path(), + _ => (None, None), + } + } +} +impl<'a> MaybeResPath<'a> for &PatExpr<'a> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match &self.kind { + PatExprKind::Path(qpath) => qpath.opt_res_path(), + _ => (None, None), + } + } +} +impl<'a, AmbigArg> MaybeResPath<'a> for &hir::Ty<'a, AmbigArg> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match &self.kind { + TyKind::Path(qpath) => qpath.opt_res_path(), + _ => (None, None), + } + } +} +impl<'a> MaybeResPath<'a> for &Pat<'a> { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match self.kind { + PatKind::Expr(e) => e.opt_res_path(), + _ => (None, None), + } + } +} +impl<'a, T: MaybeResPath<'a>> MaybeResPath<'a> for Option { + #[inline] + fn opt_res_path(self) -> OptResPath<'a> { + match self { + Some(x) => T::opt_res_path(x), + None => (None, None), + } + } + + #[inline] + fn basic_res(self) -> &'a Res { + self.map_or(&Res::Err, T::basic_res) + } +} + +/// A type which may either contain a `DefId` or be referred to by a `DefId`. +pub trait MaybeDef: Copy { + fn opt_def_id(self) -> Option; + + /// Gets this definition's id and kind. This will lookup the kind in the def + /// tree if needed. + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)>; + + /// Gets the diagnostic name of this definition if it has one. + #[inline] + fn opt_diag_name<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option { + self.opt_def_id().and_then(|id| tcx.tcx().get_diagnostic_name(id)) + } + + /// Checks if this definition has the specified diagnostic name. + #[inline] + fn is_diag_item<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>, name: Symbol) -> bool { + self.opt_def_id() + .is_some_and(|id| tcx.tcx().is_diagnostic_item(name, id)) + } + + /// Checks if this definition is the specified `LangItem`. + #[inline] + fn is_lang_item<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>, item: LangItem) -> bool { + self.opt_def_id() + .is_some_and(|id| tcx.tcx().lang_items().get(item) == Some(id)) + } + + /// If this definition is an impl block gets its type. + #[inline] + fn opt_impl_ty<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option>> { + match self.opt_def(tcx) { + Some((DefKind::Impl { .. }, id)) => Some(tcx.tcx().type_of(id)), + _ => None, + } + } + + /// Gets the parent of this definition if it has one. + #[inline] + fn opt_parent<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option { + self.opt_def_id().and_then(|id| tcx.tcx().opt_parent(id)) + } + + /// Checks if this definition is an impl block. + #[inline] + fn is_impl<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> bool { + matches!(self.opt_def(tcx), Some((DefKind::Impl { .. }, _))) + } + + /// If this definition is a constructor gets the `DefId` of it's type or variant. + #[inline] + fn ctor_parent<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option { + match self.opt_def(tcx) { + Some((DefKind::Ctor(..), id)) => tcx.tcx().opt_parent(id), + _ => None, + } + } + + /// If this definition is an associated item of an impl or trait gets the + /// `DefId` of its parent. + #[inline] + fn assoc_parent<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option { + match self.opt_def(tcx) { + Some((DefKind::AssocConst | DefKind::AssocFn | DefKind::AssocTy, id)) => tcx.tcx().opt_parent(id), + _ => None, + } + } + + /// If this definition is an associated function of an impl or trait gets the + /// `DefId` of its parent. + #[inline] + fn assoc_fn_parent<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option { + match self.opt_def(tcx) { + Some((DefKind::AssocFn, id)) => tcx.tcx().opt_parent(id), + _ => None, + } + } +} +impl MaybeDef for DefId { + #[inline] + fn opt_def_id(self) -> Option { + Some(self) + } + + #[inline] + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + self.opt_def_id().map(|id| (tcx.tcx().def_kind(id), id)) + } +} +impl MaybeDef for (DefKind, DefId) { + #[inline] + fn opt_def_id(self) -> Option { + Some(self.1) + } + + #[inline] + fn opt_def<'tcx>(self, _: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + Some(self) + } +} +impl MaybeDef for AdtDef<'_> { + #[inline] + fn opt_def_id(self) -> Option { + Some(self.did()) + } + + #[inline] + fn opt_def<'tcx>(self, _: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + let did = self.did(); + match self.adt_kind() { + AdtKind::Enum => Some((DefKind::Enum, did)), + AdtKind::Struct => Some((DefKind::Struct, did)), + AdtKind::Union => Some((DefKind::Union, did)), + } + } +} +impl MaybeDef for Ty<'_> { + #[inline] + fn opt_def_id(self) -> Option { + self.ty_adt_def().opt_def_id() + } + + #[inline] + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + self.ty_adt_def().opt_def(tcx) + } +} +impl MaybeDef for Res { + #[inline] + fn opt_def_id(self) -> Option { + Res::opt_def_id(&self) + } + + #[inline] + fn opt_def<'tcx>(self, _: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + match self { + Res::Def(kind, id) => Some((kind, id)), + _ => None, + } + } +} +impl MaybeDef for Option { + #[inline] + fn opt_def_id(self) -> Option { + self.and_then(T::opt_def_id) + } + + #[inline] + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + self.and_then(|x| T::opt_def(x, tcx)) + } +} +impl MaybeDef for EarlyBinder<'_, T> { + #[inline] + fn opt_def_id(self) -> Option { + self.skip_binder().opt_def_id() + } + + #[inline] + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + self.skip_binder().opt_def(tcx) + } +} +impl MaybeDef for Binder<'_, T> { + #[inline] + fn opt_def_id(self) -> Option { + self.skip_binder().opt_def_id() + } + + #[inline] + fn opt_def<'tcx>(self, tcx: &impl HasTyCtxt<'tcx>) -> Option<(DefKind, DefId)> { + self.skip_binder().opt_def(tcx) + } +} diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index e675291b6f3a..e29d45551d1b 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -13,8 +13,8 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::source_map::{SourceMap, original_sp}; use rustc_span::{ - BytePos, DUMMY_SP, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData, - SyntaxContext, hygiene, + BytePos, DUMMY_SP, DesugaringKind, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, + Span, SpanData, SyntaxContext, hygiene, }; use std::borrow::Cow; use std::fmt; @@ -143,7 +143,6 @@ pub trait SpanRangeExt: SpanRange { map_range(cx.sess().source_map(), self.into_range(), f) } - #[allow(rustdoc::invalid_rust_codeblocks, reason = "The codeblock is intentionally broken")] /// Extends the range to include all preceding whitespace characters. /// /// The range will not be expanded if it would cross a line boundary, the line the range would @@ -215,6 +214,11 @@ impl fmt::Display for SourceText { self.as_str().fmt(f) } } +impl fmt::Debug for SourceText { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} fn get_source_range(sm: &SourceMap, sp: Range) -> Option { let start = sm.lookup_byte_offset(sp.start); @@ -666,6 +670,14 @@ fn snippet_with_context_sess<'a>( default: &'a str, applicability: &mut Applicability, ) -> (Cow<'a, str>, bool) { + // If it is just range desugaring, use the desugaring span since it may include parenthesis. + if span.desugaring_kind() == Some(DesugaringKind::RangeExpr) && span.parent_callsite().unwrap().ctxt() == outer { + return ( + snippet_with_applicability_sess(sess, span, default, applicability), + false, + ); + } + let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else( || { // The span is from a macro argument, and the outer context is the macro using the argument diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index a63333c9b48f..2593df103527 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -33,7 +33,7 @@ pub enum Sugg<'a> { /// or `-`, but only if the type with and without the operator is kept identical. /// It means that doubling the operator can be used to remove it instead, in /// order to provide better suggestions. - UnOp(UnOp, Box>), + UnOp(UnOp, Box), } /// Literal constant `0`, for convenience. @@ -59,7 +59,7 @@ impl<'a> Sugg<'a> { pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { let ctxt = expr.span.ctxt(); let get_snippet = |span| snippet_with_context(cx, span, ctxt, "", &mut Applicability::Unspecified).0; - snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet)) + snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(cx, expr, get_snippet)) } /// Convenience function around `hir_opt` for suggestions with a default @@ -115,7 +115,7 @@ impl<'a> Sugg<'a> { Box::new(Self::hir_with_context(cx, inner, ctxt, default, applicability)), ) } else { - Self::hir_from_snippet(expr, |span| { + Self::hir_from_snippet(cx, expr, |span| { snippet_with_context(cx, span, ctxt, default, applicability).0 }) } @@ -127,8 +127,12 @@ impl<'a> Sugg<'a> { /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*` /// function variants of `Sugg`, since these use different snippet functions. - fn hir_from_snippet(expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self { - if let Some(range) = higher::Range::hir(expr) { + fn hir_from_snippet( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + mut get_snippet: impl FnMut(Span) -> Cow<'a, str>, + ) -> Self { + if let Some(range) = higher::Range::hir(cx, expr) { let op = AssocOp::Range(range.limits); let start = range.start.map_or("".into(), |expr| get_snippet(expr.span)); let end = range.end.map_or("".into(), |expr| get_snippet(expr.span)); @@ -166,7 +170,7 @@ impl<'a> Sugg<'a> { | ExprKind::Use(..) | ExprKind::Err(_) | ExprKind::UnsafeBinderCast(..) => Sugg::NonParen(get_snippet(expr.span)), - ExprKind::DropTemps(inner) => Self::hir_from_snippet(inner, get_snippet), + ExprKind::DropTemps(inner) => Self::hir_from_snippet(cx, inner, get_snippet), ExprKind::Assign(lhs, rhs, _) => { Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span)) }, @@ -765,7 +769,7 @@ pub struct DerefClosure { /// such as explicit deref and borrowing cases. /// Returns `None` if no such use cases have been triggered in closure body /// -/// note: this only works on single line immutable closures with exactly one input parameter. +/// note: This only works on immutable closures with exactly one input parameter. pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option { if let ExprKind::Closure(&Closure { fn_decl, def_id, body, .. diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 278101ac27f9..8e8a80a6a9c9 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -34,6 +34,7 @@ macro_rules! generate { // // `cargo dev fmt` ensures that the content of the `generate!()` macro call stays sorted. generate! { + Applicability, AsyncReadExt, AsyncWriteExt, BACKSLASH_SINGLE_QUOTE: r"\'", @@ -45,10 +46,12 @@ generate! { Current, DOUBLE_QUOTE: "\"", Deserialize, + EarlyContext, EarlyLintPass, IntoIter, Itertools, LF: "\n", + LateContext, Lazy, Lint, LowerExp, @@ -75,7 +78,9 @@ generate! { Weak, abs, ambiguous_glob_reexports, + app, append, + applicability, arg, as_bytes, as_deref, @@ -111,6 +116,8 @@ generate! { clone_into, cloned, cognitive_complexity, + collapsible_else_if, + collapsible_if, collect, const_ptr, contains, @@ -122,9 +129,11 @@ generate! { count_ones, create, create_new, + cx, cycle, cyclomatic_complexity, de, + deprecated_in_future, diagnostics, disallowed_types, drain, @@ -172,6 +181,7 @@ generate! { hidden_glob_reexports, hygiene, insert, + insert_str, inspect, int_roundings, into, @@ -188,13 +198,14 @@ generate! { is_none, is_none_or, is_ok, + is_partitioned, is_some, is_some_and, + is_sorted_by_key, isqrt, itertools, join, kw, - last, lazy_static, lint_vec, ln, @@ -208,6 +219,7 @@ generate! { map_continue, map_or, map_or_else, + map_while, match_indices, matches, max, @@ -253,12 +265,14 @@ generate! { powi, product, push, + push_str, read, read_exact, read_line, read_to_end, read_to_string, read_unaligned, + read_volatile, redundant_imports, redundant_pub_crate, regex, @@ -280,8 +294,10 @@ generate! { rsplit_terminator, rsplitn, rsplitn_mut, + rustc_errors, rustc_lint, rustc_lint_defs, + rustc_middle, rustc_span, rustfmt_skip, rwlock, @@ -324,6 +340,7 @@ generate! { symbol, take, take_while, + tcx, then, then_some, to_ascii_lowercase, @@ -344,6 +361,7 @@ generate! { trim_start, trim_start_matches, truncate, + try_for_each, unreachable_pub, unsafe_removed_from_name, unused, @@ -367,6 +385,7 @@ generate! { wrapping_offset, write, write_unaligned, + write_volatile, writeln, zip, } diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 8e302f9d2ad1..a90d64e972c1 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -3,7 +3,6 @@ #![allow(clippy::module_name_repetitions)] use core::ops::ControlFlow; -use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -11,7 +10,7 @@ use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, FnDecl, LangItem, TyKind, find_attr}; +use rustc_hir::{Expr, FnDecl, LangItem, find_attr}; use rustc_hir_analysis::lower_ty; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; @@ -21,8 +20,8 @@ use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ - self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, - GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, BoundVarIndexKind, FnSig, GenericArg, + GenericArgKind, GenericArgsRef, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; @@ -34,8 +33,8 @@ use std::assert_matches::debug_assert_matches; use std::collections::hash_map::Entry; use std::{iter, mem}; -use crate::path_res; use crate::paths::{PathNS, lookup_path_str}; +use crate::res::{MaybeDef, MaybeQPath}; mod type_certainty; pub use type_certainty::expr_type_is_certain; @@ -43,13 +42,8 @@ pub use type_certainty::expr_type_is_certain; /// Lower a [`hir::Ty`] to a [`rustc_middle::ty::Ty`]. pub fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> { cx.maybe_typeck_results() - .and_then(|results| { - if results.hir_owner == hir_ty.hir_id.owner { - results.node_type_opt(hir_ty.hir_id) - } else { - None - } - }) + .filter(|results| results.hir_owner == hir_ty.hir_id.owner) + .and_then(|results| results.node_type_opt(hir_ty.hir_id)) .unwrap_or_else(|| lower_ty(cx.tcx, hir_ty)) } @@ -162,20 +156,6 @@ pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Optio .and_then(|iter_did| cx.get_associated_type(ty, iter_did, sym::Item)) } -/// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type -/// implements a trait marked with a diagnostic item use [`implements_trait`]. -/// -/// For a further exploitation what diagnostic items are see [diagnostic items] in -/// rustc-dev-guide. -/// -/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html -pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option { - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.get_diagnostic_name(adt.did()), - _ => None, - } -} - /// Returns true if `ty` is a type on which calling `Clone` through a function instead of /// as a method, such as `Arc::clone()` is considered idiomatic. /// @@ -183,7 +163,7 @@ pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option, ty: Ty<'_>) -> bool { matches!( - get_type_diagnostic_name(cx, ty), + ty.opt_diag_name(cx), Some(sym::Arc | sym::ArcWeak | sym::Rc | sym::RcWeak) ) } @@ -285,7 +265,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( let _ = tcx.hir_body_owner_kind(callee_id); } - let ty = tcx.erase_regions(ty); + let ty = tcx.erase_and_anonymize_regions(ty); if ty.has_escaping_bound_vars() { return false; } @@ -349,7 +329,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { } false }, - ty::Dynamic(binder, _, _) => { + ty::Dynamic(binder, _) => { for predicate in *binder { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() && find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { .. }) @@ -384,42 +364,6 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { } } -/// Checks if the type is a reference equals to a diagnostic item -pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { - match ty.kind() { - ty::Ref(_, ref_ty, _) => is_type_diagnostic_item(cx, *ref_ty, diag_item), - _ => false, - } -} - -/// Checks if the type is equal to a diagnostic item. To check if a type implements a -/// trait marked with a diagnostic item use [`implements_trait`]. -/// -/// For a further exploitation what diagnostic items are see [diagnostic items] in -/// rustc-dev-guide. -/// -/// --- -/// -/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` -/// -/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html -pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()), - _ => false, - } -} - -/// Checks if the type is equal to a lang item. -/// -/// Returns `false` if the `LangItem` is not defined. -pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: LangItem) -> bool { - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.lang_items().get(lang_item) == Some(adt.did()), - _ => false, - } -} - /// Return `true` if the passed `typ` is `isize` or `usize`. pub fn is_isize_or_usize(typ: Ty<'_>) -> bool { matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) @@ -438,9 +382,9 @@ pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { false } // Check for std types which implement drop, but only for memory allocation. - else if is_type_lang_item(cx, ty, LangItem::OwnedBox) + else if ty.is_lang_item(cx, LangItem::OwnedBox) || matches!( - get_type_diagnostic_name(cx, ty), + ty.opt_diag_name(cx), Some(sym::HashSet | sym::Rc | sym::Arc | sym::cstring_type | sym::RcWeak | sym::ArcWeak) ) { @@ -475,63 +419,50 @@ pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) } -/// Peels off all references on the type. Returns the underlying type, the number of references -/// removed, and whether the pointer is ultimately mutable or not. -pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { - fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { - match ty.kind() { - ty::Ref(_, ty, Mutability::Mut) => f(*ty, count + 1, mutability), - ty::Ref(_, ty, Mutability::Not) => f(*ty, count + 1, Mutability::Not), - _ => (ty, count, mutability), - } - } - f(ty, 0, Mutability::Mut) -} - -/// Returns `true` if the given type is an `unsafe` function. -pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { +/// Returns `true` if `ty` denotes an `unsafe fn`. +pub fn is_unsafe_fn<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe() } -/// Returns the base type for HIR references and pointers. -pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { - match ty.kind { - TyKind::Ptr(ref mut_ty) | TyKind::Ref(_, ref mut_ty) => walk_ptrs_hir_ty(mut_ty.ty), - _ => ty, - } -} - -/// Returns the base type for references and raw pointers, and count reference -/// depth. -pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { - fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { - match ty.kind() { - ty::Ref(_, ty, _) => inner(*ty, depth + 1), - _ => (ty, depth), - } +/// Peels off all references on the type. Returns the underlying type, the number of references +/// removed, and, if there were any such references, whether the pointer is ultimately mutable or +/// not. +pub fn peel_and_count_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize, Option) { + let mut count = 0; + let mut mutbl = None; + while let ty::Ref(_, dest_ty, m) = ty.kind() { + ty = *dest_ty; + count += 1; + mutbl.replace(mutbl.map_or(*m, |mutbl: Mutability| mutbl.min(*m))); } - inner(ty, 0) + (ty, count, mutbl) } -/// Returns `true` if types `a` and `b` are same types having same `Const` generic args, -/// otherwise returns `false` -pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { +/// Checks whether `a` and `b` are same types having same `Const` generic args, but ignores +/// lifetimes. +/// +/// For example, the function would return `true` for +/// - `u32` and `u32` +/// - `[u8; N]` and `[u8; M]`, if `N=M` +/// - `Option` and `Option`, if `same_type_modulo_regions(T, U)` holds +/// - `&'a str` and `&'b str` +/// +/// and `false` for: +/// - `Result` and `Result` +pub fn same_type_modulo_regions<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { match (&a.kind(), &b.kind()) { (&ty::Adt(did_a, args_a), &ty::Adt(did_b, args_b)) => { if did_a != did_b { return false; } - args_a - .iter() - .zip(args_b.iter()) - .all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { - (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, - (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { - same_type_and_consts(type_a, type_b) - }, - _ => true, - }) + iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { + (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { + same_type_modulo_regions(type_a, type_b) + }, + _ => true, + }) }, _ => a == b, } @@ -646,7 +577,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { - if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) { + if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = expr.res(cx) { Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate_identity(), Some(id))) } else { ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) @@ -673,7 +604,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option Some(ExprFnSig::Sig(sig_tys.with(hdr), None)), - ty::Dynamic(bounds, _, _) => { + ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); match bounds.principal() { Some(bound) @@ -844,7 +775,7 @@ pub fn for_each_top_level_late_bound_region( impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow> TypeVisitor> for V { type Result = ControlFlow; fn visit_region(&mut self, r: Region<'tcx>) -> Self::Result { - if let RegionKind::ReBound(idx, bound) = r.kind() + if let RegionKind::ReBound(BoundVarIndexKind::Bound(idx), bound) = r.kind() && idx.as_u32() == self.index { (self.f)(bound) @@ -971,9 +902,10 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { } } +#[cfg(debug_assertions)] /// Asserts that the given arguments match the generic parameters of the given item. -#[allow(dead_code)] fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[GenericArg<'tcx>]) { + use itertools::Itertools; let g = tcx.generics_of(did); let parent = g.parent.map(|did| tcx.generics_of(did)); let count = g.parent_count + g.own_params.len(); @@ -989,7 +921,7 @@ fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[Generi note: the expected arguments are: `[{}]`\n\ the given arguments are: `{args:#?}`", args.len(), - params.clone().map(GenericParamDefKind::descr).format(", "), + params.clone().map(ty::GenericParamDefKind::descr).format(", "), ); if let Some((idx, (param, arg))) = @@ -998,13 +930,13 @@ fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[Generi .zip(args.iter().map(|&x| x.kind())) .enumerate() .find(|(_, (param, arg))| match (param, arg) { - (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) - | (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) - | (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false, + (ty::GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) + | (ty::GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) + | (ty::GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false, ( - GenericParamDefKind::Lifetime - | GenericParamDefKind::Type { .. } - | GenericParamDefKind::Const { .. }, + ty::GenericParamDefKind::Lifetime + | ty::GenericParamDefKind::Type { .. } + | ty::GenericParamDefKind::Const { .. }, _, ) => true, }) @@ -1014,7 +946,7 @@ fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[Generi note: the expected arguments are `[{}]`\n\ the given arguments are `{args:#?}`", param.descr(), - params.clone().map(GenericParamDefKind::descr).format(", "), + params.clone().map(ty::GenericParamDefKind::descr).format(", "), ); } } @@ -1317,11 +1249,14 @@ pub fn get_field_by_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> /// Check if `ty` is an `Option` and return its argument type if it is. pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { - match ty.kind() { - ty::Adt(adt, args) => cx - .tcx - .is_diagnostic_item(sym::Option, adt.did()) - .then(|| args.type_at(0)), + match *ty.kind() { + ty::Adt(adt, args) + if let [arg] = &**args + && let Some(arg) = arg.as_type() + && adt.is_diag_item(cx, sym::Option) => + { + Some(arg) + }, _ => None, } } @@ -1375,7 +1310,7 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<' /// Check if `ty` is slice-like, i.e., `&[T]`, `[T; N]`, or `Vec`. pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - ty.is_slice() || ty.is_array() || is_type_diagnostic_item(cx, ty, sym::Vec) + ty.is_slice() || ty.is_array() || ty.is_diag_item(cx, sym::Vec) } pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option { diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index d9c7e6eac9f6..eadb07a11be0 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -197,19 +197,6 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo QPath::TypeRelative(ty, path_segment) => { path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type) }, - - QPath::LangItem(lang_item, ..) => cx - .tcx - .lang_items() - .get(*lang_item) - .map_or(Certainty::Uncertain, |def_id| { - let generics = cx.tcx.generics_of(def_id); - if generics.is_empty() { - Certainty::Certain(if resolves_to_type { Some(def_id) } else { None }) - } else { - Certainty::Uncertain - } - }), }; debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); certainty @@ -329,7 +316,7 @@ fn update_res( None } -#[allow(clippy::cast_possible_truncation)] +#[expect(clippy::cast_possible_truncation)] fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let Some(callee_def_id) = (match expr.kind { ExprKind::Call(callee, _) => { diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 6eccbcdb1228..e27f1dabeefa 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,4 +1,5 @@ use crate::macros::root_macro_call_first_node; +use crate::res::MaybeResPath; use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures}; use crate::{self as utils, get_enclosing_loop_or_multi_call_closure}; use core::ops::ControlFlow; @@ -196,7 +197,7 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool { for_each_expr(cx, v, |e| { - if utils::path_to_local_id(e, local_id) { + if e.res_local_id() == Some(local_id) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) @@ -222,7 +223,7 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr let mut past_expr = false; for_each_expr(cx, block, |e| { if past_expr { - if utils::path_to_local_id(e, local_id) { + if e.res_local_id() == Some(local_id) { ControlFlow::Break(()) } else { ControlFlow::Continue(Descend::Yes) diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index c9f5401ebe77..84e4f043e34f 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,7 +1,8 @@ +use crate::get_enclosing_block; use crate::msrvs::Msrv; use crate::qualify_min_const_fn::is_stable_const_fn; +use crate::res::MaybeResPath; use crate::ty::needs_ordered_drop; -use crate::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_ast::visit::{VisitorResult, try_visit}; use rustc_hir::def::{CtorKind, DefKind, Res}; @@ -312,7 +313,7 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { /// Checks if the given local is used. pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { for_each_expr(cx, visitable, |e| { - if path_to_local_id(e, id) { + if e.res_local_id() == Some(id) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) @@ -564,7 +565,7 @@ pub fn for_each_local_use_after_expr<'tcx, B>( if self.res.is_break() { return; } - if path_to_local_id(e, self.local_id) { + if e.res_local_id() == Some(self.local_id) { self.res = (self.f)(e); } else { walk_expr(self, e); @@ -591,7 +592,7 @@ pub fn for_each_local_use_after_expr<'tcx, B>( // Calls the given function for every unconsumed temporary created by the expression. Note the // function is only guaranteed to be called for types which need to be dropped, but it may be called // for other types. -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] pub fn for_each_unconsumed_temporary<'tcx, B>( cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>, @@ -740,7 +741,7 @@ pub fn for_each_local_assignment<'tcx, B>( fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { if let ExprKind::Assign(lhs, rhs, _) = e.kind && self.res.is_continue() - && path_to_local_id(lhs, self.local_id) + && lhs.res_local_id() == Some(self.local_id) { self.res = (self.f)(rhs); self.visit_expr(rhs); @@ -785,7 +786,7 @@ pub fn local_used_once<'tcx>( let mut expr = None; let cf = for_each_expr(cx, visitable, |e| { - if path_to_local_id(e, id) && expr.replace(e).is_some() { + if e.res_local_id() == Some(id) && expr.replace(e).is_some() { ControlFlow::Break(()) } else { ControlFlow::Continue(()) diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index ec0e59e70549..b73a7c7bb4d9 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.91" +version = "0.1.93" edition = "2024" repository = "https://github.com/rust-lang/rust-clippy" license = "MIT OR Apache-2.0" diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 55e588f5ec73..0d0b80c309dd 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -22,6 +22,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" strip-ansi-escapes = "0.2.0" tar = "0.4" -toml = "0.7.3" +toml = "0.9.7" ureq = { version = "2.2", features = ["json"] } walkdir = "2.3" diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index 3b2ebf0c28ac..5d5214805b96 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use std::num::NonZero; use std::path::PathBuf; -#[allow(clippy::struct_excessive_bools)] +#[expect(clippy::struct_excessive_bools)] #[derive(Parser, Clone, Debug)] #[command(args_conflicts_with_subcommands = true)] pub(crate) struct LintcheckConfig { diff --git a/lintcheck/src/input.rs b/lintcheck/src/input.rs index 1ed059d2fb11..7dda2b7b25f8 100644 --- a/lintcheck/src/input.rs +++ b/lintcheck/src/input.rs @@ -180,7 +180,7 @@ impl CrateWithSource { /// copies a local folder #[expect(clippy::too_many_lines)] fn download_and_extract(&self) -> Crate { - #[allow(clippy::result_large_err)] + #[expect(clippy::result_large_err)] fn get(path: &str) -> Result { const MAX_RETRIES: u8 = 4; let mut retries = 0; diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 3a60cfa79f41..b30df7902378 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -66,7 +66,7 @@ struct Crate { impl Crate { /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy /// issued - #[allow(clippy::too_many_arguments, clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn run_clippy_lints( &self, clippy_driver_path: &Path, @@ -314,7 +314,7 @@ fn main() { } } -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn lintcheck(config: LintcheckConfig) { let clippy_ver = build_clippy(config.perf); let clippy_driver_path = fs::canonicalize(format!( diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ec2f24a0a6d8..d23fd74d9acc 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-09-04" +channel = "nightly-2025-10-31" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/driver.rs b/src/driver.rs index 6bddcbfd94ce..102ca3fa69f7 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -133,8 +133,7 @@ struct ClippyCallbacks { } impl rustc_driver::Callbacks for ClippyCallbacks { - // JUSTIFICATION: necessary in clippy driver to set `mir_opt_level` - #[allow(rustc::bad_opt_access)] + #[expect(rustc::bad_opt_access, reason = "necessary in clippy driver to set `mir_opt_level`")] fn config(&mut self, config: &mut interface::Config) { let conf_path = clippy_config::lookup_conf_file(); let previous = config.register_lints.take(); @@ -182,15 +181,13 @@ impl rustc_driver::Callbacks for ClippyCallbacks { } } -#[allow(clippy::ignored_unit_patterns)] fn display_help() { println!("{}", help_message()); } const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml"; -#[allow(clippy::too_many_lines)] -#[allow(clippy::ignored_unit_patterns)] +#[expect(clippy::too_many_lines)] pub fn main() { // See docs in https://github.com/rust-lang/rust/blob/master/compiler/rustc/src/main.rs // about jemalloc. diff --git a/src/main.rs b/src/main.rs index 3c2eec1f05b9..688161c7bfcb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,10 @@ use std::process::{self, Command}; use anstream::println; -#[allow(clippy::ignored_unit_patterns)] fn show_help() { println!("{}", help_message()); } -#[allow(clippy::ignored_unit_patterns)] fn show_version() { let version_info = rustc_tools_util::get_version_info!(); println!("{version_info}"); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 6b6dfd7b81ea..1ac688935278 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -291,7 +291,6 @@ fn run_ui_toml(cx: &TestContext) { } // Allow `Default::default` as `OptWithSpan` is not nameable -#[allow(clippy::default_trait_access)] fn run_ui_cargo(cx: &TestContext) { if IS_RUSTC_TEST_SUITE { return; @@ -405,11 +404,18 @@ fn ui_cargo_toml_metadata() { continue; } - let toml = fs::read_to_string(path).unwrap().parse::().unwrap(); + let toml = fs::read_to_string(path).unwrap(); + let toml = toml::de::DeTable::parse(&toml).unwrap(); - let package = toml.as_table().unwrap().get("package").unwrap().as_table().unwrap(); + let package = toml.get_ref().get("package").unwrap().get_ref().as_table().unwrap(); - let name = package.get("name").unwrap().as_str().unwrap().replace('-', "_"); + let name = package + .get("name") + .unwrap() + .as_ref() + .as_str() + .unwrap() + .replace('-', "_"); assert!( path.parent() .unwrap() @@ -421,7 +427,10 @@ fn ui_cargo_toml_metadata() { path.display(), ); - let publish = package.get("publish").and_then(toml::Value::as_bool).unwrap_or(true); + let publish = package + .get("publish") + .and_then(|x| x.get_ref().as_bool()) + .unwrap_or(true); assert!( !publish || publish_exceptions.contains(&path.parent().unwrap().to_path_buf()), "`{}` lacks `publish = false`", @@ -463,7 +472,7 @@ struct DiagnosticCollector { } impl DiagnosticCollector { - #[allow(clippy::assertions_on_constants)] + #[expect(clippy::assertions_on_constants)] fn spawn() -> (Self, thread::JoinHandle<()>) { assert!(!IS_RUSTC_TEST_SUITE && !RUN_INTERNAL_TESTS); diff --git a/tests/missing-test-files.rs b/tests/missing-test-files.rs index 63f960c92fa3..9fff3132498d 100644 --- a/tests/missing-test-files.rs +++ b/tests/missing-test-files.rs @@ -1,6 +1,5 @@ #![warn(rust_2018_idioms, unused_lifetimes)] #![allow(clippy::assertions_on_constants)] -#![cfg_attr(bootstrap, feature(path_file_prefix))] use std::cmp::Ordering; use std::ffi::OsStr; diff --git a/tests/no-profile-in-cargo-toml.rs b/tests/no-profile-in-cargo-toml.rs index 2ad9bfb75dee..1f8c4fae9b31 100644 --- a/tests/no-profile-in-cargo-toml.rs +++ b/tests/no-profile-in-cargo-toml.rs @@ -17,6 +17,9 @@ fn no_profile_in_cargo_toml() { // keep it fast and simple. for entry in WalkDir::new(".") .into_iter() + // Do not recurse into `target` as lintcheck might put some sources (and their + // `Cargo.toml`) there. + .filter_entry(|e| e.file_name() != "target") .filter_map(Result::ok) .filter(|e| e.file_name().to_str() == Some("Cargo.toml")) { diff --git a/tests/symbols-used.rs b/tests/symbols-used.rs index a1049ba64d54..f78f15103cc4 100644 --- a/tests/symbols-used.rs +++ b/tests/symbols-used.rs @@ -18,7 +18,6 @@ type Result = std::result::Result; type AnyError = Box; #[test] -#[allow(clippy::case_sensitive_file_extension_comparisons)] fn all_symbols_are_used() -> Result<()> { if option_env!("RUSTC_TEST_SUITE").is_some() { return Ok(()); diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr new file mode 100644 index 000000000000..c7490c5da027 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr @@ -0,0 +1,11 @@ +error: `mod.rs` files are required, found `src/foo.rs` + --> src/foo.rs:1:1 + | +1 | pub mod bar; + | ^ + | + = help: move `src/foo.rs` to `src/foo/mod.rs` + = note: `-D clippy::self-named-module-files` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]` + +error: could not compile `duplicated-mod-names-14697` (lib) due to 1 previous error diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml new file mode 100644 index 000000000000..569082f2f659 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml @@ -0,0 +1,11 @@ +# Should trigger when multiple mods with the same name exist and not all of them follow self-named convention. +# See issue #14697. +[package] +name = "duplicated-mod-names-14697" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs new file mode 100644 index 000000000000..46f285ca47d6 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs @@ -0,0 +1 @@ +pub mod bar; diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs new file mode 100644 index 000000000000..a85dae574816 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::self_named_module_files)] + +pub mod foo; +pub mod other; diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs new file mode 100644 index 000000000000..b52703b25740 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs @@ -0,0 +1 @@ +pub mod foo; diff --git a/tests/ui-cargo/module_style/fail_mod/Cargo.stderr b/tests/ui-cargo/module_style/fail_mod/Cargo.stderr index 902330e17853..f134943e69bf 100644 --- a/tests/ui-cargo/module_style/fail_mod/Cargo.stderr +++ b/tests/ui-cargo/module_style/fail_mod/Cargo.stderr @@ -1,19 +1,19 @@ -error: `mod.rs` files are required, found `src/bad/inner.rs` - --> src/bad/inner.rs:1:1 +error: `mod.rs` files are required, found `src/bad/inner/stuff.rs` + --> src/bad/inner/stuff.rs:1:1 | -1 | pub mod stuff; +1 | pub mod most; | ^ | - = help: move `src/bad/inner.rs` to `src/bad/inner/mod.rs` + = help: move `src/bad/inner/stuff.rs` to `src/bad/inner/stuff/mod.rs` = note: `-D clippy::self-named-module-files` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]` -error: `mod.rs` files are required, found `src/bad/inner/stuff.rs` - --> src/bad/inner/stuff.rs:1:1 +error: `mod.rs` files are required, found `src/bad/inner.rs` + --> src/bad/inner.rs:1:1 | -1 | pub mod most; +1 | pub mod stuff; | ^ | - = help: move `src/bad/inner/stuff.rs` to `src/bad/inner/stuff/mod.rs` + = help: move `src/bad/inner.rs` to `src/bad/inner/mod.rs` error: could not compile `fail-mod` (bin "fail-mod") due to 2 previous errors diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml new file mode 100644 index 000000000000..5c2fabd2283d --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml @@ -0,0 +1,10 @@ +# Should not produce FP when irrelavant path segment shares the same name with module. +# See issue #10271 and #11916. +[package] +name = "segment-with-mod-name-10271-11916" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] +members = ["foo/bar"] \ No newline at end of file diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml new file mode 100644 index 000000000000..1f68c0dccac5 --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "bar" +version = "0.1.0" +edition = "2024" +publish = false diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs new file mode 100644 index 000000000000..b52703b25740 --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs @@ -0,0 +1 @@ +pub mod foo; diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml b/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml new file mode 100644 index 000000000000..d867377545e1 --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml @@ -0,0 +1,10 @@ +# Should not lint mod tagged with `#[path = ...]` +[package] +name = "with-path-attr-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs b/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs b/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs new file mode 100644 index 000000000000..a5c2109ece7d --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::mod_module_files)] + +#[path = "bar/mod.rs"] +pub mod foo; diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml b/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml new file mode 100644 index 000000000000..ddf2ac394cdd --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml @@ -0,0 +1,10 @@ +# Should not lint mod tagged with `#[path = ...]` +[package] +name = "with-path-attr-no-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs new file mode 100644 index 000000000000..3b12aefa3d5f --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs @@ -0,0 +1,2 @@ +#[path = "bar.rs"] +mod bar; diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs new file mode 100644 index 000000000000..bf2a5d019335 --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs @@ -0,0 +1,3 @@ +#![warn(clippy::self_named_module_files)] + +pub mod foo; diff --git a/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.stderr b/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.stderr new file mode 100644 index 000000000000..51c46e4f97b2 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.stderr @@ -0,0 +1,7 @@ +error: error reading Clippy's configuration file: unknown variant `FooBar`, expected one of `crate`, `file`, `module` + --> $DIR/tests/ui-cargo/multiple_inherent_impl/config_fail/clippy.toml:1:28 + | +1 | inherent-impl-lint-scope = "FooBar" + | ^^^^^^^^ + +error: could not compile `config_fail` (bin "config_fail") due to 1 previous error diff --git a/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.toml b/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.toml new file mode 100644 index 000000000000..0a4cb6e368f6 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/config_fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "config_fail" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/multiple_inherent_impl/config_fail/clippy.toml b/tests/ui-cargo/multiple_inherent_impl/config_fail/clippy.toml new file mode 100644 index 000000000000..7ad426743fb8 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/config_fail/clippy.toml @@ -0,0 +1 @@ +inherent-impl-lint-scope = "FooBar" diff --git a/tests/ui-cargo/multiple_inherent_impl/config_fail/src/main.rs b/tests/ui-cargo/multiple_inherent_impl/config_fail/src/main.rs new file mode 100644 index 000000000000..7d5e78302248 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/config_fail/src/main.rs @@ -0,0 +1,3 @@ +#![allow(dead_code)] +#![deny(clippy::multiple_inherent_impl)] +fn main() {} diff --git a/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.stderr b/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.stderr new file mode 100644 index 000000000000..15e0086cf99c --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.stderr @@ -0,0 +1,57 @@ +error: multiple implementations of this structure + --> src/main.rs:11:1 + | +11 | / impl S { +12 | | //^ Must trigger +13 | | fn second() {} +14 | | } + | |_^ + | +note: first implementation here + --> src/main.rs:7:1 + | + 7 | / impl S { + 8 | | fn first() {} + 9 | | } + | |_^ +note: the lint level is defined here + --> src/main.rs:2:9 + | + 2 | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> src/main.rs:22:5 + | +22 | / impl T { +23 | | //^ Must trigger +24 | | fn second() {} +25 | | } + | |_____^ + | +note: first implementation here + --> src/main.rs:16:1 + | +16 | / impl T { +17 | | fn first() {} +18 | | } + | |_^ + +error: multiple implementations of this structure + --> src/main.rs:36:1 + | +36 | / impl b::T { +37 | | //^ Must trigger +38 | | fn second() {} +39 | | } + | |_^ + | +note: first implementation here + --> src/b.rs:4:1 + | + 4 | / impl T { + 5 | | fn first() {} + 6 | | } + | |_^ + +error: could not compile `crate_fail` (bin "crate_fail") due to 3 previous errors diff --git a/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.toml b/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.toml new file mode 100644 index 000000000000..a280da1bd896 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/crate_fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crate_fail" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/multiple_inherent_impl/crate_fail/clippy.toml b/tests/ui-cargo/multiple_inherent_impl/crate_fail/clippy.toml new file mode 100644 index 000000000000..262e42e6fcca --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/crate_fail/clippy.toml @@ -0,0 +1 @@ +inherent-impl-lint-scope = "crate" diff --git a/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/b.rs b/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/b.rs new file mode 100644 index 000000000000..9d01e61925f7 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/b.rs @@ -0,0 +1,6 @@ +pub struct S; +pub struct T; + +impl T { + fn first() {} +} diff --git a/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/main.rs b/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/main.rs new file mode 100644 index 000000000000..ad95a4295509 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/crate_fail/src/main.rs @@ -0,0 +1,41 @@ +#![allow(dead_code)] +#![deny(clippy::multiple_inherent_impl)] + +struct S; +struct T; + +impl S { + fn first() {} +} + +impl S { + //^ Must trigger + fn second() {} +} + +impl T { + fn first() {} +} + +mod a { + use super::T; + impl T { + //^ Must trigger + fn second() {} + } +} + +mod b; + +impl b::S { + //^ Must NOT trigger + fn first() {} + fn second() {} +} + +impl b::T { + //^ Must trigger + fn second() {} +} + +fn main() {} diff --git a/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.stderr b/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.stderr new file mode 100644 index 000000000000..eb7cf4e0e6ff --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.stderr @@ -0,0 +1,58 @@ +error: multiple implementations of this structure + --> src/main.rs:13:5 + | +13 | / impl S { +14 | | //^ Must trigger +15 | | fn second() {} +16 | | } + | |_____^ + | +note: first implementation here + --> src/main.rs:6:1 + | + 6 | / impl S { + 7 | | fn first() {} + 8 | | } + | |_^ +note: the lint level is defined here + --> src/main.rs:2:9 + | + 2 | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> src/main.rs:26:5 + | +26 | / impl S { +27 | | //^ Must trigger +28 | | +29 | | fn second() {} +30 | | } + | |_____^ + | +note: first implementation here + --> src/main.rs:22:5 + | +22 | / impl S { +23 | | fn first() {} +24 | | } + | |_____^ + +error: multiple implementations of this structure + --> src/c.rs:17:5 + | +17 | / impl T { +18 | | //^ Must trigger +19 | | fn second() {} +20 | | } + | |_____^ + | +note: first implementation here + --> src/c.rs:10:5 + | +10 | / impl T { +11 | | fn first() {} +12 | | } + | |_____^ + +error: could not compile `file_fail` (bin "file_fail") due to 3 previous errors diff --git a/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.toml b/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.toml new file mode 100644 index 000000000000..7f767c65b98c --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/file_fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "file_fail" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/multiple_inherent_impl/file_fail/clippy.toml b/tests/ui-cargo/multiple_inherent_impl/file_fail/clippy.toml new file mode 100644 index 000000000000..4229797a91f3 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/file_fail/clippy.toml @@ -0,0 +1 @@ +inherent-impl-lint-scope = "file" diff --git a/tests/ui-cargo/multiple_inherent_impl/file_fail/src/c.rs b/tests/ui-cargo/multiple_inherent_impl/file_fail/src/c.rs new file mode 100644 index 000000000000..7757061e87b0 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/file_fail/src/c.rs @@ -0,0 +1,21 @@ +pub struct S; +struct T; + +impl S { + fn first() {} +} + +mod d { + use super::T; + impl T { + fn first() {} + } +} + +mod e { + use super::T; + impl T { + //^ Must trigger + fn second() {} + } +} diff --git a/tests/ui-cargo/multiple_inherent_impl/file_fail/src/main.rs b/tests/ui-cargo/multiple_inherent_impl/file_fail/src/main.rs new file mode 100644 index 000000000000..97514fd30e04 --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/file_fail/src/main.rs @@ -0,0 +1,40 @@ +#![allow(dead_code)] +#![deny(clippy::multiple_inherent_impl)] + +struct S; + +impl S { + fn first() {} +} + +mod a { + use super::S; + + impl S { + //^ Must trigger + fn second() {} + } +} + +mod b { + struct S; + + impl S { + fn first() {} + } + + impl S { + //^ Must trigger + + fn second() {} + } +} + +mod c; + +impl c::S { + //^ Must NOT trigger + fn second() {} +} + +fn main() {} diff --git a/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.stderr b/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.stderr new file mode 100644 index 000000000000..dd02afa1d39b --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.stderr @@ -0,0 +1,41 @@ +error: multiple implementations of this structure + --> src/main.rs:26:5 + | +26 | / impl S { +27 | | //^ Must trigger +28 | | +29 | | fn second() {} +30 | | } + | |_____^ + | +note: first implementation here + --> src/main.rs:22:5 + | +22 | / impl S { +23 | | fn first() {} +24 | | } + | |_____^ +note: the lint level is defined here + --> src/main.rs:2:9 + | + 2 | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> src/c.rs:12:1 + | +12 | / impl T { +13 | | //^ Must trigger +14 | | fn second() {} +15 | | } + | |_^ + | +note: first implementation here + --> src/c.rs:8:1 + | + 8 | / impl T { + 9 | | fn first() {} +10 | | } + | |_^ + +error: could not compile `module_fail` (bin "module_fail") due to 2 previous errors diff --git a/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.toml b/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.toml new file mode 100644 index 000000000000..4b57af0d8fef --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/module_fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "module_fail" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/multiple_inherent_impl/module_fail/clippy.toml b/tests/ui-cargo/multiple_inherent_impl/module_fail/clippy.toml new file mode 100644 index 000000000000..293dfa183fce --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/module_fail/clippy.toml @@ -0,0 +1 @@ +inherent-impl-lint-scope = "module" diff --git a/tests/ui-cargo/multiple_inherent_impl/module_fail/src/c.rs b/tests/ui-cargo/multiple_inherent_impl/module_fail/src/c.rs new file mode 100644 index 000000000000..1d51ebe5d22a --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/module_fail/src/c.rs @@ -0,0 +1,15 @@ +pub struct S; +struct T; + +impl S { + fn first() {} +} + +impl T { + fn first() {} +} + +impl T { + //^ Must trigger + fn second() {} +} diff --git a/tests/ui-cargo/multiple_inherent_impl/module_fail/src/main.rs b/tests/ui-cargo/multiple_inherent_impl/module_fail/src/main.rs new file mode 100644 index 000000000000..17b1b777f7be --- /dev/null +++ b/tests/ui-cargo/multiple_inherent_impl/module_fail/src/main.rs @@ -0,0 +1,40 @@ +#![allow(dead_code)] +#![deny(clippy::multiple_inherent_impl)] + +struct S; + +impl S { + fn first() {} +} + +mod a { + use super::S; + + impl S { + //^ Must NOT trigger + fn second() {} + } +} + +mod b { + struct S; + + impl S { + fn first() {} + } + + impl S { + //^ Must trigger + + fn second() {} + } +} + +mod c; + +impl c::S { + //^ Must NOT trigger + fn second() {} +} + +fn main() {} diff --git a/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr index 59a7146ac90f..bfe2486c8502 100644 --- a/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr +++ b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr @@ -5,10 +5,10 @@ error: module has unnecessary safety comment | ^^^^^^^^ | help: consider removing the safety comment - --> src/main.rs:1:1 + --> src/main.rs:1:4 | 1 | // SAFETY: ... - | ^^^^^^^^^^^^^^ + | ^^^^^^^ = note: requested on the command line with `-D clippy::unnecessary-safety-comment` error: module has unnecessary safety comment @@ -18,9 +18,9 @@ error: module has unnecessary safety comment | ^^^^^^^^ | help: consider removing the safety comment - --> src/main.rs:4:1 + --> src/main.rs:4:4 | 4 | // SAFETY: ... - | ^^^^^^^^^^^^^^ + | ^^^^^^^ error: could not compile `undocumented_unsafe_blocks` (bin "undocumented_unsafe_blocks") due to 2 previous errors diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed index 0dc0fc230c8d..ec45dfd2033a 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed @@ -1,7 +1,7 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] +#![warn(clippy::collapsible_if)] #[rustfmt::skip] -#[warn(clippy::collapsible_if)] fn main() { let (x, y) = ("hello", "world"); @@ -48,3 +48,20 @@ fn main() { } //~^^^^^^ collapsible_else_if } + +fn issue_13365() { + // the comments don't stop us from linting, so the the `expect` *will* be fulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } +} diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.rs b/tests/ui-toml/collapsible_if/collapsible_else_if.rs index 8344c122f16c..54315a3c32bf 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.rs +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.rs @@ -1,7 +1,7 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] +#![warn(clippy::collapsible_if)] #[rustfmt::skip] -#[warn(clippy::collapsible_if)] fn main() { let (x, y) = ("hello", "world"); @@ -53,3 +53,20 @@ fn main() { } //~^^^^^^ collapsible_else_if } + +fn issue_13365() { + // the comments don't stop us from linting, so the the `expect` *will* be fulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } +} diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs index ecb43dc34a8a..b28e46af0a46 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -1,5 +1,3 @@ -#![allow(clippy::uninlined_format_args)] - fn main() {} #[warn(clippy::cognitive_complexity)] @@ -8,7 +6,7 @@ fn cognitive_complexity() { let x = vec![1, 2, 3]; for i in x { if i == 1 { - println!("{}", i); + println!("{i}"); } } } diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr index 627498dc175c..95b0508189e9 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr @@ -11,7 +11,7 @@ LL | blacklisted-names = [ "..", "wibble" ] | ^^^^^^^^^^^^^^^^^ error: the function has a cognitive complexity of (3/2) - --> tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs:6:4 + --> tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs:4:4 | LL | fn cognitive_complexity() { | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.rs b/tests/ui-toml/excessive_nesting/excessive_nesting.rs index 205cd8ba4ee8..001a6ceb1b17 100644 --- a/tests/ui-toml/excessive_nesting/excessive_nesting.rs +++ b/tests/ui-toml/excessive_nesting/excessive_nesting.rs @@ -9,7 +9,7 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::never_loop, - clippy::needless_if, + clippy::needless_ifs, clippy::collapsible_if, clippy::blocks_in_conditions, clippy::single_match, diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed index 2877871d0bf4..36540bf1dcf7 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed @@ -1,4 +1,3 @@ -#![allow(clippy::uninlined_format_args)] #![deny(clippy::index_refutable_slice)] fn below_limit() { diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs index f958b92a102a..da76bb20fd96 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs @@ -1,4 +1,3 @@ -#![allow(clippy::uninlined_format_args)] #![deny(clippy::index_refutable_slice)] fn below_limit() { diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr index e1a8941e102f..022deb330e6e 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr @@ -1,11 +1,11 @@ error: this binding can be a slice pattern to avoid indexing - --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:6:17 + --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:5:17 | LL | if let Some(slice) = slice { | ^^^^^ | note: the lint level is defined here - --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:2:9 + --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:1:9 | LL | #![deny(clippy::index_refutable_slice)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed index 8da607ec6584..419e62f92f46 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed @@ -67,3 +67,11 @@ fn main() { printlnfoo!["test if printlnfoo is triggered by println"]; } + +#[rustfmt::skip] +#[expect(clippy::no_effect)] +fn issue9913() { + println!("hello world"); + [0]; // separate statement, not indexing into the result of println. + //~^^ nonstandard_macro_braces +} diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs index e35844a209fa..b0bbced4ea3c 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs @@ -67,3 +67,11 @@ fn main() { printlnfoo!["test if printlnfoo is triggered by println"]; } + +#[rustfmt::skip] +#[expect(clippy::no_effect)] +fn issue9913() { + println! {"hello world"} + [0]; // separate statement, not indexing into the result of println. + //~^^ nonstandard_macro_braces +} diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr index fda6addc7aa3..87325f05c9bc 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr @@ -54,5 +54,11 @@ error: use of irregular braces for `eprint!` macro LL | eprint!("test if user config overrides defaults"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]` -error: aborting due to 8 previous errors +error: use of irregular braces for `println!` macro + --> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:74:5 + | +LL | println! {"hello world"} + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `println!("hello world");` + +error: aborting due to 9 previous errors diff --git a/tests/ui/ref_option/all/clippy.toml b/tests/ui-toml/ref_option/all/clippy.toml similarity index 100% rename from tests/ui/ref_option/all/clippy.toml rename to tests/ui-toml/ref_option/all/clippy.toml diff --git a/tests/ui/ref_option/private/clippy.toml b/tests/ui-toml/ref_option/private/clippy.toml similarity index 100% rename from tests/ui/ref_option/private/clippy.toml rename to tests/ui-toml/ref_option/private/clippy.toml diff --git a/tests/ui-toml/ref_option/ref_option.all.fixed b/tests/ui-toml/ref_option/ref_option.all.fixed new file mode 100644 index 000000000000..f8f097e9a75e --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.all.fixed @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: Option<&u8>) {} +//~^ ref_option +fn opt_gen(a: Option<&T>) {} +//~^ ref_option +fn opt_string(a: std::option::Option<&String>) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> Option<&'static u8> { + //~^ ref_option + panic!() +} +fn mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~^ ref_option +fn ret_box<'a>() -> Option<&'a Box> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: Option<&String>) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: Option<&()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> Option<&String> { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: Option<&()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> Option<&String> { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option.all.stderr b/tests/ui-toml/ref_option/ref_option.all.stderr similarity index 62% rename from tests/ui/ref_option/ref_option.all.stderr rename to tests/ui-toml/ref_option/ref_option.all.stderr index bd43c28336eb..45ce105e0308 100644 --- a/tests/ui/ref_option/ref_option.all.stderr +++ b/tests/ui-toml/ref_option/ref_option.all.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:8:1 + --> tests/ui-toml/ref_option/ref_option.rs:9:1 | LL | fn opt_u8(a: &Option) {} | ^^^^^^^^^^^^^-----------^^^^ @@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option) {} = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:10:1 + --> tests/ui-toml/ref_option/ref_option.rs:11:1 | LL | fn opt_gen(a: &Option) {} | ^^^^^^^^^^^^^^^^^----------^^^^ @@ -18,7 +18,7 @@ LL | fn opt_gen(a: &Option) {} | help: change this to: `Option<&T>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:12:1 + --> tests/ui-toml/ref_option/ref_option.rs:13:1 | LL | fn opt_string(a: &std::option::Option) {} | ^^^^^^^^^^^^^^^^^----------------------------^^^^ @@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option) {} | help: change this to: `std::option::Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:14:1 + --> tests/ui-toml/ref_option/ref_option.rs:15:1 | -LL | fn ret_string<'a>(p: &'a str) -> &'a Option { - | ^ -------------- help: change this to: `Option<&'a u8>` +LL | fn ret_u8<'a>(p: &'a str) -> &'a Option { + | ^ -------------- help: change this to: `Option<&'a u8>` | _| | | LL | | @@ -38,10 +38,10 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:18:1 + --> tests/ui-toml/ref_option/ref_option.rs:19:1 | -LL | fn ret_string_static() -> &'static Option { - | ^ ------------------- help: change this to: `Option<&'static u8>` +LL | fn ret_u8_static() -> &'static Option { + | ^ ------------------- help: change this to: `Option<&'static u8>` | _| | | LL | | @@ -50,7 +50,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:22:1 + --> tests/ui-toml/ref_option/ref_option.rs:23:1 | LL | fn mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:24:1 + --> tests/ui-toml/ref_option/ref_option.rs:25:1 | LL | fn ret_box<'a>() -> &'a Option> { | ^ ------------------- help: change this to: `Option<&'a Box>` @@ -74,7 +74,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:29:1 + --> tests/ui-toml/ref_option/ref_option.rs:30:1 | LL | pub fn pub_opt_string(a: &Option) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^ @@ -82,7 +82,7 @@ LL | pub fn pub_opt_string(a: &Option) {} | help: change this to: `Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:31:1 + --> tests/ui-toml/ref_option/ref_option.rs:32:1 | LL | pub fn pub_mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,39 +94,7 @@ LL + pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:35:5 - | -LL | fn pub_trait_opt(&self, a: &Option>); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^^ - | | - | help: change this to: `Option<&Vec>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:37:5 - | -LL | fn pub_trait_ret(&self) -> &Option>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^ - | | - | help: change this to: `Option<&Vec>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:42:5 - | -LL | fn trait_opt(&self, a: &Option); - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:44:5 - | -LL | fn trait_ret(&self) -> &Option; - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:51:5 + --> tests/ui-toml/ref_option/ref_option.rs:38:5 | LL | pub fn pub_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -134,7 +102,7 @@ LL | pub fn pub_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:53:5 + --> tests/ui-toml/ref_option/ref_option.rs:40:5 | LL | pub fn pub_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -146,7 +114,7 @@ LL | | } | |_____^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:58:5 + --> tests/ui-toml/ref_option/ref_option.rs:45:5 | LL | fn private_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -154,7 +122,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:60:5 + --> tests/ui-toml/ref_option/ref_option.rs:47:5 | LL | fn private_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -165,5 +133,5 @@ LL | | panic!() LL | | } | |_____^ -error: aborting due to 17 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui-toml/ref_option/ref_option.private.fixed b/tests/ui-toml/ref_option/ref_option.private.fixed new file mode 100644 index 000000000000..4dd14a822067 --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.private.fixed @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: Option<&u8>) {} +//~^ ref_option +fn opt_gen(a: Option<&T>) {} +//~^ ref_option +fn opt_string(a: std::option::Option<&String>) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> Option<&'static u8> { + //~^ ref_option + panic!() +} +fn mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~^ ref_option +fn ret_box<'a>() -> Option<&'a Box> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: &Option) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: &Option, b: &Option>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> &Option { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: Option<&()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> Option<&String> { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option.private.stderr b/tests/ui-toml/ref_option/ref_option.private.stderr similarity index 63% rename from tests/ui/ref_option/ref_option.private.stderr rename to tests/ui-toml/ref_option/ref_option.private.stderr index 88c65e429d87..a63efd60a036 100644 --- a/tests/ui/ref_option/ref_option.private.stderr +++ b/tests/ui-toml/ref_option/ref_option.private.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:8:1 + --> tests/ui-toml/ref_option/ref_option.rs:9:1 | LL | fn opt_u8(a: &Option) {} | ^^^^^^^^^^^^^-----------^^^^ @@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option) {} = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:10:1 + --> tests/ui-toml/ref_option/ref_option.rs:11:1 | LL | fn opt_gen(a: &Option) {} | ^^^^^^^^^^^^^^^^^----------^^^^ @@ -18,7 +18,7 @@ LL | fn opt_gen(a: &Option) {} | help: change this to: `Option<&T>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:12:1 + --> tests/ui-toml/ref_option/ref_option.rs:13:1 | LL | fn opt_string(a: &std::option::Option) {} | ^^^^^^^^^^^^^^^^^----------------------------^^^^ @@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option) {} | help: change this to: `std::option::Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:14:1 + --> tests/ui-toml/ref_option/ref_option.rs:15:1 | -LL | fn ret_string<'a>(p: &'a str) -> &'a Option { - | ^ -------------- help: change this to: `Option<&'a u8>` +LL | fn ret_u8<'a>(p: &'a str) -> &'a Option { + | ^ -------------- help: change this to: `Option<&'a u8>` | _| | | LL | | @@ -38,10 +38,10 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:18:1 + --> tests/ui-toml/ref_option/ref_option.rs:19:1 | -LL | fn ret_string_static() -> &'static Option { - | ^ ------------------- help: change this to: `Option<&'static u8>` +LL | fn ret_u8_static() -> &'static Option { + | ^ ------------------- help: change this to: `Option<&'static u8>` | _| | | LL | | @@ -50,7 +50,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:22:1 + --> tests/ui-toml/ref_option/ref_option.rs:23:1 | LL | fn mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:24:1 + --> tests/ui-toml/ref_option/ref_option.rs:25:1 | LL | fn ret_box<'a>() -> &'a Option> { | ^ ------------------- help: change this to: `Option<&'a Box>` @@ -74,23 +74,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:42:5 - | -LL | fn trait_opt(&self, a: &Option); - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:44:5 - | -LL | fn trait_ret(&self) -> &Option; - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:58:5 + --> tests/ui-toml/ref_option/ref_option.rs:45:5 | LL | fn private_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -98,7 +82,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:60:5 + --> tests/ui-toml/ref_option/ref_option.rs:47:5 | LL | fn private_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -109,5 +93,5 @@ LL | | panic!() LL | | } | |_____^ -error: aborting due to 11 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui-toml/ref_option/ref_option.rs b/tests/ui-toml/ref_option/ref_option.rs new file mode 100644 index 000000000000..8397c2213d17 --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.rs @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: &Option) {} +//~^ ref_option +fn opt_gen(a: &Option) {} +//~^ ref_option +fn opt_string(a: &std::option::Option) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> &'a Option { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> &'static Option { + //~^ ref_option + panic!() +} +fn mult_string(a: &Option, b: &Option>) {} +//~^ ref_option +fn ret_box<'a>() -> &'a Option> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: &Option) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: &Option, b: &Option>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> &Option { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> &Option { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option_traits.all.stderr b/tests/ui-toml/ref_option/ref_option_traits.all.stderr similarity index 85% rename from tests/ui/ref_option/ref_option_traits.all.stderr rename to tests/ui-toml/ref_option/ref_option_traits.all.stderr index 886bf2b03498..602e148be601 100644 --- a/tests/ui/ref_option/ref_option_traits.all.stderr +++ b/tests/ui-toml/ref_option/ref_option_traits.all.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:9:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:10:5 | LL | fn pub_trait_opt(&self, a: &Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^^ @@ -10,7 +10,7 @@ LL | fn pub_trait_opt(&self, a: &Option>); = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:11:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:12:5 | LL | fn pub_trait_ret(&self) -> &Option>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^ @@ -18,7 +18,7 @@ LL | fn pub_trait_ret(&self) -> &Option>; | help: change this to: `Option<&Vec>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:16:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:17:5 | LL | fn trait_opt(&self, a: &Option); | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ @@ -26,7 +26,7 @@ LL | fn trait_opt(&self, a: &Option); | help: change this to: `Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:18:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:19:5 | LL | fn trait_ret(&self) -> &Option; | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ diff --git a/tests/ui/ref_option/ref_option_traits.private.stderr b/tests/ui-toml/ref_option/ref_option_traits.private.stderr similarity index 86% rename from tests/ui/ref_option/ref_option_traits.private.stderr rename to tests/ui-toml/ref_option/ref_option_traits.private.stderr index cfab7fa5734c..20bea400edfe 100644 --- a/tests/ui/ref_option/ref_option_traits.private.stderr +++ b/tests/ui-toml/ref_option/ref_option_traits.private.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:16:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:17:5 | LL | fn trait_opt(&self, a: &Option); | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ @@ -10,7 +10,7 @@ LL | fn trait_opt(&self, a: &Option); = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:18:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:19:5 | LL | fn trait_ret(&self) -> &Option; | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ diff --git a/tests/ui-toml/ref_option/ref_option_traits.rs b/tests/ui-toml/ref_option/ref_option_traits.rs new file mode 100644 index 000000000000..e1477d7f8463 --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option_traits.rs @@ -0,0 +1,71 @@ +//@no-rustfix: fixes are only done to traits, not the impls +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![warn(clippy::ref_option)] + +pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + //~[all]^ ref_option + fn pub_trait_ret(&self) -> &Option>; + //~[all]^ ref_option +} + +trait PrivateTrait { + fn trait_opt(&self, a: &Option); + //~^ ref_option + fn trait_ret(&self) -> &Option; + //~^ ref_option +} + +pub struct PubStruct; + +impl PubTrait for PubStruct { + fn pub_trait_opt(&self, a: &Option>) {} + fn pub_trait_ret(&self) -> &Option> { + panic!() + } +} + +struct PrivateStruct; + +impl PrivateTrait for PrivateStruct { + fn trait_opt(&self, a: &Option) {} + fn trait_ret(&self) -> &Option { + panic!() + } +} + +pub mod external { + proc_macros::external!( + pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + fn pub_trait_ret(&self) -> &Option>; + } + + trait PrivateTrait { + fn trait_opt(&self, a: &Option); + fn trait_ret(&self) -> &Option; + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + fn pub_trait_ret(&self) -> &Option>; + } + + trait PrivateTrait { + fn trait_opt(&self, a: &Option); + fn trait_ret(&self) -> &Option; + } + ); +} + +fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 20aeb4bb8498..2d9503c5ac53 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -49,6 +49,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect excessive-nesting-threshold future-size-threshold ignore-interior-mutability + inherent-impl-lint-scope large-error-threshold lint-commented-code literal-representation-threshold @@ -66,6 +67,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect msrv pass-by-value-size-limit pub-underscore-fields-behavior + recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold @@ -144,6 +146,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect excessive-nesting-threshold future-size-threshold ignore-interior-mutability + inherent-impl-lint-scope large-error-threshold lint-commented-code literal-representation-threshold @@ -161,6 +164,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect msrv pass-by-value-size-limit pub-underscore-fields-behavior + recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold @@ -239,6 +243,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni excessive-nesting-threshold future-size-threshold ignore-interior-mutability + inherent-impl-lint-scope large-error-threshold lint-commented-code literal-representation-threshold @@ -256,6 +261,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni msrv pass-by-value-size-limit pub-underscore-fields-behavior + recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr index bfc14be5421f..61e5af81d827 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr @@ -247,10 +247,10 @@ LL | const BIG_NUMBER: i32 = 1000000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` @@ -289,10 +289,10 @@ LL | | }; | |______^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8 | LL | // SAFETY: this is more than one level away, so it should warn - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12 @@ -342,17 +342,137 @@ LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 }; | = help: consider adding a safety comment on the preceding line +error: constant has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:701:5 + | +LL | const UNIX_EPOCH_JULIAN_DAY: i32 = + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:699:8 + | +LL | // SAFETY: fail ONLY if `accept-comment-above-attribute = false` + | ^^^^^^^ + error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:719:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5 | LL | _ = bar(); | ^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:718:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8 | LL | // SAFETY: unnecessary_safety_comment triggers here - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5 + | +LL | mod x {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:746:5 + | +LL | mod y {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:744:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5 + | +LL | mod z {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:759:5 + | +LL | mod y {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:757:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: statement has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:774:9 + | +LL | let x = 34; + | ^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:772:12 + | +LL | // SAFETY: ... + | ^^^^^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5 + | +LL | unsafe fn unsafe_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider changing the `safety` comment for a `# Safety` doc comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8 + | +LL | // SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5 + | +LL | unsafe fn unsafe_block_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider changing the `safety` comment for a `# Safety` doc comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8 + | +LL | SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5 + | +LL | fn safe_comment() {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8 + | +LL | // SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5 + | +LL | fn safe_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9 + | +LL | /// SAFETY: Bla + | ^^^^^^^ -error: aborting due to 40 previous errors +error: aborting due to 50 previous errors diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr index cebfc48a884f..e252cffea916 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr @@ -247,10 +247,10 @@ LL | const BIG_NUMBER: i32 = 1000000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` @@ -297,10 +297,10 @@ LL | | }; | |______^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8 | LL | // SAFETY: this is more than one level away, so it should warn - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12 @@ -439,24 +439,104 @@ LL | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian = help: consider adding a safety comment on the preceding line error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:719:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5 | LL | _ = bar(); | ^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:718:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8 | LL | // SAFETY: unnecessary_safety_comment triggers here - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:733:12 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:735:12 | LL | return unsafe { h() }; | ^^^^^^^^^^^^^^ | = help: consider adding a safety comment on the preceding line -error: aborting due to 53 previous errors +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5 + | +LL | mod x {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: module has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5 + | +LL | mod z {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8 + | +LL | // SAFETY: ... + | ^^^^^^^ + +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:766:9 + | +LL | unsafe {} + | ^^^^^^^^^ + | + = help: consider adding a safety comment on the preceding line + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5 + | +LL | unsafe fn unsafe_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider changing the `safety` comment for a `# Safety` doc comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8 + | +LL | // SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5 + | +LL | unsafe fn unsafe_block_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider changing the `safety` comment for a `# Safety` doc comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8 + | +LL | SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5 + | +LL | fn safe_comment() {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8 + | +LL | // SAFETY: Bla + | ^^^^^^^ + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5 + | +LL | fn safe_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9 + | +LL | /// SAFETY: Bla + | ^^^^^^^ + +error: aborting due to 60 previous errors diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs index a2d7c1b6c796..db9e81cf10a1 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs @@ -701,6 +701,8 @@ mod issue_11709_regression { const UNIX_EPOCH_JULIAN_DAY: i32 = unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian_day_just_make_this_line_longer(); //~[disabled]^ undocumented_unsafe_blocks + // This shouldn't be linted, Issue #15755 + //~[default]^^^^ unnecessary_safety_comment } fn issue_13039() { @@ -734,4 +736,64 @@ fn rfl_issue15034() -> i32 { //~[disabled]^ ERROR: unsafe block missing a safety comment } +mod issue_14555 { + // SAFETY: ... + mod x {} + //~^ unnecessary_safety_comment + + // SAFETY: ... + #[doc(hidden)] + mod y {} + //~[default]^ unnecessary_safety_comment + + #[doc(hidden)] + // SAFETY: ... + mod z {} + //~^ unnecessary_safety_comment +} + +mod issue_15754 { + #[must_use] + // SAFETY: ... + #[doc(hidden)] + mod y {} + //~[default]^ unnecessary_safety_comment + + fn foo() { + #[doc(hidden)] + // SAFETY: unnecessary_safety_comment should not trigger here + #[allow(unsafe_code)] + unsafe {} + //~[disabled]^ undocumented_unsafe_blocks + } + + fn bar() { + #[doc(hidden)] + // SAFETY: ... + #[allow(clippy::unnecessary_cast)] + let x = 34; + //~[default]^ unnecessary_safety_comment + } +} + +mod unsafe_fns { + // SAFETY: Bla + unsafe fn unsafe_comment() {} + //~^ unnecessary_safety_comment + + /* + SAFETY: Bla + */ + unsafe fn unsafe_block_comment() {} + //~^ unnecessary_safety_comment + + // SAFETY: Bla + fn safe_comment() {} + //~^ unnecessary_safety_comment + + /// SAFETY: Bla + fn safe_doc_comment() {} + //~^ unnecessary_safety_comment +} + fn main() {} diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.fixed b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.fixed new file mode 100644 index 000000000000..cc8d5028b727 --- /dev/null +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.fixed @@ -0,0 +1,20 @@ +//@aux-build:../../ui/auxiliary/proc_macro_unsafe.rs +//@revisions: default disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/default +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/disabled + +#![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)] + +mod unsafe_fns { + /// # Safety Bla + unsafe fn unsafe_doc_comment() {} + //~^ unnecessary_safety_comment + + /** + * # Safety Bla + */ + unsafe fn unsafe_block_doc_comment() {} + //~^ unnecessary_safety_comment +} + +fn main() {} diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.stderr new file mode 100644 index 000000000000..95e47dc7ed63 --- /dev/null +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.default.stderr @@ -0,0 +1,22 @@ +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs:10:5 + | +LL | /// SAFETY: Bla + | ------- help: consider changing it to a `# Safety` section: `# Safety` +LL | unsafe fn unsafe_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs:16:5 + | +LL | * SAFETY: Bla + | ------- help: consider changing it to a `# Safety` section: `# Safety` +LL | */ +LL | unsafe fn unsafe_block_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.fixed b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.fixed new file mode 100644 index 000000000000..cc8d5028b727 --- /dev/null +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.fixed @@ -0,0 +1,20 @@ +//@aux-build:../../ui/auxiliary/proc_macro_unsafe.rs +//@revisions: default disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/default +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/disabled + +#![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)] + +mod unsafe_fns { + /// # Safety Bla + unsafe fn unsafe_doc_comment() {} + //~^ unnecessary_safety_comment + + /** + * # Safety Bla + */ + unsafe fn unsafe_block_doc_comment() {} + //~^ unnecessary_safety_comment +} + +fn main() {} diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.stderr new file mode 100644 index 000000000000..95e47dc7ed63 --- /dev/null +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.disabled.stderr @@ -0,0 +1,22 @@ +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs:10:5 + | +LL | /// SAFETY: Bla + | ------- help: consider changing it to a `# Safety` section: `# Safety` +LL | unsafe fn unsafe_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` + +error: function has unnecessary safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs:16:5 + | +LL | * SAFETY: Bla + | ------- help: consider changing it to a `# Safety` section: `# Safety` +LL | */ +LL | unsafe fn unsafe_block_doc_comment() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs new file mode 100644 index 000000000000..14b91126caa6 --- /dev/null +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks_fixable.rs @@ -0,0 +1,20 @@ +//@aux-build:../../ui/auxiliary/proc_macro_unsafe.rs +//@revisions: default disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/default +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_unsafe_blocks/disabled + +#![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)] + +mod unsafe_fns { + /// SAFETY: Bla + unsafe fn unsafe_doc_comment() {} + //~^ unnecessary_safety_comment + + /** + * SAFETY: Bla + */ + unsafe fn unsafe_block_doc_comment() {} + //~^ unnecessary_safety_comment +} + +fn main() {} diff --git a/tests/ui-toml/use_self/default/clippy.toml b/tests/ui-toml/use_self/default/clippy.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/ui-toml/use_self/disabled/clippy.toml b/tests/ui-toml/use_self/disabled/clippy.toml new file mode 100644 index 000000000000..866cc5c624b5 --- /dev/null +++ b/tests/ui-toml/use_self/disabled/clippy.toml @@ -0,0 +1 @@ +recursive-self-in-type-definitions = false diff --git a/tests/ui-toml/use_self/use_self.default.fixed b/tests/ui-toml/use_self/use_self.default.fixed new file mode 100644 index 000000000000..288e304c60a8 --- /dev/null +++ b/tests/ui-toml/use_self/use_self.default.fixed @@ -0,0 +1,17 @@ +//@revisions: default disabled +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/default + +#![warn(clippy::use_self)] + +fn main() {} + +struct Basic { + flag: Option>, + //~[default]^ use_self +} + +impl Basic { + fn x(_: Self) {} + //~[default,disabled]^ use_self +} diff --git a/tests/ui-toml/use_self/use_self.default.stderr b/tests/ui-toml/use_self/use_self.default.stderr new file mode 100644 index 000000000000..34cfdfd938aa --- /dev/null +++ b/tests/ui-toml/use_self/use_self.default.stderr @@ -0,0 +1,17 @@ +error: unnecessary structure name repetition + --> tests/ui-toml/use_self/use_self.rs:10:22 + | +LL | flag: Option>, + | ^^^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::use_self)]` + +error: unnecessary structure name repetition + --> tests/ui-toml/use_self/use_self.rs:15:13 + | +LL | fn x(_: Basic) {} + | ^^^^^ help: use the applicable keyword: `Self` + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/use_self/use_self.disabled.fixed b/tests/ui-toml/use_self/use_self.disabled.fixed new file mode 100644 index 000000000000..227606e69005 --- /dev/null +++ b/tests/ui-toml/use_self/use_self.disabled.fixed @@ -0,0 +1,17 @@ +//@revisions: default disabled +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/default + +#![warn(clippy::use_self)] + +fn main() {} + +struct Basic { + flag: Option>, + //~[default]^ use_self +} + +impl Basic { + fn x(_: Self) {} + //~[default,disabled]^ use_self +} diff --git a/tests/ui-toml/use_self/use_self.disabled.stderr b/tests/ui-toml/use_self/use_self.disabled.stderr new file mode 100644 index 000000000000..1801744f0d41 --- /dev/null +++ b/tests/ui-toml/use_self/use_self.disabled.stderr @@ -0,0 +1,11 @@ +error: unnecessary structure name repetition + --> tests/ui-toml/use_self/use_self.rs:15:13 + | +LL | fn x(_: Basic) {} + | ^^^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::use_self)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/use_self/use_self.rs b/tests/ui-toml/use_self/use_self.rs new file mode 100644 index 000000000000..d20006d4df1a --- /dev/null +++ b/tests/ui-toml/use_self/use_self.rs @@ -0,0 +1,17 @@ +//@revisions: default disabled +//@[disabled] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/disabled +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/use_self/default + +#![warn(clippy::use_self)] + +fn main() {} + +struct Basic { + flag: Option>, + //~[default]^ use_self +} + +impl Basic { + fn x(_: Basic) {} + //~[default,disabled]^ use_self +} diff --git a/tests/ui/as_underscore_unfixable.rs b/tests/ui/as_underscore_unfixable.rs new file mode 100644 index 000000000000..854feca7174f --- /dev/null +++ b/tests/ui/as_underscore_unfixable.rs @@ -0,0 +1,14 @@ +//@no-rustfix + +#![warn(clippy::as_underscore)] + +fn main() { + // From issue #15282 + let f = async || (); + let _: Box _> = Box::new(f) as _; + //~^ as_underscore + + let barr = || (|| ()); + let _: Box _> = Box::new(barr) as _; + //~^ as_underscore +} diff --git a/tests/ui/as_underscore_unfixable.stderr b/tests/ui/as_underscore_unfixable.stderr new file mode 100644 index 000000000000..7385bea5c650 --- /dev/null +++ b/tests/ui/as_underscore_unfixable.stderr @@ -0,0 +1,20 @@ +error: using `as _` conversion + --> tests/ui/as_underscore_unfixable.rs:8:37 + | +LL | let _: Box _> = Box::new(f) as _; + | ^^^^^^^^^^^^^^^^ + | + = help: consider giving the type explicitly + = note: `-D clippy::as-underscore` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::as_underscore)]` + +error: using `as _` conversion + --> tests/ui/as_underscore_unfixable.rs:12:33 + | +LL | let _: Box _> = Box::new(barr) as _; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider giving the type explicitly + +error: aborting due to 2 previous errors + diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index c2516c541475..f467d4966aef 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -42,8 +42,8 @@ fn main() { assert_const!(3); assert_const!(-1); - // Don't lint if based on `cfg!(..)`: assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); + //~^ assertions_on_constants let flag: bool = cfg!(not(feature = "asdf")); assert!(flag); @@ -62,9 +62,37 @@ fn main() { const _: () = assert!(N.is_power_of_two()); } +const C: bool = true; + const _: () = { assert!(true); //~^ assertions_on_constants assert!(8 == (7 + 1)); + //~^ assertions_on_constants + + assert!(C); }; + +#[clippy::msrv = "1.57"] +fn _f1() { + assert!(C); + //~^ assertions_on_constants +} + +#[clippy::msrv = "1.56"] +fn _f2() { + assert!(C); +} + +#[clippy::msrv = "1.79"] +fn _f3() { + assert!(C); + //~^ assertions_on_constants +} + +#[clippy::msrv = "1.78"] +fn _f4() { + assert!(C); + //~^ assertions_on_constants +} diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 8b7440ec4832..a996c41b6942 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -1,100 +1,140 @@ -error: `assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:10:5 | LL | assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion = note: `-D clippy::assertions-on-constants` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::assertions_on_constants)]` -error: `assert!(false)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:13:5 | LL | assert!(false); | ^^^^^^^^^^^^^^ | - = help: use `panic!()` or `unreachable!()` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:16:5 | LL | assert!(true, "true message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(false, ..)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:19:5 | LL | assert!(false, "false message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(false, ..)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:23:5 | LL | assert!(false, "{}", msg.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(true)` will be optimized out by the compiler +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:27:5 | LL | assert!(B); | ^^^^^^^^^^ | - = help: remove it + = help: consider moving this into a const block: `const { assert!(..) }` -error: `assert!(false)` should probably be replaced +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:31:5 | LL | assert!(C); | ^^^^^^^^^^ | - = help: use `panic!()` or `unreachable!()` + = help: consider moving this into a const block: `const { assert!(..) }` -error: `assert!(false, ..)` should probably be replaced +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:34:5 | LL | assert!(C, "C message"); | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: consider moving this into a const block: `const { assert!(..) }` -error: `debug_assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:37:5 | LL | debug_assert!(true); | ^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:45:5 + | +LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider moving this into a const block: `const { assert!(..) }` + +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:54:19 | LL | const _: () = assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:57:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:66:5 +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:68:5 | LL | assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion + +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:71:5 + | +LL | assert!(8 == (7 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove the assertion + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:79:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:90:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this into a const block: `const { assert!(..) }` + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:96:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` -error: aborting due to 12 previous errors +error: aborting due to 17 previous errors diff --git a/tests/ui/author/blocks.stdout b/tests/ui/author/blocks.stdout index e453299edbcf..ff9fe2425ff9 100644 --- a/tests/ui/author/blocks.stdout +++ b/tests/ui/author/blocks.stdout @@ -23,13 +23,13 @@ if let ExprKind::Block(block, None) = expr.kind && let StmtKind::Let(local) = block.stmts[0].kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind - && is_path_diagnostic_item(cx, func, sym::string_new) + && func.res(cx).is_diag_item(cx, sym::string_new) && args.is_empty() && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind && name.as_str() == "expr" && let Some(trailing_expr) = block.expr && let ExprKind::Call(func1, args1) = trailing_expr.kind - && is_path_diagnostic_item(cx, func1, sym::mem_drop) + && func1.res(cx).is_diag_item(cx, sym::mem_drop) && args1.len() == 1 { // report your lint here diff --git a/tests/ui/author/call.stdout b/tests/ui/author/call.stdout index 2b179d45112e..024121b14f9b 100644 --- a/tests/ui/author/call.stdout +++ b/tests/ui/author/call.stdout @@ -1,7 +1,7 @@ if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind - && is_path_diagnostic_item(cx, func, sym::cmp_min) + && func.res(cx).is_diag_item(cx, sym::cmp_min) && args.len() == 2 && let ExprKind::Lit(ref lit) = args[0].kind && let LitKind::Int(3, LitIntType::Unsuffixed) = lit.node diff --git a/tests/ui/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout index f02ea5bf075f..b88058f974db 100644 --- a/tests/ui/author/issue_3849.stdout +++ b/tests/ui/author/issue_3849.stdout @@ -1,7 +1,7 @@ if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind - && is_path_diagnostic_item(cx, func, sym::transmute) + && func.res(cx).is_diag_item(cx, sym::transmute) && args.len() == 1 && let PatKind::Wild = local.pat.kind { diff --git a/tests/ui/author/loop.stdout b/tests/ui/author/loop.stdout index 79794cec9269..6a3159939dd1 100644 --- a/tests/ui/author/loop.stdout +++ b/tests/ui/author/loop.stdout @@ -2,7 +2,8 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && let PatKind::Binding(BindingMode::NONE, _, name, None) = pat.kind && name.as_str() == "y" && let ExprKind::Struct(qpath, fields, None) = arg.kind - && matches!(qpath, QPath::LangItem(LangItem::Range, _)) + && let Some(def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() + && paths::CORE_OPS_RANGE_RANGE.matches(cx, def_id) // Add the path to `clippy_utils::paths` if needed && fields.len() == 2 && fields[0].ident.as_str() == "start" && let ExprKind::Lit(ref lit) = fields[0].expr.kind @@ -23,7 +24,8 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr) && let PatKind::Wild = pat.kind && let ExprKind::Struct(qpath, fields, None) = arg.kind - && matches!(qpath, QPath::LangItem(LangItem::Range, _)) + && let Some(def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() + && paths::CORE_OPS_RANGE_RANGE.matches(cx, def_id) // Add the path to `clippy_utils::paths` if needed && fields.len() == 2 && fields[0].ident.as_str() == "start" && let ExprKind::Lit(ref lit) = fields[0].expr.kind @@ -43,7 +45,8 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr) && let PatKind::Wild = pat.kind && let ExprKind::Struct(qpath, fields, None) = arg.kind - && matches!(qpath, QPath::LangItem(LangItem::Range, _)) + && let Some(def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() + && paths::CORE_OPS_RANGE_RANGE.matches(cx, def_id) // Add the path to `clippy_utils::paths` if needed && fields.len() == 2 && fields[0].ident.as_str() == "start" && let ExprKind::Lit(ref lit) = fields[0].expr.kind diff --git a/tests/ui/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout index 49595e2fec25..786c61e0c018 100644 --- a/tests/ui/author/macro_in_closure.stdout +++ b/tests/ui/author/macro_in_closure.stdout @@ -10,34 +10,42 @@ if let StmtKind::Let(local) = stmt.kind && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 && let ExprKind::Block(block1, None) = args[0].kind - && block1.stmts.len() == 1 + && block1.stmts.len() == 2 && let StmtKind::Let(local1) = block1.stmts[0].kind && let Some(init1) = local1.init - && let ExprKind::Array(elements) = init1.kind + && let ExprKind::Tup(elements) = init1.kind && elements.len() == 1 - && let ExprKind::Call(func1, args1) = elements[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = elements[0].kind && let PatKind::Binding(BindingMode::NONE, _, name, None) = local1.pat.kind && name.as_str() == "args" + && let StmtKind::Let(local2) = block1.stmts[1].kind + && let Some(init2) = local2.init + && let ExprKind::Array(elements1) = init2.kind + && elements1.len() == 1 + && let ExprKind::Call(func1, args1) = elements1[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 + && let ExprKind::Field(object, field_name) = args1[0].kind + && field_name.as_str() == "0" + && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local2.pat.kind + && name1.as_str() == "args" && let Some(trailing_expr) = block1.expr && let ExprKind::Call(func2, args2) = trailing_expr.kind && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed && args2.len() == 2 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 2 - && let ExprKind::Lit(ref lit) = elements1[0].kind + && let ExprKind::Array(elements2) = inner1.kind + && elements2.len() == 2 + && let ExprKind::Lit(ref lit) = elements2[0].kind && let LitKind::Str(s, _) = lit.node && s.as_str() == "" - && let ExprKind::Lit(ref lit1) = elements1[1].kind + && let ExprKind::Lit(ref lit1) = elements2[1].kind && let LitKind::Str(s1, _) = lit1.node && s1.as_str() == "\n" && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind && block.expr.is_none() - && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind - && name1.as_str() == "print_text" + && let PatKind::Binding(BindingMode::NONE, _, name2, None) = local.pat.kind + && name2.as_str() == "print_text" { // report your lint here } diff --git a/tests/ui/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout index 4fc7b49464db..ba3b7e244204 100644 --- a/tests/ui/author/macro_in_loop.stdout +++ b/tests/ui/author/macro_in_loop.stdout @@ -2,7 +2,8 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && let PatKind::Binding(BindingMode::NONE, _, name, None) = pat.kind && name.as_str() == "i" && let ExprKind::Struct(qpath, fields, None) = arg.kind - && matches!(qpath, QPath::LangItem(LangItem::Range, _)) + && let Some(def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() + && paths::CORE_OPS_RANGE_RANGE.matches(cx, def_id) // Add the path to `clippy_utils::paths` if needed && fields.len() == 2 && fields[0].ident.as_str() == "start" && let ExprKind::Lit(ref lit) = fields[0].expr.kind @@ -20,28 +21,36 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 && let ExprKind::Block(block2, None) = args[0].kind - && block2.stmts.len() == 1 + && block2.stmts.len() == 2 && let StmtKind::Let(local) = block2.stmts[0].kind && let Some(init) = local.init - && let ExprKind::Array(elements) = init.kind + && let ExprKind::Tup(elements) = init.kind && elements.len() == 1 - && let ExprKind::Call(func1, args1) = elements[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = elements[0].kind && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind && name1.as_str() == "args" + && let StmtKind::Let(local1) = block2.stmts[1].kind + && let Some(init1) = local1.init + && let ExprKind::Array(elements1) = init1.kind + && elements1.len() == 1 + && let ExprKind::Call(func1, args1) = elements1[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 + && let ExprKind::Field(object, field_name) = args1[0].kind + && field_name.as_str() == "0" + && let PatKind::Binding(BindingMode::NONE, _, name2, None) = local1.pat.kind + && name2.as_str() == "args" && let Some(trailing_expr) = block2.expr && let ExprKind::Call(func2, args2) = trailing_expr.kind && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed && args2.len() == 2 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 2 - && let ExprKind::Lit(ref lit2) = elements1[0].kind + && let ExprKind::Array(elements2) = inner1.kind + && elements2.len() == 2 + && let ExprKind::Lit(ref lit2) = elements2[0].kind && let LitKind::Str(s, _) = lit2.node && s.as_str() == "" - && let ExprKind::Lit(ref lit3) = elements1[1].kind + && let ExprKind::Lit(ref lit3) = elements2[1].kind && let LitKind::Str(s1, _) = lit3.node && s1.as_str() == "\n" && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 9efbb3908497..1f1afe4ea3a5 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -57,3 +57,15 @@ macro_rules! bad_transmute { std::mem::transmute($e) }; } + +#[macro_export] +#[rustfmt::skip] +macro_rules! double_parens { + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (()); + let b = ((5)); + let c = std::convert::identity((5)); + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; +} diff --git a/tests/ui/auxiliary/option_helpers.rs b/tests/ui/auxiliary/option_helpers.rs index f9bc9436b079..796d4dabf04d 100644 --- a/tests/ui/auxiliary/option_helpers.rs +++ b/tests/ui/auxiliary/option_helpers.rs @@ -25,6 +25,10 @@ impl IteratorFalsePositives { self } + pub fn next_back(self) -> IteratorFalsePositives { + self + } + pub fn find(self) -> Option { Some(self.foo) } diff --git a/tests/ui/auxiliary/proc_macro_derive.rs b/tests/ui/auxiliary/proc_macro_derive.rs index 546509228717..629a500ff6f5 100644 --- a/tests/ui/auxiliary/proc_macro_derive.rs +++ b/tests/ui/auxiliary/proc_macro_derive.rs @@ -230,3 +230,14 @@ pub fn allow_lint_same_span_derive(input: TokenStream) -> TokenStream { span_help(Group::new(Delimiter::Brace, TokenStream::new()).into()), ]) } + +#[proc_macro_derive(DoubleParens)] +pub fn derive_double_parens(_: TokenStream) -> TokenStream { + quote! { + fn foo() { + let a = (()); + let b = ((5)); + let c = std::convert::identity((5)); + } + } +} diff --git a/tests/ui/auxiliary/proc_macros.rs b/tests/ui/auxiliary/proc_macros.rs index bb55539617fc..a7c20a787519 100644 --- a/tests/ui/auxiliary/proc_macros.rs +++ b/tests/ui/auxiliary/proc_macros.rs @@ -1,5 +1,5 @@ #![feature(proc_macro_span)] -#![allow(clippy::needless_if, dead_code)] +#![allow(clippy::needless_ifs, dead_code)] extern crate proc_macro; diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed index 80e010e2dfd7..fa35a01242d1 100644 --- a/tests/ui/bind_instead_of_map.fixed +++ b/tests/ui/bind_instead_of_map.fixed @@ -1,5 +1,4 @@ #![deny(clippy::bind_instead_of_map)] -#![allow(clippy::uninlined_format_args)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs index 09aa8480cbd9..403077e72ff9 100644 --- a/tests/ui/bind_instead_of_map.rs +++ b/tests/ui/bind_instead_of_map.rs @@ -1,5 +1,4 @@ #![deny(clippy::bind_instead_of_map)] -#![allow(clippy::uninlined_format_args)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr index 08f85fb58549..3f8d631591e9 100644 --- a/tests/ui/bind_instead_of_map.stderr +++ b/tests/ui/bind_instead_of_map.stderr @@ -1,5 +1,5 @@ error: using `Option.and_then(Some)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:8:13 + --> tests/ui/bind_instead_of_map.rs:7:13 | LL | let _ = x.and_then(Some); | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` @@ -11,13 +11,13 @@ LL | #![deny(clippy::bind_instead_of_map)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` - --> tests/ui/bind_instead_of_map.rs:10:13 + --> tests/ui/bind_instead_of_map.rs:9:13 | LL | let _ = x.and_then(|o| Some(o + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map(|o| o + 1)` error: using `Result.and_then(Ok)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:17:13 + --> tests/ui/bind_instead_of_map.rs:16:13 | LL | let _ = x.and_then(Ok); | ^^^^^^^^^^^^^^ help: use the expression directly: `x` diff --git a/tests/ui/blocks_in_conditions.fixed b/tests/ui/blocks_in_conditions.fixed index 6ae5b0cb2f04..417ed370e7e5 100644 --- a/tests/ui/blocks_in_conditions.fixed +++ b/tests/ui/blocks_in_conditions.fixed @@ -4,7 +4,7 @@ #![allow( unused, unnecessary_transmutes, - clippy::needless_if, + clippy::needless_ifs, clippy::missing_transmute_annotations )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/blocks_in_conditions.rs b/tests/ui/blocks_in_conditions.rs index 3fd060620728..fd67ad372114 100644 --- a/tests/ui/blocks_in_conditions.rs +++ b/tests/ui/blocks_in_conditions.rs @@ -4,7 +4,7 @@ #![allow( unused, unnecessary_transmutes, - clippy::needless_if, + clippy::needless_ifs, clippy::missing_transmute_annotations )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/bool_comparison.fixed b/tests/ui/bool_comparison.fixed index b0b60104c0b9..52564c1cb092 100644 --- a/tests/ui/bool_comparison.fixed +++ b/tests/ui/bool_comparison.fixed @@ -1,4 +1,4 @@ -#![allow(non_local_definitions, clippy::needless_if)] +#![allow(non_local_definitions, clippy::needless_ifs)] #![warn(clippy::bool_comparison)] #![allow(clippy::non_canonical_partial_ord_impl)] diff --git a/tests/ui/bool_comparison.rs b/tests/ui/bool_comparison.rs index 1b1108d6ce50..a78cf167e84b 100644 --- a/tests/ui/bool_comparison.rs +++ b/tests/ui/bool_comparison.rs @@ -1,4 +1,4 @@ -#![allow(non_local_definitions, clippy::needless_if)] +#![allow(non_local_definitions, clippy::needless_ifs)] #![warn(clippy::bool_comparison)] #![allow(clippy::non_canonical_partial_ord_impl)] diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs index e848f0601e32..1646b5705b6d 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs @@ -1,6 +1,5 @@ #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)] #![allow(dead_code)] -#![allow(clippy::uninlined_format_args)] //@no-rustfix // branches_sharing_code at the top and bottom of the if blocks @@ -70,7 +69,7 @@ fn complexer_example() { let b = 0xffff00ff; let e_id = gen_id(a, b); - println!("From the a `{}` to the b `{}`", a, b); + println!("From the a `{a}` to the b `{b}`"); let pack = DataPack { id: e_id, @@ -83,7 +82,7 @@ fn complexer_example() { let b = 0xffff00ff; let e_id = gen_id(a, b); - println!("The new ID is '{}'", e_id); + println!("The new ID is '{e_id}'"); let pack = DataPack { id: e_id, diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 40f3453edb9a..f5c51f7888d0 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -1,5 +1,5 @@ error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:17:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:16:5 | LL | / if x == 7 { LL | | @@ -10,7 +10,7 @@ LL | | let _overlap_end = 2 * t; | |_________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:31:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:30:5 | LL | / let _u = 9; LL | | } @@ -34,7 +34,7 @@ LL + let _u = 9; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:35:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:34:5 | LL | / if x == 99 { LL | | @@ -45,7 +45,7 @@ LL | | let _overlap_middle = r * r; | |____________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:48:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:47:5 | LL | / let _overlap_end = r * r * r; LL | | let z = "end"; @@ -67,7 +67,7 @@ LL + let z = "end"; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:66:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:65:5 | LL | / if (x > 7 && y < 13) || (x + y) % 2 == 1 { LL | | @@ -78,7 +78,7 @@ LL | | let e_id = gen_id(a, b); | |________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:88:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:87:5 | LL | / let pack = DataPack { LL | | id: e_id, @@ -108,7 +108,7 @@ LL + process_data(pack); | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:101:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:100:5 | LL | / let _ = if x == 7 { ... | @@ -116,7 +116,7 @@ LL | | let _ = 19; | |___________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:112:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:111:5 | LL | / x << 2 LL | | }; @@ -134,7 +134,7 @@ LL ~ x << 2; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:115:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:114:5 | LL | / if x == 9 { ... | @@ -142,7 +142,7 @@ LL | | let _ = 17; | |___________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:126:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:125:5 | LL | / x * 4 LL | | } @@ -160,7 +160,7 @@ LL + x * 4 | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:158:9 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:157:9 | LL | / if false { LL | | @@ -168,7 +168,7 @@ LL | | let x = 1; | |______________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:166:9 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:165:9 | LL | / let y = 1; LL | | } diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 525be8216500..fab02bf7b24e 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -569,3 +569,16 @@ fn issue12721() { (255 % 999999u64) as u8; //~^ cast_possible_truncation } + +mod issue14150 { + #[clippy::msrv = "1.87"] + fn msrv_supports_cast_signed() { + _ = 1u8 as i8; + //~^ cast_possible_wrap + } + #[clippy::msrv = "1.86"] + fn msrv_doesnt_supports_cast_signed() { + _ = 1u8 as i8; + //~^ cast_possible_wrap + } +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 1cb30d956679..8c48855123f9 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -194,7 +194,7 @@ error: casting `u8` to `i8` may wrap around the value --> tests/ui/cast.rs:88:5 | LL | 1u8 as i8; - | ^^^^^^^^^ + | ^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u8.cast_signed()` | = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -203,25 +203,25 @@ error: casting `u16` to `i16` may wrap around the value --> tests/ui/cast.rs:91:5 | LL | 1u16 as i16; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u16.cast_signed()` error: casting `u32` to `i32` may wrap around the value --> tests/ui/cast.rs:94:5 | LL | 1u32 as i32; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u32.cast_signed()` error: casting `u64` to `i64` may wrap around the value --> tests/ui/cast.rs:97:5 | LL | 1u64 as i64; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u64.cast_signed()` error: casting `usize` to `isize` may wrap around the value --> tests/ui/cast.rs:100:5 | LL | 1usize as isize; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1usize.cast_signed()` error: casting `usize` to `i8` may truncate the value --> tests/ui/cast.rs:104:5 @@ -321,43 +321,43 @@ error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:138:5 | LL | -1i32 as u32; - | ^^^^^^^^^^^^ + | ^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1i32).cast_unsigned()` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:142:5 | LL | -1isize as usize; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1isize).cast_unsigned()` error: casting `i8` to `u8` may lose the sign of the value --> tests/ui/cast.rs:154:5 | LL | (i8::MIN).abs() as u8; - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(i8::MIN).abs().cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:159:5 | LL | (-1i64).abs() as u64; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1i64).abs().cast_unsigned()` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:161:5 | LL | (-1isize).abs() as usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1isize).abs().cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:169:5 | LL | (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(unsafe { (-1i64).checked_abs().unwrap_unchecked() }).cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:185:5 | LL | (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }).cast_unsigned()` error: casting `i64` to `i8` may truncate the value --> tests/ui/cast.rs:237:5 @@ -495,79 +495,79 @@ error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:438:9 | LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:444:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x * x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 | LL | (2_i32).checked_pow(3).unwrap() as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(2_i32).checked_pow(3).unwrap().cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:449:5 | LL | (-2_i32).pow(3) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-2_i32).pow(3).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:454:5 | LL | (-5_i32 % 2) as u32; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-5_i32 % 2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:457:5 | LL | (-5_i32 % -2) as u32; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-5_i32 % -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:461:5 | LL | (-2_i32 >> 1) as u32; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-2_i32 >> 1).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:465:5 | LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:467:5 | LL | (x * x * x) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x * x).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:471:5 | LL | (y * y * y * y * -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y * y * y * -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:474:5 | LL | (y * y * y / y * 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y * y / y * 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:476:5 | LL | (y * y / y * 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y / y * 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:479:5 | LL | (y / y * y * -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y / y * y * -2).cast_unsigned()` error: equal expressions as operands to `/` --> tests/ui/cast.rs:479:6 @@ -581,97 +581,97 @@ error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:483:5 | LL | (y + y + y + -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y + y + y + -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:486:5 | LL | (y + y + y + 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y + y + y + 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:490:5 | LL | (z + -2) as u16; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(z + -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:493:5 | LL | (z + z + 2) as u16; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(z + z + 2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:497:9 | LL | (a * a * b * b * c * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * a * b * b * c * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:499:9 | LL | (a * b * c) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:502:9 | LL | (a * -b * c) as u32; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * -b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:505:9 | LL | (a * b * c * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:507:9 | LL | (a * -2) as u32; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:510:9 | LL | (a * b * c * -2) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c * -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:513:9 | LL | (a / b) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:515:9 | LL | (a / b * c) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:518:9 | LL | (a / b + b * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b + b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:521:9 | LL | a.saturating_pow(3) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `a.saturating_pow(3).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:524:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a.abs() * b.pow(2) / c.abs()).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:532:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `i32::MIN.cast_unsigned()` ... LL | m!(); | ---- in this macro invocation @@ -752,5 +752,17 @@ LL - (255 % 999999u64) as u8; LL + u8::try_from(255 % 999999u64); | -error: aborting due to 92 previous errors +error: casting `u8` to `i8` may wrap around the value + --> tests/ui/cast.rs:576:13 + | +LL | _ = 1u8 as i8; + | ^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u8.cast_signed()` + +error: casting `u8` to `i8` may wrap around the value + --> tests/ui/cast.rs:581:13 + | +LL | _ = 1u8 as i8; + | ^^^^^^^^^ + +error: aborting due to 94 previous errors diff --git a/tests/ui/cast_abs_to_unsigned.fixed b/tests/ui/cast_abs_to_unsigned.fixed index b55c22f5ca83..44cb66a8d184 100644 --- a/tests/ui/cast_abs_to_unsigned.fixed +++ b/tests/ui/cast_abs_to_unsigned.fixed @@ -1,11 +1,11 @@ #![warn(clippy::cast_abs_to_unsigned)] -#![allow(clippy::uninlined_format_args, unused)] +#![allow(unused)] fn main() { let x: i32 = -42; let y: u32 = x.unsigned_abs(); //~^ cast_abs_to_unsigned - println!("The absolute value of {} is {}", x, y); + println!("The absolute value of {x} is {y}"); let a: i32 = -3; let _: usize = a.unsigned_abs() as usize; diff --git a/tests/ui/cast_abs_to_unsigned.rs b/tests/ui/cast_abs_to_unsigned.rs index 466aa6aeb1fb..555b9090fe43 100644 --- a/tests/ui/cast_abs_to_unsigned.rs +++ b/tests/ui/cast_abs_to_unsigned.rs @@ -1,11 +1,11 @@ #![warn(clippy::cast_abs_to_unsigned)] -#![allow(clippy::uninlined_format_args, unused)] +#![allow(unused)] fn main() { let x: i32 = -42; let y: u32 = x.abs() as u32; //~^ cast_abs_to_unsigned - println!("The absolute value of {} is {}", x, y); + println!("The absolute value of {x} is {y}"); let a: i32 = -3; let _: usize = a.abs() as usize; diff --git a/tests/ui/char_lit_as_u8_unfixable.rs b/tests/ui/char_lit_as_u8_unfixable.rs index e5c094f158ec..c8774c7f3091 100644 --- a/tests/ui/char_lit_as_u8_unfixable.rs +++ b/tests/ui/char_lit_as_u8_unfixable.rs @@ -1,4 +1,3 @@ -//@no-rustfix #![warn(clippy::char_lit_as_u8)] fn main() { diff --git a/tests/ui/char_lit_as_u8_unfixable.stderr b/tests/ui/char_lit_as_u8_unfixable.stderr index 49e555ae638a..4c2770cd2a4e 100644 --- a/tests/ui/char_lit_as_u8_unfixable.stderr +++ b/tests/ui/char_lit_as_u8_unfixable.stderr @@ -1,5 +1,5 @@ error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8_unfixable.rs:6:13 + --> tests/ui/char_lit_as_u8_unfixable.rs:5:13 | LL | let _ = '❤' as u8; | ^^^^^^^^^ diff --git a/tests/ui/checked_unwrap/if_let_chains.rs b/tests/ui/checked_unwrap/if_let_chains.rs new file mode 100644 index 000000000000..cfa7715965cd --- /dev/null +++ b/tests/ui/checked_unwrap/if_let_chains.rs @@ -0,0 +1,24 @@ +//@require-annotations-for-level: ERROR +#![deny(clippy::unnecessary_unwrap)] + +#[clippy::msrv = "1.85"] +fn if_let_chains_unsupported(a: Option, b: Option) { + if a.is_none() || b.is_none() { + println!("a or b is not set"); + } else { + println!("the value of a is {}", a.unwrap()); + //~^ unnecessary_unwrap + //~| HELP: try using `match` + } +} + +#[clippy::msrv = "1.88"] +fn if_let_chains_supported(a: Option, b: Option) { + if a.is_none() || b.is_none() { + println!("a or b is not set"); + } else { + println!("the value of a is {}", a.unwrap()); + //~^ unnecessary_unwrap + //~| HELP: try using `if let` or `match` + } +} diff --git a/tests/ui/checked_unwrap/if_let_chains.stderr b/tests/ui/checked_unwrap/if_let_chains.stderr new file mode 100644 index 000000000000..8a4137de37a3 --- /dev/null +++ b/tests/ui/checked_unwrap/if_let_chains.stderr @@ -0,0 +1,29 @@ +error: called `unwrap` on `a` after checking its variant with `is_none` + --> tests/ui/checked_unwrap/if_let_chains.rs:9:42 + | +LL | if a.is_none() || b.is_none() { + | ----------- the check is happening here +... +LL | println!("the value of a is {}", a.unwrap()); + | ^^^^^^^^^^ + | + = help: try using `match` +note: the lint level is defined here + --> tests/ui/checked_unwrap/if_let_chains.rs:2:9 + | +LL | #![deny(clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `a` after checking its variant with `is_none` + --> tests/ui/checked_unwrap/if_let_chains.rs:20:42 + | +LL | if a.is_none() || b.is_none() { + | ----------- the check is happening here +... +LL | println!("the value of a is {}", a.unwrap()); + | ^^^^^^^^^^ + | + = help: try using `if let` or `match` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index 785b2473c053..bba264080b40 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -273,6 +273,28 @@ const ISSUE14763: fn(Option) = |x| { } }; +fn issue12295() { + let option = Some(()); + + if option.is_some() { + println!("{:?}", option.unwrap()); + //~^ unnecessary_unwrap + } else { + println!("{:?}", option.unwrap()); + //~^ panicking_unwrap + } + + let result = Ok::<(), ()>(()); + + if result.is_ok() { + println!("{:?}", result.unwrap()); + //~^ unnecessary_unwrap + } else { + println!("{:?}", result.unwrap()); + //~^ panicking_unwrap + } +} + fn check_expect() { let x = Some(()); if x.is_some() { diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 939b509d85c9..2007a8595413 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -322,6 +322,40 @@ LL | if x.is_some() { LL | _ = x.unwrap(); | ^^^^^^^^^^ +error: called `unwrap` on `option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:280:26 + | +LL | if option.is_some() { + | ------------------- help: try: `if let Some() = option` +LL | println!("{:?}", option.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:283:26 + | +LL | if option.is_some() { + | ---------------- because of this check +... +LL | println!("{:?}", option.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: called `unwrap` on `result` after checking its variant with `is_ok` + --> tests/ui/checked_unwrap/simple_conditionals.rs:290:26 + | +LL | if result.is_ok() { + | ----------------- help: try: `if let Ok() = result` +LL | println!("{:?}", result.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:293:26 + | +LL | if result.is_ok() { + | -------------- because of this check +... +LL | println!("{:?}", result.unwrap()); + | ^^^^^^^^^^^^^^^ + error: creating a shared reference to mutable static --> tests/ui/checked_unwrap/simple_conditionals.rs:183:12 | @@ -332,5 +366,5 @@ LL | if X.is_some() { = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default -error: aborting due to 36 previous errors +error: aborting due to 40 previous errors diff --git a/tests/ui/clone_on_copy.fixed b/tests/ui/clone_on_copy.fixed index 2dd8af152515..469121bbd740 100644 --- a/tests/ui/clone_on_copy.fixed +++ b/tests/ui/clone_on_copy.fixed @@ -1,25 +1,16 @@ +#![warn(clippy::clone_on_copy)] #![allow( - unused, clippy::redundant_clone, clippy::deref_addrof, clippy::no_effect, clippy::unnecessary_operation, - clippy::vec_init_then_push, clippy::toplevel_ref_arg, clippy::needless_borrow )] use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; -fn main() {} - -fn is_ascii(ch: char) -> bool { - ch.is_ascii() -} - -fn clone_on_copy() -> Option<(i32)> { +fn main() { 42; //~^ clone_on_copy @@ -65,20 +56,66 @@ fn clone_on_copy() -> Option<(i32)> { let x = 42; let ref y = x.clone(); // ok, binds by reference let ref mut y = x.clone(); // ok, binds by reference +} + +mod issue3052 { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = *****a; + //~^ clone_on_copy + + let _: E = *****a; + } +} + +fn issue4348() { + fn is_ascii(ch: char) -> bool { + ch.is_ascii() + } - // Issue #4348 let mut x = 43; let _ = &x.clone(); // ok, getting a ref 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate is_ascii('z'); //~^ clone_on_copy +} - // Issue #5436 +#[expect(clippy::vec_init_then_push)] +fn issue5436() { let mut vec = Vec::new(); vec.push(42); //~^ clone_on_copy +} - // Issue #9277 +fn issue9277() -> Option { let opt: &Option = &None; let value = (*opt)?; // operator precedence needed (*opt)? // diff --git a/tests/ui/clone_on_copy.rs b/tests/ui/clone_on_copy.rs index a371afb04a78..b05f1d3aa35e 100644 --- a/tests/ui/clone_on_copy.rs +++ b/tests/ui/clone_on_copy.rs @@ -1,25 +1,16 @@ +#![warn(clippy::clone_on_copy)] #![allow( - unused, clippy::redundant_clone, clippy::deref_addrof, clippy::no_effect, clippy::unnecessary_operation, - clippy::vec_init_then_push, clippy::toplevel_ref_arg, clippy::needless_borrow )] use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; -fn main() {} - -fn is_ascii(ch: char) -> bool { - ch.is_ascii() -} - -fn clone_on_copy() -> Option<(i32)> { +fn main() { 42.clone(); //~^ clone_on_copy @@ -65,20 +56,66 @@ fn clone_on_copy() -> Option<(i32)> { let x = 42; let ref y = x.clone(); // ok, binds by reference let ref mut y = x.clone(); // ok, binds by reference +} + +mod issue3052 { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = a.clone(); + //~^ clone_on_copy + + let _: E = *****a; + } +} + +fn issue4348() { + fn is_ascii(ch: char) -> bool { + ch.is_ascii() + } - // Issue #4348 let mut x = 43; let _ = &x.clone(); // ok, getting a ref 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate is_ascii('z'.clone()); //~^ clone_on_copy +} - // Issue #5436 +#[expect(clippy::vec_init_then_push)] +fn issue5436() { let mut vec = Vec::new(); vec.push(42.clone()); //~^ clone_on_copy +} - // Issue #9277 +fn issue9277() -> Option { let opt: &Option = &None; let value = opt.clone()?; // operator precedence needed (*opt)? // diff --git a/tests/ui/clone_on_copy.stderr b/tests/ui/clone_on_copy.stderr index 92cdd635d20a..c87d1d488dde 100644 --- a/tests/ui/clone_on_copy.stderr +++ b/tests/ui/clone_on_copy.stderr @@ -1,5 +1,5 @@ error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:23:5 + --> tests/ui/clone_on_copy.rs:14:5 | LL | 42.clone(); | ^^^^^^^^^^ help: try removing the `clone` call: `42` @@ -8,52 +8,58 @@ LL | 42.clone(); = help: to override `-D warnings` add `#[allow(clippy::clone_on_copy)]` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:28:5 + --> tests/ui/clone_on_copy.rs:19:5 | LL | (&42).clone(); | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:32:5 + --> tests/ui/clone_on_copy.rs:23:5 | LL | rc.borrow().clone(); | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()` error: using `clone` on type `u32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:36:5 + --> tests/ui/clone_on_copy.rs:27:5 | LL | x.clone().rotate_left(1); | ^^^^^^^^^ help: try removing the `clone` call: `x` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:51:5 + --> tests/ui/clone_on_copy.rs:42:5 | LL | m!(42).clone(); | ^^^^^^^^^^^^^^ help: try removing the `clone` call: `m!(42)` error: using `clone` on type `[u32; 2]` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:62:5 + --> tests/ui/clone_on_copy.rs:53:5 | LL | x.clone()[0]; | ^^^^^^^^^ help: try dereferencing it: `(*x)` +error: using `clone` on type `E` which implements the `Copy` trait + --> tests/ui/clone_on_copy.rs:92:20 + | +LL | let _: E = a.clone(); + | ^^^^^^^^^ help: try dereferencing it: `*****a` + error: using `clone` on type `char` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:73:14 + --> tests/ui/clone_on_copy.rs:107:14 | LL | is_ascii('z'.clone()); | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:78:14 + --> tests/ui/clone_on_copy.rs:114:14 | LL | vec.push(42.clone()); | ^^^^^^^^^^ help: try removing the `clone` call: `42` error: using `clone` on type `Option` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:83:17 + --> tests/ui/clone_on_copy.rs:120:17 | LL | let value = opt.clone()?; // operator precedence needed (*opt)? | ^^^^^^^^^^^ help: try dereferencing it: `(*opt)` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/clone_on_ref_ptr.fixed b/tests/ui/clone_on_ref_ptr.fixed new file mode 100644 index 000000000000..ede9d171517e --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.fixed @@ -0,0 +1,81 @@ +#![warn(clippy::clone_on_ref_ptr)] + +use std::rc::{Rc, Weak as RcWeak}; +use std::sync::{Arc, Weak as ArcWeak}; + +fn main() {} + +fn clone_on_ref_ptr(rc: Rc, rc_weak: RcWeak, arc: Arc, arc_weak: ArcWeak) { + std::rc::Rc::::clone(&rc); + //~^ clone_on_ref_ptr + std::rc::Weak::::clone(&rc_weak); + //~^ clone_on_ref_ptr + std::sync::Arc::::clone(&arc); + //~^ clone_on_ref_ptr + std::sync::Weak::::clone(&arc_weak); + //~^ clone_on_ref_ptr + + Rc::clone(&rc); + Arc::clone(&arc); + RcWeak::clone(&rc_weak); + ArcWeak::clone(&arc_weak); +} + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn trait_object() { + let x = Arc::new(SomeImpl); + let _: Arc = std::sync::Arc::::clone(&x); + //~^ clone_on_ref_ptr +} + +mod issue2076 { + use std::rc::Rc; + + macro_rules! try_opt { + ($expr: expr) => { + match $expr { + Some(value) => value, + None => return None, + } + }; + } + + fn func() -> Option> { + let rc = Rc::new(42); + Some(std::rc::Rc::::clone(&try_opt!(Some(rc)))) + //~^ clone_on_ref_ptr + } +} + +#[allow( + clippy::needless_borrow, + reason = "the suggestion creates `Weak::clone(&rec)`, but `rec` is already a reference" +)] +mod issue15009 { + use std::rc::{Rc, Weak}; + use std::sync::atomic::{AtomicU32, Ordering}; + + fn main() { + let counter = AtomicU32::new(0); + let counter_ref = &counter; + let factorial = Rc::new_cyclic(move |rec| { + let rec = std::rc::Weak::::clone(&rec) as Weak u32>; + //~^ clone_on_ref_ptr + move |x| { + // can capture env + counter_ref.fetch_add(1, Ordering::Relaxed); + match x { + 0 => 1, + x => x * rec.upgrade().unwrap()(x - 1), + } + } + }); + println!("{}", factorial(5)); // 120 + println!("{}", counter.load(Ordering::Relaxed)); // 6 + println!("{}", factorial(7)); // 5040 + println!("{}", counter.load(Ordering::Relaxed)); // 14 + } +} diff --git a/tests/ui/clone_on_ref_ptr.rs b/tests/ui/clone_on_ref_ptr.rs new file mode 100644 index 000000000000..5999b4069d0f --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.rs @@ -0,0 +1,81 @@ +#![warn(clippy::clone_on_ref_ptr)] + +use std::rc::{Rc, Weak as RcWeak}; +use std::sync::{Arc, Weak as ArcWeak}; + +fn main() {} + +fn clone_on_ref_ptr(rc: Rc, rc_weak: RcWeak, arc: Arc, arc_weak: ArcWeak) { + rc.clone(); + //~^ clone_on_ref_ptr + rc_weak.clone(); + //~^ clone_on_ref_ptr + arc.clone(); + //~^ clone_on_ref_ptr + arc_weak.clone(); + //~^ clone_on_ref_ptr + + Rc::clone(&rc); + Arc::clone(&arc); + RcWeak::clone(&rc_weak); + ArcWeak::clone(&arc_weak); +} + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn trait_object() { + let x = Arc::new(SomeImpl); + let _: Arc = x.clone(); + //~^ clone_on_ref_ptr +} + +mod issue2076 { + use std::rc::Rc; + + macro_rules! try_opt { + ($expr: expr) => { + match $expr { + Some(value) => value, + None => return None, + } + }; + } + + fn func() -> Option> { + let rc = Rc::new(42); + Some(try_opt!(Some(rc)).clone()) + //~^ clone_on_ref_ptr + } +} + +#[allow( + clippy::needless_borrow, + reason = "the suggestion creates `Weak::clone(&rec)`, but `rec` is already a reference" +)] +mod issue15009 { + use std::rc::{Rc, Weak}; + use std::sync::atomic::{AtomicU32, Ordering}; + + fn main() { + let counter = AtomicU32::new(0); + let counter_ref = &counter; + let factorial = Rc::new_cyclic(move |rec| { + let rec = rec.clone() as Weak u32>; + //~^ clone_on_ref_ptr + move |x| { + // can capture env + counter_ref.fetch_add(1, Ordering::Relaxed); + match x { + 0 => 1, + x => x * rec.upgrade().unwrap()(x - 1), + } + } + }); + println!("{}", factorial(5)); // 120 + println!("{}", counter.load(Ordering::Relaxed)); // 6 + println!("{}", factorial(7)); // 5040 + println!("{}", counter.load(Ordering::Relaxed)); // 14 + } +} diff --git a/tests/ui/clone_on_ref_ptr.stderr b/tests/ui/clone_on_ref_ptr.stderr new file mode 100644 index 000000000000..b8ddc3058c01 --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.stderr @@ -0,0 +1,47 @@ +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:9:5 + | +LL | rc.clone(); + | ^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&rc)` + | + = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::clone_on_ref_ptr)]` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:11:5 + | +LL | rc_weak.clone(); + | ^^^^^^^^^^^^^^^ help: try: `std::rc::Weak::::clone(&rc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:13:5 + | +LL | arc.clone(); + | ^^^^^^^^^^^ help: try: `std::sync::Arc::::clone(&arc)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:15:5 + | +LL | arc_weak.clone(); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::Weak::::clone(&arc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:30:33 + | +LL | let _: Arc = x.clone(); + | ^^^^^^^^^ help: try: `std::sync::Arc::::clone(&x)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:48:14 + | +LL | Some(try_opt!(Some(rc)).clone()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&try_opt!(Some(rc)))` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:65:23 + | +LL | let rec = rec.clone() as Weak u32>; + | ^^^^^^^^^^^ help: try: `std::rc::Weak::::clone(&rec)` + +error: aborting due to 7 previous errors + diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 04b8ec50160b..c12279cf12e6 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m.is_null() { //~^ cmp_null diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 6f7762e6ae83..2771a16e00c5 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m == ptr::null_mut() { //~^ cmp_null diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 8a75b0501119..381747cb3c65 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:9:8 + --> tests/ui/cmp_null.rs:8:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,31 +8,31 @@ LL | if p == ptr::null() { = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:14:8 + --> tests/ui/cmp_null.rs:13:8 | LL | if ptr::null() == p { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:22:8 + --> tests/ui/cmp_null.rs:21:8 | LL | if m == ptr::null_mut() { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:27:8 + --> tests/ui/cmp_null.rs:26:8 | LL | if ptr::null_mut() == m { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:33:13 + --> tests/ui/cmp_null.rs:32:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:39:19 + --> tests/ui/cmp_null.rs:38:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed index 8d543b042200..2e57078d8e16 100644 --- a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed +++ b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_clone, clippy::derive_partial_eq_without_eq )] // See #5700 diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.rs b/tests/ui/cmp_owned/asymmetric_partial_eq.rs index 6da311c50ee7..7040d8a41030 100644 --- a/tests/ui/cmp_owned/asymmetric_partial_eq.rs +++ b/tests/ui/cmp_owned/asymmetric_partial_eq.rs @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_clone, clippy::derive_partial_eq_without_eq )] // See #5700 diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index 3d709fe9b8e0..e7439beef186 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] #![warn(clippy::collapsible_if, clippy::collapsible_else_if)] #[rustfmt::skip] @@ -87,6 +87,33 @@ fn issue_7318() { //~^^^ collapsible_else_if } +fn issue_13365() { + // all the `expect`s that we should fulfill + if true { + } else { + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::style)] + if false {} + } + + if true { + } else { + #[expect(clippy::all)] + if false {} + } + + if true { + } else { + #[expect(warnings)] + if false {} + } +} + fn issue14799() { use std::ops::ControlFlow; diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index 51868e039086..434ba3654f98 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -1,4 +1,4 @@ -#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] #![warn(clippy::collapsible_if, clippy::collapsible_else_if)] #[rustfmt::skip] @@ -103,6 +103,33 @@ fn issue_7318() { //~^^^ collapsible_else_if } +fn issue_13365() { + // all the `expect`s that we should fulfill + if true { + } else { + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::style)] + if false {} + } + + if true { + } else { + #[expect(clippy::all)] + if false {} + } + + if true { + } else { + #[expect(warnings)] + if false {} + } +} + fn issue14799() { use std::ops::ControlFlow; diff --git a/tests/ui/collapsible_else_if.stderr b/tests/ui/collapsible_else_if.stderr index 1a7bcec7fd5d..ce1da593a8e9 100644 --- a/tests/ui/collapsible_else_if.stderr +++ b/tests/ui/collapsible_else_if.stderr @@ -151,7 +151,7 @@ LL | | } | |_____^ help: collapse nested if block: `if false {}` error: this `else { if .. }` block can be collapsed - --> tests/ui/collapsible_else_if.rs:130:12 + --> tests/ui/collapsible_else_if.rs:157:12 | LL | } else { | ____________^ diff --git a/tests/ui/collapsible_else_if_unfixable.rs b/tests/ui/collapsible_else_if_unfixable.rs new file mode 100644 index 000000000000..e5c18cf3ba90 --- /dev/null +++ b/tests/ui/collapsible_else_if_unfixable.rs @@ -0,0 +1,22 @@ +//@no-rustfix +#![warn(clippy::collapsible_else_if)] + +fn issue_13365() { + // in the following examples, we won't lint because of the comments, + // so the the `expect` will be unfulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + //~^^^ ERROR: this lint expectation is unfulfilled + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } + //~^^^^ ERROR: this lint expectation is unfulfilled +} diff --git a/tests/ui/collapsible_else_if_unfixable.stderr b/tests/ui/collapsible_else_if_unfixable.stderr new file mode 100644 index 000000000000..b461ceba6de7 --- /dev/null +++ b/tests/ui/collapsible_else_if_unfixable.stderr @@ -0,0 +1,17 @@ +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_else_if_unfixable.rs:10:18 + | +LL | #[expect(clippy::collapsible_else_if)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unfulfilled-lint-expectations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]` + +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_else_if_unfixable.rs:17:18 + | +LL | #[expect(clippy::collapsible_else_if)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index 78354c2d7cf8..6e3fd0f78ddf 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -1,7 +1,7 @@ #![allow( clippy::assertions_on_constants, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::nonminimal_bool, clippy::eq_op, clippy::redundant_pattern_matching @@ -144,6 +144,24 @@ fn layout_check() -> u32 { //~^^^^^ collapsible_if } +fn issue13365() { + // all the `expect`s that we should fulfill + if true { + #[expect(clippy::collapsible_if)] + if true {} + } + + if true { + #[expect(clippy::style)] + if true {} + } + + if true { + #[expect(clippy::all)] + if true {} + } +} + fn issue14722() { let x = if true { Some(1) diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index 5d9afa109569..666252a52654 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -1,7 +1,7 @@ #![allow( clippy::assertions_on_constants, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::nonminimal_bool, clippy::eq_op, clippy::redundant_pattern_matching @@ -154,6 +154,24 @@ fn layout_check() -> u32 { //~^^^^^ collapsible_if } +fn issue13365() { + // all the `expect`s that we should fulfill + if true { + #[expect(clippy::collapsible_if)] + if true {} + } + + if true { + #[expect(clippy::style)] + if true {} + } + + if true { + #[expect(clippy::all)] + if true {} + } +} + fn issue14722() { let x = if true { Some(1) diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr index a685cc2e9291..b1f26630f529 100644 --- a/tests/ui/collapsible_if.stderr +++ b/tests/ui/collapsible_if.stderr @@ -191,7 +191,7 @@ LL ~ ; 3 | error: this `if` statement can be collapsed - --> tests/ui/collapsible_if.rs:178:5 + --> tests/ui/collapsible_if.rs:196:5 | LL | / if true { LL | | (if true { diff --git a/tests/ui/collapsible_if_unfixable.rs b/tests/ui/collapsible_if_unfixable.rs new file mode 100644 index 000000000000..643520ac0f5d --- /dev/null +++ b/tests/ui/collapsible_if_unfixable.rs @@ -0,0 +1,20 @@ +//@ no-rustfix +#![warn(clippy::collapsible_if)] + +fn issue13365() { + // in the following examples, we won't lint because of the comments, + // so the the `expect` will be unfulfilled + if true { + // don't collapsible because of this comment + #[expect(clippy::collapsible_if)] + if true {} + } + //~^^^ ERROR: this lint expectation is unfulfilled + + if true { + #[expect(clippy::collapsible_if)] + // don't collapsible because of this comment + if true {} + } + //~^^^^ ERROR: this lint expectation is unfulfilled +} diff --git a/tests/ui/collapsible_if_unfixable.stderr b/tests/ui/collapsible_if_unfixable.stderr new file mode 100644 index 000000000000..64c8fb8da2b4 --- /dev/null +++ b/tests/ui/collapsible_if_unfixable.stderr @@ -0,0 +1,17 @@ +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_if_unfixable.rs:9:18 + | +LL | #[expect(clippy::collapsible_if)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unfulfilled-lint-expectations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]` + +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_if_unfixable.rs:15:18 + | +LL | #[expect(clippy::collapsible_if)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs index 8931a3aa09c6..84f958ee8458 100644 --- a/tests/ui/collapsible_match.rs +++ b/tests/ui/collapsible_match.rs @@ -304,16 +304,25 @@ pub fn test_2(x: Issue9647) { } } -// https://github.com/rust-lang/rust-clippy/issues/14281 -fn lint_emitted_at_right_node(opt: Option>) { - let n = match opt { - #[expect(clippy::collapsible_match)] - Some(n) => match n { - Ok(n) => n, - _ => return, - }, - None => return, - }; +mod issue_13287 { + enum Token { + Name, + Other, + } + + struct Error { + location: u32, + token: Option, + } + + fn struct_field_pat_with_binding_mode(err: Option) { + if let Some(Error { ref token, .. }) = err { + if let Some(Token::Name) = token { + //~^ collapsible_match + println!("token used as a ref"); + } + } + } } pub fn issue_14155() { @@ -357,6 +366,18 @@ pub fn issue_14155() { } } +// https://github.com/rust-lang/rust-clippy/issues/14281 +fn lint_emitted_at_right_node(opt: Option>) { + let n = match opt { + #[expect(clippy::collapsible_match)] + Some(n) => match n { + Ok(n) => n, + _ => return, + }, + None => return, + }; +} + fn make() -> T { unimplemented!() } diff --git a/tests/ui/collapsible_match.stderr b/tests/ui/collapsible_match.stderr index 14b1c1b187e4..d217948d4ca6 100644 --- a/tests/ui/collapsible_match.stderr +++ b/tests/ui/collapsible_match.stderr @@ -230,7 +230,7 @@ help: the outer pattern can be modified to include the inner pattern LL | if let Issue9647::A { a, .. } = x { | ^ replace this binding LL | if let Some(u) = a { - | ^^^^^^^ with this pattern, prefixed by `a`: + | ^^^^^^^ with this pattern, prefixed by `a: ` error: this `if let` can be collapsed into the outer `if let` --> tests/ui/collapsible_match.rs:299:9 @@ -250,8 +250,25 @@ LL | if let Issue9647::A { a: Some(a), .. } = x { LL | if let Some(u) = a { | ^^^^^^^ with this pattern +error: this `if let` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:320:13 + | +LL | / if let Some(Token::Name) = token { +LL | | +LL | | println!("token used as a ref"); +LL | | } + | |_____________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:319:29 + | +LL | if let Some(Error { ref token, .. }) = err { + | ^^^^^^^^^ replace this binding +LL | if let Some(Token::Name) = token { + | ^^^^^^^^^^^^^^^^^ with this pattern, prefixed by `token: ` + error: this `match` can be collapsed into the outer `if let` - --> tests/ui/collapsible_match.rs:322:9 + --> tests/ui/collapsible_match.rs:331:9 | LL | / match *last { LL | | @@ -263,7 +280,7 @@ LL | | } | |_________^ | help: the outer pattern can be modified to include the inner pattern - --> tests/ui/collapsible_match.rs:321:17 + --> tests/ui/collapsible_match.rs:330:17 | LL | if let Some(last) = arr.last() { | ^^^^ ---------- use: `arr.last().copied()` @@ -274,7 +291,7 @@ LL | "a" | "b" => { | ^^^^^^^^^ with this pattern error: this `match` can be collapsed into the outer `if let` - --> tests/ui/collapsible_match.rs:332:9 + --> tests/ui/collapsible_match.rs:341:9 | LL | / match &last { LL | | @@ -286,7 +303,7 @@ LL | | } | |_________^ | help: the outer pattern can be modified to include the inner pattern - --> tests/ui/collapsible_match.rs:331:17 + --> tests/ui/collapsible_match.rs:340:17 | LL | if let Some(last) = arr.last() { | ^^^^ ---------- use: `arr.last().as_ref()` @@ -297,7 +314,7 @@ LL | &&"a" | &&"b" => { | ^^^^^^^^^^^^^ with this pattern error: this `match` can be collapsed into the outer `if let` - --> tests/ui/collapsible_match.rs:342:9 + --> tests/ui/collapsible_match.rs:351:9 | LL | / match &mut last { LL | | @@ -309,7 +326,7 @@ LL | | } | |_________^ | help: the outer pattern can be modified to include the inner pattern - --> tests/ui/collapsible_match.rs:341:17 + --> tests/ui/collapsible_match.rs:350:17 | LL | if let Some(mut last) = arr.last_mut() { | ^^^^^^^^ -------------- use: `arr.last_mut().as_mut()` @@ -319,5 +336,5 @@ LL | if let Some(mut last) = arr.last_mut() { LL | &mut &mut "a" | &mut &mut "b" => { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern -error: aborting due to 16 previous errors +error: aborting due to 17 previous errors diff --git a/tests/ui/comparison_to_empty.fixed b/tests/ui/comparison_to_empty.fixed index 7a71829dd62c..4c3b6004e800 100644 --- a/tests/ui/comparison_to_empty.fixed +++ b/tests/ui/comparison_to_empty.fixed @@ -1,5 +1,5 @@ #![warn(clippy::comparison_to_empty)] -#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)] fn main() { // Disallow comparisons to empty diff --git a/tests/ui/comparison_to_empty.rs b/tests/ui/comparison_to_empty.rs index 5d213a09e812..0d4bbe4abf8a 100644 --- a/tests/ui/comparison_to_empty.rs +++ b/tests/ui/comparison_to_empty.rs @@ -1,5 +1,5 @@ #![warn(clippy::comparison_to_empty)] -#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)] fn main() { // Disallow comparisons to empty diff --git a/tests/ui/const_comparisons.rs b/tests/ui/const_comparisons.rs index b732d7d142fc..a2a1f0a7b4c0 100644 --- a/tests/ui/const_comparisons.rs +++ b/tests/ui/const_comparisons.rs @@ -1,9 +1,11 @@ -#![allow(unused)] -#![warn(clippy::impossible_comparisons)] -#![warn(clippy::redundant_comparisons)] -#![allow(clippy::no_effect)] -#![allow(clippy::short_circuit_statement)] -#![allow(clippy::manual_range_contains)] +#![allow( + unused, + clippy::identity_op, + clippy::manual_range_contains, + clippy::no_effect, + clippy::short_circuit_statement +)] +#![warn(clippy::impossible_comparisons, clippy::redundant_comparisons)] const STATUS_BAD_REQUEST: u16 = 400; const STATUS_SERVER_ERROR: u16 = 500; diff --git a/tests/ui/const_comparisons.stderr b/tests/ui/const_comparisons.stderr index 48a2c6e8d487..1ce62c23ff29 100644 --- a/tests/ui/const_comparisons.stderr +++ b/tests/ui/const_comparisons.stderr @@ -1,5 +1,5 @@ error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:45:5 + --> tests/ui/const_comparisons.rs:47:5 | LL | status_code <= 400 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | status_code <= 400 && status_code > 500; = help: to override `-D warnings` add `#[allow(clippy::impossible_comparisons)]` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:48:5 + --> tests/ui/const_comparisons.rs:50:5 | LL | status_code > 500 && status_code < 400; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | status_code > 500 && status_code < 400; = note: since `500` > `400`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:51:5 + --> tests/ui/const_comparisons.rs:53:5 | LL | status_code < 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | status_code < 500 && status_code > 500; = note: `status_code` cannot simultaneously be greater than and less than `500` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:55:5 + --> tests/ui/const_comparisons.rs:57:5 | LL | status_code < { 400 } && status_code > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | status_code < { 400 } && status_code > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:58:5 + --> tests/ui/const_comparisons.rs:60:5 | LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:61:5 + --> tests/ui/const_comparisons.rs:63:5 | LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:64:5 + --> tests/ui/const_comparisons.rs:66:5 | LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,7 +57,7 @@ LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; = note: `status_code` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:68:5 + --> tests/ui/const_comparisons.rs:70:5 | LL | status < { 400 } && status > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | status < { 400 } && status > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:71:5 + --> tests/ui/const_comparisons.rs:73:5 | LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:74:5 + --> tests/ui/const_comparisons.rs:76:5 | LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,7 +81,7 @@ LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:77:5 + --> tests/ui/const_comparisons.rs:79:5 | LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; = note: `status` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:86:5 + --> tests/ui/const_comparisons.rs:88:5 | LL | 500 >= status_code && 600 < status_code; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | 500 >= status_code && 600 < status_code; = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:90:5 + --> tests/ui/const_comparisons.rs:92:5 | LL | 500 >= status_code && status_code > 600; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | 500 >= status_code && status_code > 600; = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:99:5 + --> tests/ui/const_comparisons.rs:101:5 | LL | 500 >= status && 600 < status; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -113,7 +113,7 @@ LL | 500 >= status && 600 < status; = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:103:5 + --> tests/ui/const_comparisons.rs:105:5 | LL | 500 >= status && status > 600; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -121,13 +121,13 @@ LL | 500 >= status && status > 600; = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:107:5 + --> tests/ui/const_comparisons.rs:109:5 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 200` evaluates to true, status_code <= 299` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:107:23 + --> tests/ui/const_comparisons.rs:109:23 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^ @@ -135,67 +135,67 @@ LL | status_code < 200 && status_code <= 299; = help: to override `-D warnings` add `#[allow(clippy::redundant_comparisons)]` error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:110:5 + --> tests/ui/const_comparisons.rs:112:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code >= 299` evaluates to true, status_code > 200` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:110:5 + --> tests/ui/const_comparisons.rs:112:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:114:5 + --> tests/ui/const_comparisons.rs:116:5 | LL | status_code >= 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:114:5 + --> tests/ui/const_comparisons.rs:116:5 | LL | status_code >= 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:118:5 + --> tests/ui/const_comparisons.rs:120:5 | LL | status_code > 500 && status_code >= 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:118:23 + --> tests/ui/const_comparisons.rs:120:23 | LL | status_code > 500 && status_code >= 500; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:122:5 + --> tests/ui/const_comparisons.rs:124:5 | LL | status_code <= 500 && status_code < 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:122:5 + --> tests/ui/const_comparisons.rs:124:5 | LL | status_code <= 500 && status_code < 500; | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:126:5 + --> tests/ui/const_comparisons.rs:128:5 | LL | status_code < 500 && status_code <= 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:126:23 + --> tests/ui/const_comparisons.rs:128:23 | LL | status_code < 500 && status_code <= 500; | ^^^^^^^^^^^^^^^^^^^^^ error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:131:5 + --> tests/ui/const_comparisons.rs:133:5 | LL | name < "Jennifer" && name > "Shannon"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -203,7 +203,7 @@ LL | name < "Jennifer" && name > "Shannon"; = note: since `"Jennifer"` < `"Shannon"`, the expression evaluates to false for any value of `name` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:135:5 + --> tests/ui/const_comparisons.rs:137:5 | LL | numbers < [3, 4] && numbers > [5, 6]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -211,7 +211,7 @@ LL | numbers < [3, 4] && numbers > [5, 6]; = note: since `[3, 4]` < `[5, 6]`, the expression evaluates to false for any value of `numbers` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:139:5 + --> tests/ui/const_comparisons.rs:141:5 | LL | letter < 'b' && letter > 'c'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -219,7 +219,7 @@ LL | letter < 'b' && letter > 'c'; = note: since `'b'` < `'c'`, the expression evaluates to false for any value of `letter` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:143:5 + --> tests/ui/const_comparisons.rs:145:5 | LL | area < std::f32::consts::E && area > std::f32::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/const_is_empty.rs b/tests/ui/const_is_empty.rs index 63c6342a323c..2ad1b5276def 100644 --- a/tests/ui/const_is_empty.rs +++ b/tests/ui/const_is_empty.rs @@ -55,53 +55,6 @@ const NON_EMPTY_ARRAY_REPEAT: [u32; 2] = [1; 2]; const EMPTY_REF_ARRAY: &[u32; 0] = &[]; const NON_EMPTY_REF_ARRAY: &[u32; 3] = &[1, 2, 3]; -fn test_from_const() { - let _ = EMPTY_STR.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_STR.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_BSTR.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_BSTR.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_ARRAY_REPEAT.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_U8_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_U8_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_ARRAY_REPEAT.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_REF_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_REF_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_SLICE_REPEAT.is_empty(); - //~^ const_is_empty -} - fn main() { let value = "foobar"; let _ = value.is_empty(); @@ -120,7 +73,7 @@ fn main() { fn str_from_arg(var: &str) { var.is_empty(); - // Do not lint, we know nothiny about var + // Do not lint, we know nothing about var } fn update_str() { @@ -196,11 +149,9 @@ fn issue_13106() { const { assert!(EMPTY_STR.is_empty()); - //~^ const_is_empty } const { EMPTY_STR.is_empty(); - //~^ const_is_empty } } diff --git a/tests/ui/const_is_empty.stderr b/tests/ui/const_is_empty.stderr index 9a42518698e3..e1837695bc1c 100644 --- a/tests/ui/const_is_empty.stderr +++ b/tests/ui/const_is_empty.stderr @@ -37,137 +37,35 @@ error: this expression always evaluates to false LL | if non_empty2.is_empty() { | ^^^^^^^^^^^^^^^^^^^^^ -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:59:13 - | -LL | let _ = EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:62:13 - | -LL | let _ = NON_EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:65:13 - | -LL | let _ = EMPTY_BSTR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:68:13 - | -LL | let _ = NON_EMPTY_BSTR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:71:13 - | -LL | let _ = EMPTY_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:74:13 - | -LL | let _ = EMPTY_ARRAY_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:77:13 - | -LL | let _ = EMPTY_U8_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:80:13 - | -LL | let _ = NON_EMPTY_U8_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:83:13 - | -LL | let _ = NON_EMPTY_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:86:13 - | -LL | let _ = NON_EMPTY_ARRAY_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:89:13 - | -LL | let _ = EMPTY_REF_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:92:13 - | -LL | let _ = NON_EMPTY_REF_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:95:13 - | -LL | let _ = EMPTY_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:98:13 - | -LL | let _ = NON_EMPTY_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:101:13 - | -LL | let _ = NON_EMPTY_SLICE_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:107:13 + --> tests/ui/const_is_empty.rs:60:13 | LL | let _ = value.is_empty(); | ^^^^^^^^^^^^^^^^ error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:111:13 + --> tests/ui/const_is_empty.rs:64:13 | LL | let _ = x.is_empty(); | ^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:114:13 + --> tests/ui/const_is_empty.rs:67:13 | LL | let _ = "".is_empty(); | ^^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:117:13 + --> tests/ui/const_is_empty.rs:70:13 | LL | let _ = b"".is_empty(); | ^^^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:171:13 + --> tests/ui/const_is_empty.rs:124:13 | LL | let _ = val.is_empty(); | ^^^^^^^^^^^^^^ -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:198:17 - | -LL | assert!(EMPTY_STR.is_empty()); - | ^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:203:9 - | -LL | EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 28 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/crashes/ice-15657.rs b/tests/ui/crashes/ice-15657.rs new file mode 100644 index 000000000000..c6f6506cd8d0 --- /dev/null +++ b/tests/ui/crashes/ice-15657.rs @@ -0,0 +1,11 @@ +//@check-pass +#![warn(clippy::len_zero)] + +pub struct S1; +pub struct S2; + +impl S1 { + pub fn len(&self) -> S2 { + S2 + } +} diff --git a/tests/ui/crashes/ice-15666.fixed b/tests/ui/crashes/ice-15666.fixed new file mode 100644 index 000000000000..53b618765c0f --- /dev/null +++ b/tests/ui/crashes/ice-15666.fixed @@ -0,0 +1,6 @@ +#![warn(clippy::elidable_lifetime_names)] + +struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); +trait Trait<'de> {} +impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} +//~^ elidable_lifetime_names diff --git a/tests/ui/crashes/ice-15666.rs b/tests/ui/crashes/ice-15666.rs new file mode 100644 index 000000000000..1414b3d2035e --- /dev/null +++ b/tests/ui/crashes/ice-15666.rs @@ -0,0 +1,6 @@ +#![warn(clippy::elidable_lifetime_names)] + +struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); +trait Trait<'de> {} +impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +//~^ elidable_lifetime_names diff --git a/tests/ui/crashes/ice-15666.stderr b/tests/ui/crashes/ice-15666.stderr new file mode 100644 index 000000000000..b417c09b5c65 --- /dev/null +++ b/tests/ui/crashes/ice-15666.stderr @@ -0,0 +1,16 @@ +error: the following explicit lifetimes could be elided: 'a, 's + --> tests/ui/crashes/ice-15666.rs:5:11 + | +LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + | ^^ ^^ ^^ ^^ + | + = note: `-D clippy::elidable-lifetime-names` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::elidable_lifetime_names)]` +help: elide the lifetimes + | +LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/crashes/ice-15684.rs b/tests/ui/crashes/ice-15684.rs new file mode 100644 index 000000000000..12f36042a0fe --- /dev/null +++ b/tests/ui/crashes/ice-15684.rs @@ -0,0 +1,10 @@ +#![warn(clippy::unnecessary_safety_comment)] + +fn foo() -> i32 { + // SAFETY: fail ONLY if `accept-comment-above-attribute = false` + #[must_use] + return 33; + //~^ unnecessary_safety_comment +} + +fn main() {} diff --git a/tests/ui/crashes/ice-15684.stderr b/tests/ui/crashes/ice-15684.stderr new file mode 100644 index 000000000000..0d4eb624a2b1 --- /dev/null +++ b/tests/ui/crashes/ice-15684.stderr @@ -0,0 +1,16 @@ +error: statement has unnecessary safety comment + --> tests/ui/crashes/ice-15684.rs:6:5 + | +LL | return 33; + | ^^^^^^^^^^ + | +help: consider removing the safety comment + --> tests/ui/crashes/ice-15684.rs:4:8 + | +LL | // SAFETY: fail ONLY if `accept-comment-above-attribute = false` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/crashes/ice-4775.rs b/tests/ui/crashes/ice-4775.rs index dd6c6b8de25a..e8c47b4f3a77 100644 --- a/tests/ui/crashes/ice-4775.rs +++ b/tests/ui/crashes/ice-4775.rs @@ -7,7 +7,7 @@ pub struct ArrayWrapper([usize; N]); impl ArrayWrapper<{ N }> { pub fn ice(&self) { for i in self.0.iter() { - println!("{}", i); + println!("{i}"); } } } diff --git a/tests/ui/crashes/ice-7169.fixed b/tests/ui/crashes/ice-7169.fixed index 71a40ad7de71..4392e5d4c028 100644 --- a/tests/ui/crashes/ice-7169.fixed +++ b/tests/ui/crashes/ice-7169.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[derive(Default)] struct A { diff --git a/tests/ui/crashes/ice-7169.rs b/tests/ui/crashes/ice-7169.rs index d43e2cc164d7..a2aa1bdd5275 100644 --- a/tests/ui/crashes/ice-7169.rs +++ b/tests/ui/crashes/ice-7169.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[derive(Default)] struct A { diff --git a/tests/ui/default_trait_access.fixed b/tests/ui/default_trait_access.fixed index d3fe09a052ef..e3bf603da80f 100644 --- a/tests/ui/default_trait_access.fixed +++ b/tests/ui/default_trait_access.fixed @@ -1,7 +1,6 @@ //@aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] -#![allow(clippy::uninlined_format_args)] extern crate proc_macros; @@ -63,6 +62,7 @@ fn main() { let _s21: String = with_span!(s Default::default()); + #[expect(clippy::uninlined_format_args)] println!( "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, diff --git a/tests/ui/default_trait_access.rs b/tests/ui/default_trait_access.rs index cdffb2a2ee8c..8cc065e5bced 100644 --- a/tests/ui/default_trait_access.rs +++ b/tests/ui/default_trait_access.rs @@ -1,7 +1,6 @@ //@aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] -#![allow(clippy::uninlined_format_args)] extern crate proc_macros; @@ -63,6 +62,7 @@ fn main() { let _s21: String = with_span!(s Default::default()); + #[expect(clippy::uninlined_format_args)] println!( "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, diff --git a/tests/ui/default_trait_access.stderr b/tests/ui/default_trait_access.stderr index aa7eb4f89558..aa89516f175c 100644 --- a/tests/ui/default_trait_access.stderr +++ b/tests/ui/default_trait_access.stderr @@ -1,5 +1,5 @@ error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:13:22 + --> tests/ui/default_trait_access.rs:12:22 | LL | let s1: String = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `String::default()` @@ -11,43 +11,43 @@ LL | #![deny(clippy::default_trait_access)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:18:22 + --> tests/ui/default_trait_access.rs:17:22 | LL | let s3: String = D2::default(); | ^^^^^^^^^^^^^ help: try: `String::default()` error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:21:22 + --> tests/ui/default_trait_access.rs:20:22 | LL | let s4: String = std::default::Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `String::default()` error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:26:22 + --> tests/ui/default_trait_access.rs:25:22 | LL | let s6: String = default::Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `String::default()` error: calling `GenericDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:37:46 + --> tests/ui/default_trait_access.rs:36:46 | LL | let s11: GenericDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `GenericDerivedDefault::default()` error: calling `TupleDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:44:36 + --> tests/ui/default_trait_access.rs:43:36 | LL | let s14: TupleDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `TupleDerivedDefault::default()` error: calling `ArrayDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:47:36 + --> tests/ui/default_trait_access.rs:46:36 | LL | let s15: ArrayDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `ArrayDerivedDefault::default()` error: calling `TupleStructDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:52:42 + --> tests/ui/default_trait_access.rs:51:42 | LL | let s17: TupleStructDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `TupleStructDerivedDefault::default()` diff --git a/tests/ui/derivable_impls.stderr b/tests/ui/derivable_impls.stderr index cd46414cb4a8..19c7d09512a2 100644 --- a/tests/ui/derivable_impls.stderr +++ b/tests/ui/derivable_impls.stderr @@ -14,7 +14,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct FooDefault<'a> { +LL | struct FooDefault<'a> { | error: this `impl` can be derived @@ -31,7 +31,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct TupleDefault(bool, i32, u64); +LL | struct TupleDefault(bool, i32, u64); | error: this `impl` can be derived @@ -48,7 +48,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct StrDefault<'a>(&'a str); +LL | struct StrDefault<'a>(&'a str); | error: this `impl` can be derived @@ -65,7 +65,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct Y(u32); +LL | struct Y(u32); | error: this `impl` can be derived @@ -82,7 +82,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct WithoutSelfCurly { +LL | struct WithoutSelfCurly { | error: this `impl` can be derived @@ -99,7 +99,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ struct WithoutSelfParan(bool); +LL | struct WithoutSelfParan(bool); | error: this `impl` can be derived @@ -115,7 +115,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ pub struct DirectDefaultDefaultCall { +LL | pub struct DirectDefaultDefaultCall { | error: this `impl` can be derived @@ -131,7 +131,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ pub struct EquivalentToDefaultDefaultCallVec { +LL | pub struct EquivalentToDefaultDefaultCallVec { | error: this `impl` can be derived @@ -147,7 +147,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ pub struct EquivalentToDefaultDefaultCallLocal { +LL | pub struct EquivalentToDefaultDefaultCallLocal { | error: this `impl` can be derived @@ -164,7 +164,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL + #[derive(Default)] -LL ~ pub struct RepeatDefault1 { +LL | pub struct RepeatDefault1 { | error: this `impl` can be derived @@ -181,7 +181,7 @@ LL | | } help: replace the manual implementation with a derive attribute and mark the default variant | LL + #[derive(Default)] -LL ~ pub enum SimpleEnum { +LL | pub enum SimpleEnum { LL | Foo, LL ~ #[default] LL ~ Bar, diff --git a/tests/ui/derive.rs b/tests/ui/derive.rs index 2c5fa0be9755..036f6c444b64 100644 --- a/tests/ui/derive.rs +++ b/tests/ui/derive.rs @@ -130,3 +130,29 @@ fn issue14558() { } fn main() {} + +mod issue15708 { + // Check that `allow`/`expect` attributes are recognized on the type definition node + #[expect(clippy::expl_impl_clone_on_copy)] + #[derive(Copy)] + struct S; + + impl Clone for S { + fn clone(&self) -> Self { + S + } + } +} + +mod issue15842 { + #[derive(Copy)] + struct S; + + // Check that `allow`/`expect` attributes are recognized on the `impl Clone` node + #[expect(clippy::expl_impl_clone_on_copy)] + impl Clone for S { + fn clone(&self) -> Self { + S + } + } +} diff --git a/tests/ui/derive.stderr b/tests/ui/derive.stderr index ff2c24ff48ee..2701680e788d 100644 --- a/tests/ui/derive.stderr +++ b/tests/ui/derive.stderr @@ -9,16 +9,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` - --> tests/ui/derive.rs:15:1 - | -LL | / impl Clone for Qux { -LL | | -LL | | -LL | | fn clone(&self) -> Self { -... | -LL | | } - | |_^ + = help: consider deriving `Clone` or removing `Copy` = note: `-D clippy::expl-impl-clone-on-copy` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]` @@ -33,16 +24,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` - --> tests/ui/derive.rs:41:1 - | -LL | / impl<'a> Clone for Lt<'a> { -LL | | -LL | | -LL | | fn clone(&self) -> Self { -... | -LL | | } - | |_^ + = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type --> tests/ui/derive.rs:54:1 @@ -55,16 +37,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` - --> tests/ui/derive.rs:54:1 - | -LL | / impl Clone for BigArray { -LL | | -LL | | -LL | | fn clone(&self) -> Self { -... | -LL | | } - | |_^ + = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type --> tests/ui/derive.rs:67:1 @@ -77,16 +50,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` - --> tests/ui/derive.rs:67:1 - | -LL | / impl Clone for FnPtr { -LL | | -LL | | -LL | | fn clone(&self) -> Self { -... | -LL | | } - | |_^ + = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type --> tests/ui/derive.rs:89:1 @@ -99,16 +63,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` - --> tests/ui/derive.rs:89:1 - | -LL | / impl Clone for Generic2 { -LL | | -LL | | -LL | | fn clone(&self) -> Self { -... | -LL | | } - | |_^ + = help: consider deriving `Clone` or removing `Copy` error: aborting due to 5 previous errors diff --git a/tests/ui/derive_ord_xor_partial_ord.rs b/tests/ui/derive_ord_xor_partial_ord.rs index 3ef4ee9463dc..b4bb24b0d2fe 100644 --- a/tests/ui/derive_ord_xor_partial_ord.rs +++ b/tests/ui/derive_ord_xor_partial_ord.rs @@ -76,3 +76,18 @@ mod use_ord { } fn main() {} + +mod issue15708 { + use std::cmp::{Ord, Ordering}; + + // Check that the lint posts on the type definition node + #[expect(clippy::derive_ord_xor_partial_ord)] + #[derive(PartialOrd, PartialEq, Eq)] + struct DerivePartialOrdInUseOrd; + + impl Ord for DerivePartialOrdInUseOrd { + fn cmp(&self, other: &Self) -> Ordering { + Ordering::Less + } + } +} diff --git a/tests/ui/derived_hash_with_manual_eq.rs b/tests/ui/derived_hash_with_manual_eq.rs index 88b574add3f2..9f5c85d7fbc3 100644 --- a/tests/ui/derived_hash_with_manual_eq.rs +++ b/tests/ui/derived_hash_with_manual_eq.rs @@ -41,3 +41,19 @@ impl std::hash::Hash for Bah { } fn main() {} + +mod issue15708 { + // Check that the lint posts on the type definition node + #[expect(clippy::derived_hash_with_manual_eq)] + #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, Hash)] + pub struct Span { + start: usize, + end: usize, + } + + impl PartialEq for Span { + fn eq(&self, other: &Self) -> bool { + self.start.cmp(&other.start).then(self.end.cmp(&other.end)).is_eq() + } + } +} diff --git a/tests/ui/disallowed_names.rs b/tests/ui/disallowed_names.rs index 15bb67349976..331cccef9f93 100644 --- a/tests/ui/disallowed_names.rs +++ b/tests/ui/disallowed_names.rs @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![allow( dead_code, - clippy::needless_if, + clippy::needless_ifs, clippy::similar_names, clippy::single_match, clippy::toplevel_ref_arg, diff --git a/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs index 8c3217624d44..0fbf29365ecf 100644 --- a/tests/ui/doc/needless_doctest_main.rs +++ b/tests/ui/doc/needless_doctest_main.rs @@ -1,33 +1,14 @@ #![warn(clippy::needless_doctest_main)] -//! issue 10491: -//! ```rust,no_test -//! use std::collections::HashMap; -//! -//! fn main() { -//! let mut m = HashMap::new(); -//! m.insert(1u32, 2u32); -//! } -//! ``` -/// some description here -/// ```rust,no_test -/// fn main() { -/// foo() -/// } -/// ``` -fn foo() {} - -#[rustfmt::skip] /// Description /// ```rust /// fn main() { -//~^ error: needless `fn main` in doctest +//~^ needless_doctest_main /// let a = 0; /// } /// ``` fn mulpipulpi() {} -#[rustfmt::skip] /// With a `#[no_main]` /// ```rust /// #[no_main] @@ -45,7 +26,6 @@ fn pulpimulpi() {} /// ``` fn plumilupi() {} -#[rustfmt::skip] /// Additional function, shouldn't trigger /// ```rust /// fn additional_function() { @@ -58,7 +38,6 @@ fn plumilupi() {} /// ``` fn mlupipupi() {} -#[rustfmt::skip] /// Additional function AFTER main, shouldn't trigger /// ```rust /// fn main() { @@ -71,22 +50,19 @@ fn mlupipupi() {} /// ``` fn lumpimupli() {} -#[rustfmt::skip] /// Ignore code block, should not lint at all /// ```rust, ignore /// fn main() { -//~^ error: needless `fn main` in doctest /// // Hi! /// let _ = 0; /// } /// ``` fn mpulpilumi() {} -#[rustfmt::skip] /// Spaces in weird positions (including an \u{A0} after `main`) /// ```rust /// fn main (){ -//~^ error: needless `fn main` in doctest +//~^ needless_doctest_main /// let _ = 0; /// } /// ``` diff --git a/tests/ui/doc/needless_doctest_main.stderr b/tests/ui/doc/needless_doctest_main.stderr index dd5474ccb85a..693cc22fba2a 100644 --- a/tests/ui/doc/needless_doctest_main.stderr +++ b/tests/ui/doc/needless_doctest_main.stderr @@ -1,36 +1,17 @@ error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:23:5 + --> tests/ui/doc/needless_doctest_main.rs:5:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// let a = 0; -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ | = note: `-D clippy::needless-doctest-main` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]` error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:77:5 + --> tests/ui/doc/needless_doctest_main.rs:64:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// // Hi! -LL | | /// let _ = 0; -LL | | /// } - | |_____^ +LL | /// fn main (){ + | ^^^^^^^^^^^ -error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:88:5 - | -LL | /// fn main (){ - | _____^ -LL | | -LL | | /// let _ = 0; -LL | | /// } - | |_____^ - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/double_comparison.fixed b/tests/ui/double_comparison.fixed index 685e3319bf9a..0680eb35ef97 100644 --- a/tests/ui/double_comparison.fixed +++ b/tests/ui/double_comparison.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn main() { let x = 1; diff --git a/tests/ui/double_comparison.rs b/tests/ui/double_comparison.rs index 3670a050e88d..18ab7d2c4254 100644 --- a/tests/ui/double_comparison.rs +++ b/tests/ui/double_comparison.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn main() { let x = 1; diff --git a/tests/ui/double_parens.fixed b/tests/ui/double_parens.fixed new file mode 100644 index 000000000000..024af6840132 --- /dev/null +++ b/tests/ui/double_parens.fixed @@ -0,0 +1,164 @@ +//@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs +//@aux-build:macro_rules.rs +#![warn(clippy::double_parens)] +#![expect(clippy::eq_op, clippy::no_effect)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +use proc_macros::{external, with_span}; + +fn dummy_fn(_: T) {} + +struct DummyStruct; + +impl DummyStruct { + fn dummy_method(&self, _: T) {} +} + +fn simple_double_parens() -> i32 { + (0) + //~^ double_parens +} + +fn fn_double_parens() { + dummy_fn(0); + //~^ double_parens +} + +fn method_double_parens(x: DummyStruct) { + x.dummy_method(0); + //~^ double_parens +} + +fn tuple_double_parens() -> (i32, i32) { + (1, 2) + //~^ double_parens +} + +#[allow(clippy::unused_unit)] +fn unit_double_parens() { + () + //~^ double_parens +} + +fn fn_tuple_ok() { + dummy_fn((1, 2)); +} + +fn method_tuple_ok(x: DummyStruct) { + x.dummy_method((1, 2)); +} + +fn fn_unit_ok() { + dummy_fn(()); +} + +fn method_unit_ok(x: DummyStruct) { + x.dummy_method(()); +} + +// Issue #3206 +fn inside_macro() { + assert_eq!((1, 2), (1, 2), "Error"); + assert_eq!((1, 2), (1, 2), "Error"); + //~^ double_parens +} + +fn issue9000(x: DummyStruct) { + macro_rules! foo { + () => {(100)} + } + // don't lint: the inner paren comes from the macro expansion + (foo!()); + dummy_fn(foo!()); + x.dummy_method(foo!()); + + macro_rules! baz { + ($n:literal) => {($n)} + } + // don't lint: don't get confused by the expression inside the inner paren + // having the same `ctxt` as the overall expression + // (this is a bug that happened during the development of the fix) + (baz!(100)); + dummy_fn(baz!(100)); + x.dummy_method(baz!(100)); + + // should lint: both parens are from inside the macro + macro_rules! bar { + () => {(100)} + //~^ double_parens + } + bar!(); + + // should lint: both parens are from outside the macro; + // make sure to suggest the macro unexpanded + (vec![1, 2]); + //~^ double_parens + dummy_fn(vec![1, 2]); + //~^ double_parens + x.dummy_method(vec![1, 2]); + //~^ double_parens +} + +fn issue15892() { + use macro_rules::double_parens as double_parens_external; + + macro_rules! double_parens{ + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (); + //~^ double_parens + let b = (5); + //~^ double_parens + let c = std::convert::identity(5); + //~^ double_parens + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; + } + + // Don't lint: external macro + (external!((5))); + external!(((5))); + + #[repr(transparent)] + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct InterruptMask(u32); + + impl InterruptMask { + pub const OE: InterruptMask = InterruptMask(1 << 10); + pub const BE: InterruptMask = InterruptMask(1 << 9); + pub const PE: InterruptMask = InterruptMask(1 << 8); + pub const FE: InterruptMask = InterruptMask(1 << 7); + // Lint: internal macro + pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + // Don't lint: external macro + pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE); + #[allow(clippy::unnecessary_cast)] + pub const G: InterruptMask = external!( + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + #[allow(clippy::unnecessary_cast)] + // Don't lint: external proc-macro + pub const H: InterruptMask = with_span!(span + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + pub const fn into_bits(self) -> u32 { + self.0 + } + #[must_use] + pub const fn union(self, rhs: Self) -> Self { + InterruptMask(self.0 | rhs.0) + } + } +} + +fn issue15940() { + use proc_macro_derive::DoubleParens; + + #[derive(DoubleParens)] + // Don't lint: external derive macro + pub struct Person; +} + +fn main() {} diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 7c976015b4e7..8a76f2837f35 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -1,45 +1,45 @@ +//@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs +//@aux-build:macro_rules.rs #![warn(clippy::double_parens)] -#![allow(dead_code, clippy::eq_op)] +#![expect(clippy::eq_op, clippy::no_effect)] #![feature(custom_inner_attributes)] #![rustfmt::skip] +use proc_macros::{external, with_span}; + fn dummy_fn(_: T) {} struct DummyStruct; impl DummyStruct { - fn dummy_method(self, _: T) {} + fn dummy_method(&self, _: T) {} } fn simple_double_parens() -> i32 { ((0)) //~^ double_parens - - } fn fn_double_parens() { dummy_fn((0)); //~^ double_parens - } fn method_double_parens(x: DummyStruct) { x.dummy_method((0)); //~^ double_parens - } fn tuple_double_parens() -> (i32, i32) { ((1, 2)) //~^ double_parens - } +#[allow(clippy::unused_unit)] fn unit_double_parens() { (()) //~^ double_parens - } fn fn_tuple_ok() { @@ -63,7 +63,102 @@ fn inside_macro() { assert_eq!((1, 2), (1, 2), "Error"); assert_eq!(((1, 2)), (1, 2), "Error"); //~^ double_parens +} + +fn issue9000(x: DummyStruct) { + macro_rules! foo { + () => {(100)} + } + // don't lint: the inner paren comes from the macro expansion + (foo!()); + dummy_fn(foo!()); + x.dummy_method(foo!()); + + macro_rules! baz { + ($n:literal) => {($n)} + } + // don't lint: don't get confused by the expression inside the inner paren + // having the same `ctxt` as the overall expression + // (this is a bug that happened during the development of the fix) + (baz!(100)); + dummy_fn(baz!(100)); + x.dummy_method(baz!(100)); + + // should lint: both parens are from inside the macro + macro_rules! bar { + () => {((100))} + //~^ double_parens + } + bar!(); + + // should lint: both parens are from outside the macro; + // make sure to suggest the macro unexpanded + ((vec![1, 2])); + //~^ double_parens + dummy_fn((vec![1, 2])); + //~^ double_parens + x.dummy_method((vec![1, 2])); + //~^ double_parens +} + +fn issue15892() { + use macro_rules::double_parens as double_parens_external; + + macro_rules! double_parens{ + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (()); + //~^ double_parens + let b = ((5)); + //~^ double_parens + let c = std::convert::identity((5)); + //~^ double_parens + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; + } + + // Don't lint: external macro + (external!((5))); + external!(((5))); + + #[repr(transparent)] + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct InterruptMask(u32); + + impl InterruptMask { + pub const OE: InterruptMask = InterruptMask(1 << 10); + pub const BE: InterruptMask = InterruptMask(1 << 9); + pub const PE: InterruptMask = InterruptMask(1 << 8); + pub const FE: InterruptMask = InterruptMask(1 << 7); + // Lint: internal macro + pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + // Don't lint: external macro + pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE); + #[allow(clippy::unnecessary_cast)] + pub const G: InterruptMask = external!( + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + #[allow(clippy::unnecessary_cast)] + // Don't lint: external proc-macro + pub const H: InterruptMask = with_span!(span + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + pub const fn into_bits(self) -> u32 { + self.0 + } + #[must_use] + pub const fn union(self, rhs: Self) -> Self { + InterruptMask(self.0 | rhs.0) + } + } +} + +fn issue15940() { + use proc_macro_derive::DoubleParens; + #[derive(DoubleParens)] + // Don't lint: external derive macro + pub struct Person; } fn main() {} diff --git a/tests/ui/double_parens.stderr b/tests/ui/double_parens.stderr index e119f54949b1..51b5c6279b21 100644 --- a/tests/ui/double_parens.stderr +++ b/tests/ui/double_parens.stderr @@ -1,41 +1,103 @@ -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:15:5 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:20:5 | LL | ((0)) - | ^^^^^ + | ^^^^^ help: remove them: `(0)` | = note: `-D clippy::double-parens` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::double_parens)]` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:22:14 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:25:14 | LL | dummy_fn((0)); - | ^^^ + | ^^^ help: remove them: `0` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:28:20 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:30:20 | LL | x.dummy_method((0)); - | ^^^ + | ^^^ help: remove them: `0` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:34:5 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:35:5 | LL | ((1, 2)) - | ^^^^^^^^ + | ^^^^^^^^ help: remove them: `(1, 2)` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:40:5 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:41:5 | LL | (()) - | ^^^^ + | ^^^^ help: remove them: `()` -error: consider removing unnecessary double parentheses +error: unnecessary parentheses --> tests/ui/double_parens.rs:64:16 | LL | assert_eq!(((1, 2)), (1, 2), "Error"); - | ^^^^^^^^ + | ^^^^^^^^ help: remove them: `(1, 2)` -error: aborting due to 6 previous errors +error: unnecessary parentheses + --> tests/ui/double_parens.rs:89:16 + | +LL | () => {((100))} + | ^^^^^^^ help: remove them: `(100)` +... +LL | bar!(); + | ------ in this macro invocation + | + = note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:96:5 + | +LL | ((vec![1, 2])); + | ^^^^^^^^^^^^^^ help: remove them: `(vec![1, 2])` + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:98:14 + | +LL | dummy_fn((vec![1, 2])); + | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:100:20 + | +LL | x.dummy_method((vec![1, 2])); + | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:110:21 + | +LL | let a = (()); + | ^^^^ help: remove them: `()` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:112:21 + | +LL | let b = ((5)); + | ^^^^^ help: remove them: `(5)` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:114:44 + | +LL | let c = std::convert::identity((5)); + | ^^^ help: remove them: `5` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 13 previous errors diff --git a/tests/ui/duration_subsec.fixed b/tests/ui/duration_subsec.fixed index a8c2f78ca383..b6b2d156c0e0 100644 --- a/tests/ui/duration_subsec.fixed +++ b/tests/ui/duration_subsec.fixed @@ -25,8 +25,7 @@ fn main() { // Handle constants const NANOS_IN_MICRO: u32 = 1_000; - let _ = dur.subsec_micros(); - //~^ duration_subsec + let _ = dur.subsec_nanos() / NANOS_IN_MICRO; // Other literals aren't linted let _ = dur.subsec_nanos() / 699; diff --git a/tests/ui/duration_subsec.rs b/tests/ui/duration_subsec.rs index 582f4717de27..1061e6003c35 100644 --- a/tests/ui/duration_subsec.rs +++ b/tests/ui/duration_subsec.rs @@ -26,7 +26,6 @@ fn main() { // Handle constants const NANOS_IN_MICRO: u32 = 1_000; let _ = dur.subsec_nanos() / NANOS_IN_MICRO; - //~^ duration_subsec // Other literals aren't linted let _ = dur.subsec_nanos() / 699; diff --git a/tests/ui/duration_subsec.stderr b/tests/ui/duration_subsec.stderr index 1a41742e1fa6..27756bc1c20f 100644 --- a/tests/ui/duration_subsec.stderr +++ b/tests/ui/duration_subsec.stderr @@ -25,11 +25,5 @@ error: calling `subsec_micros()` is more concise than this calculation LL | let _ = (&dur).subsec_nanos() / 1_000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&dur).subsec_micros()` -error: calling `subsec_micros()` is more concise than this calculation - --> tests/ui/duration_subsec.rs:28:13 - | -LL | let _ = dur.subsec_nanos() / NANOS_IN_MICRO; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` - -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/elidable_lifetime_names.fixed b/tests/ui/elidable_lifetime_names.fixed index abeee5c4cef3..a6c4cb7a36a8 100644 --- a/tests/ui/elidable_lifetime_names.fixed +++ b/tests/ui/elidable_lifetime_names.fixed @@ -192,3 +192,85 @@ mod issue13923 { x.b } } + +fn issue15666_original() { + struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); + + trait Trait<'de> {} + + //~v elidable_lifetime_names + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + // ^^ ^^ ^^ ^^ +} + +#[allow(clippy::upper_case_acronyms)] +fn issue15666() { + struct S1<'a>(&'a ()); + struct S2<'a, 'b>(&'a &'b ()); + struct S3<'a, 'b, 'c>(&'a &'b &'c ()); + + trait T {} + trait TA<'a> {} + trait TB<'b> {} + trait TC<'c> {} + trait TAB<'a, 'b> {} + trait TAC<'a, 'c> {} + trait TBC<'b, 'c> {} + trait TABC<'a, 'b, 'c> {} + + // 1 lifetime + + impl<'a> TA<'a> for S1<'a> {} + + //~v elidable_lifetime_names + impl T for S1<'_> {} + // ^^ + + // 2 lifetimes + + impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {} + + //~v elidable_lifetime_names + impl<'a> TA<'a> for S2<'a, '_> {} + // ^^ + + //~v elidable_lifetime_names + impl<'b> TB<'b> for S2<'_, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl T for S2<'_, '_> {} + // ^^ ^^ + + // 3 lifetimes + + impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {} + + //~v elidable_lifetime_names + impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a> TA<'a> for S3<'a, '_, '_> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'b> TB<'b> for S3<'_, 'b, '_> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'c> TC<'c> for S3<'_, '_, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl T for S3<'_, '_, '_> {} + // ^^ ^^ ^^ +} diff --git a/tests/ui/elidable_lifetime_names.rs b/tests/ui/elidable_lifetime_names.rs index fae3577a8e96..e08056b2fb56 100644 --- a/tests/ui/elidable_lifetime_names.rs +++ b/tests/ui/elidable_lifetime_names.rs @@ -192,3 +192,85 @@ mod issue13923 { x.b } } + +fn issue15666_original() { + struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); + + trait Trait<'de> {} + + //~v elidable_lifetime_names + impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + // ^^ ^^ ^^ ^^ +} + +#[allow(clippy::upper_case_acronyms)] +fn issue15666() { + struct S1<'a>(&'a ()); + struct S2<'a, 'b>(&'a &'b ()); + struct S3<'a, 'b, 'c>(&'a &'b &'c ()); + + trait T {} + trait TA<'a> {} + trait TB<'b> {} + trait TC<'c> {} + trait TAB<'a, 'b> {} + trait TAC<'a, 'c> {} + trait TBC<'b, 'c> {} + trait TABC<'a, 'b, 'c> {} + + // 1 lifetime + + impl<'a> TA<'a> for S1<'a> {} + + //~v elidable_lifetime_names + impl<'a> T for S1<'a> {} + // ^^ + + // 2 lifetimes + + impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {} + + //~v elidable_lifetime_names + impl<'a, 'b> TA<'a> for S2<'a, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b> TB<'b> for S2<'a, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b> T for S2<'a, 'b> {} + // ^^ ^^ + + // 3 lifetimes + + impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {} + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} + // ^^ ^^ ^^ +} diff --git a/tests/ui/elidable_lifetime_names.stderr b/tests/ui/elidable_lifetime_names.stderr index a60dfc697564..03fe383b8f67 100644 --- a/tests/ui/elidable_lifetime_names.stderr +++ b/tests/ui/elidable_lifetime_names.stderr @@ -158,5 +158,149 @@ LL | o: &'t str, LL ~ ) -> Content<'t, '_> { | -error: aborting due to 12 previous errors +error: the following explicit lifetimes could be elided: 'a, 's + --> tests/ui/elidable_lifetime_names.rs:202:15 + | +LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:226:10 + | +LL | impl<'a> T for S1<'a> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a> T for S1<'a> {} +LL + impl T for S1<'_> {} + | + +error: the following explicit lifetimes could be elided: 'b + --> tests/ui/elidable_lifetime_names.rs:234:14 + | +LL | impl<'a, 'b> TA<'a> for S2<'a, 'b> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> TA<'a> for S2<'a, 'b> {} +LL + impl<'a> TA<'a> for S2<'a, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:238:10 + | +LL | impl<'a, 'b> TB<'b> for S2<'a, 'b> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> TB<'b> for S2<'a, 'b> {} +LL + impl<'b> TB<'b> for S2<'_, 'b> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b + --> tests/ui/elidable_lifetime_names.rs:242:10 + | +LL | impl<'a, 'b> T for S2<'a, 'b> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> T for S2<'a, 'b> {} +LL + impl T for S2<'_, '_> {} + | + +error: the following explicit lifetimes could be elided: 'c + --> tests/ui/elidable_lifetime_names.rs:250:18 + | +LL | impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} +LL + impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {} + | + +error: the following explicit lifetimes could be elided: 'b + --> tests/ui/elidable_lifetime_names.rs:254:14 + | +LL | impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} +LL + impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'b, 'c + --> tests/ui/elidable_lifetime_names.rs:258:14 + | +LL | impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} +LL + impl<'a> TA<'a> for S3<'a, '_, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:262:10 + | +LL | impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} +LL + impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'c + --> tests/ui/elidable_lifetime_names.rs:266:10 + | +LL | impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} +LL + impl<'b> TB<'b> for S3<'_, 'b, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b + --> tests/ui/elidable_lifetime_names.rs:270:10 + | +LL | impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} +LL + impl<'c> TC<'c> for S3<'_, '_, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b, 'c + --> tests/ui/elidable_lifetime_names.rs:274:10 + | +LL | impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} +LL + impl T for S3<'_, '_, '_> {} + | + +error: aborting due to 24 previous errors diff --git a/tests/ui/empty_enum.rs b/tests/ui/empty_enum.rs deleted file mode 100644 index 439fd0974f5f..000000000000 --- a/tests/ui/empty_enum.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(dead_code)] -#![warn(clippy::empty_enum)] -// Enable never type to test empty enum lint -#![feature(never_type)] -enum Empty {} -//~^ empty_enum - -fn main() {} diff --git a/tests/ui/empty_enums.rs b/tests/ui/empty_enums.rs new file mode 100644 index 000000000000..0deb0f57b0e4 --- /dev/null +++ b/tests/ui/empty_enums.rs @@ -0,0 +1,25 @@ +#![warn(clippy::empty_enums)] +// Enable never type to test empty enum lint +#![feature(never_type)] + +enum Empty {} +//~^ empty_enums + +mod issue15910 { + enum NotReallyEmpty { + #[cfg(false)] + Hidden, + } + + enum OneVisibleVariant { + #[cfg(false)] + Hidden, + Visible, + } + + enum CfgInsideVariant { + Variant(#[cfg(false)] String), + } +} + +fn main() {} diff --git a/tests/ui/empty_enum.stderr b/tests/ui/empty_enums.stderr similarity index 75% rename from tests/ui/empty_enum.stderr rename to tests/ui/empty_enums.stderr index 6a1ded9298ed..5aa2347b4ae0 100644 --- a/tests/ui/empty_enum.stderr +++ b/tests/ui/empty_enums.stderr @@ -1,12 +1,12 @@ error: enum with no variants - --> tests/ui/empty_enum.rs:5:1 + --> tests/ui/empty_enums.rs:5:1 | LL | enum Empty {} | ^^^^^^^^^^^^^ | = help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated - = note: `-D clippy::empty-enum` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::empty_enum)]` + = note: `-D clippy::empty-enums` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::empty_enums)]` error: aborting due to 1 previous error diff --git a/tests/ui/empty_enum_without_never_type.rs b/tests/ui/empty_enums_without_never_type.rs similarity index 67% rename from tests/ui/empty_enum_without_never_type.rs rename to tests/ui/empty_enums_without_never_type.rs index 3661a1537208..17ccac83ce98 100644 --- a/tests/ui/empty_enum_without_never_type.rs +++ b/tests/ui/empty_enums_without_never_type.rs @@ -1,7 +1,6 @@ //@ check-pass -#![allow(dead_code)] -#![warn(clippy::empty_enum)] +#![warn(clippy::empty_enums)] // `never_type` is not enabled; this test has no stderr file enum Empty {} diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index ce8b67f9ca7b..58fbad64a78d 100644 --- a/tests/ui/equatable_if_let.fixed +++ b/tests/ui/equatable_if_let.fixed @@ -4,7 +4,7 @@ unused_variables, dead_code, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #![warn(clippy::equatable_if_let)] diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs index ff09533f2651..cca97c76b509 100644 --- a/tests/ui/equatable_if_let.rs +++ b/tests/ui/equatable_if_let.rs @@ -4,7 +4,7 @@ unused_variables, dead_code, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #![warn(clippy::equatable_if_let)] diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 6944a979c05e..107318e5323d 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -635,3 +635,11 @@ fn issue8817() { //~| HELP: replace the closure with the tuple variant itself .unwrap(); // just for nicer formatting } + +async fn issue13892<'a, T, F>(maybe: Option<&'a T>, visitor: F) +where + F: AsyncFn(&'a T), + T: 'a, +{ + maybe.map(|x| visitor(x)); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 5bcc1cb26fd7..b85e8e75153a 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -635,3 +635,11 @@ fn issue8817() { //~| HELP: replace the closure with the tuple variant itself .unwrap(); // just for nicer formatting } + +async fn issue13892<'a, T, F>(maybe: Option<&'a T>, visitor: F) +where + F: AsyncFn(&'a T), + T: 'a, +{ + maybe.map(|x| visitor(x)); +} diff --git a/tests/ui/expect_tool_lint_rfc_2383.rs b/tests/ui/expect_tool_lint_rfc_2383.rs index 2295691c8127..82ac4db172d8 100644 --- a/tests/ui/expect_tool_lint_rfc_2383.rs +++ b/tests/ui/expect_tool_lint_rfc_2383.rs @@ -10,7 +10,7 @@ //! This test can't cover every lint from Clippy, rustdoc and potentially other //! tools that will be developed. This therefore only tests a small subset of lints #![expect(rustdoc::missing_crate_level_docs)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] mod rustc_ok { //! See diff --git a/tests/ui/explicit_counter_loop.rs b/tests/ui/explicit_counter_loop.rs index 13934785d7b1..ec4cecf37766 100644 --- a/tests/ui/explicit_counter_loop.rs +++ b/tests/ui/explicit_counter_loop.rs @@ -1,5 +1,5 @@ #![warn(clippy::explicit_counter_loop)] -#![allow(clippy::uninlined_format_args, clippy::useless_vec)] +#![allow(clippy::useless_vec)] //@no-rustfix: suggestion does not remove the `+= 1` fn main() { let mut vec = vec![1, 2, 3, 4]; @@ -89,13 +89,13 @@ mod issue_1219 { for _v in &vec { index += 1 } - println!("index: {}", index); + println!("index: {index}"); // should not trigger the lint because the count is conditional #1219 let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); if ch == 'a' { continue; } @@ -106,7 +106,7 @@ mod issue_1219 { let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); if ch == 'a' { count += 1; } @@ -118,7 +118,7 @@ mod issue_1219 { for ch in text.chars() { //~^ explicit_counter_loop - println!("{}", count); + println!("{count}"); count += 1; if ch == 'a' { continue; @@ -131,7 +131,7 @@ mod issue_1219 { for ch in text.chars() { //~^ explicit_counter_loop - println!("{}", count); + println!("{count}"); count += 1; for i in 0..2 { let _ = 123; @@ -142,7 +142,7 @@ mod issue_1219 { let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); count += 1; for i in 0..2 { count += 1; @@ -157,7 +157,7 @@ mod issue_3308 { let mut skips = 0; let erasures = vec![]; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); while erasures.contains(&(i + skips)) { skips += 1; } @@ -166,7 +166,7 @@ mod issue_3308 { // should not trigger the lint because the count is incremented multiple times let mut skips = 0; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); let mut j = 0; while j < 5 { skips += 1; @@ -177,7 +177,7 @@ mod issue_3308 { // should not trigger the lint because the count is incremented multiple times let mut skips = 0; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); for j in 0..5 { skips += 1; } @@ -205,7 +205,7 @@ mod issue_4732 { for _v in slice { index += 1 } - let _closure = || println!("index: {}", index); + let _closure = || println!("index: {index}"); } } @@ -217,7 +217,7 @@ mod issue_4677 { let mut count = 0; for _i in slice { count += 1; - println!("{}", count); + println!("{count}"); } } } diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 52c4d1b1f301..97e8e0bafe4f 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![warn(clippy::explicit_deref_methods)] #![allow(unused_variables, unused_must_use)] #![allow( @@ -14,6 +15,8 @@ use std::ops::{Deref, DerefMut}; +extern crate proc_macros; + fn concat(deref_str: &str) -> String { format!("{}bar", deref_str) } @@ -121,6 +124,18 @@ fn main() { let b: &str = expr_deref!(&*a); //~^ explicit_deref_methods + proc_macros::external! { + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + + // Issue #15168 + proc_macros::with_span! { + span + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + // The struct does not implement Deref trait #[derive(Copy, Clone)] struct NoLint(u32); diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index 706d6cb2b79a..b689649d49dd 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![warn(clippy::explicit_deref_methods)] #![allow(unused_variables, unused_must_use)] #![allow( @@ -14,6 +15,8 @@ use std::ops::{Deref, DerefMut}; +extern crate proc_macros; + fn concat(deref_str: &str) -> String { format!("{}bar", deref_str) } @@ -121,6 +124,18 @@ fn main() { let b: &str = expr_deref!(a.deref()); //~^ explicit_deref_methods + proc_macros::external! { + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + + // Issue #15168 + proc_macros::with_span! { + span + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + // The struct does not implement Deref trait #[derive(Copy, Clone)] struct NoLint(u32); diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index 5036884366cf..e2f2e68720b1 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:55:19 + --> tests/ui/explicit_deref_methods.rs:58:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try: `&*a` @@ -8,73 +8,73 @@ LL | let b: &str = a.deref(); = help: to override `-D warnings` add `#[allow(clippy::explicit_deref_methods)]` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:58:23 + --> tests/ui/explicit_deref_methods.rs:61:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try: `&mut **a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:62:39 + --> tests/ui/explicit_deref_methods.rs:65:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:62:50 + --> tests/ui/explicit_deref_methods.rs:65:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:66:20 + --> tests/ui/explicit_deref_methods.rs:69:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:70:11 + --> tests/ui/explicit_deref_methods.rs:73:11 | LL | match a.deref() { | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:75:28 + --> tests/ui/explicit_deref_methods.rs:78:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:78:13 + --> tests/ui/explicit_deref_methods.rs:81:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:81:28 + --> tests/ui/explicit_deref_methods.rs:84:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:121:31 + --> tests/ui/explicit_deref_methods.rs:124:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:139:14 + --> tests/ui/explicit_deref_methods.rs:154:14 | LL | let _ = &Deref::deref(&"foo"); | ^^^^^^^^^^^^^^^^^^^^ help: try: `*&"foo"` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:141:14 + --> tests/ui/explicit_deref_methods.rs:156:14 | LL | let _ = &DerefMut::deref_mut(&mut x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut **&mut x` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:142:14 + --> tests/ui/explicit_deref_methods.rs:157:14 | LL | let _ = &DerefMut::deref_mut((&mut &mut x).deref_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut ***(&mut &mut x)` diff --git a/tests/ui/explicit_write.fixed b/tests/ui/explicit_write.fixed index 024999fc609e..ab28c1ccd8e0 100644 --- a/tests/ui/explicit_write.fixed +++ b/tests/ui/explicit_write.fixed @@ -1,6 +1,5 @@ #![warn(clippy::explicit_write)] #![allow(unused_imports)] -#![allow(clippy::uninlined_format_args)] fn stdout() -> String { String::new() @@ -40,7 +39,7 @@ fn main() { //~^ explicit_write let value = 1; - eprintln!("with {}", value); + eprintln!("with {value}"); //~^ explicit_write eprintln!("with {} {}", 2, value); //~^ explicit_write @@ -49,7 +48,7 @@ fn main() { eprintln!("macro arg {}", one!()); //~^ explicit_write let width = 2; - eprintln!("{:w$}", value, w = width); + eprintln!("{value:w$}", w = width); //~^ explicit_write } // these should not warn, different destination diff --git a/tests/ui/explicit_write.rs b/tests/ui/explicit_write.rs index c83c760d48c8..975ee103b627 100644 --- a/tests/ui/explicit_write.rs +++ b/tests/ui/explicit_write.rs @@ -1,6 +1,5 @@ #![warn(clippy::explicit_write)] #![allow(unused_imports)] -#![allow(clippy::uninlined_format_args)] fn stdout() -> String { String::new() @@ -40,7 +39,7 @@ fn main() { //~^ explicit_write let value = 1; - writeln!(std::io::stderr(), "with {}", value).unwrap(); + writeln!(std::io::stderr(), "with {value}").unwrap(); //~^ explicit_write writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); //~^ explicit_write @@ -49,7 +48,7 @@ fn main() { writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); //~^ explicit_write let width = 2; - writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap(); + writeln!(std::io::stderr(), "{value:w$}", w = width).unwrap(); //~^ explicit_write } // these should not warn, different destination diff --git a/tests/ui/explicit_write.stderr b/tests/ui/explicit_write.stderr index 670a0411b310..ef4b2a049a6d 100644 --- a/tests/ui/explicit_write.stderr +++ b/tests/ui/explicit_write.stderr @@ -1,5 +1,5 @@ error: use of `write!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:23:9 + --> tests/ui/explicit_write.rs:22:9 | LL | write!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `print!("test")` @@ -8,76 +8,76 @@ LL | write!(std::io::stdout(), "test").unwrap(); = help: to override `-D warnings` add `#[allow(clippy::explicit_write)]` error: use of `write!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:25:9 + --> tests/ui/explicit_write.rs:24:9 | LL | write!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:27:9 + --> tests/ui/explicit_write.rs:26:9 | LL | writeln!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `println!("test")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:29:9 + --> tests/ui/explicit_write.rs:28:9 | LL | writeln!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("test")` error: use of `stdout().write_fmt(...).unwrap()` - --> tests/ui/explicit_write.rs:31:9 + --> tests/ui/explicit_write.rs:30:9 | LL | std::io::stdout().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `print!("test")` error: use of `stderr().write_fmt(...).unwrap()` - --> tests/ui/explicit_write.rs:33:9 + --> tests/ui/explicit_write.rs:32:9 | LL | std::io::stderr().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:37:9 + --> tests/ui/explicit_write.rs:36:9 | LL | writeln!(std::io::stdout(), "test\ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `println!("test\ntest")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:39:9 + --> tests/ui/explicit_write.rs:38:9 | LL | writeln!(std::io::stderr(), "test\ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("test\ntest")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:43:9 + --> tests/ui/explicit_write.rs:42:9 | -LL | writeln!(std::io::stderr(), "with {}", value).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {}", value)` +LL | writeln!(std::io::stderr(), "with {value}").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {value}")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:45:9 + --> tests/ui/explicit_write.rs:44:9 | LL | writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {} {}", 2, value)` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:47:9 + --> tests/ui/explicit_write.rs:46:9 | LL | writeln!(std::io::stderr(), "with {value}").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {value}")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:49:9 + --> tests/ui/explicit_write.rs:48:9 | LL | writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("macro arg {}", one!())` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:52:9 + --> tests/ui/explicit_write.rs:51:9 | -LL | writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("{:w$}", value, w = width)` +LL | writeln!(std::io::stderr(), "{value:w$}", w = width).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("{value:w$}", w = width)` error: aborting due to 13 previous errors diff --git a/tests/ui/explicit_write_in_test.rs b/tests/ui/explicit_write_in_test.rs new file mode 100644 index 000000000000..df020b7f1382 --- /dev/null +++ b/tests/ui/explicit_write_in_test.rs @@ -0,0 +1,9 @@ +//@ check-pass +#![warn(clippy::explicit_write)] + +#[test] +fn test() { + use std::io::Write; + writeln!(std::io::stderr(), "I am an explicit write.").unwrap(); + eprintln!("I am not an explicit write."); +} diff --git a/tests/ui/explicit_write_in_test.stderr b/tests/ui/explicit_write_in_test.stderr new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/ui/fallible_impl_from.rs b/tests/ui/fallible_impl_from.rs index 1c62c1e937b6..28bb1157f6e5 100644 --- a/tests/ui/fallible_impl_from.rs +++ b/tests/ui/fallible_impl_from.rs @@ -1,5 +1,4 @@ #![deny(clippy::fallible_impl_from)] -#![allow(clippy::uninlined_format_args)] // docs example struct Foo(i32); @@ -62,7 +61,7 @@ impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { if s.parse::().ok().unwrap() != 42 { - panic!("{:?}", s); + panic!("{s:?}"); } Invalid } diff --git a/tests/ui/fallible_impl_from.stderr b/tests/ui/fallible_impl_from.stderr index 402494b39f30..25ecc8b0a39a 100644 --- a/tests/ui/fallible_impl_from.stderr +++ b/tests/ui/fallible_impl_from.stderr @@ -1,5 +1,5 @@ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:6:1 + --> tests/ui/fallible_impl_from.rs:5:1 | LL | / impl From for Foo { LL | | @@ -11,7 +11,7 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:10:13 + --> tests/ui/fallible_impl_from.rs:9:13 | LL | Foo(s.parse().unwrap()) | ^^^^^^^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL | #![deny(clippy::fallible_impl_from)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:29:1 + --> tests/ui/fallible_impl_from.rs:28:1 | LL | / impl From for Invalid { LL | | @@ -34,13 +34,13 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:34:13 + --> tests/ui/fallible_impl_from.rs:33:13 | LL | panic!(); | ^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:40:1 + --> tests/ui/fallible_impl_from.rs:39:1 | LL | / impl From> for Invalid { LL | | @@ -52,7 +52,7 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:44:17 + --> tests/ui/fallible_impl_from.rs:43:17 | LL | let s = s.unwrap(); | ^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:60:1 + --> tests/ui/fallible_impl_from.rs:59:1 | LL | / impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { LL | | @@ -77,12 +77,12 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:64:12 + --> tests/ui/fallible_impl_from.rs:63:12 | LL | if s.parse::().ok().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | panic!("{:?}", s); - | ^^^^^^^^^^^^^^^^^ +LL | panic!("{s:?}"); + | ^^^^^^^^^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/ui/filetype_is_file.rs b/tests/ui/filetype_is_file.rs index 8ca01b91210f..2ec5b3b81446 100644 --- a/tests/ui/filetype_is_file.rs +++ b/tests/ui/filetype_is_file.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #![warn(clippy::filetype_is_file)] fn main() -> std::io::Result<()> { diff --git a/tests/ui/floating_point_log.fixed b/tests/ui/floating_point_log.fixed index 275c9b4a3ab9..e831e30a71d8 100644 --- a/tests/ui/floating_point_log.fixed +++ b/tests/ui/floating_point_log.fixed @@ -14,10 +14,8 @@ fn check_log_base() { //~^ suboptimal_flops let _ = x.ln(); //~^ suboptimal_flops - let _ = x.log2(); - //~^ suboptimal_flops - let _ = x.ln(); - //~^ suboptimal_flops + let _ = x.log(TWO); + let _ = x.log(E); let _ = (x as f32).log2(); //~^ suboptimal_flops diff --git a/tests/ui/floating_point_log.rs b/tests/ui/floating_point_log.rs index a372ccbb9fb0..06cb1c8d9603 100644 --- a/tests/ui/floating_point_log.rs +++ b/tests/ui/floating_point_log.rs @@ -15,9 +15,7 @@ fn check_log_base() { let _ = x.log(std::f32::consts::E); //~^ suboptimal_flops let _ = x.log(TWO); - //~^ suboptimal_flops let _ = x.log(E); - //~^ suboptimal_flops let _ = (x as f32).log(2f32); //~^ suboptimal_flops diff --git a/tests/ui/floating_point_log.stderr b/tests/ui/floating_point_log.stderr index e93b3af851cb..3e141de626d9 100644 --- a/tests/ui/floating_point_log.stderr +++ b/tests/ui/floating_point_log.stderr @@ -19,44 +19,32 @@ error: logarithm for bases 2, 10 and e can be computed more accurately LL | let _ = x.log(std::f32::consts::E); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` -error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:17:13 - | -LL | let _ = x.log(TWO); - | ^^^^^^^^^^ help: consider using: `x.log2()` - error: logarithm for bases 2, 10 and e can be computed more accurately --> tests/ui/floating_point_log.rs:19:13 | -LL | let _ = x.log(E); - | ^^^^^^^^ help: consider using: `x.ln()` - -error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:21:13 - | LL | let _ = (x as f32).log(2f32); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).log2()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:25:13 + --> tests/ui/floating_point_log.rs:23:13 | LL | let _ = x.log(2f64); | ^^^^^^^^^^^ help: consider using: `x.log2()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:27:13 + --> tests/ui/floating_point_log.rs:25:13 | LL | let _ = x.log(10f64); | ^^^^^^^^^^^^ help: consider using: `x.log10()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:29:13 + --> tests/ui/floating_point_log.rs:27:13 | LL | let _ = x.log(std::f64::consts::E); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:35:13 + --> tests/ui/floating_point_log.rs:33:13 | LL | let _ = (1f32 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` @@ -65,118 +53,118 @@ LL | let _ = (1f32 + 2.).ln(); = help: to override `-D warnings` add `#[allow(clippy::imprecise_flops)]` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:37:13 + --> tests/ui/floating_point_log.rs:35:13 | LL | let _ = (1f32 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:39:13 + --> tests/ui/floating_point_log.rs:37:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:41:13 + --> tests/ui/floating_point_log.rs:39:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:43:13 + --> tests/ui/floating_point_log.rs:41:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:45:13 + --> tests/ui/floating_point_log.rs:43:13 | LL | let _ = (1.0 + x.powi(3) / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(3) / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:47:13 + --> tests/ui/floating_point_log.rs:45:13 | LL | let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(std::f32::consts::E - 1.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:49:13 + --> tests/ui/floating_point_log.rs:47:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:51:13 + --> tests/ui/floating_point_log.rs:49:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:53:13 + --> tests/ui/floating_point_log.rs:51:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:55:13 + --> tests/ui/floating_point_log.rs:53:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:64:13 + --> tests/ui/floating_point_log.rs:62:13 | LL | let _ = (1f64 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:66:13 + --> tests/ui/floating_point_log.rs:64:13 | LL | let _ = (1f64 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:68:13 + --> tests/ui/floating_point_log.rs:66:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:70:13 + --> tests/ui/floating_point_log.rs:68:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:72:13 + --> tests/ui/floating_point_log.rs:70:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:74:13 + --> tests/ui/floating_point_log.rs:72:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:76:13 + --> tests/ui/floating_point_log.rs:74:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:78:13 + --> tests/ui/floating_point_log.rs:76:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:80:13 + --> tests/ui/floating_point_log.rs:78:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` -error: aborting due to 29 previous errors +error: aborting due to 27 previous errors diff --git a/tests/ui/if_same_then_else2.rs b/tests/ui/if_same_then_else2.rs index 5b74aecdacbe..6ac5fe6e7b54 100644 --- a/tests/ui/if_same_then_else2.rs +++ b/tests/ui/if_same_then_else2.rs @@ -5,7 +5,7 @@ clippy::equatable_if_let, clippy::collapsible_if, clippy::ifs_same_cond, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::single_element_loop, clippy::branches_sharing_code diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index 0fd130609aee..7da9401a308f 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -206,3 +206,15 @@ fn dont_lint_inside_macros() { } let _: Option = mac!(true, 42); } + +mod issue15770 { + fn maybe_error() -> Result { + Err("error!") + } + + pub fn trying(b: bool) -> Result<(), &'static str> { + let _x: Option = if b { Some(maybe_error()?) } else { None }; + // Process _x locally + Ok(()) + } +} diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 640828aa9bf6..02962f83ce8a 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -262,3 +262,15 @@ fn dont_lint_inside_macros() { } let _: Option = mac!(true, 42); } + +mod issue15770 { + fn maybe_error() -> Result { + Err("error!") + } + + pub fn trying(b: bool) -> Result<(), &'static str> { + let _x: Option = if b { Some(maybe_error()?) } else { None }; + // Process _x locally + Ok(()) + } +} diff --git a/tests/ui/ifs_same_cond.rs b/tests/ui/ifs_same_cond.rs index 7067434953d6..486903f653de 100644 --- a/tests/ui/ifs_same_cond.rs +++ b/tests/ui/ifs_same_cond.rs @@ -1,5 +1,5 @@ #![warn(clippy::ifs_same_cond)] -#![allow(clippy::if_same_then_else, clippy::needless_if, clippy::needless_else)] // all empty blocks +#![allow(clippy::if_same_then_else, clippy::needless_ifs, clippy::needless_else)] // all empty blocks fn ifs_same_cond() { let a = 0; diff --git a/tests/ui/impl.rs b/tests/ui/impl.rs index 1b9e4a5cdee1..e6044cc50781 100644 --- a/tests/ui/impl.rs +++ b/tests/ui/impl.rs @@ -68,7 +68,42 @@ struct OneAllowedImpl; impl OneAllowedImpl {} #[allow(clippy::multiple_inherent_impl)] impl OneAllowedImpl {} -impl OneAllowedImpl {} // Lint, only one of the three blocks is allowed. +impl OneAllowedImpl {} //~^ multiple_inherent_impl +#[expect(clippy::multiple_inherent_impl)] +struct ExpectedFulfilled; + +impl ExpectedFulfilled {} +impl ExpectedFulfilled {} + +struct OneExpected; +impl OneExpected {} +#[expect(clippy::multiple_inherent_impl)] +impl OneExpected {} +impl OneExpected {} +//~^ multiple_inherent_impl + +// issue #8714 +struct Lifetime<'s> { + s: &'s str, +} + +impl Lifetime<'_> {} +impl Lifetime<'_> {} // false negative + +impl<'a> Lifetime<'a> {} +impl<'a> Lifetime<'a> {} // false negative + +impl<'b> Lifetime<'b> {} // false negative? + +impl Lifetime<'static> {} + +struct Generic { + g: Vec, +} + +impl Generic {} +impl Generic {} // false negative + fn main() {} diff --git a/tests/ui/impl.stderr b/tests/ui/impl.stderr index 355927b78253..93d4b3998f90 100644 --- a/tests/ui/impl.stderr +++ b/tests/ui/impl.stderr @@ -57,7 +57,7 @@ LL | | } error: multiple implementations of this structure --> tests/ui/impl.rs:71:1 | -LL | impl OneAllowedImpl {} // Lint, only one of the three blocks is allowed. +LL | impl OneAllowedImpl {} | ^^^^^^^^^^^^^^^^^^^^^^ | note: first implementation here @@ -66,5 +66,17 @@ note: first implementation here LL | impl OneAllowedImpl {} | ^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: multiple implementations of this structure + --> tests/ui/impl.rs:84:1 + | +LL | impl OneExpected {} + | ^^^^^^^^^^^^^^^^^^^ + | +note: first implementation here + --> tests/ui/impl.rs:81:1 + | +LL | impl OneExpected {} + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs index 882f909e30c9..3069c8139abe 100644 --- a/tests/ui/incompatible_msrv.rs +++ b/tests/ui/incompatible_msrv.rs @@ -1,6 +1,6 @@ #![warn(clippy::incompatible_msrv)] #![feature(custom_inner_attributes)] -#![allow(stable_features, clippy::diverging_sub_expression)] +#![allow(stable_features)] #![feature(strict_provenance)] // For use in test #![clippy::msrv = "1.3.0"] @@ -168,4 +168,14 @@ fn enum_variant_ok() { let _ = const { std::io::ErrorKind::InvalidFilename }; } +#[clippy::msrv = "1.38.0"] +const fn uncalled_len() { + let _ = Vec::::len; + let x = str::len; + let _ = x(""); + //~^ incompatible_msrv + let _ = "".len(); + //~^ incompatible_msrv +} + fn main() {} diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr index e42360d296f5..3c0bb595bd5b 100644 --- a/tests/ui/incompatible_msrv.stderr +++ b/tests/ui/incompatible_msrv.stderr @@ -110,5 +110,17 @@ error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item i LL | let _ = const { std::io::ErrorKind::InvalidFilename }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 17 previous errors +error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0` + --> tests/ui/incompatible_msrv.rs:175:13 + | +LL | let _ = x(""); + | ^ + +error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0` + --> tests/ui/incompatible_msrv.rs:177:16 + | +LL | let _ = "".len(); + | ^^^^^ + +error: aborting due to 19 previous errors diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed index 050cdfcba966..dc7e09bbdc7d 100644 --- a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed @@ -1,5 +1,5 @@ #![deny(clippy::index_refutable_slice)] -#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)] +#![allow(clippy::needless_lifetimes, clippy::collapsible_if)] enum SomeEnum { One(T), @@ -60,7 +60,7 @@ fn lintable_examples() { println!("{:?}", slice_1); } - println!("{:?}", slice); + println!("{slice:?}"); // This should not suggest using the `ref` keyword as the scrutinee is already // a reference @@ -70,7 +70,7 @@ fn lintable_examples() { println!("{:?}", slice_0); } - println!("{:?}", slice); + println!("{slice:?}"); } fn slice_index_above_limit() { @@ -113,7 +113,7 @@ fn check_slice_as_arg() { println!("This is interesting {}", slice[0]); } } - println!("{:?}", slice_wrapped); + println!("{slice_wrapped:?}"); } fn check_slice_in_struct() { @@ -152,7 +152,7 @@ fn check_slice_in_struct() { println!("This is super awesome! {}", slice_0); } } - println!("Complete wrap: {:?}", wrap); + println!("Complete wrap: {wrap:?}"); } /// This would be a nice additional feature to have in the future, but adding it @@ -164,14 +164,14 @@ fn mutable_slice_index() { if let Some(ref mut slice) = slice { slice[0] = String::from("Mr. Penguin"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); // Mut access on reference let mut slice: Option<[String; 1]> = Some([String::from("Cat")]); if let Some(slice) = &mut slice { slice[0] = String::from("Lord Meow Meow"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); } /// The lint will ignore bindings with sub patterns as it would be hard diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.rs b/tests/ui/index_refutable_slice/if_let_slice_binding.rs index 91429bfea276..f39ace101b45 100644 --- a/tests/ui/index_refutable_slice/if_let_slice_binding.rs +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.rs @@ -1,5 +1,5 @@ #![deny(clippy::index_refutable_slice)] -#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)] +#![allow(clippy::needless_lifetimes, clippy::collapsible_if)] enum SomeEnum { One(T), @@ -60,7 +60,7 @@ fn lintable_examples() { println!("{:?}", slice[1]); } - println!("{:?}", slice); + println!("{slice:?}"); // This should not suggest using the `ref` keyword as the scrutinee is already // a reference @@ -70,7 +70,7 @@ fn lintable_examples() { println!("{:?}", slice[0]); } - println!("{:?}", slice); + println!("{slice:?}"); } fn slice_index_above_limit() { @@ -113,7 +113,7 @@ fn check_slice_as_arg() { println!("This is interesting {}", slice[0]); } } - println!("{:?}", slice_wrapped); + println!("{slice_wrapped:?}"); } fn check_slice_in_struct() { @@ -152,7 +152,7 @@ fn check_slice_in_struct() { println!("This is super awesome! {}", slice[0]); } } - println!("Complete wrap: {:?}", wrap); + println!("Complete wrap: {wrap:?}"); } /// This would be a nice additional feature to have in the future, but adding it @@ -164,14 +164,14 @@ fn mutable_slice_index() { if let Some(ref mut slice) = slice { slice[0] = String::from("Mr. Penguin"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); // Mut access on reference let mut slice: Option<[String; 1]> = Some([String::from("Cat")]); if let Some(slice) = &mut slice { slice[0] = String::from("Lord Meow Meow"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); } /// The lint will ignore bindings with sub patterns as it would be hard diff --git a/tests/ui/inefficient_to_string.fixed b/tests/ui/inefficient_to_string.fixed index a0d34e58a925..29cf6de6ae5e 100644 --- a/tests/ui/inefficient_to_string.fixed +++ b/tests/ui/inefficient_to_string.fixed @@ -2,6 +2,7 @@ use std::borrow::Cow; +#[clippy::msrv = "1.81"] fn main() { let rstr: &str = "hello"; let rrstr: &&str = &rstr; @@ -34,3 +35,10 @@ fn main() { let _: String = (**rrrcow).to_string(); //~^ inefficient_to_string } + +#[clippy::msrv = "1.82"] +fn sufficient_msrv() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let _: String = rrstr.to_string(); +} diff --git a/tests/ui/inefficient_to_string.rs b/tests/ui/inefficient_to_string.rs index cbe90d4a125b..724955c60f79 100644 --- a/tests/ui/inefficient_to_string.rs +++ b/tests/ui/inefficient_to_string.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; +#[clippy::msrv = "1.81"] fn main() { let rstr: &str = "hello"; let rrstr: &&str = &rstr; @@ -34,3 +35,10 @@ fn main() { let _: String = rrrcow.to_string(); //~^ inefficient_to_string } + +#[clippy::msrv = "1.82"] +fn sufficient_msrv() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let _: String = rrstr.to_string(); +} diff --git a/tests/ui/inefficient_to_string.stderr b/tests/ui/inefficient_to_string.stderr index 8593c0addc5f..ea3dd7e0ae2f 100644 --- a/tests/ui/inefficient_to_string.stderr +++ b/tests/ui/inefficient_to_string.stderr @@ -1,5 +1,5 @@ error: calling `to_string` on `&&str` - --> tests/ui/inefficient_to_string.rs:10:21 + --> tests/ui/inefficient_to_string.rs:11:21 | LL | let _: String = rrstr.to_string(); | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstr).to_string()` @@ -12,7 +12,7 @@ LL | #![deny(clippy::inefficient_to_string)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: calling `to_string` on `&&&str` - --> tests/ui/inefficient_to_string.rs:12:21 + --> tests/ui/inefficient_to_string.rs:13:21 | LL | let _: String = rrrstr.to_string(); | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstr).to_string()` @@ -20,7 +20,7 @@ LL | let _: String = rrrstr.to_string(); = help: `&&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` error: calling `to_string` on `&&std::string::String` - --> tests/ui/inefficient_to_string.rs:21:21 + --> tests/ui/inefficient_to_string.rs:22:21 | LL | let _: String = rrstring.to_string(); | ^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstring).to_string()` @@ -28,7 +28,7 @@ LL | let _: String = rrstring.to_string(); = help: `&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` error: calling `to_string` on `&&&std::string::String` - --> tests/ui/inefficient_to_string.rs:23:21 + --> tests/ui/inefficient_to_string.rs:24:21 | LL | let _: String = rrrstring.to_string(); | ^^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstring).to_string()` @@ -36,7 +36,7 @@ LL | let _: String = rrrstring.to_string(); = help: `&&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` error: calling `to_string` on `&&std::borrow::Cow<'_, str>` - --> tests/ui/inefficient_to_string.rs:32:21 + --> tests/ui/inefficient_to_string.rs:33:21 | LL | let _: String = rrcow.to_string(); | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrcow).to_string()` @@ -44,7 +44,7 @@ LL | let _: String = rrcow.to_string(); = help: `&std::borrow::Cow<'_, str>` implements `ToString` through a slower blanket impl, but `std::borrow::Cow<'_, str>` has a fast specialization of `ToString` error: calling `to_string` on `&&&std::borrow::Cow<'_, str>` - --> tests/ui/inefficient_to_string.rs:34:21 + --> tests/ui/inefficient_to_string.rs:35:21 | LL | let _: String = rrrcow.to_string(); | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrcow).to_string()` diff --git a/tests/ui/infinite_iter.rs b/tests/ui/infinite_iter.rs index 701a86534ba0..4e1668ed04fb 100644 --- a/tests/ui/infinite_iter.rs +++ b/tests/ui/infinite_iter.rs @@ -1,4 +1,4 @@ -#![allow(clippy::uninlined_format_args, clippy::double_ended_iterator_last)] +#![allow(clippy::double_ended_iterator_last)] use std::iter::repeat; fn square_is_lower_64(x: &u32) -> bool { @@ -30,7 +30,7 @@ fn infinite_iters() { .rev() .cycle() .map(|x| x + 1_u32) - .for_each(|x| println!("{}", x)); + .for_each(|x| println!("{x}")); // infinite iter (0..3_u32).flat_map(|x| x..).sum::(); // infinite iter diff --git a/tests/ui/infinite_iter.stderr b/tests/ui/infinite_iter.stderr index b9e7c008f93e..3db97313b621 100644 --- a/tests/ui/infinite_iter.stderr +++ b/tests/ui/infinite_iter.stderr @@ -30,8 +30,8 @@ LL | | LL | | .rev() LL | | .cycle() LL | | .map(|x| x + 1_u32) -LL | | .for_each(|x| println!("{}", x)); - | |________________________________________^ +LL | | .for_each(|x| println!("{x}")); + | |______________________________________^ error: infinite iteration detected --> tests/ui/infinite_iter.rs:37:5 diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index 7d01a7fb61fc..0bde31aca030 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -1,7 +1,7 @@ //@no-rustfix: multiple suggestions add `-> !` to the same fn //@aux-build:proc_macros.rs -#![allow(clippy::never_loop)] +#![allow(clippy::never_loop, clippy::while_let_loop)] #![warn(clippy::infinite_loop)] extern crate proc_macros; diff --git a/tests/ui/integer_division_remainder_used.stderr b/tests/ui/integer_division_remainder_used.stderr index ea9f0e716c7d..32a61a8585f0 100644 --- a/tests/ui/integer_division_remainder_used.stderr +++ b/tests/ui/integer_division_remainder_used.stderr @@ -1,4 +1,4 @@ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:10:14 | LL | Self(self.0 / rhs.0) @@ -7,49 +7,49 @@ LL | Self(self.0 / rhs.0) = note: `-D clippy::integer-division-remainder-used` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::integer_division_remainder_used)]` -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:18:14 | LL | Self(self.0 % rhs.0) | ^^^^^^^^^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:27:13 | LL | let c = a / b; | ^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:29:13 | LL | let d = a % b; | ^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:31:13 | LL | let e = &a / b; | ^^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:33:13 | LL | let f = a % &b; | ^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:35:13 | LL | let g = &a / &b; | ^^^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:37:13 | LL | let h = &10 % b; | ^^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:39:13 | LL | let i = a / &4; diff --git a/tests/ui/invalid_upcast_comparisons.rs b/tests/ui/invalid_upcast_comparisons.rs index 4f3194869f43..88ecca65e150 100644 --- a/tests/ui/invalid_upcast_comparisons.rs +++ b/tests/ui/invalid_upcast_comparisons.rs @@ -133,3 +133,15 @@ fn main() { -5 == (u32 as i32); } + +fn issue15662() { + macro_rules! add_one { + ($x:expr) => { + $x + 1 + }; + } + + let x: u8 = 1; + (add_one!(x) as u32) > 300; + //~^ invalid_upcast_comparisons +} diff --git a/tests/ui/invalid_upcast_comparisons.stderr b/tests/ui/invalid_upcast_comparisons.stderr index ef36f18eabc9..cc042a7c4b01 100644 --- a/tests/ui/invalid_upcast_comparisons.stderr +++ b/tests/ui/invalid_upcast_comparisons.stderr @@ -163,5 +163,11 @@ error: because of the numeric bounds on `u8` prior to casting, this expression i LL | -5 >= (u8 as i32); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 27 previous errors +error: because of the numeric bounds on `add_one!(x)` prior to casting, this expression is always false + --> tests/ui/invalid_upcast_comparisons.rs:145:5 + | +LL | (add_one!(x) as u32) > 300; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors diff --git a/tests/ui/ip_constant.fixed b/tests/ui/ip_constant.fixed index c94796821394..afdf581bacf7 100644 --- a/tests/ui/ip_constant.fixed +++ b/tests/ui/ip_constant.fixed @@ -72,33 +72,44 @@ const CONST_U16_1: u16 = 1; fn const_test1() { use std::net::Ipv4Addr; - let _ = Ipv4Addr::LOCALHOST; - //~^ ip_constant - let _ = Ipv4Addr::BROADCAST; - //~^ ip_constant - let _ = Ipv4Addr::UNSPECIFIED; - //~^ ip_constant + let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); + let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); + let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); use std::net::Ipv6Addr; - let _ = Ipv6Addr::LOCALHOST; - - let _ = Ipv6Addr::UNSPECIFIED; + let _ = Ipv6Addr::new( + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_1, + ); + + let _ = Ipv6Addr::new( + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + ); } fn const_test2() { use std::net::Ipv4Addr; let _ = Ipv4Addr::LOCALHOST; //~^ ip_constant - let _ = Ipv4Addr::BROADCAST; - //~^ ip_constant - let _ = Ipv4Addr::UNSPECIFIED; - //~^ ip_constant + let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); + let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); use std::net::Ipv6Addr; - let _ = Ipv6Addr::LOCALHOST; - //~^ ip_constant - let _ = Ipv6Addr::LOCALHOST; - //~^ ip_constant + let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); + let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); } macro_rules! ipv4_new { diff --git a/tests/ui/ip_constant.rs b/tests/ui/ip_constant.rs index 69a5c3b4e923..04fc2f0f6fda 100644 --- a/tests/ui/ip_constant.rs +++ b/tests/ui/ip_constant.rs @@ -73,15 +73,11 @@ const CONST_U16_1: u16 = 1; fn const_test1() { use std::net::Ipv4Addr; let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); - //~^ ip_constant let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); - //~^ ip_constant let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); - //~^ ip_constant use std::net::Ipv6Addr; let _ = Ipv6Addr::new( - //~^ ip_constant CONST_U16_0, CONST_U16_0, CONST_U16_0, @@ -93,7 +89,6 @@ fn const_test1() { ); let _ = Ipv6Addr::new( - //~^ ip_constant CONST_U16_0, CONST_U16_0, CONST_U16_0, @@ -110,15 +105,11 @@ fn const_test2() { let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); //~^ ip_constant let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); - //~^ ip_constant let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); - //~^ ip_constant use std::net::Ipv6Addr; let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); - //~^ ip_constant let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); - //~^ ip_constant } macro_rules! ipv4_new { diff --git a/tests/ui/ip_constant.stderr b/tests/ui/ip_constant.stderr index 07d912b18a57..44e3d6448dbd 100644 --- a/tests/ui/ip_constant.stderr +++ b/tests/ui/ip_constant.stderr @@ -241,101 +241,7 @@ LL + let _ = std::net::Ipv6Addr::UNSPECIFIED; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:75:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); -LL + let _ = Ipv4Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:77:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); -LL + let _ = Ipv4Addr::BROADCAST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:79:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); -LL + let _ = Ipv4Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:83:13 - | -LL | let _ = Ipv6Addr::new( - | _____________^ -LL | | -LL | | CONST_U16_0, -LL | | CONST_U16_0, -... | -LL | | CONST_U16_1, -LL | | ); - | |_____^ - | -help: use - | -LL - let _ = Ipv6Addr::new( -LL - -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_1, -LL - ); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:95:13 - | -LL | let _ = Ipv6Addr::new( - | _____________^ -LL | | -LL | | CONST_U16_0, -LL | | CONST_U16_0, -... | -LL | | CONST_U16_0, -LL | | ); - | |_____^ - | -help: use - | -LL - let _ = Ipv6Addr::new( -LL - -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - ); -LL + let _ = Ipv6Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:110:13 + --> tests/ui/ip_constant.rs:105:13 | LL | let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -346,53 +252,5 @@ LL - let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); LL + let _ = Ipv4Addr::LOCALHOST; | -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:112:13 - | -LL | let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); -LL + let _ = Ipv4Addr::BROADCAST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:114:13 - | -LL | let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); -LL + let _ = Ipv4Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:118:13 - | -LL | let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:120:13 - | -LL | let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: aborting due to 30 previous errors +error: aborting due to 21 previous errors diff --git a/tests/ui/issue_2356.fixed b/tests/ui/issue_2356.fixed index 46ba653eba2c..3e066df77bfb 100644 --- a/tests/ui/issue_2356.fixed +++ b/tests/ui/issue_2356.fixed @@ -1,6 +1,5 @@ #![deny(clippy::while_let_on_iterator)] #![allow(unused_mut)] -#![allow(clippy::uninlined_format_args)] use std::iter::Iterator; @@ -16,7 +15,7 @@ impl Foo { fn foo2>(mut it: I) { for e in it { //~^ while_let_on_iterator - println!("{:?}", e); + println!("{e:?}"); } } } diff --git a/tests/ui/issue_2356.rs b/tests/ui/issue_2356.rs index defe2584a93e..98600d17c6df 100644 --- a/tests/ui/issue_2356.rs +++ b/tests/ui/issue_2356.rs @@ -1,6 +1,5 @@ #![deny(clippy::while_let_on_iterator)] #![allow(unused_mut)] -#![allow(clippy::uninlined_format_args)] use std::iter::Iterator; @@ -16,7 +15,7 @@ impl Foo { fn foo2>(mut it: I) { while let Some(e) = it.next() { //~^ while_let_on_iterator - println!("{:?}", e); + println!("{e:?}"); } } } diff --git a/tests/ui/issue_2356.stderr b/tests/ui/issue_2356.stderr index eae2ce97fc6b..ddee91fcfcd5 100644 --- a/tests/ui/issue_2356.stderr +++ b/tests/ui/issue_2356.stderr @@ -1,5 +1,5 @@ error: this loop could be written as a `for` loop - --> tests/ui/issue_2356.rs:17:9 + --> tests/ui/issue_2356.rs:16:9 | LL | while let Some(e) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for e in it` diff --git a/tests/ui/issue_4266.rs b/tests/ui/issue_4266.rs index 664f0b84a207..b2a01124995c 100644 --- a/tests/ui/issue_4266.rs +++ b/tests/ui/issue_4266.rs @@ -1,5 +1,4 @@ #![allow(dead_code)] -#![allow(clippy::uninlined_format_args)] async fn sink1<'a>(_: &'a str) {} // lint //~^ needless_lifetimes @@ -39,7 +38,7 @@ impl Foo { // rust-lang/rust#61115 // ok async fn print(s: &str) { - println!("{}", s); + println!("{s}"); } fn main() {} diff --git a/tests/ui/issue_4266.stderr b/tests/ui/issue_4266.stderr index 0e181025430f..b80a738a50be 100644 --- a/tests/ui/issue_4266.stderr +++ b/tests/ui/issue_4266.stderr @@ -1,5 +1,5 @@ error: the following explicit lifetimes could be elided: 'a - --> tests/ui/issue_4266.rs:4:16 + --> tests/ui/issue_4266.rs:3:16 | LL | async fn sink1<'a>(_: &'a str) {} // lint | ^^ ^^ @@ -8,13 +8,13 @@ LL | async fn sink1<'a>(_: &'a str) {} // lint = help: to override `-D warnings` add `#[allow(clippy::needless_lifetimes)]` error: the following explicit lifetimes could be elided: 'a - --> tests/ui/issue_4266.rs:10:21 + --> tests/ui/issue_4266.rs:9:21 | LL | async fn one_to_one<'a>(s: &'a str) -> &'a str { | ^^ ^^ ^^ error: methods called `new` usually take no `self` - --> tests/ui/issue_4266.rs:32:22 + --> tests/ui/issue_4266.rs:31:22 | LL | pub async fn new(&mut self) -> Self { | ^^^^^^^^^ diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index b0e548f17909..4171f19469a4 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().take(2).cloned().collect(); //~^ iter_overeager_cloned @@ -77,19 +77,19 @@ fn main() { } let _ = vec.iter().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index cedf62a6b473..fe6aba24dd3e 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().cloned().take(2).collect(); //~^ iter_overeager_cloned @@ -78,19 +78,19 @@ fn main() { } let _ = vec.iter().cloned().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 1616dec95b79..f234d19e4aaa 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -25,8 +25,8 @@ LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); | | | help: try: `.count()` | - = note: `-D clippy::redundant-clone` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::redundant_clone)]` + = note: `-D clippy::redundant-iter-cloned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_iter_cloned)]` error: unnecessarily eager cloning of iterator items --> tests/ui/iter_overeager_cloned.rs:21:21 diff --git a/tests/ui/legacy_numeric_constants_unfixable.rs b/tests/ui/legacy_numeric_constants_unfixable.rs index 9bf0f7f355ae..084d97fdc0f7 100644 --- a/tests/ui/legacy_numeric_constants_unfixable.rs +++ b/tests/ui/legacy_numeric_constants_unfixable.rs @@ -77,3 +77,14 @@ fn msrv_juust_right() { use std::u32::MAX; //~^ ERROR: importing a legacy numeric constant } + +macro_rules! foo { + ($a: ty) => { + let _ = <$a>::max_value(); + let _ = (<$a>::max_value)(); + }; +} + +fn issue15805() { + foo!(u8); +} diff --git a/tests/ui/len_zero.fixed b/tests/ui/len_zero.fixed index b8573ef13b0e..50b96f7712d7 100644 --- a/tests/ui/len_zero.fixed +++ b/tests/ui/len_zero.fixed @@ -2,7 +2,7 @@ #![allow( dead_code, unused, - clippy::needless_if, + clippy::needless_ifs, clippy::len_without_is_empty, clippy::const_is_empty )] @@ -275,3 +275,7 @@ fn no_infinite_recursion() -> bool { // Do not crash while checking if S implements `.is_empty()` S == "" } + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 +} diff --git a/tests/ui/len_zero.rs b/tests/ui/len_zero.rs index ef3c49c1ab30..a6acc2be714b 100644 --- a/tests/ui/len_zero.rs +++ b/tests/ui/len_zero.rs @@ -2,7 +2,7 @@ #![allow( dead_code, unused, - clippy::needless_if, + clippy::needless_ifs, clippy::len_without_is_empty, clippy::const_is_empty )] @@ -275,3 +275,7 @@ fn no_infinite_recursion() -> bool { // Do not crash while checking if S implements `.is_empty()` S == "" } + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 +} diff --git a/tests/ui/len_zero_unstable.fixed b/tests/ui/len_zero_unstable.fixed new file mode 100644 index 000000000000..8d4e6c2cc006 --- /dev/null +++ b/tests/ui/len_zero_unstable.fixed @@ -0,0 +1,7 @@ +#![warn(clippy::len_zero)] +#![feature(exact_size_is_empty)] + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.is_empty() + //~^ len_zero +} diff --git a/tests/ui/len_zero_unstable.rs b/tests/ui/len_zero_unstable.rs new file mode 100644 index 000000000000..f59056c5c55b --- /dev/null +++ b/tests/ui/len_zero_unstable.rs @@ -0,0 +1,7 @@ +#![warn(clippy::len_zero)] +#![feature(exact_size_is_empty)] + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 + //~^ len_zero +} diff --git a/tests/ui/len_zero_unstable.stderr b/tests/ui/len_zero_unstable.stderr new file mode 100644 index 000000000000..103ccf3dcbf5 --- /dev/null +++ b/tests/ui/len_zero_unstable.stderr @@ -0,0 +1,11 @@ +error: length comparison to zero + --> tests/ui/len_zero_unstable.rs:5:5 + | +LL | vertices.len() == 0 + | ^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `vertices.is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::len_zero)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/let_and_return.edition2021.fixed b/tests/ui/let_and_return.edition2021.fixed index 70d503018e0f..e89e4476bf82 100644 --- a/tests/ui/let_and_return.edition2021.fixed +++ b/tests/ui/let_and_return.edition2021.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2024.fixed b/tests/ui/let_and_return.edition2024.fixed index 9990c3b71205..d2c76673ca03 100644 --- a/tests/ui/let_and_return.edition2024.fixed +++ b/tests/ui/let_and_return.edition2024.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 48c20cdd60db..1af5f8ba5c16 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 381d4cac4622..6d984a495d2b 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -1,5 +1,10 @@ #![warn(clippy::let_unit_value)] -#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow( + clippy::no_effect, + clippy::needless_late_init, + path_statements, + clippy::match_single_binding +)] macro_rules! let_and_return { ($n:expr) => {{ @@ -15,12 +20,12 @@ fn main() { if true { // do not lint this, since () is explicit let _a = (); - let () = dummy(); + let () = returns_unit(); let () = (); - () = dummy(); + () = returns_unit(); () = (); let _a: () = (); - let _a: () = dummy(); + let _a: () = returns_unit(); } consume_units_with_for_loop(); // should be fine as well @@ -30,7 +35,7 @@ fn main() { let_and_return!(()) // should be fine } -fn dummy() {} +fn returns_unit() {} // Related to issue #1964 fn consume_units_with_for_loop() { @@ -181,8 +186,6 @@ async fn issue10433() { pub async fn issue11502(a: ()) {} pub fn issue12594() { - fn returns_unit() {} - fn returns_result(res: T) -> Result { Ok(res) } @@ -199,13 +202,40 @@ pub fn issue12594() { } } +fn takes_unit(x: ()) {} + fn issue15061() { - fn return_unit() {} - fn do_something(x: ()) {} + let res = (); + returns_unit(); + //~^ let_unit_value + takes_unit(()); + println!("{res:?}"); +} + +fn issue15771() { + match "Example String" { + _ => returns_unit(), + //~^ let_unit_value + } + if true {} + //~^ let_unit_value +} + +fn issue_15784() { let res = (); - return_unit(); + eprintln!("I return unit"); //~^ let_unit_value - do_something(()); + takes_unit(()); println!("{res:?}"); } + +fn issue15789() { + struct Foo { + value: (), + } + println!(); + //~^ let_unit_value + + Foo { value: () }; +} diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index cdfc74991c40..a0e32f0b67a0 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -1,5 +1,10 @@ #![warn(clippy::let_unit_value)] -#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow( + clippy::no_effect, + clippy::needless_late_init, + path_statements, + clippy::match_single_binding +)] macro_rules! let_and_return { ($n:expr) => {{ @@ -15,12 +20,12 @@ fn main() { if true { // do not lint this, since () is explicit let _a = (); - let () = dummy(); + let () = returns_unit(); let () = (); - () = dummy(); + () = returns_unit(); () = (); let _a: () = (); - let _a: () = dummy(); + let _a: () = returns_unit(); } consume_units_with_for_loop(); // should be fine as well @@ -30,7 +35,7 @@ fn main() { let_and_return!(()) // should be fine } -fn dummy() {} +fn returns_unit() {} // Related to issue #1964 fn consume_units_with_for_loop() { @@ -181,8 +186,6 @@ async fn issue10433() { pub async fn issue11502(a: ()) {} pub fn issue12594() { - fn returns_unit() {} - fn returns_result(res: T) -> Result { Ok(res) } @@ -199,12 +202,38 @@ pub fn issue12594() { } } +fn takes_unit(x: ()) {} + fn issue15061() { - fn return_unit() {} - fn do_something(x: ()) {} + let res = returns_unit(); + //~^ let_unit_value + takes_unit(res); + println!("{res:?}"); +} + +fn issue15771() { + match "Example String" { + _ => _ = returns_unit(), + //~^ let_unit_value + } - let res = return_unit(); + _ = if true {} //~^ let_unit_value - do_something(res); +} + +fn issue_15784() { + let res = eprintln!("I return unit"); + //~^ let_unit_value + takes_unit(res); println!("{res:?}"); } + +fn issue15789() { + struct Foo { + value: (), + } + let value = println!(); + //~^ let_unit_value + + Foo { value }; +} diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 637c9ff686bd..6e7b958df4d9 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -1,14 +1,19 @@ error: this let-binding has unit value - --> tests/ui/let_unit.rs:11:5 + --> tests/ui/let_unit.rs:16:5 | LL | let _x = println!("x"); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::let-unit-value` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]` +help: omit the `let` binding + | +LL - let _x = println!("x"); +LL + println!("x"); + | error: this let-binding has unit value - --> tests/ui/let_unit.rs:60:5 + --> tests/ui/let_unit.rs:65:5 | LL | / let _ = v LL | | @@ -21,18 +26,12 @@ LL | | .unwrap(); | help: omit the `let` binding | -LL ~ v -LL + -LL + .into_iter() -LL + .map(|i| i * 2) -LL + .filter(|i| i.is_multiple_of(2)) -LL + .map(|_| ()) -LL + .next() -LL + .unwrap(); +LL - let _ = v +LL + v | error: this let-binding has unit value - --> tests/ui/let_unit.rs:110:5 + --> tests/ui/let_unit.rs:115:5 | LL | / let x = match Some(0) { LL | | @@ -45,17 +44,12 @@ LL | | }; | help: omit the `let` binding | -LL ~ match Some(0) { -LL + -LL + None => f2(1), -LL + Some(0) => f(), -LL + Some(1) => f2(3), -LL + Some(_) => (), -LL + }; +LL - let x = match Some(0) { +LL + match Some(0) { | error: this let-binding has unit value - --> tests/ui/let_unit.rs:192:9 + --> tests/ui/let_unit.rs:195:9 | LL | let res = returns_unit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,18 +63,70 @@ LL ~ returns_result(()).unwrap(); | error: this let-binding has unit value - --> tests/ui/let_unit.rs:206:5 + --> tests/ui/let_unit.rs:208:5 | -LL | let res = return_unit(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let res = returns_unit(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: replace variable usages with `()` | LL ~ let res = (); -LL ~ return_unit(); +LL ~ returns_unit(); +LL | +LL ~ takes_unit(()); + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:216:14 + | +LL | _ => _ = returns_unit(), + | ^^^^^^^^^^^^^^^^^^ + | +help: omit the `let` binding + | +LL - _ => _ = returns_unit(), +LL + _ => returns_unit(), + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:220:5 + | +LL | _ = if true {} + | ^^^^^^^^^^^^^^ + | +help: omit the `let` binding + | +LL - _ = if true {} +LL + if true {} + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:225:5 + | +LL | let res = eprintln!("I return unit"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace variable usages with `()` + | +LL ~ let res = (); +LL ~ eprintln!("I return unit"); +LL | +LL ~ takes_unit(()); + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:235:5 + | +LL | let value = println!(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: omit the `let` binding and replace variable usages with `()` + | +LL ~ println!(); +LL | LL | -LL ~ do_something(()); +LL ~ Foo { value: () }; | -error: aborting due to 5 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/lines_filter_map_ok.fixed b/tests/ui/lines_filter_map_ok.fixed index 977e31c744a2..0617f06fafb7 100644 --- a/tests/ui/lines_filter_map_ok.fixed +++ b/tests/ui/lines_filter_map_ok.fixed @@ -1,39 +1,37 @@ -#![allow(unused, clippy::map_identity)] +#![allow(clippy::map_identity)] #![warn(clippy::lines_filter_map_ok)] use std::io::{self, BufRead, BufReader}; fn main() -> io::Result<()> { + // Lint: + let f = std::fs::File::open("/")?; - // Lint BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - let s = "foo\nbar\nbaz\n"; - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Do not lint (not a `Lines` iterator) + + // Do not lint: + + // not a `Lines` iterator io::stdin() .lines() .map(std::convert::identity) .filter_map(|x| x.ok()) .for_each(|_| ()); - // Do not lint (not a `Result::ok()` extractor) + // not a `Result::ok()` extractor io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); Ok(()) } diff --git a/tests/ui/lines_filter_map_ok.rs b/tests/ui/lines_filter_map_ok.rs index 2196075bc445..dfd1ec431a52 100644 --- a/tests/ui/lines_filter_map_ok.rs +++ b/tests/ui/lines_filter_map_ok.rs @@ -1,39 +1,37 @@ -#![allow(unused, clippy::map_identity)] +#![allow(clippy::map_identity)] #![warn(clippy::lines_filter_map_ok)] use std::io::{self, BufRead, BufReader}; fn main() -> io::Result<()> { + // Lint: + let f = std::fs::File::open("/")?; - // Lint BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().flatten().for_each(|_| ()); //~^ lines_filter_map_ok - let s = "foo\nbar\nbaz\n"; - // Lint io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().flatten().for_each(|_| ()); //~^ lines_filter_map_ok - // Do not lint (not a `Lines` iterator) + + // Do not lint: + + // not a `Lines` iterator io::stdin() .lines() .map(std::convert::identity) .filter_map(|x| x.ok()) .for_each(|_| ()); - // Do not lint (not a `Result::ok()` extractor) + // not a `Result::ok()` extractor io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); Ok(()) } diff --git a/tests/ui/lines_filter_map_ok.stderr b/tests/ui/lines_filter_map_ok.stderr index f9038eec9fb2..c05dadd1b5f2 100644 --- a/tests/ui/lines_filter_map_ok.stderr +++ b/tests/ui/lines_filter_map_ok.stderr @@ -1,11 +1,11 @@ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:9:31 + --> tests/ui/lines_filter_map_ok.rs:10:31 | LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:9:5 + --> tests/ui/lines_filter_map_ok.rs:10:5 | LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,49 +25,49 @@ LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `flatten()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:17:31 + --> tests/ui/lines_filter_map_ok.rs:16:31 | LL | BufReader::new(f).lines().flatten().for_each(|_| ()); | ^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:17:5 + --> tests/ui/lines_filter_map_ok.rs:16:5 | LL | BufReader::new(f).lines().flatten().for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:22:25 + --> tests/ui/lines_filter_map_ok.rs:19:25 | LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:22:5 + --> tests/ui/lines_filter_map_ok.rs:19:5 | LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:25:25 + --> tests/ui/lines_filter_map_ok.rs:21:25 | LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:25:5 + --> tests/ui/lines_filter_map_ok.rs:21:5 | LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ error: `flatten()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:28:25 + --> tests/ui/lines_filter_map_ok.rs:23:25 | LL | io::stdin().lines().flatten().for_each(|_| ()); | ^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:28:5 + --> tests/ui/lines_filter_map_ok.rs:23:5 | LL | io::stdin().lines().flatten().for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/manual_assert.edition2018.fixed b/tests/ui/manual_assert.edition2018.fixed new file mode 100644 index 000000000000..d1f798bd5c54 --- /dev/null +++ b/tests/ui/manual_assert.edition2018.fixed @@ -0,0 +1,98 @@ +//@revisions: edition2018 edition2021 +//@[edition2018] edition:2018 +//@[edition2021] edition:2021 + +#![warn(clippy::manual_assert)] +#![allow(dead_code, unused_doc_comments)] +#![allow(clippy::nonminimal_bool, clippy::uninlined_format_args, clippy::useless_vec)] + +macro_rules! one { + () => { + 1 + }; +} + +fn main() { + let a = vec![1, 2, 3]; + let c = Some(2); + if !a.is_empty() + && a.len() == 3 + && c.is_some() + && !a.is_empty() + && a.len() == 3 + && !a.is_empty() + && a.len() == 3 + && !a.is_empty() + && a.len() == 3 + { + panic!("qaqaq{:?}", a); + } + //~^ manual_assert + assert!(a.is_empty(), "qaqaq{:?}", a); + //~^ manual_assert + assert!(a.is_empty(), "qwqwq"); + if a.len() == 3 { + println!("qwq"); + println!("qwq"); + println!("qwq"); + } + if let Some(b) = c { + panic!("orz {}", b); + } + if a.len() == 3 { + panic!("qaqaq"); + } else { + println!("qwq"); + } + let b = vec![1, 2, 3]; + //~^ manual_assert + assert!(!b.is_empty(), "panic1"); + //~^ manual_assert + assert!(!(b.is_empty() && a.is_empty()), "panic2"); + //~^ manual_assert + assert!(!(a.is_empty() && !b.is_empty()), "panic3"); + //~^ manual_assert + assert!(!(b.is_empty() || a.is_empty()), "panic4"); + //~^ manual_assert + assert!(!(a.is_empty() || !b.is_empty()), "panic5"); + //~^ manual_assert + assert!(!a.is_empty(), "with expansion {}", one!()); + if a.is_empty() { + let _ = 0; + } else if a.len() == 1 { + panic!("panic6"); + } +} + +fn issue7730(a: u8) { + // Suggestion should preserve comment + //~^ manual_assert + // comment + /* this is a + multiline + comment */ + /// Doc comment + // comment after `panic!` + assert!(a <= 2, "panic with comment"); +} + +fn issue12505() { + struct Foo(T); + + impl Foo { + const BAR: () = //~^ manual_assert + assert!(N != 0, ); + } +} + +fn issue15227(left: u64, right: u64) -> u64 { + macro_rules! is_x86_feature_detected { + ($feature:literal) => { + $feature.len() > 0 && $feature.starts_with("ss") + }; + } + + //~^ manual_assert + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + unsafe { todo!() } +} diff --git a/tests/ui/manual_assert.edition2018.stderr b/tests/ui/manual_assert.edition2018.stderr index 2e9c9045caae..c81a85527527 100644 --- a/tests/ui/manual_assert.edition2018.stderr +++ b/tests/ui/manual_assert.edition2018.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:32:5 + --> tests/ui/manual_assert.rs:30:5 | LL | / if !a.is_empty() { LL | | @@ -9,17 +9,14 @@ LL | | } | = note: `-D clippy::manual-assert` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_assert)]` -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !a.is_empty() { -LL - -LL - panic!("qaqaq{:?}", a); -LL - } +LL ~ LL + assert!(a.is_empty(), "qaqaq{:?}", a); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:36:5 + --> tests/ui/manual_assert.rs:34:5 | LL | / if !a.is_empty() { LL | | @@ -27,17 +24,14 @@ LL | | panic!("qwqwq"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !a.is_empty() { -LL - -LL - panic!("qwqwq"); -LL - } +LL ~ LL + assert!(a.is_empty(), "qwqwq"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:54:5 + --> tests/ui/manual_assert.rs:52:5 | LL | / if b.is_empty() { LL | | @@ -45,17 +39,14 @@ LL | | panic!("panic1"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() { -LL - -LL - panic!("panic1"); -LL - } +LL ~ LL + assert!(!b.is_empty(), "panic1"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:58:5 + --> tests/ui/manual_assert.rs:56:5 | LL | / if b.is_empty() && a.is_empty() { LL | | @@ -63,17 +54,14 @@ LL | | panic!("panic2"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() && a.is_empty() { -LL - -LL - panic!("panic2"); -LL - } +LL ~ LL + assert!(!(b.is_empty() && a.is_empty()), "panic2"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:62:5 + --> tests/ui/manual_assert.rs:60:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | @@ -81,17 +69,14 @@ LL | | panic!("panic3"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() && !b.is_empty() { -LL - -LL - panic!("panic3"); -LL - } +LL ~ LL + assert!(!(a.is_empty() && !b.is_empty()), "panic3"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:66:5 + --> tests/ui/manual_assert.rs:64:5 | LL | / if b.is_empty() || a.is_empty() { LL | | @@ -99,17 +84,14 @@ LL | | panic!("panic4"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() || a.is_empty() { -LL - -LL - panic!("panic4"); -LL - } +LL ~ LL + assert!(!(b.is_empty() || a.is_empty()), "panic4"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:70:5 + --> tests/ui/manual_assert.rs:68:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | @@ -117,17 +99,14 @@ LL | | panic!("panic5"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() || !b.is_empty() { -LL - -LL - panic!("panic5"); -LL - } +LL ~ LL + assert!(!(a.is_empty() || !b.is_empty()), "panic5"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:74:5 + --> tests/ui/manual_assert.rs:72:5 | LL | / if a.is_empty() { LL | | @@ -135,17 +114,14 @@ LL | | panic!("with expansion {}", one!()) LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() { -LL - -LL - panic!("with expansion {}", one!()) -LL - } +LL ~ LL + assert!(!a.is_empty(), "with expansion {}", one!()); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:87:5 + --> tests/ui/manual_assert.rs:85:5 | LL | / if a > 2 { LL | | @@ -156,22 +132,20 @@ LL | | panic!("panic with comment") // comment after `panic!` LL | | } | |_____^ | -help: try instead - | -LL - if a > 2 { -LL - -LL - // comment -LL - /* this is a -LL - multiline -LL - comment */ -LL - /// Doc comment -LL - panic!("panic with comment") // comment after `panic!` -LL - } +help: replace `if`-then-`panic!` with `assert!` + | +LL ~ +LL + // comment +LL + /* this is a +LL + multiline +LL + comment */ +LL + /// Doc comment +LL + // comment after `panic!` LL + assert!(a <= 2, "panic with comment"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:102:25 + --> tests/ui/manual_assert.rs:100:25 | LL | const BAR: () = if N == 0 { | _________________________^ @@ -180,17 +154,14 @@ LL | | panic!() LL | | }; | |_________^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - const BAR: () = if N == 0 { -LL - -LL - panic!() -LL - }; -LL + const BAR: () = assert!(N != 0, ); +LL ~ const BAR: () = +LL ~ assert!(N != 0, ); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:116:5 + --> tests/ui/manual_assert.rs:114:5 | LL | / if !is_x86_feature_detected!("ssse3") { LL | | @@ -198,12 +169,9 @@ LL | | panic!("SSSE3 is not supported"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !is_x86_feature_detected!("ssse3") { -LL - -LL - panic!("SSSE3 is not supported"); -LL - } +LL ~ LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); | diff --git a/tests/ui/manual_assert.edition2021.fixed b/tests/ui/manual_assert.edition2021.fixed new file mode 100644 index 000000000000..d1f798bd5c54 --- /dev/null +++ b/tests/ui/manual_assert.edition2021.fixed @@ -0,0 +1,98 @@ +//@revisions: edition2018 edition2021 +//@[edition2018] edition:2018 +//@[edition2021] edition:2021 + +#![warn(clippy::manual_assert)] +#![allow(dead_code, unused_doc_comments)] +#![allow(clippy::nonminimal_bool, clippy::uninlined_format_args, clippy::useless_vec)] + +macro_rules! one { + () => { + 1 + }; +} + +fn main() { + let a = vec![1, 2, 3]; + let c = Some(2); + if !a.is_empty() + && a.len() == 3 + && c.is_some() + && !a.is_empty() + && a.len() == 3 + && !a.is_empty() + && a.len() == 3 + && !a.is_empty() + && a.len() == 3 + { + panic!("qaqaq{:?}", a); + } + //~^ manual_assert + assert!(a.is_empty(), "qaqaq{:?}", a); + //~^ manual_assert + assert!(a.is_empty(), "qwqwq"); + if a.len() == 3 { + println!("qwq"); + println!("qwq"); + println!("qwq"); + } + if let Some(b) = c { + panic!("orz {}", b); + } + if a.len() == 3 { + panic!("qaqaq"); + } else { + println!("qwq"); + } + let b = vec![1, 2, 3]; + //~^ manual_assert + assert!(!b.is_empty(), "panic1"); + //~^ manual_assert + assert!(!(b.is_empty() && a.is_empty()), "panic2"); + //~^ manual_assert + assert!(!(a.is_empty() && !b.is_empty()), "panic3"); + //~^ manual_assert + assert!(!(b.is_empty() || a.is_empty()), "panic4"); + //~^ manual_assert + assert!(!(a.is_empty() || !b.is_empty()), "panic5"); + //~^ manual_assert + assert!(!a.is_empty(), "with expansion {}", one!()); + if a.is_empty() { + let _ = 0; + } else if a.len() == 1 { + panic!("panic6"); + } +} + +fn issue7730(a: u8) { + // Suggestion should preserve comment + //~^ manual_assert + // comment + /* this is a + multiline + comment */ + /// Doc comment + // comment after `panic!` + assert!(a <= 2, "panic with comment"); +} + +fn issue12505() { + struct Foo(T); + + impl Foo { + const BAR: () = //~^ manual_assert + assert!(N != 0, ); + } +} + +fn issue15227(left: u64, right: u64) -> u64 { + macro_rules! is_x86_feature_detected { + ($feature:literal) => { + $feature.len() > 0 && $feature.starts_with("ss") + }; + } + + //~^ manual_assert + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + unsafe { todo!() } +} diff --git a/tests/ui/manual_assert.edition2021.stderr b/tests/ui/manual_assert.edition2021.stderr index 2e9c9045caae..c81a85527527 100644 --- a/tests/ui/manual_assert.edition2021.stderr +++ b/tests/ui/manual_assert.edition2021.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:32:5 + --> tests/ui/manual_assert.rs:30:5 | LL | / if !a.is_empty() { LL | | @@ -9,17 +9,14 @@ LL | | } | = note: `-D clippy::manual-assert` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_assert)]` -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !a.is_empty() { -LL - -LL - panic!("qaqaq{:?}", a); -LL - } +LL ~ LL + assert!(a.is_empty(), "qaqaq{:?}", a); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:36:5 + --> tests/ui/manual_assert.rs:34:5 | LL | / if !a.is_empty() { LL | | @@ -27,17 +24,14 @@ LL | | panic!("qwqwq"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !a.is_empty() { -LL - -LL - panic!("qwqwq"); -LL - } +LL ~ LL + assert!(a.is_empty(), "qwqwq"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:54:5 + --> tests/ui/manual_assert.rs:52:5 | LL | / if b.is_empty() { LL | | @@ -45,17 +39,14 @@ LL | | panic!("panic1"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() { -LL - -LL - panic!("panic1"); -LL - } +LL ~ LL + assert!(!b.is_empty(), "panic1"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:58:5 + --> tests/ui/manual_assert.rs:56:5 | LL | / if b.is_empty() && a.is_empty() { LL | | @@ -63,17 +54,14 @@ LL | | panic!("panic2"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() && a.is_empty() { -LL - -LL - panic!("panic2"); -LL - } +LL ~ LL + assert!(!(b.is_empty() && a.is_empty()), "panic2"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:62:5 + --> tests/ui/manual_assert.rs:60:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | @@ -81,17 +69,14 @@ LL | | panic!("panic3"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() && !b.is_empty() { -LL - -LL - panic!("panic3"); -LL - } +LL ~ LL + assert!(!(a.is_empty() && !b.is_empty()), "panic3"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:66:5 + --> tests/ui/manual_assert.rs:64:5 | LL | / if b.is_empty() || a.is_empty() { LL | | @@ -99,17 +84,14 @@ LL | | panic!("panic4"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if b.is_empty() || a.is_empty() { -LL - -LL - panic!("panic4"); -LL - } +LL ~ LL + assert!(!(b.is_empty() || a.is_empty()), "panic4"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:70:5 + --> tests/ui/manual_assert.rs:68:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | @@ -117,17 +99,14 @@ LL | | panic!("panic5"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() || !b.is_empty() { -LL - -LL - panic!("panic5"); -LL - } +LL ~ LL + assert!(!(a.is_empty() || !b.is_empty()), "panic5"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:74:5 + --> tests/ui/manual_assert.rs:72:5 | LL | / if a.is_empty() { LL | | @@ -135,17 +114,14 @@ LL | | panic!("with expansion {}", one!()) LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if a.is_empty() { -LL - -LL - panic!("with expansion {}", one!()) -LL - } +LL ~ LL + assert!(!a.is_empty(), "with expansion {}", one!()); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:87:5 + --> tests/ui/manual_assert.rs:85:5 | LL | / if a > 2 { LL | | @@ -156,22 +132,20 @@ LL | | panic!("panic with comment") // comment after `panic!` LL | | } | |_____^ | -help: try instead - | -LL - if a > 2 { -LL - -LL - // comment -LL - /* this is a -LL - multiline -LL - comment */ -LL - /// Doc comment -LL - panic!("panic with comment") // comment after `panic!` -LL - } +help: replace `if`-then-`panic!` with `assert!` + | +LL ~ +LL + // comment +LL + /* this is a +LL + multiline +LL + comment */ +LL + /// Doc comment +LL + // comment after `panic!` LL + assert!(a <= 2, "panic with comment"); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:102:25 + --> tests/ui/manual_assert.rs:100:25 | LL | const BAR: () = if N == 0 { | _________________________^ @@ -180,17 +154,14 @@ LL | | panic!() LL | | }; | |_________^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - const BAR: () = if N == 0 { -LL - -LL - panic!() -LL - }; -LL + const BAR: () = assert!(N != 0, ); +LL ~ const BAR: () = +LL ~ assert!(N != 0, ); | error: only a `panic!` in `if`-then statement - --> tests/ui/manual_assert.rs:116:5 + --> tests/ui/manual_assert.rs:114:5 | LL | / if !is_x86_feature_detected!("ssse3") { LL | | @@ -198,12 +169,9 @@ LL | | panic!("SSSE3 is not supported"); LL | | } | |_____^ | -help: try instead +help: replace `if`-then-`panic!` with `assert!` | -LL - if !is_x86_feature_detected!("ssse3") { -LL - -LL - panic!("SSSE3 is not supported"); -LL - } +LL ~ LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); | diff --git a/tests/ui/manual_assert.rs b/tests/ui/manual_assert.rs index ab02bd5f5e53..761179554223 100644 --- a/tests/ui/manual_assert.rs +++ b/tests/ui/manual_assert.rs @@ -2,8 +2,6 @@ //@[edition2018] edition:2018 //@[edition2021] edition:2021 -//@no-rustfix: need to change the suggestion to a multipart suggestion - #![warn(clippy::manual_assert)] #![allow(dead_code, unused_doc_comments)] #![allow(clippy::nonminimal_bool, clippy::uninlined_format_args, clippy::useless_vec)] diff --git a/tests/ui/manual_div_ceil.fixed b/tests/ui/manual_div_ceil.fixed index 58ee6978fc12..cd91be87ec17 100644 --- a/tests/ui/manual_div_ceil.fixed +++ b/tests/ui/manual_div_ceil.fixed @@ -100,3 +100,8 @@ fn issue_13950() { let _ = (-8 + y) / -7; let _ = (y - 8) / -7; } + +fn issue_15705(size: u64, c: &u64) { + let _ = size.div_ceil(*c); + //~^ manual_div_ceil +} diff --git a/tests/ui/manual_div_ceil.rs b/tests/ui/manual_div_ceil.rs index aa0d81b22a0e..9899c7d775c2 100644 --- a/tests/ui/manual_div_ceil.rs +++ b/tests/ui/manual_div_ceil.rs @@ -100,3 +100,8 @@ fn issue_13950() { let _ = (-8 + y) / -7; let _ = (y - 8) / -7; } + +fn issue_15705(size: u64, c: &u64) { + let _ = (size + c - 1) / c; + //~^ manual_div_ceil +} diff --git a/tests/ui/manual_div_ceil.stderr b/tests/ui/manual_div_ceil.stderr index 9be5a19bf391..44de3ba99be7 100644 --- a/tests/ui/manual_div_ceil.stderr +++ b/tests/ui/manual_div_ceil.stderr @@ -125,5 +125,11 @@ error: manually reimplementing `div_ceil` LL | let _ = (7 + x) / 8; | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` -error: aborting due to 19 previous errors +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:105:13 + | +LL | let _ = (size + c - 1) / c; + | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `size.div_ceil(*c)` + +error: aborting due to 20 previous errors diff --git a/tests/ui/manual_float_methods.rs b/tests/ui/manual_float_methods.rs index 62cdc1c141d0..3cd469fd43a0 100644 --- a/tests/ui/manual_float_methods.rs +++ b/tests/ui/manual_float_methods.rs @@ -1,6 +1,6 @@ //@no-rustfix: overlapping suggestions //@aux-build:proc_macros.rs -#![allow(clippy::needless_if, unused)] +#![allow(clippy::needless_ifs, unused)] #![warn(clippy::manual_is_infinite, clippy::manual_is_finite)] // FIXME(f16_f128): add tests for these types once constants are available @@ -8,9 +8,6 @@ #[macro_use] extern crate proc_macros; -const INFINITE: f32 = f32::INFINITY; -const NEG_INFINITE: f32 = f32::NEG_INFINITY; - fn fn_test() -> f64 { f64::NEG_INFINITY } @@ -25,10 +22,6 @@ fn main() { //~^ manual_is_infinite if x != f32::INFINITY && x != f32::NEG_INFINITY {} //~^ manual_is_finite - if x == INFINITE || x == NEG_INFINITE {} - //~^ manual_is_infinite - if x != INFINITE && x != NEG_INFINITE {} - //~^ manual_is_finite let x = 1.0f64; if x == f64::INFINITY || x == f64::NEG_INFINITY {} //~^ manual_is_infinite @@ -64,4 +57,12 @@ fn main() { if x == f32::INFINITY || x == f32::NEG_INFINITY {} if x != f32::INFINITY && x != f32::NEG_INFINITY {} } + + { + let x = 1.0f32; + const X: f32 = f32::INFINITY; + const Y: f32 = f32::NEG_INFINITY; + if x == X || x == Y {} + if x != X && x != Y {} + } } diff --git a/tests/ui/manual_float_methods.stderr b/tests/ui/manual_float_methods.stderr index 352c879c87d7..0a27e0eac48b 100644 --- a/tests/ui/manual_float_methods.stderr +++ b/tests/ui/manual_float_methods.stderr @@ -1,5 +1,5 @@ error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:24:8 + --> tests/ui/manual_float_methods.rs:21:8 | LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` @@ -8,7 +8,7 @@ LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {} = help: to override `-D warnings` add `#[allow(clippy::manual_is_infinite)]` error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:26:8 + --> tests/ui/manual_float_methods.rs:23:8 | LL | if x != f32::INFINITY && x != f32::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,41 +32,13 @@ LL + if !x.is_infinite() {} | error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:28:8 - | -LL | if x == INFINITE || x == NEG_INFINITE {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` - -error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:30:8 - | -LL | if x != INFINITE && x != NEG_INFINITE {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use the dedicated method instead - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if x.is_finite() {} - | -help: this will alter how it handles NaN; if that is a problem, use instead - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if x.is_finite() || x.is_nan() {} - | -help: or, for conciseness - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if !x.is_infinite() {} - | - -error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:33:8 + --> tests/ui/manual_float_methods.rs:26:8 | LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:35:8 + --> tests/ui/manual_float_methods.rs:28:8 | LL | if x != f64::INFINITY && x != f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,10 +60,10 @@ LL + if !x.is_infinite() {} | error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:50:12 + --> tests/ui/manual_float_methods.rs:43:12 | LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/manual_instant_elapsed.fixed b/tests/ui/manual_instant_elapsed.fixed index 187802bb76c9..a04c601e08c1 100644 --- a/tests/ui/manual_instant_elapsed.fixed +++ b/tests/ui/manual_instant_elapsed.fixed @@ -1,6 +1,6 @@ #![warn(clippy::manual_instant_elapsed)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::unchecked_duration_subtraction)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(unused_variables)] #![allow(unused_must_use)] diff --git a/tests/ui/manual_instant_elapsed.rs b/tests/ui/manual_instant_elapsed.rs index 61e14e5a3d9d..7c67f6acf85d 100644 --- a/tests/ui/manual_instant_elapsed.rs +++ b/tests/ui/manual_instant_elapsed.rs @@ -1,6 +1,6 @@ #![warn(clippy::manual_instant_elapsed)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::unchecked_duration_subtraction)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(unused_variables)] #![allow(unused_must_use)] diff --git a/tests/ui/manual_let_else.rs b/tests/ui/manual_let_else.rs index 3781ba1676f5..4523edec3c76 100644 --- a/tests/ui/manual_let_else.rs +++ b/tests/ui/manual_let_else.rs @@ -6,7 +6,7 @@ clippy::let_unit_value, clippy::match_single_binding, clippy::never_loop, - clippy::needless_if, + clippy::needless_ifs, clippy::diverging_sub_expression, clippy::single_match, clippy::manual_unwrap_or_default @@ -546,3 +546,28 @@ mod issue14598 { todo!() } } + +mod issue15914 { + // https://github.com/rust-lang/rust-clippy/issues/15914 + unsafe fn something_unsafe() -> Option { + None + } + + fn foo() { + let value = if let Some(value) = unsafe { something_unsafe() } { + //~^ manual_let_else + value + } else { + return; + }; + + let some_flag = true; + + let value = if let Some(value) = if some_flag { None } else { Some(3) } { + //~^ manual_let_else + value + } else { + return; + }; + } +} diff --git a/tests/ui/manual_let_else.stderr b/tests/ui/manual_let_else.stderr index a1eea0419291..f4b1644c44ba 100644 --- a/tests/ui/manual_let_else.stderr +++ b/tests/ui/manual_let_else.stderr @@ -549,5 +549,41 @@ LL | | Some(x) => x, LL | | }; | |__________^ help: consider writing: `let Some(v) = w else { return Err("abc") };` -error: aborting due to 35 previous errors +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else.rs:557:9 + | +LL | / let value = if let Some(value) = unsafe { something_unsafe() } { +LL | | +LL | | value +LL | | } else { +LL | | return; +LL | | }; + | |__________^ + | +help: consider writing + | +LL ~ let Some(value) = (unsafe { something_unsafe() }) else { +LL + return; +LL + }; + | + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else.rs:566:9 + | +LL | / let value = if let Some(value) = if some_flag { None } else { Some(3) } { +LL | | +LL | | value +LL | | } else { +LL | | return; +LL | | }; + | |__________^ + | +help: consider writing + | +LL ~ let Some(value) = (if some_flag { None } else { Some(3) }) else { +LL + return; +LL + }; + | + +error: aborting due to 37 previous errors diff --git a/tests/ui/manual_option_as_slice.stderr b/tests/ui/manual_option_as_slice.stderr index e240ae8eb7d9..37113e9eafe1 100644 --- a/tests/ui/manual_option_as_slice.stderr +++ b/tests/ui/manual_option_as_slice.stderr @@ -1,4 +1,4 @@ -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:5:9 | LL | _ = match x.as_ref() { @@ -7,12 +7,21 @@ LL | | LL | | Some(f) => std::slice::from_ref(f), LL | | None => &[], LL | | }; - | |_____^ help: use: `x.as_slice()` + | |_____^ | = note: `-D clippy::manual-option-as-slice` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_option_as_slice)]` +help: use `Option::as_slice` directly + | +LL - _ = match x.as_ref() { +LL - +LL - Some(f) => std::slice::from_ref(f), +LL - None => &[], +LL - }; +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:11:9 | LL | _ = if let Some(f) = x.as_ref() { @@ -23,37 +32,79 @@ LL | | std::slice::from_ref(f) LL | | } else { LL | | &[] LL | | }; - | |_____^ help: use: `x.as_slice()` + | |_____^ + | +help: use `Option::as_slice` directly + | +LL - _ = if let Some(f) = x.as_ref() { +LL - +LL - +LL - std::slice::from_ref(f) +LL - } else { +LL - &[] +LL - }; +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:19:9 | LL | _ = x.as_ref().map_or(&[][..], std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:22:9 | LL | _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:25:9 | LL | _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:28:9 | LL | _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:33:13 | LL | _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); +LL + _ = x.as_slice(); + | error: aborting due to 7 previous errors diff --git a/tests/ui/manual_rotate.fixed b/tests/ui/manual_rotate.fixed index 49db8661369e..1012ffc1aa2d 100644 --- a/tests/ui/manual_rotate.fixed +++ b/tests/ui/manual_rotate.fixed @@ -4,6 +4,7 @@ fn main() { let (x_u8, x_u16, x_u32, x_u64) = (1u8, 1u16, 1u32, 1u64); let (x_i8, x_i16, x_i32, x_i64) = (1i8, 1i16, 1i32, 1i64); let a_u32 = 1u32; + const N: u32 = 5; // True positives let y_u8 = x_u8.rotate_right(3); //~^ manual_rotate @@ -29,6 +30,9 @@ fn main() { //~^ manual_rotate let y_u64_as = (x_u32 as u64).rotate_right(8); //~^ manual_rotate + // shift by a const + let _ = x_i64.rotate_right(N); + //~^ manual_rotate // False positives - can't be replaced with a rotation let y_u8_false = (x_u8 >> 6) | (x_u8 << 3); @@ -40,3 +44,20 @@ fn main() { let mut l = vec![12_u8, 34]; let y = (l.pop().unwrap() << 3) + (l.pop().unwrap() >> 5); } + +fn issue13028() { + let s = 5; + let u = 5; + let x: u32 = 123456; + + let _ = x.rotate_left(s); + //~^ manual_rotate + let _ = x.rotate_left(s); + //~^ manual_rotate + // still works with consts + let _ = x.rotate_right(9); + //~^ manual_rotate + + // don't lint, because `s` and `u` are different variables, albeit with the same value + let _ = (x << s) | (x >> (32 - u)); +} diff --git a/tests/ui/manual_rotate.rs b/tests/ui/manual_rotate.rs index 6445e60aa25d..3cdc79673c81 100644 --- a/tests/ui/manual_rotate.rs +++ b/tests/ui/manual_rotate.rs @@ -4,6 +4,7 @@ fn main() { let (x_u8, x_u16, x_u32, x_u64) = (1u8, 1u16, 1u32, 1u64); let (x_i8, x_i16, x_i32, x_i64) = (1i8, 1i16, 1i32, 1i64); let a_u32 = 1u32; + const N: u32 = 5; // True positives let y_u8 = (x_u8 >> 3) | (x_u8 << 5); //~^ manual_rotate @@ -29,6 +30,9 @@ fn main() { //~^ manual_rotate let y_u64_as = (x_u32 as u64 >> 8) | ((x_u32 as u64) << 56); //~^ manual_rotate + // shift by a const + let _ = (x_i64 >> N) | (x_i64 << (64 - N)); + //~^ manual_rotate // False positives - can't be replaced with a rotation let y_u8_false = (x_u8 >> 6) | (x_u8 << 3); @@ -40,3 +44,20 @@ fn main() { let mut l = vec![12_u8, 34]; let y = (l.pop().unwrap() << 3) + (l.pop().unwrap() >> 5); } + +fn issue13028() { + let s = 5; + let u = 5; + let x: u32 = 123456; + + let _ = (x << s) | (x >> (32 - s)); + //~^ manual_rotate + let _ = (x << s) | (x >> (31 ^ s)); + //~^ manual_rotate + // still works with consts + let _ = (x >> 9) | (x << (32 - 9)); + //~^ manual_rotate + + // don't lint, because `s` and `u` are different variables, albeit with the same value + let _ = (x << s) | (x >> (32 - u)); +} diff --git a/tests/ui/manual_rotate.stderr b/tests/ui/manual_rotate.stderr index a28721fbb94c..ea04ee028db6 100644 --- a/tests/ui/manual_rotate.stderr +++ b/tests/ui/manual_rotate.stderr @@ -1,5 +1,5 @@ error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:8:16 + --> tests/ui/manual_rotate.rs:9:16 | LL | let y_u8 = (x_u8 >> 3) | (x_u8 << 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u8.rotate_right(3)` @@ -8,64 +8,88 @@ LL | let y_u8 = (x_u8 >> 3) | (x_u8 << 5); = help: to override `-D warnings` add `#[allow(clippy::manual_rotate)]` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:10:17 + --> tests/ui/manual_rotate.rs:11:17 | LL | let y_u16 = (x_u16 >> 7) | (x_u16 << 9); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u16.rotate_right(7)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:12:17 + --> tests/ui/manual_rotate.rs:13:17 | LL | let y_u32 = (x_u32 >> 8) | (x_u32 << 24); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u32.rotate_right(8)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:14:17 + --> tests/ui/manual_rotate.rs:15:17 | LL | let y_u64 = (x_u64 >> 9) | (x_u64 << 55); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u64.rotate_right(9)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:16:16 + --> tests/ui/manual_rotate.rs:17:16 | LL | let y_i8 = (x_i8 >> 3) | (x_i8 << 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i8.rotate_right(3)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:18:17 + --> tests/ui/manual_rotate.rs:19:17 | LL | let y_i16 = (x_i16 >> 7) | (x_i16 << 9); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i16.rotate_right(7)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:20:17 + --> tests/ui/manual_rotate.rs:21:17 | LL | let y_i32 = (x_i32 >> 8) | (x_i32 << 24); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i32.rotate_right(8)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:22:17 + --> tests/ui/manual_rotate.rs:23:17 | LL | let y_i64 = (x_i64 >> 9) | (x_i64 << 55); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i64.rotate_right(9)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:25:22 + --> tests/ui/manual_rotate.rs:26:22 | LL | let y_u32_plus = (x_u32 >> 8) + (x_u32 << 24); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u32.rotate_right(8)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:28:25 + --> tests/ui/manual_rotate.rs:29:25 | LL | let y_u32_complex = ((x_u32 | 3256) >> 8) | ((x_u32 | 3256) << 24); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `(x_u32 | 3256).rotate_right(8)` error: there is no need to manually implement bit rotation - --> tests/ui/manual_rotate.rs:30:20 + --> tests/ui/manual_rotate.rs:31:20 | LL | let y_u64_as = (x_u32 as u64 >> 8) | ((x_u32 as u64) << 56); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `(x_u32 as u64).rotate_right(8)` -error: aborting due to 11 previous errors +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:34:13 + | +LL | let _ = (x_i64 >> N) | (x_i64 << (64 - N)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i64.rotate_right(N)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:53:13 + | +LL | let _ = (x << s) | (x >> (32 - s)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.rotate_left(s)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:55:13 + | +LL | let _ = (x << s) | (x >> (31 ^ s)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.rotate_left(s)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:58:13 + | +LL | let _ = (x >> 9) | (x << (32 - 9)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.rotate_right(9)` + +error: aborting due to 15 previous errors diff --git a/tests/ui/manual_strip.stderr b/tests/ui/manual_strip.stderr index a323ef700e76..d147cdae1f3b 100644 --- a/tests/ui/manual_strip.stderr +++ b/tests/ui/manual_strip.stderr @@ -97,9 +97,6 @@ LL | if s.starts_with(PREFIX) { help: try using the `strip_prefix` method | LL ~ if let Some() = s.strip_prefix(PREFIX) { -LL ~ str::to_string(); -LL | -LL | LL ~ str::to_string(); | diff --git a/tests/ui/manual_unwrap_or.fixed b/tests/ui/manual_unwrap_or.fixed index e12287a70939..8dd9e7a8a6fa 100644 --- a/tests/ui/manual_unwrap_or.fixed +++ b/tests/ui/manual_unwrap_or.fixed @@ -250,4 +250,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 { Some(42).unwrap_or(0) } +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 2 }; + + let x = uncopyable_res; + let _ = x.unwrap_or(2); + //~^ manual_unwrap_or + + let copyable_res: Result = Ok(1); + let _ = copyable_res.unwrap_or(2); + //~^ manual_unwrap_or + let _ = copyable_res; +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.rs b/tests/ui/manual_unwrap_or.rs index 53cffcab5b56..f8e2c5f43745 100644 --- a/tests/ui/manual_unwrap_or.rs +++ b/tests/ui/manual_unwrap_or.rs @@ -329,4 +329,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 { } } +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 2 }; + + let x = uncopyable_res; + let _ = if let Ok(v) = x { v } else { 2 }; + //~^ manual_unwrap_or + + let copyable_res: Result = Ok(1); + let _ = if let Ok(v) = copyable_res { v } else { 2 }; + //~^ manual_unwrap_or + let _ = copyable_res; +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.stderr b/tests/ui/manual_unwrap_or.stderr index 320e895fb823..18764d1501d8 100644 --- a/tests/ui/manual_unwrap_or.stderr +++ b/tests/ui/manual_unwrap_or.stderr @@ -201,5 +201,17 @@ LL | | 0 LL | | } | |_____^ help: replace with: `Some(42).unwrap_or(0)` -error: aborting due to 18 previous errors +error: this pattern reimplements `Result::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:337:13 + | +LL | let _ = if let Ok(v) = x { v } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `x.unwrap_or(2)` + +error: this pattern reimplements `Result::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:341:13 + | +LL | let _ = if let Ok(v) = copyable_res { v } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `copyable_res.unwrap_or(2)` + +error: aborting due to 20 previous errors diff --git a/tests/ui/manual_unwrap_or_default.fixed b/tests/ui/manual_unwrap_or_default.fixed index 189fe876aa5d..0c64ed1826b2 100644 --- a/tests/ui/manual_unwrap_or_default.fixed +++ b/tests/ui/manual_unwrap_or_default.fixed @@ -32,6 +32,15 @@ fn main() { let x: Result = Ok(String::new()); x.unwrap_or_default(); + + // edge case + // because the `Some(bizarro)` pattern is not actually reachable, + // changing this match to `unwrap_or_default` would have side effects + let bizarro = Some(String::new()); + match bizarro { + _ => String::new(), + Some(bizarro) => bizarro, + }; } // Issue #12531 @@ -119,3 +128,17 @@ mod issue14716 { }; } } + +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 0 }; + + let x = uncopyable_res; + let _ = x.unwrap_or_default(); + //~^ manual_unwrap_or_default + + let copyable_res: Result = Ok(1); + let _ = copyable_res.unwrap_or_default(); + //~^ manual_unwrap_or_default + let _ = copyable_res; +} diff --git a/tests/ui/manual_unwrap_or_default.rs b/tests/ui/manual_unwrap_or_default.rs index ca87926763c9..e99f7d44dde1 100644 --- a/tests/ui/manual_unwrap_or_default.rs +++ b/tests/ui/manual_unwrap_or_default.rs @@ -64,6 +64,15 @@ fn main() { } else { String::new() }; + + // edge case + // because the `Some(bizarro)` pattern is not actually reachable, + // changing this match to `unwrap_or_default` would have side effects + let bizarro = Some(String::new()); + match bizarro { + _ => String::new(), + Some(bizarro) => bizarro, + }; } // Issue #12531 @@ -160,3 +169,17 @@ mod issue14716 { }; } } + +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 0 }; + + let x = uncopyable_res; + let _ = if let Ok(v) = x { v } else { 0 }; + //~^ manual_unwrap_or_default + + let copyable_res: Result = Ok(1); + let _ = if let Ok(v) = copyable_res { v } else { 0 }; + //~^ manual_unwrap_or_default + let _ = copyable_res; +} diff --git a/tests/ui/manual_unwrap_or_default.stderr b/tests/ui/manual_unwrap_or_default.stderr index e8f38a2e3899..a9e78afe6f06 100644 --- a/tests/ui/manual_unwrap_or_default.stderr +++ b/tests/ui/manual_unwrap_or_default.stderr @@ -76,7 +76,7 @@ LL | | }; | |_____^ help: replace it with: `x.unwrap_or_default()` error: match can be simplified with `.unwrap_or_default()` - --> tests/ui/manual_unwrap_or_default.rs:74:24 + --> tests/ui/manual_unwrap_or_default.rs:83:24 | LL | Some(_) => match *b { | ________________________^ @@ -87,7 +87,7 @@ LL | | }, | |_____________^ help: replace it with: `(*b).unwrap_or_default()` error: if let can be simplified with `.unwrap_or_default()` - --> tests/ui/manual_unwrap_or_default.rs:143:5 + --> tests/ui/manual_unwrap_or_default.rs:152:5 | LL | / if let Some(x) = Some(42) { LL | | @@ -97,5 +97,17 @@ LL | | 0 LL | | } | |_____^ help: replace it with: `Some(42).unwrap_or_default()` -error: aborting due to 9 previous errors +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default.rs:178:13 + | +LL | let _ = if let Ok(v) = x { v } else { 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x.unwrap_or_default()` + +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default.rs:182:13 + | +LL | let _ = if let Ok(v) = copyable_res { v } else { 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `copyable_res.unwrap_or_default()` + +error: aborting due to 11 previous errors diff --git a/tests/ui/match_as_ref.fixed b/tests/ui/match_as_ref.fixed index 8c07076af4a4..09a6ed169390 100644 --- a/tests/ui/match_as_ref.fixed +++ b/tests/ui/match_as_ref.fixed @@ -41,3 +41,52 @@ fn main() { None => None, }; } + +mod issue15691 { + use std::ops::{Deref, DerefMut}; + + struct A(B); + struct B; + + impl Deref for A { + type Target = B; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for A { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + fn func() { + let mut a = Some(A(B)); + let mut b = Some(B); + // Do not lint, we don't have `None => None` + let _ = match b { + Some(ref mut x) => Some(x), + None => a.as_deref_mut(), + }; + } +} + +fn recv_requiring_parens() { + struct S; + + impl std::ops::Not for S { + type Output = Option; + fn not(self) -> Self::Output { + None + } + } + + let _ = (!S).as_ref(); +} + +fn issue15932() { + let _: Option<&u32> = Some(0).as_ref(); + + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); +} diff --git a/tests/ui/match_as_ref.rs b/tests/ui/match_as_ref.rs index 3a5b1227331e..347b6d186887 100644 --- a/tests/ui/match_as_ref.rs +++ b/tests/ui/match_as_ref.rs @@ -53,3 +53,64 @@ fn main() { None => None, }; } + +mod issue15691 { + use std::ops::{Deref, DerefMut}; + + struct A(B); + struct B; + + impl Deref for A { + type Target = B; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for A { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + fn func() { + let mut a = Some(A(B)); + let mut b = Some(B); + // Do not lint, we don't have `None => None` + let _ = match b { + Some(ref mut x) => Some(x), + None => a.as_deref_mut(), + }; + } +} + +fn recv_requiring_parens() { + struct S; + + impl std::ops::Not for S { + type Output = Option; + fn not(self) -> Self::Output { + None + } + } + + let _ = match !S { + //~^ match_as_ref + None => None, + Some(ref v) => Some(v), + }; +} + +fn issue15932() { + let _: Option<&u32> = match Some(0) { + //~^ match_as_ref + None => None, + Some(ref mut v) => Some(v), + }; + + let _: Option<&dyn std::fmt::Debug> = match Some(0) { + //~^ match_as_ref + None => None, + Some(ref mut v) => Some(v), + }; +} diff --git a/tests/ui/match_as_ref.stderr b/tests/ui/match_as_ref.stderr index 7b40cb80dc6e..df06e358f296 100644 --- a/tests/ui/match_as_ref.stderr +++ b/tests/ui/match_as_ref.stderr @@ -1,4 +1,4 @@ -error: use `as_ref()` instead +error: manual implementation of `Option::as_ref` --> tests/ui/match_as_ref.rs:6:33 | LL | let borrowed: Option<&()> = match owned { @@ -7,12 +7,21 @@ LL | | LL | | None => None, LL | | Some(ref v) => Some(v), LL | | }; - | |_____^ help: try: `owned.as_ref()` + | |_____^ | = note: `-D clippy::match-as-ref` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::match_as_ref)]` +help: use `Option::as_ref()` directly + | +LL - let borrowed: Option<&()> = match owned { +LL - +LL - None => None, +LL - Some(ref v) => Some(v), +LL - }; +LL + let borrowed: Option<&()> = owned.as_ref(); + | -error: use `as_mut()` instead +error: manual implementation of `Option::as_mut` --> tests/ui/match_as_ref.rs:13:39 | LL | let borrow_mut: Option<&mut ()> = match mut_owned { @@ -21,9 +30,19 @@ LL | | LL | | None => None, LL | | Some(ref mut v) => Some(v), LL | | }; - | |_____^ help: try: `mut_owned.as_mut()` + | |_____^ + | +help: use `Option::as_mut()` directly + | +LL - let borrow_mut: Option<&mut ()> = match mut_owned { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let borrow_mut: Option<&mut ()> = mut_owned.as_mut(); + | -error: use `as_ref()` instead +error: manual implementation of `Option::as_ref` --> tests/ui/match_as_ref.rs:32:13 | LL | / match self.source { @@ -31,7 +50,82 @@ LL | | LL | | Some(ref s) => Some(s), LL | | None => None, LL | | } - | |_____________^ help: try: `self.source.as_ref().map(|x| x as _)` + | |_____________^ + | +help: use `Option::as_ref()` directly + | +LL - match self.source { +LL - +LL - Some(ref s) => Some(s), +LL - None => None, +LL - } +LL + self.source.as_ref().map(|x| x as _) + | + +error: manual implementation of `Option::as_ref` + --> tests/ui/match_as_ref.rs:97:13 + | +LL | let _ = match !S { + | _____________^ +LL | | +LL | | None => None, +LL | | Some(ref v) => Some(v), +LL | | }; + | |_____^ + | +help: use `Option::as_ref()` directly + | +LL - let _ = match !S { +LL - +LL - None => None, +LL - Some(ref v) => Some(v), +LL - }; +LL + let _ = (!S).as_ref(); + | + +error: manual implementation of `Option::as_mut` + --> tests/ui/match_as_ref.rs:105:27 + | +LL | let _: Option<&u32> = match Some(0) { + | ___________________________^ +LL | | +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ + | + = note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead +help: use `Option::as_ref()` + | +LL - let _: Option<&u32> = match Some(0) { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let _: Option<&u32> = Some(0).as_ref(); + | + +error: manual implementation of `Option::as_mut` + --> tests/ui/match_as_ref.rs:111:43 + | +LL | let _: Option<&dyn std::fmt::Debug> = match Some(0) { + | ___________________________________________^ +LL | | +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ + | + = note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead +help: use `Option::as_ref()` + | +LL - let _: Option<&dyn std::fmt::Debug> = match Some(0) { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); + | -error: aborting due to 3 previous errors +error: aborting due to 6 previous errors diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_like_matches_macro.fixed similarity index 97% rename from tests/ui/match_expr_like_matches_macro.fixed rename to tests/ui/match_like_matches_macro.fixed index 8530ab16bfd7..a1c95e8a94f1 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_like_matches_macro.fixed @@ -1,7 +1,6 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, - dead_code, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -14,11 +13,11 @@ fn main() { let _y = matches!(x, Some(0)); //~^^^^ match_like_matches_macro - // Lint + // No lint: covered by `redundant_pattern_matching` let _w = x.is_some(); //~^^^^ redundant_pattern_matching - // Turn into is_none + // No lint: covered by `redundant_pattern_matching` let _z = x.is_none(); //~^^^^ redundant_pattern_matching diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_like_matches_macro.rs similarity index 97% rename from tests/ui/match_expr_like_matches_macro.rs rename to tests/ui/match_like_matches_macro.rs index 81017936889e..eb419ba5bf8d 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_like_matches_macro.rs @@ -1,7 +1,6 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, - dead_code, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -17,14 +16,14 @@ fn main() { }; //~^^^^ match_like_matches_macro - // Lint + // No lint: covered by `redundant_pattern_matching` let _w = match x { Some(_) => true, _ => false, }; //~^^^^ redundant_pattern_matching - // Turn into is_none + // No lint: covered by `redundant_pattern_matching` let _z = match x { Some(_) => false, None => true, diff --git a/tests/ui/match_expr_like_matches_macro.stderr b/tests/ui/match_like_matches_macro.stderr similarity index 84% rename from tests/ui/match_expr_like_matches_macro.stderr rename to tests/ui/match_like_matches_macro.stderr index 8fceb05bc6e8..ae277ce4dca6 100644 --- a/tests/ui/match_expr_like_matches_macro.stderr +++ b/tests/ui/match_like_matches_macro.stderr @@ -1,5 +1,5 @@ error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:14:14 + --> tests/ui/match_like_matches_macro.rs:13:14 | LL | let _y = match x { | ______________^ @@ -12,7 +12,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]` error: redundant pattern matching, consider using `is_some()` - --> tests/ui/match_expr_like_matches_macro.rs:21:14 + --> tests/ui/match_like_matches_macro.rs:20:14 | LL | let _w = match x { | ______________^ @@ -25,7 +25,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]` error: redundant pattern matching, consider using `is_none()` - --> tests/ui/match_expr_like_matches_macro.rs:28:14 + --> tests/ui/match_like_matches_macro.rs:27:14 | LL | let _z = match x { | ______________^ @@ -35,7 +35,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:35:15 + --> tests/ui/match_like_matches_macro.rs:34:15 | LL | let _zz = match x { | _______________^ @@ -45,13 +45,13 @@ LL | | }; | |_____^ help: try: `!matches!(x, Some(r) if r == 0)` error: if let .. else expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:42:16 + --> tests/ui/match_like_matches_macro.rs:41:16 | LL | let _zzz = if let Some(5) = x { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:67:20 + --> tests/ui/match_like_matches_macro.rs:66:20 | LL | let _ans = match x { | ____________________^ @@ -62,7 +62,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:78:20 + --> tests/ui/match_like_matches_macro.rs:77:20 | LL | let _ans = match x { | ____________________^ @@ -74,7 +74,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:89:20 + --> tests/ui/match_like_matches_macro.rs:88:20 | LL | let _ans = match x { | ____________________^ @@ -85,7 +85,7 @@ LL | | }; | |_________^ help: try: `!matches!(x, E::B(_) | E::C)` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:150:18 + --> tests/ui/match_like_matches_macro.rs:149:18 | LL | let _z = match &z { | __________________^ @@ -95,7 +95,7 @@ LL | | }; | |_________^ help: try: `matches!(z, Some(3))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:160:18 + --> tests/ui/match_like_matches_macro.rs:159:18 | LL | let _z = match &z { | __________________^ @@ -105,7 +105,7 @@ LL | | }; | |_________^ help: try: `matches!(&z, Some(3))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:178:21 + --> tests/ui/match_like_matches_macro.rs:177:21 | LL | let _ = match &z { | _____________________^ @@ -115,7 +115,7 @@ LL | | }; | |_____________^ help: try: `matches!(&z, AnEnum::X)` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:193:20 + --> tests/ui/match_like_matches_macro.rs:192:20 | LL | let _res = match &val { | ____________________^ @@ -125,7 +125,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:206:20 + --> tests/ui/match_like_matches_macro.rs:205:20 | LL | let _res = match &val { | ____________________^ @@ -135,7 +135,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:265:14 + --> tests/ui/match_like_matches_macro.rs:264:14 | LL | let _y = match Some(5) { | ______________^ diff --git a/tests/ui/match_overlapping_arm.rs b/tests/ui/match_overlapping_arm.rs index 176287e3d2e0..224cac055f25 100644 --- a/tests/ui/match_overlapping_arm.rs +++ b/tests/ui/match_overlapping_arm.rs @@ -1,6 +1,6 @@ #![warn(clippy::match_overlapping_arm)] #![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::if_same_then_else, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::if_same_then_else, clippy::equatable_if_let, clippy::needless_ifs)] fn overlapping() { const FOO: u64 = 2; diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index e29fb87dbc30..7e899a476666 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -4,7 +4,6 @@ clippy::let_unit_value, clippy::no_effect, clippy::toplevel_ref_arg, - clippy::uninlined_format_args, clippy::useless_vec )] @@ -32,11 +31,11 @@ fn main() { // Lint let (x, y, z) = (a, b, c); { - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } // Lint let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); // Ok foo!(a); // Ok @@ -47,7 +46,7 @@ fn main() { // Ok let d = Some(5); match d { - Some(d) => println!("{}", d), + Some(d) => println!("{d}"), _ => println!("None"), } // Lint @@ -55,7 +54,7 @@ fn main() { // Lint { let x = 29; - println!("x has a value of {}", x); + println!("x has a value of {x}"); } // Lint { @@ -67,18 +66,18 @@ fn main() { // Lint let p = Point { x: 0, y: 7 }; let Point { x, y } = p; - println!("Coords: ({}, {})", x, y); + println!("Coords: ({x}, {y})"); // Lint let Point { x: x1, y: y1 } = p; - println!("Coords: ({}, {})", x1, y1); + println!("Coords: ({x1}, {y1})"); // Lint let x = 5; let ref r = x; - println!("Got a reference to {}", r); + println!("Got a reference to {r}"); // Lint let mut x = 5; let ref mut mr = x; - println!("Got a mutable reference to {}", mr); + println!("Got a mutable reference to {mr}"); // Lint let Point { x, y } = coords(); let product = x * y; @@ -122,7 +121,7 @@ fn issue_8723() { let (pre, suf) = val.split_at(idx); val = { - println!("{}", pre); + println!("{pre}"); suf }; @@ -210,20 +209,20 @@ mod issue15018 { let x = 1; { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } println!("x = {x}"); } fn not_used_later(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } #[allow(irrefutable_let_patterns)] fn not_used_later_but_shadowed(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); let x = 1; println!("x = {x}"); } @@ -231,27 +230,27 @@ mod issue15018 { #[allow(irrefutable_let_patterns)] fn not_used_later_but_shadowed_nested(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {x} {y}"); if let (x, y, z) = (a, b, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } { let x: i32 = 1; { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } if let (x, y, z) = (a, x, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } } { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); let fn_ = |y| { - println!("{} {} {}", a, b, y); + println!("{a} {b} {y}"); }; fn_(c); } diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index ede1ab32beb5..37a96f2287c8 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -4,7 +4,6 @@ clippy::let_unit_value, clippy::no_effect, clippy::toplevel_ref_arg, - clippy::uninlined_format_args, clippy::useless_vec )] @@ -33,13 +32,13 @@ fn main() { match (a, b, c) { //~^ match_single_binding (x, y, z) => { - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); }, } // Lint match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } // Ok foo!(a); @@ -51,7 +50,7 @@ fn main() { // Ok let d = Some(5); match d { - Some(d) => println!("{}", d), + Some(d) => println!("{d}"), _ => println!("None"), } // Lint @@ -64,7 +63,7 @@ fn main() { //~^ match_single_binding _ => { let x = 29; - println!("x has a value of {}", x); + println!("x has a value of {x}"); }, } // Lint @@ -81,24 +80,24 @@ fn main() { let p = Point { x: 0, y: 7 }; match p { //~^ match_single_binding - Point { x, y } => println!("Coords: ({}, {})", x, y), + Point { x, y } => println!("Coords: ({x}, {y})"), } // Lint match p { //~^ match_single_binding - Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), + Point { x: x1, y: y1 } => println!("Coords: ({x1}, {y1})"), } // Lint let x = 5; match x { //~^ match_single_binding - ref r => println!("Got a reference to {}", r), + ref r => println!("Got a reference to {r}"), } // Lint let mut x = 5; match x { //~^ match_single_binding - ref mut mr => println!("Got a mutable reference to {}", mr), + ref mut mr => println!("Got a mutable reference to {mr}"), } // Lint let product = match coords() { @@ -150,7 +149,7 @@ fn issue_8723() { val = match val.split_at(idx) { //~^ match_single_binding (pre, suf) => { - println!("{}", pre); + println!("{pre}"); suf }, }; @@ -273,7 +272,7 @@ mod issue15018 { let x = 1; match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } println!("x = {x}"); } @@ -281,7 +280,7 @@ mod issue15018 { fn not_used_later(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } } @@ -289,7 +288,7 @@ mod issue15018 { fn not_used_later_but_shadowed(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } let x = 1; println!("x = {x}"); @@ -299,30 +298,30 @@ mod issue15018 { fn not_used_later_but_shadowed_nested(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {x} {y}"), } if let (x, y, z) = (a, b, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } { let x: i32 = 1; match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } if let (x, y, z) = (a, x, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } } { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } let fn_ = |y| { - println!("{} {} {}", a, b, y); + println!("{a} {b} {y}"); }; fn_(c); } diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index eea71777890e..82fc43aaa5ea 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -1,10 +1,10 @@ error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:33:5 + --> tests/ui/match_single_binding.rs:32:5 | LL | / match (a, b, c) { LL | | LL | | (x, y, z) => { -LL | | println!("{} {} {}", x, y, z); +LL | | println!("{x} {y} {z}"); LL | | }, LL | | } | |_____^ @@ -15,27 +15,27 @@ help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); LL + { -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:40:5 + --> tests/ui/match_single_binding.rs:39:5 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:58:5 + --> tests/ui/match_single_binding.rs:57:5 | LL | / match a { LL | | @@ -44,13 +44,13 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("whatever");` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:63:5 + --> tests/ui/match_single_binding.rs:62:5 | LL | / match a { LL | | LL | | _ => { LL | | let x = 29; -LL | | println!("x has a value of {}", x); +LL | | println!("x has a value of {x}"); LL | | }, LL | | } | |_____^ @@ -59,12 +59,12 @@ help: consider using the match body instead | LL ~ { LL + let x = 29; -LL + println!("x has a value of {}", x); +LL + println!("x has a value of {x}"); LL + } | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:71:5 + --> tests/ui/match_single_binding.rs:70:5 | LL | / match a { LL | | @@ -86,67 +86,67 @@ LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:82:5 + --> tests/ui/match_single_binding.rs:81:5 | LL | / match p { LL | | -LL | | Point { x, y } => println!("Coords: ({}, {})", x, y), +LL | | Point { x, y } => println!("Coords: ({x}, {y})"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let Point { x, y } = p; -LL + println!("Coords: ({}, {})", x, y); +LL + println!("Coords: ({x}, {y})"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:87:5 + --> tests/ui/match_single_binding.rs:86:5 | LL | / match p { LL | | -LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), +LL | | Point { x: x1, y: y1 } => println!("Coords: ({x1}, {y1})"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let Point { x: x1, y: y1 } = p; -LL + println!("Coords: ({}, {})", x1, y1); +LL + println!("Coords: ({x1}, {y1})"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:93:5 + --> tests/ui/match_single_binding.rs:92:5 | LL | / match x { LL | | -LL | | ref r => println!("Got a reference to {}", r), +LL | | ref r => println!("Got a reference to {r}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let ref r = x; -LL + println!("Got a reference to {}", r); +LL + println!("Got a reference to {r}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:99:5 + --> tests/ui/match_single_binding.rs:98:5 | LL | / match x { LL | | -LL | | ref mut mr => println!("Got a mutable reference to {}", mr), +LL | | ref mut mr => println!("Got a mutable reference to {mr}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let ref mut mr = x; -LL + println!("Got a mutable reference to {}", mr); +LL + println!("Got a mutable reference to {mr}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:104:5 + --> tests/ui/match_single_binding.rs:103:5 | LL | / let product = match coords() { LL | | @@ -161,7 +161,7 @@ LL + let product = x * y; | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:113:18 + --> tests/ui/match_single_binding.rs:112:18 | LL | .map(|i| match i.unwrap() { | __________________^ @@ -179,7 +179,7 @@ LL ~ }) | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:140:5 + --> tests/ui/match_single_binding.rs:139:5 | LL | / match x { LL | | @@ -189,12 +189,12 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("Not an array index start")` error: this assignment could be simplified - --> tests/ui/match_single_binding.rs:150:5 + --> tests/ui/match_single_binding.rs:149:5 | LL | / val = match val.split_at(idx) { LL | | LL | | (pre, suf) => { -LL | | println!("{}", pre); +LL | | println!("{pre}"); LL | | suf LL | | }, LL | | }; @@ -204,13 +204,13 @@ help: consider removing the `match` expression | LL ~ let (pre, suf) = val.split_at(idx); LL + val = { -LL + println!("{}", pre); +LL + println!("{pre}"); LL + suf LL ~ }; | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:164:16 + --> tests/ui/match_single_binding.rs:163:16 | LL | let _ = || match side_effects() { | ________________^ @@ -228,7 +228,7 @@ LL ~ }; | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:171:5 + --> tests/ui/match_single_binding.rs:170:5 | LL | / match r { LL | | @@ -253,7 +253,7 @@ LL ~ }; | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:185:5 + --> tests/ui/match_single_binding.rs:184:5 | LL | / match 1 { LL | | @@ -262,7 +262,7 @@ LL | | } | |_____^ help: consider using the match body instead: `();` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:190:13 + --> tests/ui/match_single_binding.rs:189:13 | LL | let a = match 1 { | _____________^ @@ -272,7 +272,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:195:5 + --> tests/ui/match_single_binding.rs:194:5 | LL | / match 1 { LL | | @@ -281,7 +281,7 @@ LL | | } | |_____^ help: consider using the match body instead: `side_effects();` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:200:13 + --> tests/ui/match_single_binding.rs:199:13 | LL | let b = match 1 { | _____________^ @@ -291,7 +291,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `side_effects()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:205:5 + --> tests/ui/match_single_binding.rs:204:5 | LL | / match 1 { LL | | @@ -300,7 +300,7 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("1");` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:210:13 + --> tests/ui/match_single_binding.rs:209:13 | LL | let c = match 1 { | _____________^ @@ -310,7 +310,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `println!("1")` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:216:9 + --> tests/ui/match_single_binding.rs:215:9 | LL | / match 1 { LL | | @@ -319,7 +319,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:220:9 + --> tests/ui/match_single_binding.rs:219:9 | LL | / match 1 { LL | | @@ -328,7 +328,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `side_effects()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:224:9 + --> tests/ui/match_single_binding.rs:223:9 | LL | / match 1 { LL | | @@ -337,7 +337,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `println!("1")` error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:239:5 + --> tests/ui/match_single_binding.rs:238:5 | LL | / match dbg!(3) { LL | | _ => println!("here"), @@ -351,7 +351,7 @@ LL + println!("here"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:243:5 + --> tests/ui/match_single_binding.rs:242:5 | LL | / match dbg!(3) { LL | | id!(a) => println!("found {a}"), @@ -365,7 +365,7 @@ LL + println!("found {a}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:247:5 + --> tests/ui/match_single_binding.rs:246:5 | LL | / let id!(_a) = match dbg!(3) { LL | | id!(b) => dbg!(b + 1), @@ -379,7 +379,7 @@ LL + let id!(_a) = dbg!(b + 1); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:255:21 + --> tests/ui/match_single_binding.rs:254:21 | LL | inner: [(); match 1 { | _____________________^ @@ -397,7 +397,7 @@ LL ~ }], | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:263:13 + --> tests/ui/match_single_binding.rs:262:13 | LL | / match 1 { LL | | @@ -412,11 +412,11 @@ LL + 42 | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:274:9 + --> tests/ui/match_single_binding.rs:273:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | @@ -424,61 +424,61 @@ help: consider using a `let` statement | LL ~ { LL + let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:282:9 + --> tests/ui/match_single_binding.rs:281:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z) +LL + println!("{x} {y} {z}") | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:290:9 + --> tests/ui/match_single_binding.rs:289:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:300:9 + --> tests/ui/match_single_binding.rs:299:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {x} {y}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {x} {y}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:310:13 + --> tests/ui/match_single_binding.rs:309:13 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____________^ | @@ -486,27 +486,27 @@ help: consider using a `let` statement | LL ~ { LL + let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:320:13 + --> tests/ui/match_single_binding.rs:319:13 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:335:12 + --> tests/ui/match_single_binding.rs:334:12 | LL | && match b { | ____________^ @@ -516,7 +516,7 @@ LL | | }; | |_________^ help: consider using the match body instead: `b < c` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:341:12 + --> tests/ui/match_single_binding.rs:340:12 | LL | && match (a, b) { | ____________^ diff --git a/tests/ui/match_single_binding2.fixed b/tests/ui/match_single_binding2.fixed index 988121f50d0f..f00987470ae1 100644 --- a/tests/ui/match_single_binding2.fixed +++ b/tests/ui/match_single_binding2.fixed @@ -1,6 +1,5 @@ #![warn(clippy::match_single_binding)] #![allow(unused_variables)] -#![allow(clippy::uninlined_format_args)] fn main() { // Lint (additional curly braces needed, see #6572) @@ -29,7 +28,7 @@ fn main() { #[rustfmt::skip] Some((first, _second)) => { let (a, b) = get_tup(); - println!("a {:?} and b {:?}", a, b) + println!("a {a:?} and b {b:?}") }, None => println!("nothing"), } diff --git a/tests/ui/match_single_binding2.rs b/tests/ui/match_single_binding2.rs index a4fb2bd6f381..5416f647b4e6 100644 --- a/tests/ui/match_single_binding2.rs +++ b/tests/ui/match_single_binding2.rs @@ -1,6 +1,5 @@ #![warn(clippy::match_single_binding)] #![allow(unused_variables)] -#![allow(clippy::uninlined_format_args)] fn main() { // Lint (additional curly braces needed, see #6572) @@ -30,7 +29,7 @@ fn main() { Some((first, _second)) => { match get_tup() { //~^ match_single_binding - (a, b) => println!("a {:?} and b {:?}", a, b), + (a, b) => println!("a {a:?} and b {b:?}"), } }, None => println!("nothing"), diff --git a/tests/ui/match_single_binding2.stderr b/tests/ui/match_single_binding2.stderr index a24cbe3eed76..65b8aa6acd5e 100644 --- a/tests/ui/match_single_binding2.stderr +++ b/tests/ui/match_single_binding2.stderr @@ -1,5 +1,5 @@ error: this match could be written as a `let` statement - --> tests/ui/match_single_binding2.rs:17:36 + --> tests/ui/match_single_binding2.rs:16:36 | LL | Some((iter, _item)) => match iter.size_hint() { | ____________________________________^ @@ -19,22 +19,22 @@ LL ~ }, | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding2.rs:31:13 + --> tests/ui/match_single_binding2.rs:30:13 | LL | / match get_tup() { LL | | -LL | | (a, b) => println!("a {:?} and b {:?}", a, b), +LL | | (a, b) => println!("a {a:?} and b {b:?}"), LL | | } | |_____________^ | help: consider using a `let` statement | LL ~ let (a, b) = get_tup(); -LL + println!("a {:?} and b {:?}", a, b) +LL + println!("a {a:?} and b {b:?}") | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding2.rs:43:5 + --> tests/ui/match_single_binding2.rs:42:5 | LL | / match side_effects() { LL | | @@ -49,7 +49,7 @@ LL + println!("Side effects"); | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding2.rs:51:5 + --> tests/ui/match_single_binding2.rs:50:5 | LL | / match match x { LL | | diff --git a/tests/ui/mem_replace.fixed b/tests/ui/mem_replace.fixed index 870ef23113a2..94ad1aad3eb7 100644 --- a/tests/ui/mem_replace.fixed +++ b/tests/ui/mem_replace.fixed @@ -179,3 +179,9 @@ fn mem_replace_option_with_some_bad_msrv() { let mut an_option = Some(0); let replaced = mem::replace(&mut an_option, Some(1)); } + +fn issue15785() { + let mut text = String::from("foo"); + let replaced = std::mem::take(dbg!(&mut text)); + //~^ mem_replace_with_default +} diff --git a/tests/ui/mem_replace.rs b/tests/ui/mem_replace.rs index b4ed5eafea95..ac79660f0f1e 100644 --- a/tests/ui/mem_replace.rs +++ b/tests/ui/mem_replace.rs @@ -179,3 +179,9 @@ fn mem_replace_option_with_some_bad_msrv() { let mut an_option = Some(0); let replaced = mem::replace(&mut an_option, Some(1)); } + +fn issue15785() { + let mut text = String::from("foo"); + let replaced = std::mem::replace(dbg!(&mut text), String::default()); + //~^ mem_replace_with_default +} diff --git a/tests/ui/mem_replace.stderr b/tests/ui/mem_replace.stderr index fb4a367266d3..104c98540028 100644 --- a/tests/ui/mem_replace.stderr +++ b/tests/ui/mem_replace.stderr @@ -181,5 +181,11 @@ error: replacing an `Option` with `Some(..)` LL | let replaced = mem::replace(if b { &mut opt1 } else { &mut opt2 }, Some(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::replace()` instead: `(if b { &mut opt1 } else { &mut opt2 }).replace(1)` -error: aborting due to 29 previous errors +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> tests/ui/mem_replace.rs:185:20 + | +LL | let replaced = std::mem::replace(dbg!(&mut text), String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(dbg!(&mut text))` + +error: aborting due to 30 previous errors diff --git a/tests/ui/methods.rs b/tests/ui/methods.rs index f73fe288b0f8..9595888b99f8 100644 --- a/tests/ui/methods.rs +++ b/tests/ui/methods.rs @@ -135,6 +135,26 @@ fn filter_next() { let _ = foo.filter(42).next(); } +#[rustfmt::skip] +fn filter_next_back() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Multi-line case. + let _ = v.iter().filter(|&x| { + //~^ filter_next + *x < 0 + } + ).next_back(); + + // Check that we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.filter().next_back(); + + let foo = IteratorMethodFalsePositives {}; + let _ = foo.filter(42).next_back(); +} + fn main() { filter_next(); + filter_next_back(); } diff --git a/tests/ui/methods.stderr b/tests/ui/methods.stderr index b226ce7c65da..45efea4ee01c 100644 --- a/tests/ui/methods.stderr +++ b/tests/ui/methods.stderr @@ -24,5 +24,16 @@ LL | | ).next(); = note: `-D clippy::filter-next` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::filter_next)]` -error: aborting due to 2 previous errors +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods.rs:143:13 + | +LL | let _ = v.iter().filter(|&x| { + | _____________^ +LL | | +LL | | *x < 0 +LL | | } +LL | | ).next_back(); + | |________________________________^ + +error: aborting due to 3 previous errors diff --git a/tests/ui/methods_fixable.fixed b/tests/ui/methods_fixable.fixed index 49730d811558..287d8d881ec2 100644 --- a/tests/ui/methods_fixable.fixed +++ b/tests/ui/methods_fixable.fixed @@ -8,4 +8,18 @@ fn main() { // Single-line case. let _ = v.iter().find(|&x| *x < 0); //~^ filter_next + + let _ = v.iter().rfind(|&x| *x < 0); + //~^ filter_next +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + let _ = vec![1].into_iter().rfind(|&x| x < 0); + //~^ filter_next +} + +#[clippy::msrv = "1.26"] +fn msrv_1_26() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); } diff --git a/tests/ui/methods_fixable.rs b/tests/ui/methods_fixable.rs index a499b63b6f5b..11ce1b63560d 100644 --- a/tests/ui/methods_fixable.rs +++ b/tests/ui/methods_fixable.rs @@ -8,4 +8,18 @@ fn main() { // Single-line case. let _ = v.iter().filter(|&x| *x < 0).next(); //~^ filter_next + + let _ = v.iter().filter(|&x| *x < 0).next_back(); + //~^ filter_next +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); + //~^ filter_next +} + +#[clippy::msrv = "1.26"] +fn msrv_1_26() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); } diff --git a/tests/ui/methods_fixable.stderr b/tests/ui/methods_fixable.stderr index 852e7a224a6e..d26b5e9ac271 100644 --- a/tests/ui/methods_fixable.stderr +++ b/tests/ui/methods_fixable.stderr @@ -7,5 +7,17 @@ LL | let _ = v.iter().filter(|&x| *x < 0).next(); = note: `-D clippy::filter-next` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::filter_next)]` -error: aborting due to 1 previous error +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods_fixable.rs:12:13 + | +LL | let _ = v.iter().filter(|&x| *x < 0).next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.iter().rfind(|&x| *x < 0)` + +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods_fixable.rs:18:13 + | +LL | let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec![1].into_iter().rfind(|&x| x < 0)` + +error: aborting due to 3 previous errors diff --git a/tests/ui/module_inception.rs b/tests/ui/module_inception.rs index 15b7fb877770..5735dd5867d5 100644 --- a/tests/ui/module_inception.rs +++ b/tests/ui/module_inception.rs @@ -38,4 +38,13 @@ mod bar { mod bar {} } +mod with_inner_impl { + struct S; + impl S { + fn f() { + mod with_inner_impl {} + } + } +} + fn main() {} diff --git a/tests/ui/multiple_inherent_impl_cfg.normal.stderr b/tests/ui/multiple_inherent_impl_cfg.normal.stderr new file mode 100644 index 000000000000..b7d95fbfa8f5 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.normal.stderr @@ -0,0 +1,31 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:11:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:3:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/multiple_inherent_impl_cfg.rs b/tests/ui/multiple_inherent_impl_cfg.rs new file mode 100644 index 000000000000..15c8b7c50878 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.rs @@ -0,0 +1,46 @@ +//@compile-flags: --cfg test +#![deny(clippy::multiple_inherent_impl)] + +// issue #13040 + +fn main() {} + +struct A; + +impl A {} + +impl A {} +//~^ multiple_inherent_impl + +#[cfg(test)] +impl A {} // false positive +//~^ multiple_inherent_impl + +#[cfg(test)] +impl A {} +//~^ multiple_inherent_impl + +struct B; + +impl B {} + +#[cfg(test)] +impl B {} // false positive +//~^ multiple_inherent_impl + +impl B {} +//~^ multiple_inherent_impl + +#[cfg(test)] +impl B {} +//~^ multiple_inherent_impl + +#[cfg(test)] +struct C; + +#[cfg(test)] +impl C {} + +#[cfg(test)] +impl C {} +//~^ multiple_inherent_impl diff --git a/tests/ui/multiple_inherent_impl_cfg.stderr b/tests/ui/multiple_inherent_impl_cfg.stderr new file mode 100644 index 000000000000..9d408ce3dec3 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.stderr @@ -0,0 +1,91 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:12:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:2:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 + | +LL | impl A {} // false positive + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:20:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 + | +LL | impl B {} // false positive + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:31:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:35:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:45:1 + | +LL | impl C {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:42:1 + | +LL | impl C {} + | ^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/multiple_inherent_impl_cfg.withtest.stderr b/tests/ui/multiple_inherent_impl_cfg.withtest.stderr new file mode 100644 index 000000000000..1e98b1f18801 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.withtest.stderr @@ -0,0 +1,91 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:11:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:3:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:14:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:17:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:23:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:36:1 + | +LL | impl C {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:34:1 + | +LL | impl C {} + | ^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/multiple_unsafe_ops_per_block.rs b/tests/ui/multiple_unsafe_ops_per_block.rs index 016fd89a7b7a..132673d5164a 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.rs +++ b/tests/ui/multiple_unsafe_ops_per_block.rs @@ -1,10 +1,10 @@ //@needs-asm-support //@aux-build:proc_macros.rs -#![allow(unused)] -#![allow(deref_nullptr)] -#![allow(clippy::unnecessary_operation)] -#![allow(dropping_copy_types)] -#![allow(clippy::assign_op_pattern)] +#![expect( + dropping_copy_types, + clippy::unnecessary_operation, + clippy::unnecessary_literal_unwrap +)] #![warn(clippy::multiple_unsafe_ops_per_block)] extern crate proc_macros; @@ -105,17 +105,17 @@ fn correct3() { } } -// tests from the issue (https://github.com/rust-lang/rust-clippy/issues/10064) - -unsafe fn read_char_bad(ptr: *const u8) -> char { - unsafe { char::from_u32_unchecked(*ptr.cast::()) } - //~^ multiple_unsafe_ops_per_block -} +fn issue10064() { + unsafe fn read_char_bad(ptr: *const u8) -> char { + unsafe { char::from_u32_unchecked(*ptr.cast::()) } + //~^ multiple_unsafe_ops_per_block + } -// no lint -unsafe fn read_char_good(ptr: *const u8) -> char { - let int_value = unsafe { *ptr.cast::() }; - unsafe { core::char::from_u32_unchecked(int_value) } + // no lint + unsafe fn read_char_good(ptr: *const u8) -> char { + let int_value = unsafe { *ptr.cast::() }; + unsafe { core::char::from_u32_unchecked(int_value) } + } } // no lint @@ -126,42 +126,87 @@ fn issue10259() { }); } -fn _fn_ptr(x: unsafe fn()) { - unsafe { - //~^ multiple_unsafe_ops_per_block - x(); - x(); +fn issue10367() { + fn fn_ptr(x: unsafe fn()) { + unsafe { + //~^ multiple_unsafe_ops_per_block + x(); + x(); + } } -} -fn _assoc_const() { - trait X { - const X: unsafe fn(); + fn assoc_const() { + trait X { + const X: unsafe fn(); + } + fn _f() { + unsafe { + //~^ multiple_unsafe_ops_per_block + T::X(); + T::X(); + } + } } - fn _f() { + + fn field_fn_ptr(x: unsafe fn()) { + struct X(unsafe fn()); + let x = X(x); unsafe { //~^ multiple_unsafe_ops_per_block - T::X(); - T::X(); + x.0(); + x.0(); } } } -fn _field_fn_ptr(x: unsafe fn()) { - struct X(unsafe fn()); - let x = X(x); +// await expands to an unsafe block with several operations, but this is fine. +async fn issue11312() { + async fn helper() {} + + helper().await; +} + +async fn issue13879() { + async fn foo() {} + + // no lint: nothing unsafe beyond the `await` which we ignore + unsafe { + foo().await; + } + + // no lint: only one unsafe call beyond the `await` + unsafe { + not_very_safe(); + foo().await; + } + + // lint: two unsafe calls beyond the `await` unsafe { //~^ multiple_unsafe_ops_per_block - x.0(); - x.0(); + not_very_safe(); + STATIC += 1; + foo().await; } -} -// await expands to an unsafe block with several operations, but this is fine.: #11312 -async fn await_desugaring_silent() { - async fn helper() {} + async unsafe fn foo_unchecked() {} - helper().await; + // no lint: only one unsafe call in the `await`ed expr + unsafe { + foo_unchecked().await; + } + + // lint: one unsafe call in the `await`ed expr, and one outside + unsafe { + //~^ multiple_unsafe_ops_per_block + not_very_safe(); + foo_unchecked().await; + } + + // lint: two unsafe calls in the `await`ed expr + unsafe { + //~^ multiple_unsafe_ops_per_block + Some(foo_unchecked()).unwrap_unchecked().await; + } } fn main() {} diff --git a/tests/ui/multiple_unsafe_ops_per_block.stderr b/tests/ui/multiple_unsafe_ops_per_block.stderr index 3130cecc2525..922a464c6b6e 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.stderr +++ b/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -113,84 +113,147 @@ LL | asm!("nop"); | ^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:9 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:14 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:18 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: raw pointer dereference occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:39 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:43 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:130:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:131:9 | -LL | / unsafe { +LL | / unsafe { LL | | -LL | | x(); -LL | | x(); -LL | | } - | |_____^ +LL | | x(); +LL | | x(); +LL | | } + | |_________^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:132:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:133:13 | -LL | x(); - | ^^^ +LL | x(); + | ^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:133:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:134:13 | -LL | x(); - | ^^^ +LL | x(); + | ^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:142:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:143:13 + | +LL | / unsafe { +LL | | +LL | | T::X(); +LL | | T::X(); +LL | | } + | |_____________^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:145:17 + | +LL | T::X(); + | ^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:146:17 + | +LL | T::X(); + | ^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:154:9 | LL | / unsafe { LL | | -LL | | T::X(); -LL | | T::X(); +LL | | x.0(); +LL | | x.0(); LL | | } | |_________^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:144:13 + --> tests/ui/multiple_unsafe_ops_per_block.rs:156:13 | -LL | T::X(); - | ^^^^^^ +LL | x.0(); + | ^^^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:145:13 + --> tests/ui/multiple_unsafe_ops_per_block.rs:157:13 | -LL | T::X(); - | ^^^^^^ +LL | x.0(); + | ^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:153:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:184:5 | LL | / unsafe { LL | | -LL | | x.0(); -LL | | x.0(); +LL | | not_very_safe(); +LL | | STATIC += 1; +LL | | foo().await; LL | | } | |_____^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:155:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:186:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: modification of a mutable static occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:187:9 + | +LL | STATIC += 1; + | ^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:199:5 + | +LL | / unsafe { +LL | | +LL | | not_very_safe(); +LL | | foo_unchecked().await; +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:201:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:202:9 + | +LL | foo_unchecked().await; + | ^^^^^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:206:5 + | +LL | / unsafe { +LL | | +LL | | Some(foo_unchecked()).unwrap_unchecked().await; +LL | | } + | |_____^ + | +note: unsafe method call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:208:9 | -LL | x.0(); - | ^^^^^ +LL | Some(foo_unchecked()).unwrap_unchecked().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:156:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:208:14 | -LL | x.0(); - | ^^^^^ +LL | Some(foo_unchecked()).unwrap_unchecked().await; + | ^^^^^^^^^^^^^^^ -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/must_use_unit_unfixable.rs b/tests/ui/must_use_unit_unfixable.rs index 0dba7996bac3..8eeaf36dca29 100644 --- a/tests/ui/must_use_unit_unfixable.rs +++ b/tests/ui/must_use_unit_unfixable.rs @@ -1,5 +1,3 @@ -//@no-rustfix - #[cfg_attr(all(), must_use, deprecated)] fn issue_12320() {} //~^ must_use_unit diff --git a/tests/ui/must_use_unit_unfixable.stderr b/tests/ui/must_use_unit_unfixable.stderr index 087682199afb..8b5e556b1b2e 100644 --- a/tests/ui/must_use_unit_unfixable.stderr +++ b/tests/ui/must_use_unit_unfixable.stderr @@ -1,11 +1,11 @@ error: this unit-returning function has a `#[must_use]` attribute - --> tests/ui/must_use_unit_unfixable.rs:4:1 + --> tests/ui/must_use_unit_unfixable.rs:2:1 | LL | fn issue_12320() {} | ^^^^^^^^^^^^^^^^ | help: remove `must_use` - --> tests/ui/must_use_unit_unfixable.rs:3:19 + --> tests/ui/must_use_unit_unfixable.rs:1:19 | LL | #[cfg_attr(all(), must_use, deprecated)] | ^^^^^^^^ @@ -13,13 +13,13 @@ LL | #[cfg_attr(all(), must_use, deprecated)] = help: to override `-D warnings` add `#[allow(clippy::must_use_unit)]` error: this unit-returning function has a `#[must_use]` attribute - --> tests/ui/must_use_unit_unfixable.rs:8:1 + --> tests/ui/must_use_unit_unfixable.rs:6:1 | LL | fn issue_12320_2() {} | ^^^^^^^^^^^^^^^^^^ | help: remove `must_use` - --> tests/ui/must_use_unit_unfixable.rs:7:44 + --> tests/ui/must_use_unit_unfixable.rs:5:44 | LL | #[cfg_attr(all(), deprecated, doc = "foo", must_use)] | ^^^^^^^^ diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed new file mode 100644 index 000000000000..f9a7f5dcb5a1 --- /dev/null +++ b/tests/ui/mut_mut.fixed @@ -0,0 +1,92 @@ +//@aux-build:proc_macros.rs + +#![warn(clippy::mut_mut)] +#![allow(unused)] +#![allow( + clippy::no_effect, + clippy::uninlined_format_args, + clippy::unnecessary_operation, + clippy::needless_pass_by_ref_mut +)] + +extern crate proc_macros; +use proc_macros::{external, inline_macros}; + +fn fun(x: &mut u32) { + //~^ mut_mut +} + +fn less_fun(x: *mut *mut u32) { + let y = x; +} + +macro_rules! mut_ptr { + ($p:expr) => { + &mut $p + }; +} + +#[allow(unused_mut, unused_variables)] +#[inline_macros] +fn main() { + let mut x = &mut 1u32; + //~^ mut_mut + { + let mut y = &mut *x; + //~^ mut_mut + } + + { + let y: &mut u32 = &mut 2; + //~^ mut_mut + //~| mut_mut + } + + { + let y: &mut u32 = &mut 2; + //~^ mut_mut + //~| mut_mut + } + + let mut z = inline!(&mut $(&mut 3u32)); +} + +fn issue939() { + let array = [5, 6, 7, 8, 9]; + let mut args = array.iter().skip(2); + for &arg in &mut args { + println!("{}", arg); + } + + let args = &mut args; + for arg in args { + println!(":{}", arg); + } +} + +fn issue6922() { + // do not lint from an external macro + external!(let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;); +} + +mod issue9035 { + use std::fmt::Display; + + struct Foo<'a> { + inner: &'a mut dyn Display, + } + + impl Foo<'_> { + fn foo(&mut self) { + let hlp = &mut self.inner; + bar(hlp); + } + } + + fn bar(_: &mut impl Display) {} +} + +fn allow_works() { + #[allow(clippy::mut_mut)] + let _ = &mut &mut 1; +} diff --git a/tests/ui/mut_mut.rs b/tests/ui/mut_mut.rs index bbcdbc89b6a4..bbec48011a17 100644 --- a/tests/ui/mut_mut.rs +++ b/tests/ui/mut_mut.rs @@ -12,9 +12,8 @@ extern crate proc_macros; use proc_macros::{external, inline_macros}; -fn fun(x: &mut &mut u32) -> bool { +fn fun(x: &mut &mut u32) { //~^ mut_mut - **x > 0 } fn less_fun(x: *mut *mut u32) { @@ -37,23 +36,19 @@ fn main() { //~^ mut_mut } - if fun(x) { + { let y: &mut &mut u32 = &mut &mut 2; //~^ mut_mut //~| mut_mut - **y + **x; } - if fun(x) { + { let y: &mut &mut &mut u32 = &mut &mut &mut 2; //~^ mut_mut //~| mut_mut - //~| mut_mut - ***y + **x; } let mut z = inline!(&mut $(&mut 3u32)); - //~^ mut_mut } fn issue939() { diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 74b0c9ba145a..85e9c5649b89 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -1,61 +1,47 @@ -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:15:11 | -LL | fn fun(x: &mut &mut u32) -> bool { - | ^^^^^^^^^^^^^ +LL | fn fun(x: &mut &mut u32) { + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` | = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:33:17 +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut.rs:32:17 | LL | let mut x = &mut &mut 1u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 1u32` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:55:25 - | -LL | let mut z = inline!(&mut $(&mut 3u32)); - | ^ - | - = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: this expression mutably borrows a mutable reference. Consider reborrowing - --> tests/ui/mut_mut.rs:36:21 +error: this expression mutably borrows a mutable reference + --> tests/ui/mut_mut.rs:35:21 | LL | let mut y = &mut x; - | ^^^^^^ + | ^^^^^^ help: reborrow instead: `&mut *x` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:41:32 +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut.rs:40:32 | LL | let y: &mut &mut u32 = &mut &mut 2; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 2` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:41:16 +error: a type of form `&mut &mut _` + --> tests/ui/mut_mut.rs:40:16 | LL | let y: &mut &mut u32 = &mut &mut 2; - | ^^^^^^^^^^^^^ - -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:37 - | -LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:16 +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut.rs:46:37 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut 2` -error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:21 +error: a type of form `&mut &mut _` + --> tests/ui/mut_mut.rs:46:16 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut u32` -error: aborting due to 9 previous errors +error: aborting due to 7 previous errors diff --git a/tests/ui/mut_mut_unfixable.rs b/tests/ui/mut_mut_unfixable.rs new file mode 100644 index 000000000000..271cb7b96889 --- /dev/null +++ b/tests/ui/mut_mut_unfixable.rs @@ -0,0 +1,42 @@ +//@no-rustfix + +#![warn(clippy::mut_mut)] +#![allow(unused)] +#![expect(clippy::no_effect)] + +//! removing the extra `&mut`s will break the derefs + +fn fun(x: &mut &mut u32) -> bool { + //~^ mut_mut + **x > 0 +} + +fn main() { + let mut x = &mut &mut 1u32; + //~^ mut_mut + { + let mut y = &mut x; + //~^ mut_mut + ***y + **x; + } + + if fun(x) { + let y = &mut &mut 2; + //~^ mut_mut + **y + **x; + } + + if fun(x) { + let y = &mut &mut &mut 2; + //~^ mut_mut + ***y + **x; + } + + if fun(x) { + // The lint will remove the extra `&mut`, but the result will still be a `&mut` of an expr + // of type `&mut _` (x), so the lint will fire again. That's because we've decided that + // doing both fixes in one run is not worth it, given how improbable code like this is. + let y = &mut &mut x; + //~^ mut_mut + } +} diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr new file mode 100644 index 000000000000..cf66eb2ed1ec --- /dev/null +++ b/tests/ui/mut_mut_unfixable.stderr @@ -0,0 +1,41 @@ +error: a type of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:9:11 + | +LL | fn fun(x: &mut &mut u32) -> bool { + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` + | + = note: `-D clippy::mut-mut` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` + +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:15:17 + | +LL | let mut x = &mut &mut 1u32; + | ^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 1u32` + +error: this expression mutably borrows a mutable reference + --> tests/ui/mut_mut_unfixable.rs:18:21 + | +LL | let mut y = &mut x; + | ^^^^^^ help: reborrow instead: `&mut *x` + +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:24:17 + | +LL | let y = &mut &mut 2; + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 2` + +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:30:17 + | +LL | let y = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut 2` + +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:39:17 + | +LL | let y = &mut &mut x; + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut x` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/mut_reference.stderr b/tests/ui/mut_reference.stderr deleted file mode 100644 index 5ecfaa37416b..000000000000 --- a/tests/ui/mut_reference.stderr +++ /dev/null @@ -1,77 +0,0 @@ -error: the function `takes_ref` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:56:15 - | -LL | takes_ref(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - | - = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::unnecessary_mut_passed)]` - -error: the function `takes_ref_ref` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:58:19 - | -LL | takes_ref_ref(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` - -error: the function `takes_ref_refmut` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:60:22 - | -LL | takes_ref_refmut(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` - -error: the function `takes_raw_const` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:62:21 - | -LL | takes_raw_const(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - -error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:66:12 - | -LL | as_ptr(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - -error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:69:12 - | -LL | as_ptr(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` - -error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:72:12 - | -LL | as_ptr(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` - -error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:75:12 - | -LL | as_ptr(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - -error: the method `takes_ref` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:80:25 - | -LL | my_struct.takes_ref(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - -error: the method `takes_ref_ref` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:82:29 - | -LL | my_struct.takes_ref_ref(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` - -error: the method `takes_ref_refmut` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:84:32 - | -LL | my_struct.takes_ref_refmut(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` - -error: the method `takes_raw_const` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:86:31 - | -LL | my_struct.takes_raw_const(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` - -error: aborting due to 12 previous errors - diff --git a/tests/ui/mutex_atomic.fixed b/tests/ui/mutex_atomic.fixed new file mode 100644 index 000000000000..e4218726019f --- /dev/null +++ b/tests/ui/mutex_atomic.fixed @@ -0,0 +1,67 @@ +#![warn(clippy::mutex_integer)] +#![warn(clippy::mutex_atomic)] +#![allow(clippy::borrow_as_ptr)] + +use std::sync::Mutex; + +fn main() { + let _ = std::sync::atomic::AtomicBool::new(true); + //~^ mutex_atomic + + let _ = std::sync::atomic::AtomicUsize::new(5usize); + //~^ mutex_atomic + + let _ = std::sync::atomic::AtomicIsize::new(9isize); + //~^ mutex_atomic + + let mut x = 4u32; + // `AtomicPtr` only accepts `*mut T`, so this should not lint + let _ = Mutex::new(&x as *const u32); + + let _ = std::sync::atomic::AtomicPtr::new(&mut x as *mut u32); + //~^ mutex_atomic + + let _ = std::sync::atomic::AtomicU32::new(0u32); + //~^ mutex_integer + + let _ = std::sync::atomic::AtomicI32::new(0i32); + //~^ mutex_integer + + let _ = Mutex::new(0f32); // there are no float atomics, so this should not lint + let _ = std::sync::atomic::AtomicU8::new(0u8); + //~^ mutex_integer + + let _ = std::sync::atomic::AtomicI16::new(0i16); + //~^ mutex_integer + + let _x = std::sync::atomic::AtomicI8::new(0); + //~^ mutex_integer + + const X: i64 = 0; + let _ = std::sync::atomic::AtomicI64::new(X); + //~^ mutex_integer + + // there are no 128 atomics, so these two should not lint + { + let _ = Mutex::new(0u128); + let _x: Mutex = Mutex::new(0); + } +} + +// don't lint on _use_, only declaration +fn issue13378() { + static MTX: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + //~^ mutex_integer + + let mtx = std::sync::atomic::AtomicI32::new(0); + //~^ mutex_integer + // This will still lint, since we're reassigning the mutex to a variable -- oh well. + // But realistically something like this won't really come up. + let reassigned = mtx; + //~^ mutex_integer + + // don't eat the `)` when removing the type ascription -- see + // https://github.com/rust-lang/rust-clippy/issues/15377 + let (funky_mtx) = std::sync::atomic::AtomicU64::new(0); + //~^ mutex_integer +} diff --git a/tests/ui/mutex_atomic.rs b/tests/ui/mutex_atomic.rs index 7db5c9f274f6..95f2b135903f 100644 --- a/tests/ui/mutex_atomic.rs +++ b/tests/ui/mutex_atomic.rs @@ -2,47 +2,66 @@ #![warn(clippy::mutex_atomic)] #![allow(clippy::borrow_as_ptr)] +use std::sync::Mutex; + fn main() { - use std::sync::Mutex; - Mutex::new(true); + let _ = Mutex::new(true); //~^ mutex_atomic - Mutex::new(5usize); + let _ = Mutex::new(5usize); //~^ mutex_atomic - Mutex::new(9isize); + let _ = Mutex::new(9isize); //~^ mutex_atomic let mut x = 4u32; - Mutex::new(&x as *const u32); - //~^ mutex_atomic + // `AtomicPtr` only accepts `*mut T`, so this should not lint + let _ = Mutex::new(&x as *const u32); - Mutex::new(&mut x as *mut u32); + let _ = Mutex::new(&mut x as *mut u32); //~^ mutex_atomic - Mutex::new(0u32); + let _ = Mutex::new(0u32); //~^ mutex_integer - Mutex::new(0i32); + let _ = Mutex::new(0i32); //~^ mutex_integer - Mutex::new(0f32); // there are no float atomics, so this should not lint - Mutex::new(0u8); + let _ = Mutex::new(0f32); // there are no float atomics, so this should not lint + let _ = Mutex::new(0u8); //~^ mutex_integer - Mutex::new(0i16); + let _ = Mutex::new(0i16); //~^ mutex_integer let _x: Mutex = Mutex::new(0); //~^ mutex_integer const X: i64 = 0; - Mutex::new(X); + let _ = Mutex::new(X); //~^ mutex_integer // there are no 128 atomics, so these two should not lint { - Mutex::new(0u128); + let _ = Mutex::new(0u128); let _x: Mutex = Mutex::new(0); } } + +// don't lint on _use_, only declaration +fn issue13378() { + static MTX: Mutex = Mutex::new(0); + //~^ mutex_integer + + let mtx = Mutex::new(0); + //~^ mutex_integer + // This will still lint, since we're reassigning the mutex to a variable -- oh well. + // But realistically something like this won't really come up. + let reassigned = mtx; + //~^ mutex_integer + + // don't eat the `)` when removing the type ascription -- see + // https://github.com/rust-lang/rust-clippy/issues/15377 + let (funky_mtx): Mutex = Mutex::new(0); + //~^ mutex_integer +} diff --git a/tests/ui/mutex_atomic.stderr b/tests/ui/mutex_atomic.stderr index a6d5d60fbf05..0afc6d541dea 100644 --- a/tests/ui/mutex_atomic.stderr +++ b/tests/ui/mutex_atomic.stderr @@ -1,74 +1,134 @@ -error: consider using an `AtomicBool` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:7:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:8:13 | -LL | Mutex::new(true); - | ^^^^^^^^^^^^^^^^ +LL | let _ = Mutex::new(true); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicBool::new(true)` | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` = note: `-D clippy::mutex-atomic` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutex_atomic)]` -error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:10:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:11:13 | -LL | Mutex::new(5usize); - | ^^^^^^^^^^^^^^^^^^ - -error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:13:5 +LL | let _ = Mutex::new(5usize); + | ^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicUsize::new(5usize)` | -LL | Mutex::new(9isize); - | ^^^^^^^^^^^^^^^^^^ + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:17:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:14:13 + | +LL | let _ = Mutex::new(9isize); + | ^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicIsize::new(9isize)` | -LL | Mutex::new(&x as *const u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:20:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:21:13 | -LL | Mutex::new(&mut x as *mut u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = Mutex::new(&mut x as *mut u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicPtr::new(&mut x as *mut u32)` + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicU32` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:23:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:24:13 | -LL | Mutex::new(0u32); - | ^^^^^^^^^^^^^^^^ +LL | let _ = Mutex::new(0u32); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicU32::new(0u32)` | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` = note: `-D clippy::mutex-integer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutex_integer)]` -error: consider using an `AtomicI32` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:26:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:27:13 + | +LL | let _ = Mutex::new(0i32); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI32::new(0i32)` | -LL | Mutex::new(0i32); - | ^^^^^^^^^^^^^^^^ + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicU8` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:30:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:31:13 | -LL | Mutex::new(0u8); - | ^^^^^^^^^^^^^^^ +LL | let _ = Mutex::new(0u8); + | ^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicU8::new(0u8)` + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicI16` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:33:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:34:13 + | +LL | let _ = Mutex::new(0i16); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI16::new(0i16)` | -LL | Mutex::new(0i16); - | ^^^^^^^^^^^^^^^^ + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicI8` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:36:25 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:37:25 | LL | let _x: Mutex = Mutex::new(0); | ^^^^^^^^^^^^^ + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` +help: try + | +LL - let _x: Mutex = Mutex::new(0); +LL + let _x = std::sync::atomic::AtomicI8::new(0); + | + +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:41:13 + | +LL | let _ = Mutex::new(X); + | ^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI64::new(X)` + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:53:30 + | +LL | static MTX: Mutex = Mutex::new(0); + | ^^^^^^^^^^^^^ + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` +help: try + | +LL - static MTX: Mutex = Mutex::new(0); +LL + static MTX: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + | + +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:56:15 + | +LL | let mtx = Mutex::new(0); + | ^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI32::new(0)` + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` -error: consider using an `AtomicI64` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` - --> tests/ui/mutex_atomic.rs:40:5 +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:60:22 + | +LL | let reassigned = mtx; + | ^^^ + | + = help: consider using an `AtomicI32` instead + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:65:35 + | +LL | let (funky_mtx): Mutex = Mutex::new(0); + | ^^^^^^^^^^^^^ + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` +help: try + | +LL - let (funky_mtx): Mutex = Mutex::new(0); +LL + let (funky_mtx) = std::sync::atomic::AtomicU64::new(0); | -LL | Mutex::new(X); - | ^^^^^^^^^^^^^ -error: aborting due to 11 previous errors +error: aborting due to 14 previous errors diff --git a/tests/ui/mutex_atomic_unfixable.rs b/tests/ui/mutex_atomic_unfixable.rs new file mode 100644 index 000000000000..0c04f48cf8a9 --- /dev/null +++ b/tests/ui/mutex_atomic_unfixable.rs @@ -0,0 +1,13 @@ +//@no-rustfix +#![warn(clippy::mutex_atomic, clippy::mutex_integer)] + +use std::sync::Mutex; + +fn issue13378() { + static MTX: Mutex = Mutex::new(0); + //~^ mutex_integer + + // unfixable because we don't fix this `lock` + let mut guard = MTX.lock().unwrap(); + *guard += 1; +} diff --git a/tests/ui/mutex_atomic_unfixable.stderr b/tests/ui/mutex_atomic_unfixable.stderr new file mode 100644 index 000000000000..27ffb1304c69 --- /dev/null +++ b/tests/ui/mutex_atomic_unfixable.stderr @@ -0,0 +1,17 @@ +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic_unfixable.rs:7:30 + | +LL | static MTX: Mutex = Mutex::new(0); + | ^^^^^^^^^^^^^ + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + = note: `-D clippy::mutex-integer` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::mutex_integer)]` +help: try + | +LL - static MTX: Mutex = Mutex::new(0); +LL + static MTX: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/needless_bool/fixable.fixed b/tests/ui/needless_bool/fixable.fixed index 0664abf0944d..2589819f019b 100644 --- a/tests/ui/needless_bool/fixable.fixed +++ b/tests/ui/needless_bool/fixable.fixed @@ -5,7 +5,7 @@ clippy::no_effect, clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::self_named_constructors, clippy::struct_field_names diff --git a/tests/ui/needless_bool/fixable.rs b/tests/ui/needless_bool/fixable.rs index 7507a6af408b..f9cc0122f5e7 100644 --- a/tests/ui/needless_bool/fixable.rs +++ b/tests/ui/needless_bool/fixable.rs @@ -5,7 +5,7 @@ clippy::no_effect, clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::self_named_constructors, clippy::struct_field_names diff --git a/tests/ui/needless_borrow_pat.fixed b/tests/ui/needless_borrow_pat.fixed index fe966a716df7..507186676c16 100644 --- a/tests/ui/needless_borrow_pat.fixed +++ b/tests/ui/needless_borrow_pat.fixed @@ -1,5 +1,3 @@ -// FIXME: run-rustfix waiting on multi-span suggestions - #![warn(clippy::needless_borrow)] #![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] diff --git a/tests/ui/needless_borrow_pat.rs b/tests/ui/needless_borrow_pat.rs index a6b43855cad1..ef0f97301bcf 100644 --- a/tests/ui/needless_borrow_pat.rs +++ b/tests/ui/needless_borrow_pat.rs @@ -1,5 +1,3 @@ -// FIXME: run-rustfix waiting on multi-span suggestions - #![warn(clippy::needless_borrow)] #![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] diff --git a/tests/ui/needless_borrow_pat.stderr b/tests/ui/needless_borrow_pat.stderr index 25c570eb7ff7..34f167cca223 100644 --- a/tests/ui/needless_borrow_pat.stderr +++ b/tests/ui/needless_borrow_pat.stderr @@ -1,5 +1,5 @@ error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:59:14 + --> tests/ui/needless_borrow_pat.rs:57:14 | LL | Some(ref x) => x, | ^^^^^ help: try: `x` @@ -8,7 +8,7 @@ LL | Some(ref x) => x, = help: to override `-D warnings` add `#[allow(clippy::needless_borrow)]` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:66:14 + --> tests/ui/needless_borrow_pat.rs:64:14 | LL | Some(ref x) => *x, | ^^^^^ @@ -20,7 +20,7 @@ LL + Some(x) => x, | error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:73:14 + --> tests/ui/needless_borrow_pat.rs:71:14 | LL | Some(ref x) => { | ^^^^^ @@ -35,19 +35,19 @@ LL ~ f1(x); | error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:85:14 + --> tests/ui/needless_borrow_pat.rs:83:14 | LL | Some(ref x) => m1!(x), | ^^^^^ help: try: `x` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:91:15 + --> tests/ui/needless_borrow_pat.rs:89:15 | LL | let _ = |&ref x: &&String| { | ^^^^^ help: try: `x` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:98:10 + --> tests/ui/needless_borrow_pat.rs:96:10 | LL | let (ref y,) = (&x,); | ^^^^^ @@ -61,13 +61,13 @@ LL ~ let _: &String = y; | error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:110:14 + --> tests/ui/needless_borrow_pat.rs:108:14 | LL | Some(ref x) => x.0, | ^^^^^ help: try: `x` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:121:14 + --> tests/ui/needless_borrow_pat.rs:119:14 | LL | E::A(ref x) | E::B(ref x) => *x, | ^^^^^ ^^^^^ @@ -79,13 +79,13 @@ LL + E::A(x) | E::B(x) => x, | error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:128:21 + --> tests/ui/needless_borrow_pat.rs:126:21 | LL | if let Some(ref x) = Some(&String::new()); | ^^^^^ help: try: `x` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:138:12 + --> tests/ui/needless_borrow_pat.rs:136:12 | LL | fn f2<'a>(&ref x: &&'a String) -> &'a String { | ^^^^^ @@ -100,13 +100,13 @@ LL ~ x | error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:147:11 + --> tests/ui/needless_borrow_pat.rs:145:11 | LL | fn f(&ref x: &&String) { | ^^^^^ help: try: `x` error: this pattern creates a reference to a reference - --> tests/ui/needless_borrow_pat.rs:157:11 + --> tests/ui/needless_borrow_pat.rs:155:11 | LL | fn f(&ref x: &&String) { | ^^^^^ diff --git a/tests/ui/needless_borrowed_ref.fixed b/tests/ui/needless_borrowed_ref.fixed index 84924cac62d5..6d7489921812 100644 --- a/tests/ui/needless_borrowed_ref.fixed +++ b/tests/ui/needless_borrowed_ref.fixed @@ -4,7 +4,7 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_if + clippy::needless_ifs )] fn main() {} diff --git a/tests/ui/needless_borrowed_ref.rs b/tests/ui/needless_borrowed_ref.rs index 280cef43340c..a4cb89923164 100644 --- a/tests/ui/needless_borrowed_ref.rs +++ b/tests/ui/needless_borrowed_ref.rs @@ -4,7 +4,7 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_if + clippy::needless_ifs )] fn main() {} diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index b09efe9888f5..ba1451bf9704 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::suspicious_map, clippy::iter_count, clippy::manual_contains @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().any(|x| x == 1); //~^ needless_collect + + let _ = sample.iter().cloned().nth(1).unwrap(); + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index da4182966bb1..e054cd01e6f5 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::suspicious_map, clippy::iter_count, clippy::manual_contains @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().collect::>().contains(&1); //~^ needless_collect + + let _ = sample.iter().cloned().collect::>()[1]; + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 00745eb2923c..c77674dc55d4 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -20,100 +20,106 @@ LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:27:35 + --> tests/ui/needless_collect.rs:24:36 + | +LL | let _ = sample.iter().cloned().collect::>()[1]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `nth(1).unwrap()` + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:31:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:29:35 + --> tests/ui/needless_collect.rs:33:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:37:19 + --> tests/ui/needless_collect.rs:41:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:39:19 + --> tests/ui/needless_collect.rs:43:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:41:28 + --> tests/ui/needless_collect.rs:45:28 | LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:43:19 + --> tests/ui/needless_collect.rs:47:19 | LL | sample.iter().collect::>().contains(&&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:47:19 + --> tests/ui/needless_collect.rs:51:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:49:19 + --> tests/ui/needless_collect.rs:53:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:55:27 + --> tests/ui/needless_collect.rs:59:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:57:27 + --> tests/ui/needless_collect.rs:61:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:80:27 + --> tests/ui/needless_collect.rs:84:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:82:27 + --> tests/ui/needless_collect.rs:86:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:87:40 + --> tests/ui/needless_collect.rs:91:40 | LL | Vec::::new().extend((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:89:20 + --> tests/ui/needless_collect.rs:93:20 | LL | foo((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:91:49 + --> tests/ui/needless_collect.rs:95:49 | LL | bar((0..10).collect::>(), (0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:93:37 + --> tests/ui/needless_collect.rs:97:37 | LL | baz((0..10), (), ('a'..='z').collect::>()) | ^^^^^^^^^^^^^^^^^^^^ help: remove this call -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index fff6d2f34b8e..69764becfe66 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -1,4 +1,4 @@ -#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_if)] +#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_ifs)] #![warn(clippy::needless_collect)] //@no-rustfix use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; diff --git a/tests/ui/needless_continue.rs b/tests/ui/needless_continue.rs index e873db6dee14..88b7905e7fa8 100644 --- a/tests/ui/needless_continue.rs +++ b/tests/ui/needless_continue.rs @@ -244,3 +244,101 @@ mod issue_4077 { true } } + +#[allow(clippy::let_unit_value)] +mod issue14550 { + fn match_with_value(mut producer: impl Iterator>) -> Result { + let mut counter = 2; + loop { + match producer.next().unwrap() { + Ok(ok) => break Ok((ok + 1) as u32), + Err(12) => { + counter -= 1; + continue; + }, + err => err?, + }; + } + } + + fn inside_macro() { + macro_rules! mac { + ($e:expr => $($rest:tt);*) => { + loop { + match $e { + 1 => continue, + 2 => break, + n => println!("{n}"), + } + $($rest;)* + } + }; + } + + mac!(2 => ); + mac!(1 => {println!("foobar")}); + } + + mod partially_inside_macro { + macro_rules! select { + ( + $expr:expr, + $( $pat:pat => $then:expr ),* + ) => { + fn foo() { + loop { + match $expr { + $( + $pat => $then, + )* + } + } + } + }; + } + + select!(Some(1), + Some(1) => { + println!("one"); + continue; + }, + Some(2) => {}, + None => break, + _ => () + ); + + macro_rules! choose { + ( + $expr:expr, + $case:expr + ) => { + fn bar() { + loop { + match $expr { + $case => { + println!("matched"); + continue; + }, + _ => { + println!("not matched"); + break; + }, + } + } + } + }; + } + + choose!(todo!(), 5); + } +} + +fn issue15548() { + loop { + if todo!() { + } else { + //~^ needless_continue + continue; + } + } +} diff --git a/tests/ui/needless_continue.stderr b/tests/ui/needless_continue.stderr index 878c1e731e32..7a65872c85cd 100644 --- a/tests/ui/needless_continue.stderr +++ b/tests/ui/needless_continue.stderr @@ -220,5 +220,21 @@ LL | | } do_something(); } -error: aborting due to 15 previous errors +error: this `else` block is redundant + --> tests/ui/needless_continue.rs:339:16 + | +LL | } else { + | ________________^ +LL | | +LL | | continue; +LL | | } + | |_________^ + | + = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block + if todo!() { + // merged code follows: + + } + +error: aborting due to 16 previous errors diff --git a/tests/ui/needless_doc_main.rs b/tests/ui/needless_doc_main.rs index 8894ab0285d4..afecd4b47f5e 100644 --- a/tests/ui/needless_doc_main.rs +++ b/tests/ui/needless_doc_main.rs @@ -5,7 +5,7 @@ /// This should lint /// ``` /// fn main() { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -13,7 +13,7 @@ /// With an explicit return type it should lint too /// ```edition2015 /// fn main() -> () { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -21,7 +21,7 @@ /// This should, too. /// ```rust /// fn main() { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -29,8 +29,8 @@ /// This one too. /// ```no_run /// // the fn is not always the first line -//~^ ERROR: needless `fn main` in doctest /// fn main() { +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -38,12 +38,7 @@ fn bad_doctests() {} /// # Examples /// -/// This shouldn't lint, because the `main` is empty: -/// ``` -/// fn main(){} -/// ``` -/// -/// This shouldn't lint either, because main is async: +/// This shouldn't lint because main is async: /// ```edition2018 /// async fn main() { /// assert_eq!(42, ANSWER); diff --git a/tests/ui/needless_doc_main.stderr b/tests/ui/needless_doc_main.stderr index 9ba2ad306dac..79763b0f2248 100644 --- a/tests/ui/needless_doc_main.stderr +++ b/tests/ui/needless_doc_main.stderr @@ -1,12 +1,8 @@ error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:7:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ | = note: `-D clippy::needless-doctest-main` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]` @@ -14,33 +10,20 @@ LL | | /// } error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:15:5 | -LL | /// fn main() -> () { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() -> () { + | ^^^^^^^ error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:23:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ error: needless `fn main` in doctest - --> tests/ui/needless_doc_main.rs:31:5 + --> tests/ui/needless_doc_main.rs:32:5 | -LL | /// // the fn is not always the first line - | _____^ -LL | | -LL | | /// fn main() { -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/ui/needless_if.fixed b/tests/ui/needless_ifs.fixed similarity index 82% rename from tests/ui/needless_if.fixed rename to tests/ui/needless_ifs.fixed index c839156bed9b..0e0b0fa39c9b 100644 --- a/tests/ui/needless_if.fixed +++ b/tests/ui/needless_ifs.fixed @@ -12,7 +12,7 @@ clippy::redundant_pattern_matching, unused )] -#![warn(clippy::needless_if)] +#![warn(clippy::needless_ifs)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -24,16 +24,16 @@ fn maybe_side_effect() -> bool { fn main() { // Lint - //~^ needless_if + //~^ needless_ifs // Do not remove the condition maybe_side_effect(); - //~^ needless_if + //~^ needless_ifs // Do not lint if (true) { } else { } ({ - //~^ needless_if + //~^ needless_ifs return; }); // Do not lint if `else if` is present @@ -48,7 +48,7 @@ fn main() { if true && let true = true {} // Can lint nested `if let`s ({ - //~^ needless_if + //~^ needless_ifs if let true = true && true { @@ -92,15 +92,24 @@ fn main() { // Must be placed into an expression context to not be interpreted as a block ({ maybe_side_effect() }); - //~^ needless_if + //~^ needless_ifs // Would be a block followed by `&&true` - a double reference to `true` ({ maybe_side_effect() } && true); - //~^ needless_if + //~^ needless_ifs // Don't leave trailing attributes #[allow(unused)] true; - //~^ needless_if + //~^ needless_ifs let () = if maybe_side_effect() {}; } + +fn issue15960() -> i32 { + matches!(2, 3); + //~^ needless_ifs + matches!(2, 3) == (2 * 2 == 5); + //~^ needless_ifs + + 1 // put something here so that `if` is a statement not an expression +} diff --git a/tests/ui/needless_if.rs b/tests/ui/needless_ifs.rs similarity index 82% rename from tests/ui/needless_if.rs rename to tests/ui/needless_ifs.rs index 11103af5c559..fb0ee5c9cc83 100644 --- a/tests/ui/needless_if.rs +++ b/tests/ui/needless_ifs.rs @@ -12,7 +12,7 @@ clippy::redundant_pattern_matching, unused )] -#![warn(clippy::needless_if)] +#![warn(clippy::needless_ifs)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -24,16 +24,16 @@ fn maybe_side_effect() -> bool { fn main() { // Lint if (true) {} - //~^ needless_if + //~^ needless_ifs // Do not remove the condition if maybe_side_effect() {} - //~^ needless_if + //~^ needless_ifs // Do not lint if (true) { } else { } if { - //~^ needless_if + //~^ needless_ifs return; } {} // Do not lint if `else if` is present @@ -48,7 +48,7 @@ fn main() { if true && let true = true {} // Can lint nested `if let`s if { - //~^ needless_if + //~^ needless_ifs if let true = true && true { @@ -93,15 +93,24 @@ fn main() { // Must be placed into an expression context to not be interpreted as a block if { maybe_side_effect() } {} - //~^ needless_if + //~^ needless_ifs // Would be a block followed by `&&true` - a double reference to `true` if { maybe_side_effect() } && true {} - //~^ needless_if + //~^ needless_ifs // Don't leave trailing attributes #[allow(unused)] if true {} - //~^ needless_if + //~^ needless_ifs let () = if maybe_side_effect() {}; } + +fn issue15960() -> i32 { + if matches!(2, 3) {} + //~^ needless_ifs + if matches!(2, 3) == (2 * 2 == 5) {} + //~^ needless_ifs + + 1 // put something here so that `if` is a statement not an expression +} diff --git a/tests/ui/needless_if.stderr b/tests/ui/needless_ifs.stderr similarity index 64% rename from tests/ui/needless_if.stderr rename to tests/ui/needless_ifs.stderr index 4b56843bd522..8684ec217a0a 100644 --- a/tests/ui/needless_if.stderr +++ b/tests/ui/needless_ifs.stderr @@ -1,20 +1,20 @@ error: this `if` branch is empty - --> tests/ui/needless_if.rs:26:5 + --> tests/ui/needless_ifs.rs:26:5 | LL | if (true) {} | ^^^^^^^^^^^^ help: you can remove it | - = note: `-D clippy::needless-if` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::needless_if)]` + = note: `-D clippy::needless-ifs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_ifs)]` error: this `if` branch is empty - --> tests/ui/needless_if.rs:29:5 + --> tests/ui/needless_ifs.rs:29:5 | LL | if maybe_side_effect() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();` error: this `if` branch is empty - --> tests/ui/needless_if.rs:35:5 + --> tests/ui/needless_ifs.rs:35:5 | LL | / if { LL | | @@ -31,7 +31,7 @@ LL + }); | error: this `if` branch is empty - --> tests/ui/needless_if.rs:50:5 + --> tests/ui/needless_ifs.rs:50:5 | LL | / if { LL | | @@ -57,22 +57,34 @@ LL + } && true); | error: this `if` branch is empty - --> tests/ui/needless_if.rs:95:5 + --> tests/ui/needless_ifs.rs:95:5 | LL | if { maybe_side_effect() } {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });` error: this `if` branch is empty - --> tests/ui/needless_if.rs:98:5 + --> tests/ui/needless_ifs.rs:98:5 | LL | if { maybe_side_effect() } && true {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);` error: this `if` branch is empty - --> tests/ui/needless_if.rs:103:5 + --> tests/ui/needless_ifs.rs:103:5 | LL | if true {} | ^^^^^^^^^^ help: you can remove it: `true;` -error: aborting due to 7 previous errors +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:110:5 + | +LL | if matches!(2, 3) {} + | ^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3);` + +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:112:5 + | +LL | if matches!(2, 3) == (2 * 2 == 5) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3) == (2 * 2 == 5);` + +error: aborting due to 9 previous errors diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index 41acf44023f6..57273012a192 100644 --- a/tests/ui/needless_match.fixed +++ b/tests/ui/needless_match.fixed @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs index 936653b961bb..3eb577868f2b 100644 --- a/tests/ui/needless_match.rs +++ b/tests/ui/needless_match.rs @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index d571b97f5194..f5f8bb21e815 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -517,3 +517,10 @@ mod else_ifs { } } } + +fn issue14474() -> u64 { + return 456; + + #[cfg(false)] + 123 +} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 2e4348ea338c..495516c1c2e5 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -526,3 +526,10 @@ mod else_ifs { } } } + +fn issue14474() -> u64 { + return 456; + + #[cfg(false)] + 123 +} diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 48d4b8ad1519..9769ee0c3a37 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -1,12 +1,9 @@ #![feature(try_blocks)] -#![allow( - clippy::eq_op, - clippy::single_match, - unused_assignments, - unused_variables, - clippy::while_immutable_condition -)] +#![expect(clippy::eq_op, clippy::single_match, clippy::while_immutable_condition)] //@no-rustfix + +use std::arch::asm; + fn test1() { let mut x = 0; loop { @@ -498,3 +495,54 @@ fn issue15059() { () } } + +fn issue15350() { + 'bar: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + break 'bar; + } + } + + 'foo: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + loop { + //~^ never_loop + println!("This will still run"); + break 'foo; + } + } + } +} + +fn issue15673() { + loop { + unsafe { + // No lint since we don't analyze the inside of the asm + asm! { + "/* {} */", + label { + break; + } + } + } + } + + //~v never_loop + loop { + unsafe { + asm! { + "/* {} */", + label { + break; + } + } + } + return; + } +} diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index 54b463266a3a..49392d971ee3 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -1,5 +1,5 @@ error: this loop never actually loops - --> tests/ui/never_loop.rs:12:5 + --> tests/ui/never_loop.rs:9:5 | LL | / loop { ... | @@ -10,7 +10,7 @@ LL | | } = note: `#[deny(clippy::never_loop)]` on by default error: this loop never actually loops - --> tests/ui/never_loop.rs:36:5 + --> tests/ui/never_loop.rs:33:5 | LL | / loop { ... | @@ -19,7 +19,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:58:5 + --> tests/ui/never_loop.rs:55:5 | LL | / loop { ... | @@ -28,7 +28,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:62:9 + --> tests/ui/never_loop.rs:59:9 | LL | / while i == 0 { ... | @@ -36,7 +36,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:76:9 + --> tests/ui/never_loop.rs:73:9 | LL | / loop { ... | @@ -45,7 +45,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:114:5 + --> tests/ui/never_loop.rs:111:5 | LL | / while let Some(y) = x { ... | @@ -53,7 +53,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:123:5 + --> tests/ui/never_loop.rs:120:5 | LL | / for x in 0..10 { ... | @@ -67,7 +67,7 @@ LL + if let Some(x) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:173:5 + --> tests/ui/never_loop.rs:170:5 | LL | / 'outer: while a { ... | @@ -76,7 +76,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:190:9 + --> tests/ui/never_loop.rs:187:9 | LL | / while false { LL | | @@ -86,7 +86,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:243:13 + --> tests/ui/never_loop.rs:240:13 | LL | let _ = loop { | _____________^ @@ -99,7 +99,7 @@ LL | | }; | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:266:5 + --> tests/ui/never_loop.rs:263:5 | LL | / 'a: loop { LL | | @@ -110,7 +110,7 @@ LL | | } | |_____^ error: sub-expression diverges - --> tests/ui/never_loop.rs:271:17 + --> tests/ui/never_loop.rs:268:17 | LL | break 'a; | ^^^^^^^^ @@ -119,7 +119,7 @@ LL | break 'a; = help: to override `-D warnings` add `#[allow(clippy::diverging_sub_expression)]` error: this loop never actually loops - --> tests/ui/never_loop.rs:303:13 + --> tests/ui/never_loop.rs:300:13 | LL | / for _ in 0..20 { LL | | @@ -135,7 +135,7 @@ LL + if let Some(_) = (0..20).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:388:13 + --> tests/ui/never_loop.rs:385:13 | LL | / 'c: loop { LL | | @@ -145,7 +145,7 @@ LL | | } | |_____________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:400:5 + --> tests/ui/never_loop.rs:397:5 | LL | / loop { LL | | @@ -155,7 +155,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:405:5 + --> tests/ui/never_loop.rs:402:5 | LL | / loop { LL | | @@ -165,7 +165,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:426:5 + --> tests/ui/never_loop.rs:423:5 | LL | / for v in 0..10 { LL | | @@ -183,7 +183,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:434:5 + --> tests/ui/never_loop.rs:431:5 | LL | / 'outer: for v in 0..10 { LL | | @@ -193,6 +193,19 @@ LL | | return; LL | | } | |_____^ | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:433:9 + | +LL | / loop { +LL | | +LL | | break 'outer; +LL | | } + | |_________^ +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:437:9 + | +LL | return; + | ^^^^^^^ help: if you need the first element of the iterator, try writing | LL - 'outer: for v in 0..10 { @@ -200,7 +213,7 @@ LL + if let Some(v) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:436:9 + --> tests/ui/never_loop.rs:433:9 | LL | / loop { LL | | @@ -209,7 +222,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:443:5 + --> tests/ui/never_loop.rs:440:5 | LL | / for v in 0..10 { LL | | @@ -226,7 +239,7 @@ LL + if let Some(v) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:445:9 + --> tests/ui/never_loop.rs:442:9 | LL | / 'inner: loop { LL | | @@ -235,7 +248,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:471:5 + --> tests/ui/never_loop.rs:468:5 | LL | / 'a: for _ in 0..1 { LL | | @@ -251,7 +264,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:477:5 + --> tests/ui/never_loop.rs:474:5 | LL | / 'a: for i in 0..1 { LL | | @@ -275,7 +288,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:492:5 + --> tests/ui/never_loop.rs:489:5 | LL | / for v in 0..10 { LL | | @@ -297,5 +310,99 @@ LL ~ LL ~ | -error: aborting due to 24 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:500:5 + | +LL | / 'bar: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:502:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'bar: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:502:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:509:5 + | +LL | / 'foo: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:511:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'foo: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:511:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:514:13 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'foo; +LL | | } + | |_____________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:537:5 + | +LL | / loop { +LL | | unsafe { +LL | | asm! { +LL | | "/* {} */", +... | +LL | | return; +LL | | } + | |_____^ + +error: aborting due to 30 previous errors diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index 277c335cd885..9a5e90b48065 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -1,5 +1,4 @@ #![allow( - dead_code, clippy::missing_safety_doc, clippy::extra_unused_lifetimes, clippy::extra_unused_type_parameters, @@ -322,3 +321,91 @@ where Self { _kv: None } } } + +// From issue #14552, but with `#[cfg]`s that are actually `true` in the uitest context + +pub struct NewWithCfg; +#[cfg(not(test))] +impl Default for NewWithCfg { + fn default() -> Self { + Self::new() + } +} + +impl NewWithCfg { + #[cfg(not(test))] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWith2Cfgs; +#[cfg(not(test))] +#[cfg(panic = "unwind")] +impl Default for NewWith2Cfgs { + fn default() -> Self { + Self::new() + } +} + +impl NewWith2Cfgs { + #[cfg(not(test))] + #[cfg(panic = "unwind")] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithExtraneous; +impl Default for NewWithExtraneous { + fn default() -> Self { + Self::new() + } +} + +impl NewWithExtraneous { + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithCfgAndExtraneous; +#[cfg(not(test))] +impl Default for NewWithCfgAndExtraneous { + fn default() -> Self { + Self::new() + } +} + +impl NewWithCfgAndExtraneous { + #[cfg(not(test))] + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +mod issue15778 { + pub struct Foo(Vec); + + impl Foo { + pub fn new() -> Self { + Self(Vec::new()) + } + } + + impl<'a> IntoIterator for &'a Foo { + type Item = &'a i32; + + type IntoIter = std::slice::Iter<'a, i32>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().iter() + } + } +} diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index f2844897c93d..f7466aa32189 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -1,5 +1,4 @@ #![allow( - dead_code, clippy::missing_safety_doc, clippy::extra_unused_lifetimes, clippy::extra_unused_type_parameters, @@ -265,3 +264,63 @@ where Self { _kv: None } } } + +// From issue #14552, but with `#[cfg]`s that are actually `true` in the uitest context + +pub struct NewWithCfg; +impl NewWithCfg { + #[cfg(not(test))] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWith2Cfgs; +impl NewWith2Cfgs { + #[cfg(not(test))] + #[cfg(panic = "unwind")] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithExtraneous; +impl NewWithExtraneous { + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithCfgAndExtraneous; +impl NewWithCfgAndExtraneous { + #[cfg(not(test))] + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +mod issue15778 { + pub struct Foo(Vec); + + impl Foo { + pub fn new() -> Self { + Self(Vec::new()) + } + } + + impl<'a> IntoIterator for &'a Foo { + type Item = &'a i32; + + type IntoIter = std::slice::Iter<'a, i32>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().iter() + } + } +} diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 70a65aba464b..1e0d5e213199 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -1,5 +1,5 @@ error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:13:5 + --> tests/ui/new_without_default.rs:12:5 | LL | / pub fn new() -> Foo { LL | | @@ -20,7 +20,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Bar` - --> tests/ui/new_without_default.rs:23:5 + --> tests/ui/new_without_default.rs:22:5 | LL | / pub fn new() -> Self { LL | | @@ -39,7 +39,7 @@ LL + } | error: you should consider adding a `Default` implementation for `LtKo<'c>` - --> tests/ui/new_without_default.rs:89:5 + --> tests/ui/new_without_default.rs:88:5 | LL | / pub fn new() -> LtKo<'c> { LL | | @@ -58,7 +58,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Const` - --> tests/ui/new_without_default.rs:123:5 + --> tests/ui/new_without_default.rs:122:5 | LL | / pub const fn new() -> Const { LL | | @@ -76,7 +76,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> tests/ui/new_without_default.rs:184:5 + --> tests/ui/new_without_default.rs:183:5 | LL | / pub fn new() -> Self { LL | | @@ -95,7 +95,7 @@ LL + } | error: you should consider adding a `Default` implementation for `FooGenerics` - --> tests/ui/new_without_default.rs:194:5 + --> tests/ui/new_without_default.rs:193:5 | LL | / pub fn new() -> Self { LL | | @@ -114,7 +114,7 @@ LL + } | error: you should consider adding a `Default` implementation for `BarGenerics` - --> tests/ui/new_without_default.rs:203:5 + --> tests/ui/new_without_default.rs:202:5 | LL | / pub fn new() -> Self { LL | | @@ -133,7 +133,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:216:9 + --> tests/ui/new_without_default.rs:215:9 | LL | / pub fn new() -> Self { LL | | @@ -154,7 +154,7 @@ LL ~ impl Foo { | error: you should consider adding a `Default` implementation for `MyStruct` - --> tests/ui/new_without_default.rs:263:5 + --> tests/ui/new_without_default.rs:262:5 | LL | / pub fn new() -> Self { LL | | @@ -174,5 +174,84 @@ LL + } LL + } | -error: aborting due to 9 previous errors +error: you should consider adding a `Default` implementation for `NewWithCfg` + --> tests/ui/new_without_default.rs:273:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + impl Default for NewWithCfg { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWithCfg { + | + +error: you should consider adding a `Default` implementation for `NewWith2Cfgs` + --> tests/ui/new_without_default.rs:283:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + #[cfg(panic = "unwind")] +LL + impl Default for NewWith2Cfgs { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWith2Cfgs { + | + +error: you should consider adding a `Default` implementation for `NewWithExtraneous` + --> tests/ui/new_without_default.rs:292:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + impl Default for NewWithExtraneous { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } + | + +error: you should consider adding a `Default` implementation for `NewWithCfgAndExtraneous` + --> tests/ui/new_without_default.rs:302:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + impl Default for NewWithCfgAndExtraneous { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWithCfgAndExtraneous { + | + +error: aborting due to 13 previous errors diff --git a/tests/ui/non_canonical_clone_impl.fixed b/tests/ui/non_canonical_clone_impl.fixed index 11616f28825b..d9be98de69b1 100644 --- a/tests/ui/non_canonical_clone_impl.fixed +++ b/tests/ui/non_canonical_clone_impl.fixed @@ -4,7 +4,7 @@ #![no_main] extern crate proc_macros; -use proc_macros::with_span; +use proc_macros::inline_macros; // lint @@ -100,18 +100,45 @@ impl Clone for Uwu { impl Copy for Uwu {} -// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788 -#[derive(proc_macro_derive::NonCanonicalClone)] -pub struct G; +#[inline_macros] +mod issue12788 { + use proc_macros::{external, with_span}; -with_span!( - span + // lint -- not an external macro + inline!( + #[derive(Copy)] + pub struct A; - #[derive(Copy)] - struct H; - impl Clone for H { - fn clone(&self) -> Self { - todo!() + impl Clone for A { + fn clone(&self) -> Self { *self } } - } -); + ); + + // do not lint -- should skip external macros + external!( + #[derive(Copy)] + pub struct B; + + impl Clone for B { + fn clone(&self) -> Self { + todo!() + } + } + ); + + // do not lint -- should skip proc macros + #[derive(proc_macro_derive::NonCanonicalClone)] + pub struct C; + + with_span!( + span + + #[derive(Copy)] + struct D; + impl Clone for D { + fn clone(&self) -> Self { + todo!() + } + } + ); +} diff --git a/tests/ui/non_canonical_clone_impl.rs b/tests/ui/non_canonical_clone_impl.rs index 7d101915517f..3db22bdbcf31 100644 --- a/tests/ui/non_canonical_clone_impl.rs +++ b/tests/ui/non_canonical_clone_impl.rs @@ -4,7 +4,7 @@ #![no_main] extern crate proc_macros; -use proc_macros::with_span; +use proc_macros::inline_macros; // lint @@ -114,18 +114,48 @@ impl Clone for Uwu { impl Copy for Uwu {} -// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788 -#[derive(proc_macro_derive::NonCanonicalClone)] -pub struct G; +#[inline_macros] +mod issue12788 { + use proc_macros::{external, with_span}; -with_span!( - span + // lint -- not an external macro + inline!( + #[derive(Copy)] + pub struct A; - #[derive(Copy)] - struct H; - impl Clone for H { - fn clone(&self) -> Self { - todo!() + impl Clone for A { + fn clone(&self) -> Self { + //~^ non_canonical_clone_impl + todo!() + } } - } -); + ); + + // do not lint -- should skip external macros + external!( + #[derive(Copy)] + pub struct B; + + impl Clone for B { + fn clone(&self) -> Self { + todo!() + } + } + ); + + // do not lint -- should skip proc macros + #[derive(proc_macro_derive::NonCanonicalClone)] + pub struct C; + + with_span!( + span + + #[derive(Copy)] + struct D; + impl Clone for D { + fn clone(&self) -> Self { + todo!() + } + } + ); +} diff --git a/tests/ui/non_canonical_clone_impl.stderr b/tests/ui/non_canonical_clone_impl.stderr index 550098452717..cf36a8f49f81 100644 --- a/tests/ui/non_canonical_clone_impl.stderr +++ b/tests/ui/non_canonical_clone_impl.stderr @@ -41,5 +41,17 @@ LL | | *self = source.clone(); LL | | } | |_____^ help: remove it -error: aborting due to 4 previous errors +error: non-canonical implementation of `clone` on a `Copy` type + --> tests/ui/non_canonical_clone_impl.rs:127:37 + | +LL | fn clone(&self) -> Self { + | _____________________________________^ +LL | | +LL | | todo!() +LL | | } + | |_____________^ help: change this to: `{ *self }` + | + = note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors diff --git a/tests/ui/non_canonical_partial_ord_impl.fixed b/tests/ui/non_canonical_partial_ord_impl.fixed index 6915e1984fb1..aa23fd99ad77 100644 --- a/tests/ui/non_canonical_partial_ord_impl.fixed +++ b/tests/ui/non_canonical_partial_ord_impl.fixed @@ -1,5 +1,9 @@ +//@aux-build:proc_macro_derive.rs #![no_main] +extern crate proc_macros; +use proc_macros::inline_macros; + use std::cmp::Ordering; // lint @@ -163,6 +167,73 @@ impl PartialOrd for I { } } +#[inline_macros] +mod issue12788 { + use std::cmp::Ordering; + + use proc_macros::{external, with_span}; + + // lint -- not an external macro + inline!( + #[derive(PartialEq, Eq)] + pub struct A; + + impl Ord for A { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for A { + //~^ non_canonical_partial_ord_impl + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } + } + ); + + // do not lint -- should skip external macros + external!( + #[derive(PartialEq, Eq)] + pub struct B; + + impl Ord for B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for B { + fn partial_cmp(&self, other: &Self) -> Option { + todo!(); + } + } + + ); + + // do not lint -- should skip proc macros + #[derive(proc_macro_derive::NonCanonicalClone)] + pub struct C; + + with_span!( + span + + #[derive(PartialEq, Eq)] + pub struct D; + + impl Ord for D { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for D { + fn partial_cmp(&self, other: &Self) -> Option { + todo!(); + } + } + + ); +} + // #13640, do not lint #[derive(Eq, PartialEq)] diff --git a/tests/ui/non_canonical_partial_ord_impl.rs b/tests/ui/non_canonical_partial_ord_impl.rs index 7ce4cdc9aec8..da7f73f7c4be 100644 --- a/tests/ui/non_canonical_partial_ord_impl.rs +++ b/tests/ui/non_canonical_partial_ord_impl.rs @@ -1,5 +1,9 @@ +//@aux-build:proc_macro_derive.rs #![no_main] +extern crate proc_macros; +use proc_macros::inline_macros; + use std::cmp::Ordering; // lint @@ -167,6 +171,75 @@ impl PartialOrd for I { } } +#[inline_macros] +mod issue12788 { + use std::cmp::Ordering; + + use proc_macros::{external, with_span}; + + // lint -- not an external macro + inline!( + #[derive(PartialEq, Eq)] + pub struct A; + + impl Ord for A { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for A { + //~^ non_canonical_partial_ord_impl + fn partial_cmp(&self, other: &Self) -> Option { + todo!(); + } + } + ); + + // do not lint -- should skip external macros + external!( + #[derive(PartialEq, Eq)] + pub struct B; + + impl Ord for B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for B { + fn partial_cmp(&self, other: &Self) -> Option { + todo!(); + } + } + + ); + + // do not lint -- should skip proc macros + #[derive(proc_macro_derive::NonCanonicalClone)] + pub struct C; + + with_span!( + span + + #[derive(PartialEq, Eq)] + pub struct D; + + impl Ord for D { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } + } + + impl PartialOrd for D { + fn partial_cmp(&self, other: &Self) -> Option { + todo!(); + } + } + + ); +} + // #13640, do not lint #[derive(Eq, PartialEq)] diff --git a/tests/ui/non_canonical_partial_ord_impl.stderr b/tests/ui/non_canonical_partial_ord_impl.stderr index 9bd6b1f726df..8e55603dd9da 100644 --- a/tests/ui/non_canonical_partial_ord_impl.stderr +++ b/tests/ui/non_canonical_partial_ord_impl.stderr @@ -1,5 +1,5 @@ error: non-canonical implementation of `partial_cmp` on an `Ord` type - --> tests/ui/non_canonical_partial_ord_impl.rs:16:1 + --> tests/ui/non_canonical_partial_ord_impl.rs:20:1 | LL | / impl PartialOrd for A { LL | | @@ -15,7 +15,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::non_canonical_partial_ord_impl)]` error: non-canonical implementation of `partial_cmp` on an `Ord` type - --> tests/ui/non_canonical_partial_ord_impl.rs:51:1 + --> tests/ui/non_canonical_partial_ord_impl.rs:55:1 | LL | / impl PartialOrd for C { LL | | @@ -32,7 +32,22 @@ LL + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp | error: non-canonical implementation of `partial_cmp` on an `Ord` type - --> tests/ui/non_canonical_partial_ord_impl.rs:198:1 + --> tests/ui/non_canonical_partial_ord_impl.rs:191:9 + | +LL | / impl PartialOrd for A { +LL | | +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________________- +LL | || todo!(); +LL | || } + | ||_____________- help: change this to: `{ Some(self.cmp(other)) }` +LL | | } + | |__________^ + | + = note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: non-canonical implementation of `partial_cmp` on an `Ord` type + --> tests/ui/non_canonical_partial_ord_impl.rs:271:1 | LL | / impl PartialOrd for K { LL | | @@ -45,7 +60,7 @@ LL | | } | |__^ error: non-canonical implementation of `partial_cmp` on an `Ord` type - --> tests/ui/non_canonical_partial_ord_impl.rs:216:1 + --> tests/ui/non_canonical_partial_ord_impl.rs:289:1 | LL | / impl PartialOrd for L { LL | | @@ -57,5 +72,5 @@ LL | || } LL | | } | |__^ -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index f03f74dfafe2..d040ba6ee83c 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -2,7 +2,7 @@ #![allow( unused, clippy::diverging_sub_expression, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_pattern_matching )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index c2377491f25b..f50af147c60c 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -1,4 +1,4 @@ -#![allow(unused, clippy::diverging_sub_expression, clippy::needless_if)] +#![allow(unused, clippy::diverging_sub_expression, clippy::needless_ifs)] #![warn(clippy::nonminimal_bool)] fn methods_with_negation() { diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 1ae0f0064c6b..0ecd4775035b 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -1,4 +1,4 @@ -#![allow(unused, clippy::diverging_sub_expression, clippy::needless_if)] +#![allow(unused, clippy::diverging_sub_expression, clippy::needless_ifs)] #![warn(clippy::nonminimal_bool)] fn methods_with_negation() { diff --git a/tests/ui/only_used_in_recursion.rs b/tests/ui/only_used_in_recursion.rs index 7d6075ba9ea4..d58635969238 100644 --- a/tests/ui/only_used_in_recursion.rs +++ b/tests/ui/only_used_in_recursion.rs @@ -1,4 +1,5 @@ #![warn(clippy::only_used_in_recursion)] +#![warn(clippy::self_only_used_in_recursion)] //@no-rustfix fn _simple(x: u32) -> u32 { x @@ -74,7 +75,7 @@ impl A { } fn _method_self(&self, flag: usize, a: usize) -> usize { - //~^ only_used_in_recursion + //~^ self_only_used_in_recursion //~| only_used_in_recursion if flag == 0 { 0 } else { self._method_self(flag - 1, a) } diff --git a/tests/ui/only_used_in_recursion.stderr b/tests/ui/only_used_in_recursion.stderr index ca08319e1120..5d1e3e9c50fd 100644 --- a/tests/ui/only_used_in_recursion.stderr +++ b/tests/ui/only_used_in_recursion.stderr @@ -1,11 +1,11 @@ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:11:27 + --> tests/ui/only_used_in_recursion.rs:12:27 | LL | fn _one_unused(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:14:53 + --> tests/ui/only_used_in_recursion.rs:15:53 | LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) } | ^ @@ -13,181 +13,183 @@ LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) } = help: to override `-D warnings` add `#[allow(clippy::only_used_in_recursion)]` error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:17:27 + --> tests/ui/only_used_in_recursion.rs:18:27 | LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:21:53 + --> tests/ui/only_used_in_recursion.rs:22:53 | LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:17:35 + --> tests/ui/only_used_in_recursion.rs:18:35 | LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:21:56 + --> tests/ui/only_used_in_recursion.rs:22:56 | LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:24:26 + --> tests/ui/only_used_in_recursion.rs:25:26 | LL | fn _with_calc(flag: u32, a: i64) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:30:32 + --> tests/ui/only_used_in_recursion.rs:31:32 | LL | _with_calc(flag - 1, (-a + 10) * 5) | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:39:33 + --> tests/ui/only_used_in_recursion.rs:40:33 | LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:46:38 + --> tests/ui/only_used_in_recursion.rs:47:38 | LL | _used_with_unused(flag - 1, -a, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:39:41 + --> tests/ui/only_used_in_recursion.rs:40:41 | LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:46:45 + --> tests/ui/only_used_in_recursion.rs:47:45 | LL | _used_with_unused(flag - 1, -a, a + b) | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:50:35 + --> tests/ui/only_used_in_recursion.rs:51:35 | LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:57:39 + --> tests/ui/only_used_in_recursion.rs:58:39 | LL | _codependent_unused(flag - 1, a * b, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:50:43 + --> tests/ui/only_used_in_recursion.rs:51:43 | LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:57:43 + --> tests/ui/only_used_in_recursion.rs:58:43 | LL | _codependent_unused(flag - 1, a * b, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:61:30 + --> tests/ui/only_used_in_recursion.rs:62:30 | LL | fn _not_primitive(flag: u32, b: String) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:64:56 + --> tests/ui/only_used_in_recursion.rs:65:56 | LL | if flag == 0 { 0 } else { _not_primitive(flag - 1, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:70:29 + --> tests/ui/only_used_in_recursion.rs:71:29 | LL | fn _method(flag: usize, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:73:59 + --> tests/ui/only_used_in_recursion.rs:74:59 | LL | if flag == 0 { 0 } else { Self::_method(flag - 1, a) } | ^ -error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:76:22 +error: `self` is only used in recursion + --> tests/ui/only_used_in_recursion.rs:77:22 | LL | fn _method_self(&self, flag: usize, a: usize) -> usize { | ^^^^ | -note: parameter used here - --> tests/ui/only_used_in_recursion.rs:80:35 +note: `self` used here + --> tests/ui/only_used_in_recursion.rs:81:35 | LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } | ^^^^ + = note: `-D clippy::self-only-used-in-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::self_only_used_in_recursion)]` error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:76:41 + --> tests/ui/only_used_in_recursion.rs:77:41 | LL | fn _method_self(&self, flag: usize, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:80:63 + --> tests/ui/only_used_in_recursion.rs:81:63 | LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:90:26 + --> tests/ui/only_used_in_recursion.rs:91:26 | LL | fn method(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:93:58 + --> tests/ui/only_used_in_recursion.rs:94:58 | LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:96:38 + --> tests/ui/only_used_in_recursion.rs:97:38 | LL | fn method_self(&self, flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:99:62 + --> tests/ui/only_used_in_recursion.rs:100:62 | LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:124:26 + --> tests/ui/only_used_in_recursion.rs:125:26 | LL | fn method(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:127:58 + --> tests/ui/only_used_in_recursion.rs:128:58 | LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:130:38 + --> tests/ui/only_used_in_recursion.rs:131:38 | LL | fn method_self(&self, flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:133:62 + --> tests/ui/only_used_in_recursion.rs:134:62 | LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } | ^ diff --git a/tests/ui/op_ref.fixed b/tests/ui/op_ref.fixed index 4bf4b91888c8..fe4a48989b0f 100644 --- a/tests/ui/op_ref.fixed +++ b/tests/ui/op_ref.fixed @@ -111,7 +111,7 @@ mod issue_2597 { } } -#[allow(clippy::needless_if)] +#[allow(clippy::needless_ifs)] fn issue15063() { use std::ops::BitAnd; diff --git a/tests/ui/op_ref.rs b/tests/ui/op_ref.rs index 9a192661aafc..cd0d231497ab 100644 --- a/tests/ui/op_ref.rs +++ b/tests/ui/op_ref.rs @@ -111,7 +111,7 @@ mod issue_2597 { } } -#[allow(clippy::needless_if)] +#[allow(clippy::needless_ifs)] fn issue15063() { use std::ops::BitAnd; diff --git a/tests/ui/option_env_unwrap.stderr b/tests/ui/option_env_unwrap.stderr index bbcbfedb7882..c14a3ea23ff3 100644 --- a/tests/ui/option_env_unwrap.stderr +++ b/tests/ui/option_env_unwrap.stderr @@ -19,7 +19,7 @@ LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set error: this will panic at run-time if the environment variable doesn't exist at compile-time --> tests/ui/option_env_unwrap.rs:14:13 | -LL | let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in your env... +LL | let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in you... | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 0f86de5646cd..a2ecea773efe 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -6,7 +6,8 @@ clippy::redundant_locals, clippy::manual_midpoint, clippy::manual_unwrap_or_default, - clippy::manual_unwrap_or + clippy::manual_unwrap_or, + clippy::unnecessary_option_map_or_else )] fn bad1(string: Option<&str>) -> (bool, &str) { @@ -125,6 +126,16 @@ fn complex_subpat() -> DummyEnum { DummyEnum::Two } +// #10335 +pub fn test_result_err_ignored_1(r: Result<&[u8], &[u8]>) -> Vec { + r.map_or_else(|_| Vec::new(), |s| s.to_owned()) +} + +// #10335 +pub fn test_result_err_ignored_2(r: Result<&[u8], &[u8]>) -> Vec { + r.map_or_else(|_| Vec::new(), |s| s.to_owned()) +} + fn main() { let optional = Some(5); let _ = optional.map_or(5, |x| x + 2); diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 7aabd778f87e..3adbc785237f 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -6,7 +6,8 @@ clippy::redundant_locals, clippy::manual_midpoint, clippy::manual_unwrap_or_default, - clippy::manual_unwrap_or + clippy::manual_unwrap_or, + clippy::unnecessary_option_map_or_else )] fn bad1(string: Option<&str>) -> (bool, &str) { @@ -152,6 +153,22 @@ fn complex_subpat() -> DummyEnum { DummyEnum::Two } +// #10335 +pub fn test_result_err_ignored_1(r: Result<&[u8], &[u8]>) -> Vec { + match r { + //~^ option_if_let_else + Ok(s) => s.to_owned(), + Err(_) => Vec::new(), + } +} + +// #10335 +pub fn test_result_err_ignored_2(r: Result<&[u8], &[u8]>) -> Vec { + if let Ok(s) = r { s.to_owned() } + //~^ option_if_let_else + else { Vec::new() } +} + fn main() { let optional = Some(5); let _ = if let Some(x) = optional { x + 2 } else { 5 }; diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 2e2fe6f20492..f5578f63c946 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -1,5 +1,5 @@ error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:13:5 + --> tests/ui/option_if_let_else.rs:14:5 | LL | / if let Some(x) = string { LL | | @@ -13,19 +13,19 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::option_if_let_else)]` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:32:13 + --> tests/ui/option_if_let_else.rs:33:13 | LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:34:13 + --> tests/ui/option_if_let_else.rs:35:13 | LL | let _ = if let Some(s) = &num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:36:13 + --> tests/ui/option_if_let_else.rs:37:13 | LL | let _ = if let Some(s) = &mut num { | _____________^ @@ -47,13 +47,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:43:13 + --> tests/ui/option_if_let_else.rs:44:13 | LL | let _ = if let Some(ref s) = num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:45:13 + --> tests/ui/option_if_let_else.rs:46:13 | LL | let _ = if let Some(mut s) = num { | _____________^ @@ -75,7 +75,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:52:13 + --> tests/ui/option_if_let_else.rs:53:13 | LL | let _ = if let Some(ref mut s) = num { | _____________^ @@ -97,7 +97,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:62:5 + --> tests/ui/option_if_let_else.rs:63:5 | LL | / if let Some(x) = arg { LL | | @@ -118,7 +118,7 @@ LL + }) | error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:76:13 + --> tests/ui/option_if_let_else.rs:77:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -131,7 +131,7 @@ LL | | }; | |_____^ help: try: `arg.map_or_else(side_effect, |x| x)` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:86:13 + --> tests/ui/option_if_let_else.rs:87:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -154,7 +154,7 @@ LL ~ }, |x| x * x * x * x); | error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:120:13 + --> tests/ui/option_if_let_else.rs:121:13 | LL | / if let Some(idx) = s.find('.') { LL | | @@ -165,7 +165,7 @@ LL | | } | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:132:5 + --> tests/ui/option_if_let_else.rs:133:5 | LL | / if let Ok(binding) = variable { LL | | @@ -188,14 +188,32 @@ LL + true LL + }) | +error: use Option::map_or_else instead of an if let/else + --> tests/ui/option_if_let_else.rs:158:5 + | +LL | / match r { +LL | | +LL | | Ok(s) => s.to_owned(), +LL | | Err(_) => Vec::new(), +LL | | } + | |_____^ help: try: `r.map_or_else(|_| Vec::new(), |s| s.to_owned())` + +error: use Option::map_or_else instead of an if let/else + --> tests/ui/option_if_let_else.rs:167:5 + | +LL | / if let Ok(s) = r { s.to_owned() } +LL | | +LL | | else { Vec::new() } + | |_______________________^ help: try: `r.map_or_else(|_| Vec::new(), |s| s.to_owned())` + error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:157:13 + --> tests/ui/option_if_let_else.rs:174:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:168:13 + --> tests/ui/option_if_let_else.rs:185:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -217,13 +235,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:197:13 + --> tests/ui/option_if_let_else.rs:214:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:202:13 + --> tests/ui/option_if_let_else.rs:219:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -245,7 +263,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:242:13 + --> tests/ui/option_if_let_else.rs:259:13 | LL | let _ = match s { | _____________^ @@ -256,7 +274,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:247:13 + --> tests/ui/option_if_let_else.rs:264:13 | LL | let _ = match Some(10) { | _____________^ @@ -267,7 +285,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:254:13 + --> tests/ui/option_if_let_else.rs:271:13 | LL | let _ = match res { | _____________^ @@ -278,7 +296,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:259:13 + --> tests/ui/option_if_let_else.rs:276:13 | LL | let _ = match res { | _____________^ @@ -289,13 +307,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:264:13 + --> tests/ui/option_if_let_else.rs:281:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:282:17 + --> tests/ui/option_if_let_else.rs:299:17 | LL | let _ = match initial { | _________________^ @@ -306,7 +324,7 @@ LL | | }; | |_________^ help: try: `initial.as_ref().map_or(42, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:290:17 + --> tests/ui/option_if_let_else.rs:307:17 | LL | let _ = match initial { | _________________^ @@ -317,7 +335,7 @@ LL | | }; | |_________^ help: try: `initial.as_mut().map_or(42, |value| do_something2(value))` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:314:24 + --> tests/ui/option_if_let_else.rs:331:24 | LL | let mut _hashmap = if let Some(hm) = &opt { | ________________________^ @@ -329,19 +347,19 @@ LL | | }; | |_____^ help: try: `opt.as_ref().map_or_else(HashMap::new, |hm| hm.clone())` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:321:19 + --> tests/ui/option_if_let_else.rs:338:19 | LL | let mut _hm = if let Some(hm) = &opt { hm.clone() } else { new_map!() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.as_ref().map_or_else(|| new_map!(), |hm| hm.clone())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:372:22 + --> tests/ui/option_if_let_else.rs:389:22 | LL | let _ = unsafe { if let Some(o) = *opt_raw_ptr { o } else { 1 } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*opt_raw_ptr).map_or(1, |o| o)` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:378:13 + --> tests/ui/option_if_let_else.rs:395:13 | LL | let _ = match res { | _____________^ @@ -351,5 +369,5 @@ LL | | Err(_) => String::new(), LL | | }; | |_____^ help: try: `res.map_or_else(|_| String::new(), |s| s.clone())` -error: aborting due to 27 previous errors +error: aborting due to 29 previous errors diff --git a/tests/ui/option_map_unit_fn_fixable.fixed b/tests/ui/option_map_unit_fn_fixable.fixed index 340be7c7e932..ddba1cd1291f 100644 --- a/tests/ui/option_map_unit_fn_fixable.fixed +++ b/tests/ui/option_map_unit_fn_fixable.fixed @@ -1,6 +1,5 @@ #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)] +#![expect(clippy::unnecessary_wraps)] fn do_nothing(_: T) {} @@ -98,7 +97,7 @@ fn option_map_unit_fn() { if let Some(a) = option() { do_nothing(a) } //~^ option_map_unit_fn - if let Some(value) = option() { println!("{:?}", value) } + if let Some(value) = option() { println!("{value:?}") } //~^ option_map_unit_fn } diff --git a/tests/ui/option_map_unit_fn_fixable.rs b/tests/ui/option_map_unit_fn_fixable.rs index d902c87379b7..7bd1fe92bdb6 100644 --- a/tests/ui/option_map_unit_fn_fixable.rs +++ b/tests/ui/option_map_unit_fn_fixable.rs @@ -1,6 +1,5 @@ #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)] +#![expect(clippy::unnecessary_wraps)] fn do_nothing(_: T) {} @@ -98,7 +97,7 @@ fn option_map_unit_fn() { option().map(do_nothing); //~^ option_map_unit_fn - option().map(|value| println!("{:?}", value)); + option().map(|value| println!("{value:?}")); //~^ option_map_unit_fn } diff --git a/tests/ui/option_map_unit_fn_fixable.stderr b/tests/ui/option_map_unit_fn_fixable.stderr index 2405aa9a7ccf..70b6985406f4 100644 --- a/tests/ui/option_map_unit_fn_fixable.stderr +++ b/tests/ui/option_map_unit_fn_fixable.stderr @@ -1,173 +1,256 @@ error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:37:5 + --> tests/ui/option_map_unit_fn_fixable.rs:36:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::option_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Some(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:40:5 + --> tests/ui/option_map_unit_fn_fixable.rs:39:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Some(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:43:5 + --> tests/ui/option_map_unit_fn_fixable.rs:42:5 | LL | x.field.map(diverge); - | ^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { diverge(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(diverge); +LL + if let Some(x_field) = x.field { diverge(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:50:5 + --> tests/ui/option_map_unit_fn_fixable.rs:49:5 | LL | x.field.map(|value| x.do_option_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { x.do_option_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| x.do_option_nothing(value + captured)); +LL + if let Some(value) = x.field { x.do_option_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:53:5 + --> tests/ui/option_map_unit_fn_fixable.rs:52:5 | LL | x.field.map(|value| { x.do_option_plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { x.do_option_plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { x.do_option_plus_one(value + captured); }); +LL + if let Some(value) = x.field { x.do_option_plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:57:5 + --> tests/ui/option_map_unit_fn_fixable.rs:56:5 | LL | x.field.map(|value| do_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| do_nothing(value + captured)); +LL + if let Some(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:60:5 + --> tests/ui/option_map_unit_fn_fixable.rs:59:5 | LL | x.field.map(|value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured) }); +LL + if let Some(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:63:5 + --> tests/ui/option_map_unit_fn_fixable.rs:62:5 | LL | x.field.map(|value| { do_nothing(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured); }); +LL + if let Some(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:66:5 + --> tests/ui/option_map_unit_fn_fixable.rs:65:5 | LL | x.field.map(|value| { { do_nothing(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { do_nothing(value + captured); } }); +LL + if let Some(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:70:5 + --> tests/ui/option_map_unit_fn_fixable.rs:69:5 | LL | x.field.map(|value| diverge(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| diverge(value + captured)); +LL + if let Some(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:73:5 + --> tests/ui/option_map_unit_fn_fixable.rs:72:5 | LL | x.field.map(|value| { diverge(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured) }); +LL + if let Some(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:76:5 + --> tests/ui/option_map_unit_fn_fixable.rs:75:5 | LL | x.field.map(|value| { diverge(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured); }); +LL + if let Some(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:79:5 + --> tests/ui/option_map_unit_fn_fixable.rs:78:5 | LL | x.field.map(|value| { { diverge(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { diverge(value + captured); } }); +LL + if let Some(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:85:5 + --> tests/ui/option_map_unit_fn_fixable.rs:84:5 | LL | x.field.map(|value| { let y = plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { let y = plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { let y = plus_one(value + captured); }); +LL + if let Some(value) = x.field { let y = plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:88:5 + --> tests/ui/option_map_unit_fn_fixable.rs:87:5 | LL | x.field.map(|value| { plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { plus_one(value + captured); }); +LL + if let Some(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:91:5 + --> tests/ui/option_map_unit_fn_fixable.rs:90:5 | LL | x.field.map(|value| { { plus_one(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { plus_one(value + captured); } }); +LL + if let Some(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:95:5 + --> tests/ui/option_map_unit_fn_fixable.rs:94:5 | LL | x.field.map(|ref value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(ref value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|ref value| { do_nothing(value + captured) }); +LL + if let Some(ref value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:98:5 + --> tests/ui/option_map_unit_fn_fixable.rs:97:5 | LL | option().map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(a) = option() { do_nothing(a) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - option().map(do_nothing); +LL + if let Some(a) = option() { do_nothing(a) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:101:5 + --> tests/ui/option_map_unit_fn_fixable.rs:100:5 + | +LL | option().map(|value| println!("{value:?}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - option().map(|value| println!("{value:?}")); +LL + if let Some(value) = option() { println!("{value:?}") } | -LL | option().map(|value| println!("{:?}", value)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = option() { println!("{:?}", value) }` error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:108:5 + --> tests/ui/option_map_unit_fn_fixable.rs:107:5 | LL | x.map(|x| unsafe { f(x) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x) = x { unsafe { f(x) } }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.map(|x| unsafe { f(x) }); +LL + if let Some(x) = x { unsafe { f(x) } } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:110:5 + --> tests/ui/option_map_unit_fn_fixable.rs:109:5 | LL | x.map(|x| unsafe { { f(x) } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x) = x { unsafe { f(x) } }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.map(|x| unsafe { { f(x) } }); +LL + if let Some(x) = x { unsafe { f(x) } } + | error: aborting due to 21 previous errors diff --git a/tests/ui/option_map_unit_fn_unfixable.rs b/tests/ui/option_map_unit_fn_unfixable.rs index dd2f80fefbee..a6d7ea4a4c8d 100644 --- a/tests/ui/option_map_unit_fn_unfixable.rs +++ b/tests/ui/option_map_unit_fn_unfixable.rs @@ -1,5 +1,6 @@ +//@no-rustfix #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] +#![allow(clippy::unnecessary_wraps, clippy::unnecessary_map_on_constructor)] // only fires before the fix fn do_nothing(_: T) {} @@ -11,14 +12,19 @@ fn plus_one(value: usize) -> usize { value + 1 } +struct HasOption { + field: Option, +} + #[rustfmt::skip] fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; x.field.map(|value| { do_nothing(value); do_nothing(value) }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn // Suggestion for the let block should be `{ ... }` as it's too difficult to build a // proper suggestion for these cases @@ -26,18 +32,22 @@ fn option_map_unit_fn() { do_nothing(value); do_nothing(value) }); - //~^^^^ ERROR: cannot find value + //~^^^^ option_map_unit_fn x.field.map(|value| { do_nothing(value); do_nothing(value); }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn // The following should suggest `if let Some(_X) ...` as it's difficult to generate a proper let variable name for them Some(42).map(diverge); + //~^ option_map_unit_fn "12".parse::().ok().map(diverge); + //~^ option_map_unit_fn Some(plus_one(1)).map(do_nothing); + //~^ option_map_unit_fn // Should suggest `if let Some(_y) ...` to not override the existing foo variable let y = Some(42); y.map(do_nothing); + //~^ option_map_unit_fn } fn main() {} diff --git a/tests/ui/option_map_unit_fn_unfixable.stderr b/tests/ui/option_map_unit_fn_unfixable.stderr index 852785014329..8cc246a38d37 100644 --- a/tests/ui/option_map_unit_fn_unfixable.stderr +++ b/tests/ui/option_map_unit_fn_unfixable.stderr @@ -1,27 +1,106 @@ -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:17:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:23:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::option_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value) }); +LL + if let Some(value) = x.field { ... } + | -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:20:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:26:5 | LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); +LL + if let Some(value) = x.field { ... } + | -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:25:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:31:5 + | +LL | / x.field.map(|value| { +LL | | do_nothing(value); +LL | | do_nothing(value) +LL | | }); + | |______^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { +LL - do_nothing(value); +LL - do_nothing(value) +LL - }); +LL + if let Some(value) = x.field { ... } | -LL | x.field.map(|value| { - | ^ not found in this scope -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:30:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:36:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value); }); +LL + if let Some(value) = x.field { ... } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:40:5 + | +LL | Some(42).map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - Some(42).map(diverge); +LL + if let Some(a) = Some(42) { diverge(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:42:5 + | +LL | "12".parse::().ok().map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - "12".parse::().ok().map(diverge); +LL + if let Some(a) = "12".parse::().ok() { diverge(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:44:5 + | +LL | Some(plus_one(1)).map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - Some(plus_one(1)).map(do_nothing); +LL + if let Some(a) = Some(plus_one(1)) { do_nothing(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:49:5 + | +LL | y.map(do_nothing); + | ^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - y.map(do_nothing); +LL + if let Some(_y) = y { do_nothing(_y) } + | -error: aborting due to 4 previous errors +error: aborting due to 8 previous errors -For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/option_option.rs b/tests/ui/option_option.rs index 42f03aae7bb8..136db902a975 100644 --- a/tests/ui/option_option.rs +++ b/tests/ui/option_option.rs @@ -1,52 +1,52 @@ -#![deny(clippy::option_option)] -#![allow(clippy::unnecessary_wraps, clippy::manual_unwrap_or_default)] +#![warn(clippy::option_option)] +#![expect(clippy::unnecessary_wraps)] const C: Option> = None; -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option static S: Option> = None; -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option fn input(_: Option>) {} -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option fn output() -> Option> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option None } fn output_nested() -> Vec>> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option vec![None] } // The lint only generates one warning for this fn output_nested_nested() -> Option>> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option None } struct Struct { x: Option>, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } impl Struct { fn struct_fn() -> Option> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option None } } trait Trait { fn trait_fn() -> Option>; - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } enum Enum { Tuple(Option>), - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option Struct { x: Option> }, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } // The lint allows this @@ -88,7 +88,7 @@ mod issue_4298 { #[serde(default)] #[serde(borrow)] foo: Option>>, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom + //~^ option_option } #[allow(clippy::option_option)] diff --git a/tests/ui/option_option.stderr b/tests/ui/option_option.stderr index 0cd048e400e4..1c39f9acae8e 100644 --- a/tests/ui/option_option.stderr +++ b/tests/ui/option_option.stderr @@ -1,80 +1,100 @@ -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:4:10 | LL | const C: Option> = None; | ^^^^^^^^^^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/option_option.rs:1:9 - | -LL | #![deny(clippy::option_option)] - | ^^^^^^^^^^^^^^^^^^^^^ + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases + = note: `-D clippy::option-option` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::option_option)]` -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:6:11 | LL | static S: Option> = None; | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:9:13 | LL | fn input(_: Option>) {} | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:12:16 | LL | fn output() -> Option> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:17:27 | LL | fn output_nested() -> Vec>> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:23:30 | LL | fn output_nested_nested() -> Option>> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option>`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:29:8 | LL | x: Option>, | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:34:23 | LL | fn struct_fn() -> Option> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:41:22 | LL | fn trait_fn() -> Option>; | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:46:11 | LL | Tuple(Option>), | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:48:17 | LL | Struct { x: Option> }, | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:90:14 | LL | foo: Option>>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option>`, or a custom enum if you need to distinguish all 3 cases error: aborting due to 12 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 0a8525a12f5e..314da0804a5f 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -1,11 +1,11 @@ #![warn(clippy::or_fun_call)] -#![allow(dead_code)] #![allow( clippy::borrow_as_ptr, clippy::uninlined_format_args, clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap, clippy::unnecessary_result_map_or_else, + clippy::unnecessary_option_map_or_else, clippy::useless_vec )] @@ -77,6 +77,22 @@ fn or_fun_call() { with_default_type.unwrap_or_default(); //~^ unwrap_or_default + let with_default_literal = Some(1); + with_default_literal.unwrap_or(0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some(1.0); + with_default_literal.unwrap_or(0.0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some("foo"); + with_default_literal.unwrap_or(""); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_vec_macro = Some(vec![1, 2, 3]); + with_default_vec_macro.unwrap_or(vec![]); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + let self_default = None::; self_default.unwrap_or_else(::default); //~^ or_fun_call @@ -463,4 +479,19 @@ fn test_result_and() { //~^ or_fun_call } +#[clippy::msrv = "1.15"] +fn below_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or_default(); + //~^ unwrap_or_default + let _ = res.unwrap_or_else(|_| Default::default()); + //~^ or_fun_call +} + +#[clippy::msrv = "1.16"] +fn above_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or_default(); + //~^ unwrap_or_default + let _ = res.unwrap_or_default(); + //~^ unwrap_or_default +} fn main() {} diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index b4f9b950a7fe..2a19614026ec 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -1,11 +1,11 @@ #![warn(clippy::or_fun_call)] -#![allow(dead_code)] #![allow( clippy::borrow_as_ptr, clippy::uninlined_format_args, clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap, clippy::unnecessary_result_map_or_else, + clippy::unnecessary_option_map_or_else, clippy::useless_vec )] @@ -77,6 +77,22 @@ fn or_fun_call() { with_default_type.unwrap_or(u64::default()); //~^ unwrap_or_default + let with_default_literal = Some(1); + with_default_literal.unwrap_or(0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some(1.0); + with_default_literal.unwrap_or(0.0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some("foo"); + with_default_literal.unwrap_or(""); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_vec_macro = Some(vec![1, 2, 3]); + with_default_vec_macro.unwrap_or(vec![]); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + let self_default = None::; self_default.unwrap_or(::default()); //~^ or_fun_call @@ -86,7 +102,7 @@ fn or_fun_call() { //~^ unwrap_or_default let with_vec = Some(vec![1]); - with_vec.unwrap_or(vec![]); + with_vec.unwrap_or(Vec::new()); //~^ unwrap_or_default let without_default = Some(Foo); @@ -98,7 +114,7 @@ fn or_fun_call() { //~^ unwrap_or_default let mut map_vec = HashMap::>::new(); - map_vec.entry(42).or_insert(vec![]); + map_vec.entry(42).or_insert(Vec::new()); //~^ unwrap_or_default let mut btree = BTreeMap::::new(); @@ -106,7 +122,7 @@ fn or_fun_call() { //~^ unwrap_or_default let mut btree_vec = BTreeMap::>::new(); - btree_vec.entry(42).or_insert(vec![]); + btree_vec.entry(42).or_insert(Vec::new()); //~^ unwrap_or_default let stringy = Some(String::new()); @@ -463,4 +479,19 @@ fn test_result_and() { //~^ or_fun_call } +#[clippy::msrv = "1.15"] +fn below_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or(Default::default()); + //~^ unwrap_or_default + let _ = res.unwrap_or(Default::default()); + //~^ or_fun_call +} + +#[clippy::msrv = "1.16"] +fn above_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or(Default::default()); + //~^ unwrap_or_default + let _ = res.unwrap_or(Default::default()); + //~^ unwrap_or_default +} fn main() {} diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 3e4df772668d..3d55f2cd1f9f 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -47,175 +47,175 @@ LL | with_default_type.unwrap_or(u64::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:81:18 + --> tests/ui/or_fun_call.rs:97:18 | LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(::default)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:85:18 + --> tests/ui/or_fun_call.rs:101:18 | LL | real_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:89:14 + --> tests/ui/or_fun_call.rs:105:14 | -LL | with_vec.unwrap_or(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` +LL | with_vec.unwrap_or(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:93:21 + --> tests/ui/or_fun_call.rs:109:21 | LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(Foo::new)` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:97:19 + --> tests/ui/or_fun_call.rs:113:19 | LL | map.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:101:23 + --> tests/ui/or_fun_call.rs:117:23 | -LL | map_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` +LL | map_vec.entry(42).or_insert(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:105:21 + --> tests/ui/or_fun_call.rs:121:21 | LL | btree.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:109:25 + --> tests/ui/or_fun_call.rs:125:25 | -LL | btree_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` +LL | btree_vec.entry(42).or_insert(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:113:21 + --> tests/ui/or_fun_call.rs:129:21 | LL | let _ = stringy.unwrap_or(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `ok_or` - --> tests/ui/or_fun_call.rs:118:17 + --> tests/ui/or_fun_call.rs:134:17 | LL | let _ = opt.ok_or(format!("{} world.", hello)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ok_or_else(|| format!("{} world.", hello))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:123:21 + --> tests/ui/or_fun_call.rs:139:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:126:21 + --> tests/ui/or_fun_call.rs:142:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `or` - --> tests/ui/or_fun_call.rs:151:35 + --> tests/ui/or_fun_call.rs:167:35 | LL | let _ = Some("a".to_string()).or(Some("b".to_string())); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_else(|| Some("b".to_string()))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:194:18 + --> tests/ui/or_fun_call.rs:210:18 | LL | None.unwrap_or(ptr_to_ref(s)); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| ptr_to_ref(s))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:202:14 + --> tests/ui/or_fun_call.rs:218:14 | LL | None.unwrap_or(unsafe { ptr_to_ref(s) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:205:14 + --> tests/ui/or_fun_call.rs:221:14 | LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:281:25 + --> tests/ui/or_fun_call.rs:297:25 | LL | let _ = Some(4).map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(g, |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:283:25 + --> tests/ui/or_fun_call.rs:299:25 | LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:286:25 + --> tests/ui/or_fun_call.rs:302:25 | LL | let _ = Some(4).map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| "asd".to_string().len() as i32, f)` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:317:18 + --> tests/ui/or_fun_call.rs:333:18 | LL | with_new.unwrap_or_else(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:321:28 + --> tests/ui/or_fun_call.rs:337:28 | LL | with_default_trait.unwrap_or_else(Default::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:325:27 + --> tests/ui/or_fun_call.rs:341:27 | LL | with_default_type.unwrap_or_else(u64::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:329:22 + --> tests/ui/or_fun_call.rs:345:22 | LL | real_default.unwrap_or_else(::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:333:23 + --> tests/ui/or_fun_call.rs:349:23 | LL | map.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:337:25 + --> tests/ui/or_fun_call.rs:353:25 | LL | btree.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:341:25 + --> tests/ui/or_fun_call.rs:357:25 | LL | let _ = stringy.unwrap_or_else(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:383:17 + --> tests/ui/or_fun_call.rs:399:17 | LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:388:17 + --> tests/ui/or_fun_call.rs:404:17 | LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:393:17 + --> tests/ui/or_fun_call.rs:409:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -235,58 +235,82 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:399:17 + --> tests/ui/or_fun_call.rs:415:17 | LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)` | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:404:17 + --> tests/ui/or_fun_call.rs:420:17 | LL | let _ = opt.unwrap_or({ i32::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:411:21 + --> tests/ui/or_fun_call.rs:427:21 | LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:426:19 + --> tests/ui/or_fun_call.rs:442:19 | LL | let _ = x.map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:428:19 + --> tests/ui/or_fun_call.rs:444:19 | LL | let _ = x.map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:431:19 + --> tests/ui/or_fun_call.rs:447:19 | LL | let _ = x.map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| "asd".to_string().len() as i32, f)` error: function call inside of `get_or_insert` - --> tests/ui/or_fun_call.rs:442:15 + --> tests/ui/or_fun_call.rs:458:15 | LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:452:15 + --> tests/ui/or_fun_call.rs:468:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:462:15 + --> tests/ui/or_fun_call.rs:478:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` -error: aborting due to 45 previous errors +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:484:17 + | +LL | let _ = opt.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: function call inside of `unwrap_or` + --> tests/ui/or_fun_call.rs:486:17 + | +LL | let _ = res.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Default::default())` + +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:492:17 + | +LL | let _ = opt.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:494:17 + | +LL | let _ = res.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: aborting due to 49 previous errors diff --git a/tests/ui/panicking_overflow_checks.rs b/tests/ui/panicking_overflow_checks.rs index 29789c949756..61dfca8b3728 100644 --- a/tests/ui/panicking_overflow_checks.rs +++ b/tests/ui/panicking_overflow_checks.rs @@ -1,5 +1,5 @@ #![warn(clippy::panicking_overflow_checks)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn test(a: u32, b: u32, c: u32) { if a + b < a {} diff --git a/tests/ui/partialeq_to_none.fixed b/tests/ui/partialeq_to_none.fixed index e700cc56349f..9288529423dd 100644 --- a/tests/ui/partialeq_to_none.fixed +++ b/tests/ui/partialeq_to_none.fixed @@ -1,5 +1,5 @@ #![warn(clippy::partialeq_to_none)] -#![allow(clippy::eq_op, clippy::needless_if)] +#![allow(clippy::eq_op, clippy::needless_ifs)] struct Foobar; diff --git a/tests/ui/partialeq_to_none.rs b/tests/ui/partialeq_to_none.rs index 1bae076dd337..7940251c18a2 100644 --- a/tests/ui/partialeq_to_none.rs +++ b/tests/ui/partialeq_to_none.rs @@ -1,5 +1,5 @@ #![warn(clippy::partialeq_to_none)] -#![allow(clippy::eq_op, clippy::needless_if)] +#![allow(clippy::eq_op, clippy::needless_ifs)] struct Foobar; diff --git a/tests/ui/precedence.fixed b/tests/ui/precedence.fixed index cbb78bff68bf..f0252c4484a5 100644 --- a/tests/ui/precedence.fixed +++ b/tests/ui/precedence.fixed @@ -1,7 +1,12 @@ #![warn(clippy::precedence)] -#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] -#![allow(clippy::identity_op)] -#![allow(clippy::eq_op)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::clone_on_copy, + clippy::identity_op, + clippy::eq_op +)] macro_rules! trip { ($a:expr) => { @@ -35,3 +40,24 @@ fn main() { let b = 3; trip!(b * 8); } + +struct W(u8); +impl Clone for W { + fn clone(&self) -> Self { + W(1) + } +} + +fn closure_method_call() { + // Do not lint when the method call is applied to the block, both inside the closure + let f = |x: W| { x }.clone(); + assert!(matches!(f(W(0)), W(1))); + + let f = (|x: W| -> _ { x }).clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence + + let f = (move |x: W| -> _ { x }).clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence +} diff --git a/tests/ui/precedence.rs b/tests/ui/precedence.rs index c73a4020e2e5..5d47462114b8 100644 --- a/tests/ui/precedence.rs +++ b/tests/ui/precedence.rs @@ -1,7 +1,12 @@ #![warn(clippy::precedence)] -#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] -#![allow(clippy::identity_op)] -#![allow(clippy::eq_op)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::clone_on_copy, + clippy::identity_op, + clippy::eq_op +)] macro_rules! trip { ($a:expr) => { @@ -35,3 +40,24 @@ fn main() { let b = 3; trip!(b * 8); } + +struct W(u8); +impl Clone for W { + fn clone(&self) -> Self { + W(1) + } +} + +fn closure_method_call() { + // Do not lint when the method call is applied to the block, both inside the closure + let f = |x: W| { x }.clone(); + assert!(matches!(f(W(0)), W(1))); + + let f = |x: W| -> _ { x }.clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence + + let f = move |x: W| -> _ { x }.clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence +} diff --git a/tests/ui/precedence.stderr b/tests/ui/precedence.stderr index 50cd8f4b8146..f086cfe02890 100644 --- a/tests/ui/precedence.stderr +++ b/tests/ui/precedence.stderr @@ -1,5 +1,5 @@ error: operator precedence might not be obvious - --> tests/ui/precedence.rs:16:5 + --> tests/ui/precedence.rs:21:5 | LL | 1 << 2 + 3; | ^^^^^^^^^^ help: consider parenthesizing your expression: `1 << (2 + 3)` @@ -8,40 +8,62 @@ LL | 1 << 2 + 3; = help: to override `-D warnings` add `#[allow(clippy::precedence)]` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:18:5 + --> tests/ui/precedence.rs:23:5 | LL | 1 + 2 << 3; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 2) << 3` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:20:5 + --> tests/ui/precedence.rs:25:5 | LL | 4 >> 1 + 1; | ^^^^^^^^^^ help: consider parenthesizing your expression: `4 >> (1 + 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:22:5 + --> tests/ui/precedence.rs:27:5 | LL | 1 + 3 >> 2; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 3) >> 2` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:24:5 + --> tests/ui/precedence.rs:29:5 | LL | 1 ^ 1 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `1 ^ (1 - 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:26:5 + --> tests/ui/precedence.rs:31:5 | LL | 3 | 2 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 | (2 - 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:28:5 + --> tests/ui/precedence.rs:33:5 | LL | 3 & 5 - 2; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` -error: aborting due to 7 previous errors +error: precedence might not be obvious + --> tests/ui/precedence.rs:56:13 + | +LL | let f = |x: W| -> _ { x }.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider parenthesizing the closure + | +LL | let f = (|x: W| -> _ { x }).clone(); + | + + + +error: precedence might not be obvious + --> tests/ui/precedence.rs:60:13 + | +LL | let f = move |x: W| -> _ { x }.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider parenthesizing the closure + | +LL | let f = (move |x: W| -> _ { x }).clone(); + | + + + +error: aborting due to 9 previous errors diff --git a/tests/ui/print.rs b/tests/ui/print.rs deleted file mode 100644 index ee3d9dc0de03..000000000000 --- a/tests/ui/print.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![allow(clippy::print_literal, clippy::write_literal)] -#![warn(clippy::print_stdout, clippy::use_debug)] - -use std::fmt::{Debug, Display, Formatter, Result}; - -#[allow(dead_code)] -struct Foo; - -impl Display for Foo { - fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{:?}", 43.1415) - //~^ use_debug - } -} - -impl Debug for Foo { - fn fmt(&self, f: &mut Formatter) -> Result { - // ok, we can use `Debug` formatting in `Debug` implementations - write!(f, "{:?}", 42.718) - } -} - -fn main() { - println!("Hello"); - //~^ print_stdout - - print!("Hello"); - //~^ print_stdout - - print!("Hello {}", "World"); - //~^ print_stdout - - print!("Hello {:?}", "World"); - //~^ print_stdout - //~| use_debug - - print!("Hello {:#?}", "#orld"); - //~^ print_stdout - //~| use_debug - - assert_eq!(42, 1337); - - vec![1, 2]; -} diff --git a/tests/ui/print.stderr b/tests/ui/print.stderr deleted file mode 100644 index 9dd216bd1449..000000000000 --- a/tests/ui/print.stderr +++ /dev/null @@ -1,56 +0,0 @@ -error: use of `Debug`-based formatting - --> tests/ui/print.rs:11:20 - | -LL | write!(f, "{:?}", 43.1415) - | ^^^^ - | - = note: `-D clippy::use-debug` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::use_debug)]` - -error: use of `println!` - --> tests/ui/print.rs:24:5 - | -LL | println!("Hello"); - | ^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::print-stdout` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::print_stdout)]` - -error: use of `print!` - --> tests/ui/print.rs:27:5 - | -LL | print!("Hello"); - | ^^^^^^^^^^^^^^^ - -error: use of `print!` - --> tests/ui/print.rs:30:5 - | -LL | print!("Hello {}", "World"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `print!` - --> tests/ui/print.rs:33:5 - | -LL | print!("Hello {:?}", "World"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `Debug`-based formatting - --> tests/ui/print.rs:33:19 - | -LL | print!("Hello {:?}", "World"); - | ^^^^ - -error: use of `print!` - --> tests/ui/print.rs:37:5 - | -LL | print!("Hello {:#?}", "#orld"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `Debug`-based formatting - --> tests/ui/print.rs:37:19 - | -LL | print!("Hello {:#?}", "#orld"); - | ^^^^^ - -error: aborting due to 8 previous errors - diff --git a/tests/ui/print_stdout.rs b/tests/ui/print_stdout.rs new file mode 100644 index 000000000000..a379457f1869 --- /dev/null +++ b/tests/ui/print_stdout.rs @@ -0,0 +1,23 @@ +#![expect(clippy::print_literal)] +#![warn(clippy::print_stdout)] + +fn main() { + println!("Hello"); + //~^ print_stdout + + print!("Hello"); + //~^ print_stdout + + print!("Hello {}", "World"); + //~^ print_stdout + + print!("Hello {:?}", "World"); + //~^ print_stdout + + print!("Hello {:#?}", "#orld"); + //~^ print_stdout + + assert_eq!(42, 1337); + + vec![1, 2]; +} diff --git a/tests/ui/print_stdout.stderr b/tests/ui/print_stdout.stderr new file mode 100644 index 000000000000..e1360e59b412 --- /dev/null +++ b/tests/ui/print_stdout.stderr @@ -0,0 +1,35 @@ +error: use of `println!` + --> tests/ui/print_stdout.rs:5:5 + | +LL | println!("Hello"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-stdout` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::print_stdout)]` + +error: use of `print!` + --> tests/ui/print_stdout.rs:8:5 + | +LL | print!("Hello"); + | ^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:11:5 + | +LL | print!("Hello {}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:14:5 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:17:5 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/ptr_offset_with_cast.fixed b/tests/ui/ptr_offset_with_cast.fixed index 4fe9dcf46c35..42d1abeaa05a 100644 --- a/tests/ui/ptr_offset_with_cast.fixed +++ b/tests/ui/ptr_offset_with_cast.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::unnecessary_cast, clippy::useless_vec)] +#![expect(clippy::unnecessary_cast, clippy::useless_vec, clippy::needless_borrow)] fn main() { let vec = vec![b'a', b'b', b'c']; @@ -18,5 +18,25 @@ fn main() { //~^ ptr_offset_with_cast let _ = ptr.wrapping_offset(offset_isize as isize); let _ = ptr.wrapping_offset(offset_u8 as isize); + + let _ = S.offset(offset_usize as isize); + let _ = S.wrapping_offset(offset_usize as isize); + + let _ = (&ptr).add(offset_usize); + //~^ ptr_offset_with_cast + let _ = (&ptr).wrapping_add(offset_usize); + //~^ ptr_offset_with_cast + } +} + +#[derive(Clone, Copy)] +struct S; + +impl S { + fn offset(self, _: isize) -> Self { + self + } + fn wrapping_offset(self, _: isize) -> Self { + self } } diff --git a/tests/ui/ptr_offset_with_cast.rs b/tests/ui/ptr_offset_with_cast.rs index a1fb892733d3..6d06a6af1fa2 100644 --- a/tests/ui/ptr_offset_with_cast.rs +++ b/tests/ui/ptr_offset_with_cast.rs @@ -1,4 +1,4 @@ -#![allow(clippy::unnecessary_cast, clippy::useless_vec)] +#![expect(clippy::unnecessary_cast, clippy::useless_vec, clippy::needless_borrow)] fn main() { let vec = vec![b'a', b'b', b'c']; @@ -18,5 +18,25 @@ fn main() { //~^ ptr_offset_with_cast let _ = ptr.wrapping_offset(offset_isize as isize); let _ = ptr.wrapping_offset(offset_u8 as isize); + + let _ = S.offset(offset_usize as isize); + let _ = S.wrapping_offset(offset_usize as isize); + + let _ = (&ptr).offset(offset_usize as isize); + //~^ ptr_offset_with_cast + let _ = (&ptr).wrapping_offset(offset_usize as isize); + //~^ ptr_offset_with_cast + } +} + +#[derive(Clone, Copy)] +struct S; + +impl S { + fn offset(self, _: isize) -> Self { + self + } + fn wrapping_offset(self, _: isize) -> Self { + self } } diff --git a/tests/ui/ptr_offset_with_cast.stderr b/tests/ui/ptr_offset_with_cast.stderr index dcd5e027d182..022b3286c93b 100644 --- a/tests/ui/ptr_offset_with_cast.stderr +++ b/tests/ui/ptr_offset_with_cast.stderr @@ -2,16 +2,51 @@ error: use of `offset` with a `usize` casted to an `isize` --> tests/ui/ptr_offset_with_cast.rs:12:17 | LL | let _ = ptr.offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ptr_offset_with_cast)]` +help: use `add` instead + | +LL - let _ = ptr.offset(offset_usize as isize); +LL + let _ = ptr.add(offset_usize); + | error: use of `wrapping_offset` with a `usize` casted to an `isize` --> tests/ui/ptr_offset_with_cast.rs:17:17 | LL | let _ = ptr.wrapping_offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `wrapping_add` instead + | +LL - let _ = ptr.wrapping_offset(offset_usize as isize); +LL + let _ = ptr.wrapping_add(offset_usize); + | + +error: use of `offset` with a `usize` casted to an `isize` + --> tests/ui/ptr_offset_with_cast.rs:25:17 + | +LL | let _ = (&ptr).offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `add` instead + | +LL - let _ = (&ptr).offset(offset_usize as isize); +LL + let _ = (&ptr).add(offset_usize); + | + +error: use of `wrapping_offset` with a `usize` casted to an `isize` + --> tests/ui/ptr_offset_with_cast.rs:27:17 + | +LL | let _ = (&ptr).wrapping_offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `wrapping_add` instead + | +LL - let _ = (&ptr).wrapping_offset(offset_usize as isize); +LL + let _ = (&ptr).wrapping_add(offset_usize); + | -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 8d6f5fbadca5..2c5ee0245038 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,6 +1,4 @@ #![feature(try_blocks)] -#![allow(unreachable_code)] -#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] use std::sync::MutexGuard; @@ -465,3 +463,40 @@ fn issue_13642(x: Option) -> Option<()> { None } + +fn issue_15679() -> Result { + let some_result: Result = todo!(); + + some_result?; + + some_result?; + + some_result?; + + Ok(0) +} + +mod issue14894 { + fn use_after_question_mark(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + return Err(reason); + } + drop(result); + + let result = do_something_else(); + let x = result?; + drop(x); + + Ok(()) + } + + #[expect(dropping_copy_types)] + fn use_after_question_mark_but_is_copy(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + result?; + drop(result); + + Ok(()) + } +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index f13eee29c113..b9ff9d1565b2 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,6 +1,4 @@ #![feature(try_blocks)] -#![allow(unreachable_code)] -#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] use std::sync::MutexGuard; @@ -561,3 +559,59 @@ fn issue_13642(x: Option) -> Option<()> { None } + +fn issue_15679() -> Result { + let some_result: Result = todo!(); + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(err.into()), + }; + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(Into::into(err)), + }; + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(<&str as Into>::into(err)), + }; + + Ok(0) +} + +mod issue14894 { + fn use_after_question_mark(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + return Err(reason); + } + drop(result); + + let result = do_something_else(); + let x = match result { + //~^ question_mark + Ok(v) => v, + Err(e) => return Err(e), + }; + drop(x); + + Ok(()) + } + + #[expect(dropping_copy_types)] + fn use_after_question_mark_but_is_copy(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + //~^ question_mark + return Err(reason); + } + drop(result); + + Ok(()) + } +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index d8ce4420aeeb..9b2896328e66 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -1,5 +1,5 @@ error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:9:5 + --> tests/ui/question_mark.rs:7:5 | LL | / if a.is_none() { LL | | @@ -11,7 +11,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::question_mark)]` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:55:9 + --> tests/ui/question_mark.rs:53:9 | LL | / if (self.opt).is_none() { LL | | @@ -20,7 +20,7 @@ LL | | } | |_________^ help: replace it with: `(self.opt)?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:60:9 + --> tests/ui/question_mark.rs:58:9 | LL | / if self.opt.is_none() { LL | | @@ -29,7 +29,7 @@ LL | | } | |_________^ help: replace it with: `self.opt?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:65:17 + --> tests/ui/question_mark.rs:63:17 | LL | let _ = if self.opt.is_none() { | _________________^ @@ -41,7 +41,7 @@ LL | | }; | |_________^ help: replace it with: `Some(self.opt?)` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:72:17 + --> tests/ui/question_mark.rs:70:17 | LL | let _ = if let Some(x) = self.opt { | _________________^ @@ -53,7 +53,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:90:9 + --> tests/ui/question_mark.rs:88:9 | LL | / if self.opt.is_none() { LL | | @@ -62,7 +62,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:99:9 + --> tests/ui/question_mark.rs:97:9 | LL | / if self.opt.is_none() { LL | | @@ -71,7 +71,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:108:9 + --> tests/ui/question_mark.rs:106:9 | LL | / if self.opt.is_none() { LL | | @@ -80,7 +80,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:116:26 + --> tests/ui/question_mark.rs:114:26 | LL | let v: &Vec<_> = if let Some(ref v) = self.opt { | __________________________^ @@ -92,7 +92,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt.as_ref()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:127:17 + --> tests/ui/question_mark.rs:125:17 | LL | let v = if let Some(v) = self.opt { | _________________^ @@ -104,7 +104,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:149:5 + --> tests/ui/question_mark.rs:147:5 | LL | / if f().is_none() { LL | | @@ -113,7 +113,7 @@ LL | | } | |_____^ help: replace it with: `f()?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:154:16 + --> tests/ui/question_mark.rs:152:16 | LL | let _val = match f() { | ________________^ @@ -124,7 +124,7 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:165:5 + --> tests/ui/question_mark.rs:163:5 | LL | / match f() { LL | | @@ -134,7 +134,7 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:171:5 + --> tests/ui/question_mark.rs:169:5 | LL | / match opt_none!() { LL | | @@ -144,13 +144,13 @@ LL | | }; | |_____^ help: try instead: `opt_none!()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:198:13 + --> tests/ui/question_mark.rs:196:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:201:5 + --> tests/ui/question_mark.rs:199:5 | LL | / if x.is_err() { LL | | @@ -159,7 +159,7 @@ LL | | } | |_____^ help: replace it with: `x?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:206:16 + --> tests/ui/question_mark.rs:204:16 | LL | let _val = match func_returning_result() { | ________________^ @@ -170,7 +170,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:212:5 + --> tests/ui/question_mark.rs:210:5 | LL | / match func_returning_result() { LL | | @@ -180,7 +180,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:304:5 + --> tests/ui/question_mark.rs:302:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -189,7 +189,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:312:5 + --> tests/ui/question_mark.rs:310:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -198,7 +198,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:395:13 + --> tests/ui/question_mark.rs:393:13 | LL | / if a.is_none() { LL | | @@ -208,7 +208,7 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:456:5 + --> tests/ui/question_mark.rs:454:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; @@ -216,7 +216,7 @@ LL | | }; | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:471:5 + --> tests/ui/question_mark.rs:469:5 | LL | / let Some(ref x) = foo.opt_x else { LL | | return None; @@ -224,7 +224,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:481:5 + --> tests/ui/question_mark.rs:479:5 | LL | / let Some(ref mut x) = foo.opt_x else { LL | | return None; @@ -232,7 +232,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_mut()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:492:5 + --> tests/ui/question_mark.rs:490:5 | LL | / let Some(ref x @ ref y) = foo.opt_x else { LL | | return None; @@ -240,7 +240,7 @@ LL | | }; | |______^ help: replace it with: `let x @ y = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:496:5 + --> tests/ui/question_mark.rs:494:5 | LL | / let Some(ref x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -248,7 +248,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &WrapperStructWithString(_) = bar.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:500:5 + --> tests/ui/question_mark.rs:498:5 | LL | / let Some(ref mut x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -256,7 +256,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:522:5 + --> tests/ui/question_mark.rs:520:5 | LL | / if arg.is_none() { LL | | @@ -265,7 +265,7 @@ LL | | } | |_____^ help: replace it with: `arg?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:526:15 + --> tests/ui/question_mark.rs:524:15 | LL | let val = match arg { | _______________^ @@ -276,12 +276,62 @@ LL | | }; | |_____^ help: try instead: `arg?` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:536:5 + --> tests/ui/question_mark.rs:534:5 | LL | / let Some(a) = *a else { LL | | return None; LL | | }; | |______^ help: replace it with: `let a = (*a)?;` -error: aborting due to 30 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:566:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err.into()), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:572:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(Into::into(err)), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:578:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(<&str as Into>::into(err)), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:596:17 + | +LL | let x = match result { + | _________________^ +LL | | +LL | | Ok(v) => v, +LL | | Err(e) => return Err(e), +LL | | }; + | |_________^ help: try instead: `result?` + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:609:9 + | +LL | / if let Err(reason) = result { +LL | | +LL | | return Err(reason); +LL | | } + | |_________^ help: replace it with: `result?;` + +error: aborting due to 35 previous errors diff --git a/tests/ui/range.fixed b/tests/ui/range.fixed index 0e951d88091b..c8e654600045 100644 --- a/tests/ui/range.fixed +++ b/tests/ui/range.fixed @@ -1,11 +1,23 @@ #![allow(clippy::useless_vec)] #[warn(clippy::range_zip_with_len)] fn main() { - let v1 = vec![1, 2, 3]; - let v2 = vec![4, 5]; + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; let _x = v1.iter().enumerate(); //~^ range_zip_with_len + //~v range_zip_with_len + for (i, e) in v1.iter().enumerate() { + let _: &u64 = e; + let _: usize = i; + } + + //~v range_zip_with_len + v1.iter().enumerate().for_each(|(i, e)| { + let _: &u64 = e; + let _: usize = i; + }); + let _y = v1.iter().zip(0..v2.len()); // No error } diff --git a/tests/ui/range.rs b/tests/ui/range.rs index 534380164743..352d517eabdd 100644 --- a/tests/ui/range.rs +++ b/tests/ui/range.rs @@ -1,11 +1,23 @@ #![allow(clippy::useless_vec)] #[warn(clippy::range_zip_with_len)] fn main() { - let v1 = vec![1, 2, 3]; - let v2 = vec![4, 5]; + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; let _x = v1.iter().zip(0..v1.len()); //~^ range_zip_with_len + //~v range_zip_with_len + for (e, i) in v1.iter().zip(0..v1.len()) { + let _: &u64 = e; + let _: usize = i; + } + + //~v range_zip_with_len + v1.iter().zip(0..v1.len()).for_each(|(e, i)| { + let _: &u64 = e; + let _: usize = i; + }); + let _y = v1.iter().zip(0..v2.len()); // No error } diff --git a/tests/ui/range.stderr b/tests/ui/range.stderr index 798ce1842d8b..b0a852a69fc5 100644 --- a/tests/ui/range.stderr +++ b/tests/ui/range.stderr @@ -2,10 +2,35 @@ error: using `.zip()` with a range and `.len()` --> tests/ui/range.rs:6:14 | LL | let _x = v1.iter().zip(0..v1.len()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v1.iter().enumerate()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `v1.iter().enumerate()` | + = note: the order of the element and the index will be swapped = note: `-D clippy::range-zip-with-len` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::range_zip_with_len)]` -error: aborting due to 1 previous error +error: using `.zip()` with a range and `.len()` + --> tests/ui/range.rs:10:19 + | +LL | for (e, i) in v1.iter().zip(0..v1.len()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - for (e, i) in v1.iter().zip(0..v1.len()) { +LL + for (i, e) in v1.iter().enumerate() { + | + +error: using `.zip()` with a range and `.len()` + --> tests/ui/range.rs:16:5 + | +LL | v1.iter().zip(0..v1.len()).for_each(|(e, i)| { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - v1.iter().zip(0..v1.len()).for_each(|(e, i)| { +LL + v1.iter().enumerate().for_each(|(i, e)| { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/range_plus_minus_one.stderr b/tests/ui/range_plus_minus_one.stderr index a419d935bd62..60abe50efa10 100644 --- a/tests/ui/range_plus_minus_one.stderr +++ b/tests/ui/range_plus_minus_one.stderr @@ -32,22 +32,22 @@ LL | for _ in 1..ONE + ONE {} | ^^^^^^^^^^^^ help: use: `1..=ONE` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:70:5 + --> tests/ui/range_plus_minus_one.rs:70:6 | LL | (1..10 + 1).for_each(|_| {}); - | ^^^^^^^^^^^ help: use: `(1..=10)` + | ^^^^^^^^^ help: use: `1..=10` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:75:5 + --> tests/ui/range_plus_minus_one.rs:75:6 | LL | (1..10 + 1).into_iter().for_each(|_| {}); - | ^^^^^^^^^^^ help: use: `(1..=10)` + | ^^^^^^^^^ help: use: `1..=10` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:80:17 + --> tests/ui/range_plus_minus_one.rs:80:18 | LL | let _ = (1..10 + 1).start_bound(); - | ^^^^^^^^^^^ help: use: `(1..=10)` + | ^^^^^^^^^ help: use: `1..=10` error: an inclusive range would be more readable --> tests/ui/range_plus_minus_one.rs:86:16 @@ -80,10 +80,10 @@ LL | a[0..2 + 1][0] = 1; | ^^^^^^^^ help: use: `0..=2` error: an exclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:180:5 + --> tests/ui/range_plus_minus_one.rs:180:6 | LL | (1..=n - 1).sum() - | ^^^^^^^^^^^ help: use: `(1..n)` + | ^^^^^^^^^ help: use: `1..n` | = note: `-D clippy::range-minus-one` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]` diff --git a/tests/ui/range_unfixable.rs b/tests/ui/range_unfixable.rs new file mode 100644 index 000000000000..259be23fa1e5 --- /dev/null +++ b/tests/ui/range_unfixable.rs @@ -0,0 +1,14 @@ +//@no-rustfix +#![allow(clippy::useless_vec)] +#[warn(clippy::range_zip_with_len)] +fn main() { + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; + + // Do not autofix, `filter()` would not consume the iterator. + //~v range_zip_with_len + v1.iter().zip(0..v1.len()).filter(|(_, i)| *i < 2).for_each(|(e, i)| { + let _: &u64 = e; + let _: usize = i; + }); +} diff --git a/tests/ui/range_unfixable.stderr b/tests/ui/range_unfixable.stderr new file mode 100644 index 000000000000..fb03ea2c05f6 --- /dev/null +++ b/tests/ui/range_unfixable.stderr @@ -0,0 +1,12 @@ +error: using `.zip()` with a range and `.len()` + --> tests/ui/range_unfixable.rs:10:5 + | +LL | v1.iter().zip(0..v1.len()).filter(|(_, i)| *i < 2).for_each(|(e, i)| { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `v1.iter().enumerate()` + | + = note: the order of the element and the index will be swapped + = note: `-D clippy::range-zip-with-len` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::range_zip_with_len)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/read_zero_byte_vec.rs b/tests/ui/read_zero_byte_vec.rs index 938d61b68607..720276cb5548 100644 --- a/tests/ui/read_zero_byte_vec.rs +++ b/tests/ui/read_zero_byte_vec.rs @@ -120,3 +120,30 @@ fn allow_works(mut f: F) { } fn main() {} + +fn issue15575() -> usize { + use std::io::Read; + use std::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:9010").unwrap(); + let mut stream_and_addr = listener.accept().unwrap(); + let mut buf = Vec::with_capacity(32); + let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + //~^ read_zero_byte_vec + + let cap = 1000; + let mut buf = Vec::with_capacity(cap); + let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + //~^ read_zero_byte_vec + + let cap = 1000; + let mut buf = Vec::with_capacity(cap); + let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + //~^ read_zero_byte_vec + + use std::fs::File; + let mut f = File::open("foo.txt").unwrap(); + let mut data = Vec::with_capacity(100); + f.read(&mut data).unwrap() + //~^ read_zero_byte_vec +} diff --git a/tests/ui/read_zero_byte_vec.stderr b/tests/ui/read_zero_byte_vec.stderr index 8f255bc87ab8..8dd74592e4c1 100644 --- a/tests/ui/read_zero_byte_vec.stderr +++ b/tests/ui/read_zero_byte_vec.stderr @@ -2,16 +2,27 @@ error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:22:5 | LL | f.read_exact(&mut data).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data)` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::read_zero_byte_vec)]` +help: try + | +LL ~ data.resize(20, 0); +LL ~ f.read_exact(&mut data).unwrap(); + | error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:28:5 | LL | f.read_exact(&mut data2)?; - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)` + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ data2.resize(cap, 0); +LL ~ f.read_exact(&mut data2)?; + | error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:33:5 @@ -67,5 +78,53 @@ error: reading zero byte data to `Vec` LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 11 previous errors +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:131:30 + | +LL | let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(32, 0); +LL ~ let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:136:30 + | +LL | let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(cap, 0); +LL ~ let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:141:32 + | +LL | let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(cap, 0); +LL ~ let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:147:5 + | +LL | f.read(&mut data).unwrap() + | ^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ data.resize(100, 0); +LL ~ f.read(&mut data).unwrap() + | + +error: aborting due to 15 previous errors diff --git a/tests/ui/redundant_pattern_matching_drop_order.fixed b/tests/ui/redundant_pattern_matching_drop_order.fixed index 1141b5db3ebf..490948442e11 100644 --- a/tests/ui/redundant_pattern_matching_drop_order.fixed +++ b/tests/ui/redundant_pattern_matching_drop_order.fixed @@ -3,7 +3,7 @@ #![allow( clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] use std::task::Poll::{Pending, Ready}; diff --git a/tests/ui/redundant_pattern_matching_drop_order.rs b/tests/ui/redundant_pattern_matching_drop_order.rs index f60ddf468309..d40fe84693b0 100644 --- a/tests/ui/redundant_pattern_matching_drop_order.rs +++ b/tests/ui/redundant_pattern_matching_drop_order.rs @@ -3,7 +3,7 @@ #![allow( clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] use std::task::Poll::{Pending, Ready}; diff --git a/tests/ui/redundant_pattern_matching_if_let_true.fixed b/tests/ui/redundant_pattern_matching_if_let_true.fixed index 980455da2ee7..b746bfd1c358 100644 --- a/tests/ui/redundant_pattern_matching_if_let_true.fixed +++ b/tests/ui/redundant_pattern_matching_if_let_true.fixed @@ -1,5 +1,5 @@ #![warn(clippy::redundant_pattern_matching)] -#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] +#![allow(clippy::needless_ifs, clippy::no_effect, clippy::nonminimal_bool)] macro_rules! condition { () => { diff --git a/tests/ui/redundant_pattern_matching_if_let_true.rs b/tests/ui/redundant_pattern_matching_if_let_true.rs index 9cb0fe2e65f6..7be9f31e56bd 100644 --- a/tests/ui/redundant_pattern_matching_if_let_true.rs +++ b/tests/ui/redundant_pattern_matching_if_let_true.rs @@ -1,5 +1,5 @@ #![warn(clippy::redundant_pattern_matching)] -#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] +#![allow(clippy::needless_ifs, clippy::no_effect, clippy::nonminimal_bool)] macro_rules! condition { () => { diff --git a/tests/ui/redundant_pattern_matching_ipaddr.fixed b/tests/ui/redundant_pattern_matching_ipaddr.fixed index 1cec19ab8c99..7c05eca1b70e 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.fixed +++ b/tests/ui/redundant_pattern_matching_ipaddr.fixed @@ -2,7 +2,7 @@ #![allow( clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args )] diff --git a/tests/ui/redundant_pattern_matching_ipaddr.rs b/tests/ui/redundant_pattern_matching_ipaddr.rs index 123573a8602b..1d4abca49281 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.rs +++ b/tests/ui/redundant_pattern_matching_ipaddr.rs @@ -2,7 +2,7 @@ #![allow( clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args )] diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index dc9d6491691f..a54a4ce13d53 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -2,7 +2,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 2e9714ad8e75..7252fce8cce6 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -2,7 +2,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_poll.fixed b/tests/ui/redundant_pattern_matching_poll.fixed index 800889b5fda0..735c444bb45b 100644 --- a/tests/ui/redundant_pattern_matching_poll.fixed +++ b/tests/ui/redundant_pattern_matching_poll.fixed @@ -1,7 +1,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_poll.rs b/tests/ui/redundant_pattern_matching_poll.rs index 1668c2ff2bba..eeca51e8be76 100644 --- a/tests/ui/redundant_pattern_matching_poll.rs +++ b/tests/ui/redundant_pattern_matching_poll.rs @@ -1,7 +1,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_result.fixed b/tests/ui/redundant_pattern_matching_result.fixed index dab816716d59..261d82fc35c8 100644 --- a/tests/ui/redundant_pattern_matching_result.fixed +++ b/tests/ui/redundant_pattern_matching_result.fixed @@ -4,7 +4,7 @@ clippy::if_same_then_else, clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args, clippy::unnecessary_wraps )] diff --git a/tests/ui/redundant_pattern_matching_result.rs b/tests/ui/redundant_pattern_matching_result.rs index 3fd70515d084..6cae4cc4b6b0 100644 --- a/tests/ui/redundant_pattern_matching_result.rs +++ b/tests/ui/redundant_pattern_matching_result.rs @@ -4,7 +4,7 @@ clippy::if_same_then_else, clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args, clippy::unnecessary_wraps )] diff --git a/tests/ui/ref_option/ref_option.all.fixed b/tests/ui/ref_option/ref_option.all.fixed deleted file mode 100644 index 4159e916f199..000000000000 --- a/tests/ui/ref_option/ref_option.all.fixed +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: Option<&u8>) {} -//~^ ref_option -fn opt_gen(a: Option<&T>) {} -//~^ ref_option -fn opt_string(a: std::option::Option<&String>) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> Option<&'a u8> { - //~^ ref_option - panic!() -} -fn ret_string_static() -> Option<&'static u8> { - //~^ ref_option - panic!() -} -fn mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~^ ref_option -fn ret_box<'a>() -> Option<&'a Box> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: Option<&String>) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: Option<&Vec>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> Option<&Vec>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: Option<&String>); - //~^ ref_option - fn trait_ret(&self) -> Option<&String>; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: Option<&()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> Option<&String> { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: Option<&()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> Option<&String> { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option.private.fixed b/tests/ui/ref_option/ref_option.private.fixed deleted file mode 100644 index 3b158befb926..000000000000 --- a/tests/ui/ref_option/ref_option.private.fixed +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: Option<&u8>) {} -//~^ ref_option -fn opt_gen(a: Option<&T>) {} -//~^ ref_option -fn opt_string(a: std::option::Option<&String>) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> Option<&'a u8> { - //~^ ref_option - panic!() -} -fn ret_string_static() -> Option<&'static u8> { - //~^ ref_option - panic!() -} -fn mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~^ ref_option -fn ret_box<'a>() -> Option<&'a Box> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: &Option) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: &Option, b: &Option>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: Option<&String>); - //~^ ref_option - fn trait_ret(&self) -> Option<&String>; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: &Option<()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> &Option { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: Option<&()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> Option<&String> { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option.rs b/tests/ui/ref_option/ref_option.rs deleted file mode 100644 index 35cd94174f8b..000000000000 --- a/tests/ui/ref_option/ref_option.rs +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: &Option) {} -//~^ ref_option -fn opt_gen(a: &Option) {} -//~^ ref_option -fn opt_string(a: &std::option::Option) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> &'a Option { - //~^ ref_option - panic!() -} -fn ret_string_static() -> &'static Option { - //~^ ref_option - panic!() -} -fn mult_string(a: &Option, b: &Option>) {} -//~^ ref_option -fn ret_box<'a>() -> &'a Option> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: &Option) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: &Option, b: &Option>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: &Option); - //~^ ref_option - fn trait_ret(&self) -> &Option; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: &Option<()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> &Option { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: &Option<()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> &Option { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option_traits.rs b/tests/ui/ref_option/ref_option_traits.rs deleted file mode 100644 index 4c773e84f8da..000000000000 --- a/tests/ui/ref_option/ref_option_traits.rs +++ /dev/null @@ -1,40 +0,0 @@ -//@no-rustfix: fixes are only done to traits, not the impls -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![warn(clippy::ref_option)] - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: &Option); - //~^ ref_option - fn trait_ret(&self) -> &Option; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubTrait for PubStruct { - fn pub_trait_opt(&self, a: &Option>) {} - fn pub_trait_ret(&self) -> &Option> { - panic!() - } -} - -struct PrivateStruct; - -impl PrivateTrait for PrivateStruct { - fn trait_opt(&self, a: &Option) {} - fn trait_ret(&self) -> &Option { - panic!() - } -} - -fn main() {} diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index ff81c6426027..c08819b21ff0 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -19,6 +19,7 @@ #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] +#![allow(clippy::empty_enums)] #![allow(clippy::mixed_read_write_in_expression)] #![allow(clippy::manual_filter_map)] #![allow(clippy::manual_find_map)] @@ -42,6 +43,7 @@ #![allow(clippy::overly_complex_bool_expr)] #![allow(unexpected_cfgs)] #![allow(enum_intrinsics_non_enums)] +#![allow(clippy::needless_ifs)] #![allow(clippy::new_without_default)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::expect_used)] @@ -58,6 +60,7 @@ #![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(unnecessary_transmutes)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] @@ -82,6 +85,7 @@ #![warn(drop_bounds)] //~ ERROR: lint `clippy::drop_bounds` #![warn(dropping_copy_types)] //~ ERROR: lint `clippy::drop_copy` #![warn(dropping_references)] //~ ERROR: lint `clippy::drop_ref` +#![warn(clippy::empty_enums)] //~ ERROR: lint `clippy::empty_enum` #![warn(clippy::mixed_read_write_in_expression)] //~ ERROR: lint `clippy::eval_order_dependence` #![warn(clippy::manual_filter_map)] //~ ERROR: lint `clippy::filter_map` #![warn(clippy::manual_find_map)] //~ ERROR: lint `clippy::find_map` @@ -108,6 +112,7 @@ #![warn(unexpected_cfgs)] //~ ERROR: lint `clippy::maybe_misused_cfg` #![warn(enum_intrinsics_non_enums)] //~ ERROR: lint `clippy::mem_discriminant_non_enum` #![warn(unexpected_cfgs)] //~ ERROR: lint `clippy::mismatched_target_os` +#![warn(clippy::needless_ifs)] //~ ERROR: lint `clippy::needless_if` #![warn(clippy::new_without_default)] //~ ERROR: lint `clippy::new_without_default_derive` #![warn(clippy::bind_instead_of_map)] //~ ERROR: lint `clippy::option_and_then_some` #![warn(clippy::expect_used)] //~ ERROR: lint `clippy::option_expect_used` @@ -131,6 +136,7 @@ #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_int_to_char` #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_int_to_float` #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_num_to_bytes` +#![warn(clippy::unchecked_time_subtraction)] //~ ERROR: lint `clippy::unchecked_duration_subtraction` #![warn(undropped_manually_drops)] //~ ERROR: lint `clippy::undropped_manually_drops` #![warn(unknown_lints)] //~ ERROR: lint `clippy::unknown_clippy_lints` #![warn(unused_labels)] //~ ERROR: lint `clippy::unused_label` diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index b5d5d07e639a..0f6b58e144d9 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -19,6 +19,7 @@ #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] +#![allow(clippy::empty_enums)] #![allow(clippy::mixed_read_write_in_expression)] #![allow(clippy::manual_filter_map)] #![allow(clippy::manual_find_map)] @@ -42,6 +43,7 @@ #![allow(clippy::overly_complex_bool_expr)] #![allow(unexpected_cfgs)] #![allow(enum_intrinsics_non_enums)] +#![allow(clippy::needless_ifs)] #![allow(clippy::new_without_default)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::expect_used)] @@ -58,6 +60,7 @@ #![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(unnecessary_transmutes)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] @@ -82,6 +85,7 @@ #![warn(clippy::drop_bounds)] //~ ERROR: lint `clippy::drop_bounds` #![warn(clippy::drop_copy)] //~ ERROR: lint `clippy::drop_copy` #![warn(clippy::drop_ref)] //~ ERROR: lint `clippy::drop_ref` +#![warn(clippy::empty_enum)] //~ ERROR: lint `clippy::empty_enum` #![warn(clippy::eval_order_dependence)] //~ ERROR: lint `clippy::eval_order_dependence` #![warn(clippy::filter_map)] //~ ERROR: lint `clippy::filter_map` #![warn(clippy::find_map)] //~ ERROR: lint `clippy::find_map` @@ -108,6 +112,7 @@ #![warn(clippy::maybe_misused_cfg)] //~ ERROR: lint `clippy::maybe_misused_cfg` #![warn(clippy::mem_discriminant_non_enum)] //~ ERROR: lint `clippy::mem_discriminant_non_enum` #![warn(clippy::mismatched_target_os)] //~ ERROR: lint `clippy::mismatched_target_os` +#![warn(clippy::needless_if)] //~ ERROR: lint `clippy::needless_if` #![warn(clippy::new_without_default_derive)] //~ ERROR: lint `clippy::new_without_default_derive` #![warn(clippy::option_and_then_some)] //~ ERROR: lint `clippy::option_and_then_some` #![warn(clippy::option_expect_used)] //~ ERROR: lint `clippy::option_expect_used` @@ -131,6 +136,7 @@ #![warn(clippy::transmute_int_to_char)] //~ ERROR: lint `clippy::transmute_int_to_char` #![warn(clippy::transmute_int_to_float)] //~ ERROR: lint `clippy::transmute_int_to_float` #![warn(clippy::transmute_num_to_bytes)] //~ ERROR: lint `clippy::transmute_num_to_bytes` +#![warn(clippy::unchecked_duration_subtraction)] //~ ERROR: lint `clippy::unchecked_duration_subtraction` #![warn(clippy::undropped_manually_drops)] //~ ERROR: lint `clippy::undropped_manually_drops` #![warn(clippy::unknown_clippy_lints)] //~ ERROR: lint `clippy::unknown_clippy_lints` #![warn(clippy::unused_label)] //~ ERROR: lint `clippy::unused_label` diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 2487dfc8eba4..4a7799be37d7 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,436 +8,454 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:79:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:80:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:81:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:82:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:83:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::double_neg` has been renamed to `double_negations` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:84:9 | LL | #![warn(clippy::double_neg)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `double_negations` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:85:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:86:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:87:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` +error: lint `clippy::empty_enum` has been renamed to `clippy::empty_enums` + --> tests/ui/rename.rs:88:9 + | +LL | #![warn(clippy::empty_enum)] + | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::empty_enums` + error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:89:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::filter_map` has been renamed to `clippy::manual_filter_map` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:90:9 | LL | #![warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_filter_map` error: lint `clippy::find_map` has been renamed to `clippy::manual_find_map` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:91:9 | LL | #![warn(clippy::find_map)] | ^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_find_map` error: lint `clippy::fn_address_comparisons` has been renamed to `unpredictable_function_pointer_comparisons` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:92:9 | LL | #![warn(clippy::fn_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unpredictable_function_pointer_comparisons` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:93:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:94:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:95:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:96:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:97:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:98:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:99:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_redundant_pattern_matching` has been renamed to `clippy::redundant_pattern_matching` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:100:9 | LL | #![warn(clippy::if_let_redundant_pattern_matching)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_pattern_matching` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:101:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:102:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:103:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:104:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:105:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:106:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_null_ptr_usage` has been renamed to `invalid_null_arguments` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:107:9 | LL | #![warn(clippy::invalid_null_ptr_usage)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_null_arguments` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:108:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:109:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:110:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:111:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::maybe_misused_cfg` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:112:9 | LL | #![warn(clippy::maybe_misused_cfg)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:113:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::mismatched_target_os` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:114:9 | LL | #![warn(clippy::mismatched_target_os)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` +error: lint `clippy::needless_if` has been renamed to `clippy::needless_ifs` + --> tests/ui/rename.rs:115:9 + | +LL | #![warn(clippy::needless_if)] + | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_ifs` + error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:116:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:117:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:118:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:119:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:120:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:116:9 + --> tests/ui/rename.rs:121:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` - --> tests/ui/rename.rs:117:9 + --> tests/ui/rename.rs:122:9 | LL | #![warn(clippy::overflow_check_conditional)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:118:9 + --> tests/ui/rename.rs:123:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:119:9 + --> tests/ui/rename.rs:124:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:120:9 + --> tests/ui/rename.rs:125:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:121:9 + --> tests/ui/rename.rs:126:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:122:9 + --> tests/ui/rename.rs:127:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:123:9 + --> tests/ui/rename.rs:128:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_empty_ranges` - --> tests/ui/rename.rs:124:9 + --> tests/ui/rename.rs:129:9 | LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:125:9 + --> tests/ui/rename.rs:130:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:126:9 + --> tests/ui/rename.rs:131:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:127:9 + --> tests/ui/rename.rs:132:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:128:9 + --> tests/ui/rename.rs:133:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:129:9 + --> tests/ui/rename.rs:134:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::transmute_float_to_int` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:130:9 + --> tests/ui/rename.rs:135:9 | LL | #![warn(clippy::transmute_float_to_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_char` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:131:9 + --> tests/ui/rename.rs:136:9 | LL | #![warn(clippy::transmute_int_to_char)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_float` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:132:9 + --> tests/ui/rename.rs:137:9 | LL | #![warn(clippy::transmute_int_to_float)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_num_to_bytes` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:133:9 + --> tests/ui/rename.rs:138:9 | LL | #![warn(clippy::transmute_num_to_bytes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` +error: lint `clippy::unchecked_duration_subtraction` has been renamed to `clippy::unchecked_time_subtraction` + --> tests/ui/rename.rs:139:9 + | +LL | #![warn(clippy::unchecked_duration_subtraction)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unchecked_time_subtraction` + error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:134:9 + --> tests/ui/rename.rs:140:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:135:9 + --> tests/ui/rename.rs:141:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:136:9 + --> tests/ui/rename.rs:142:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:137:9 + --> tests/ui/rename.rs:143:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:138:9 + --> tests/ui/rename.rs:144:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:139:9 + --> tests/ui/rename.rs:145:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` -error: aborting due to 73 previous errors +error: aborting due to 76 previous errors diff --git a/tests/ui/repeat_once.fixed b/tests/ui/repeat_once.fixed index e739e176f0ac..c08d630a32f7 100644 --- a/tests/ui/repeat_once.fixed +++ b/tests/ui/repeat_once.fixed @@ -10,8 +10,7 @@ fn main() { //~^ repeat_once let b = slice.to_vec(); //~^ repeat_once - let c = "hello".to_string(); - //~^ repeat_once + let c = "hello".repeat(N); let d = "hi".to_string(); //~^ repeat_once let e = s.to_string(); diff --git a/tests/ui/repeat_once.rs b/tests/ui/repeat_once.rs index 89ab94bbaee8..d967fdc466ed 100644 --- a/tests/ui/repeat_once.rs +++ b/tests/ui/repeat_once.rs @@ -11,7 +11,6 @@ fn main() { let b = slice.repeat(1); //~^ repeat_once let c = "hello".repeat(N); - //~^ repeat_once let d = "hi".repeat(1); //~^ repeat_once let e = s.repeat(1); diff --git a/tests/ui/repeat_once.stderr b/tests/ui/repeat_once.stderr index 3db7a3568f8e..62dbf7d23375 100644 --- a/tests/ui/repeat_once.stderr +++ b/tests/ui/repeat_once.stderr @@ -14,28 +14,22 @@ LL | let b = slice.repeat(1); | ^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `slice.to_vec()` error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:13:13 - | -LL | let c = "hello".repeat(N); - | ^^^^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hello".to_string()` - -error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:15:13 + --> tests/ui/repeat_once.rs:14:13 | LL | let d = "hi".repeat(1); | ^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hi".to_string()` error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:17:13 + --> tests/ui/repeat_once.rs:16:13 | LL | let e = s.repeat(1); | ^^^^^^^^^^^ help: consider using `.to_string()` instead: `s.to_string()` error: calling `repeat(1)` on a string literal - --> tests/ui/repeat_once.rs:19:13 + --> tests/ui/repeat_once.rs:18:13 | LL | let f = string.repeat(1); | ^^^^^^^^^^^^^^^^ help: consider using `.clone()` instead: `string.clone()` -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/replace_box.fixed b/tests/ui/replace_box.fixed new file mode 100644 index 000000000000..e3fc7190a9c9 --- /dev/null +++ b/tests/ui/replace_box.fixed @@ -0,0 +1,143 @@ +#![warn(clippy::replace_box)] + +fn with_default(b: &mut Box) { + **b = T::default(); + //~^ replace_box +} + +fn with_sized(b: &mut Box, t: T) { + **b = t; + //~^ replace_box +} + +fn with_unsized(b: &mut Box<[u32]>) { + // No lint for assigning to Box where T: !Default + *b = Box::new([42; N]); +} + +macro_rules! create_default { + () => { + Default::default() + }; +} + +macro_rules! create_zero_box { + () => { + Box::new(0) + }; +} + +macro_rules! same { + ($v:ident) => { + $v + }; +} + +macro_rules! mac { + (three) => { + 3u32 + }; +} + +fn main() { + let mut b = Box::new(1u32); + *b = Default::default(); + //~^ replace_box + *b = Default::default(); + //~^ replace_box + + // No lint for assigning to the storage + *b = Default::default(); + *b = u32::default(); + + // No lint if either LHS or RHS originates in macro + b = create_default!(); + b = create_zero_box!(); + same!(b) = Default::default(); + + *b = 5; + //~^ replace_box + + *b = mac!(three); + //~^ replace_box + + // No lint for assigning to Box where T: !Default + let mut b = Box::::from("hi".to_string()); + b = Default::default(); + + // No lint for late initializations + #[allow(clippy::needless_late_init)] + let bb: Box; + bb = Default::default(); +} + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + *x = Foo { inner: String::new() }; + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + *x = Foo { inner: String::new() }; + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| **x = String::new(); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + *s.b = T::new(); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + *s.b = T::new(); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + *q.0 = T::new(); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.rs b/tests/ui/replace_box.rs new file mode 100644 index 000000000000..1d5ca1b24994 --- /dev/null +++ b/tests/ui/replace_box.rs @@ -0,0 +1,143 @@ +#![warn(clippy::replace_box)] + +fn with_default(b: &mut Box) { + *b = Box::new(T::default()); + //~^ replace_box +} + +fn with_sized(b: &mut Box, t: T) { + *b = Box::new(t); + //~^ replace_box +} + +fn with_unsized(b: &mut Box<[u32]>) { + // No lint for assigning to Box where T: !Default + *b = Box::new([42; N]); +} + +macro_rules! create_default { + () => { + Default::default() + }; +} + +macro_rules! create_zero_box { + () => { + Box::new(0) + }; +} + +macro_rules! same { + ($v:ident) => { + $v + }; +} + +macro_rules! mac { + (three) => { + 3u32 + }; +} + +fn main() { + let mut b = Box::new(1u32); + b = Default::default(); + //~^ replace_box + b = Box::default(); + //~^ replace_box + + // No lint for assigning to the storage + *b = Default::default(); + *b = u32::default(); + + // No lint if either LHS or RHS originates in macro + b = create_default!(); + b = create_zero_box!(); + same!(b) = Default::default(); + + b = Box::new(5); + //~^ replace_box + + b = Box::new(mac!(three)); + //~^ replace_box + + // No lint for assigning to Box where T: !Default + let mut b = Box::::from("hi".to_string()); + b = Default::default(); + + // No lint for late initializations + #[allow(clippy::needless_late_init)] + let bb: Box; + bb = Default::default(); +} + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| *x = Box::new(String::new()); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + s.b = Box::new(T::new()); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + s.b = Box::new(T::new()); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + q.0 = Box::new(T::new()); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.stderr b/tests/ui/replace_box.stderr new file mode 100644 index 000000000000..4b7bd4a0eeae --- /dev/null +++ b/tests/ui/replace_box.stderr @@ -0,0 +1,100 @@ +error: creating a new box + --> tests/ui/replace_box.rs:4:5 + | +LL | *b = Box::new(T::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**b = T::default()` + | + = note: this creates a needless allocation + = note: `-D clippy::replace-box` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::replace_box)]` + +error: creating a new box + --> tests/ui/replace_box.rs:9:5 + | +LL | *b = Box::new(t); + | ^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**b = t` + | + = note: this creates a needless allocation + +error: creating a new box with default content + --> tests/ui/replace_box.rs:44:5 + | +LL | b = Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with default instead: `*b = Default::default()` + | + = note: this creates a needless allocation + +error: creating a new box with default content + --> tests/ui/replace_box.rs:46:5 + | +LL | b = Box::default(); + | ^^^^^^^^^^^^^^^^^^ help: replace existing content with default instead: `*b = Default::default()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:58:5 + | +LL | b = Box::new(5); + | ^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = 5` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:61:5 + | +LL | b = Box::new(mac!(three)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = mac!(three)` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:86:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:92:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:97:38 + | +LL | static R: fn(&mut Box) = |x| *x = Box::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**x = String::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:122:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:126:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:136:5 + | +LL | q.0 = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*q.0 = T::new()` + | + = note: this creates a needless allocation + +error: aborting due to 12 previous errors + diff --git a/tests/ui/rest_pat_in_fully_bound_structs.fixed b/tests/ui/rest_pat_in_fully_bound_structs.fixed new file mode 100644 index 000000000000..ac200b3b19b7 --- /dev/null +++ b/tests/ui/rest_pat_in_fully_bound_structs.fixed @@ -0,0 +1,61 @@ +#![warn(clippy::rest_pat_in_fully_bound_structs)] +#![allow(clippy::struct_field_names)] + +struct A { + a: i32, + b: i64, + c: &'static str, +} + +macro_rules! foo { + ($param:expr) => { + match $param { + A { a: 0, b: 0, c: "", .. } => {}, + _ => {}, + } + }; +} + +fn main() { + let a_struct = A { a: 5, b: 42, c: "A" }; + + match a_struct { + A { a: 5, b: 42, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + A { a: 0, b: 0, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + _ => {}, + } + + match a_struct { + A { a: 5, b: 42, .. } => {}, + A { a: 0, b: 0, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + _ => {}, + } + + // No lint + match a_struct { + A { a: 5, .. } => {}, + A { a: 0, b: 0, .. } => {}, + _ => {}, + } + + // No lint + foo!(a_struct); + + #[non_exhaustive] + struct B { + a: u32, + b: u32, + c: u64, + } + + let b_struct = B { a: 5, b: 42, c: 342 }; + + match b_struct { + B { a: 5, b: 42, .. } => {}, + B { a: 0, b: 0, c: 128, .. } => {}, // No Lint + _ => {}, + } +} diff --git a/tests/ui/rest_pat_in_fully_bound_structs.stderr b/tests/ui/rest_pat_in_fully_bound_structs.stderr index d048933ddb7b..8a2da302b9ef 100644 --- a/tests/ui/rest_pat_in_fully_bound_structs.stderr +++ b/tests/ui/rest_pat_in_fully_bound_structs.stderr @@ -4,9 +4,13 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 5, b: 42, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding = note: `-D clippy::rest-pat-in-fully-bound-structs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rest_pat_in_fully_bound_structs)]` +help: consider removing `..` from this binding + | +LL - A { a: 5, b: 42, c: "", .. } => {}, // Lint +LL + A { a: 5, b: 42, c: "", } => {}, // Lint + | error: unnecessary use of `..` pattern in struct binding. All fields were already bound --> tests/ui/rest_pat_in_fully_bound_structs.rs:25:9 @@ -14,7 +18,11 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding +help: consider removing `..` from this binding + | +LL - A { a: 0, b: 0, c: "", .. } => {}, // Lint +LL + A { a: 0, b: 0, c: "", } => {}, // Lint + | error: unnecessary use of `..` pattern in struct binding. All fields were already bound --> tests/ui/rest_pat_in_fully_bound_structs.rs:32:9 @@ -22,7 +30,11 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding +help: consider removing `..` from this binding + | +LL - A { a: 0, b: 0, c: "", .. } => {}, // Lint +LL + A { a: 0, b: 0, c: "", } => {}, // Lint + | error: aborting due to 3 previous errors diff --git a/tests/ui/result_map_unit_fn_fixable.fixed b/tests/ui/result_map_unit_fn_fixable.fixed index 6bee013c3f47..b810e85a45fa 100644 --- a/tests/ui/result_map_unit_fn_fixable.fixed +++ b/tests/ui/result_map_unit_fn_fixable.fixed @@ -1,6 +1,4 @@ #![warn(clippy::result_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args)] fn do_nothing(_: T) {} @@ -92,7 +90,7 @@ fn result_map_unit_fn() { if let Ok(ref value) = x.field { do_nothing(value + captured) } //~^ result_map_unit_fn - if let Ok(value) = x.field { println!("{:?}", value) } + if let Ok(value) = x.field { println!("{value:?}") } //~^ result_map_unit_fn } diff --git a/tests/ui/result_map_unit_fn_fixable.rs b/tests/ui/result_map_unit_fn_fixable.rs index a206cfe6842f..18cd557f0454 100644 --- a/tests/ui/result_map_unit_fn_fixable.rs +++ b/tests/ui/result_map_unit_fn_fixable.rs @@ -1,6 +1,4 @@ #![warn(clippy::result_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args)] fn do_nothing(_: T) {} @@ -92,7 +90,7 @@ fn result_map_unit_fn() { x.field.map(|ref value| { do_nothing(value + captured) }); //~^ result_map_unit_fn - x.field.map(|value| println!("{:?}", value)); + x.field.map(|value| println!("{value:?}")); //~^ result_map_unit_fn } diff --git a/tests/ui/result_map_unit_fn_fixable.stderr b/tests/ui/result_map_unit_fn_fixable.stderr index eca844e06cc0..d8f6239764d1 100644 --- a/tests/ui/result_map_unit_fn_fixable.stderr +++ b/tests/ui/result_map_unit_fn_fixable.stderr @@ -1,149 +1,220 @@ error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:34:5 + --> tests/ui/result_map_unit_fn_fixable.rs:32:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::result_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Ok(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:37:5 + --> tests/ui/result_map_unit_fn_fixable.rs:35:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Ok(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:40:5 + --> tests/ui/result_map_unit_fn_fixable.rs:38:5 | LL | x.field.map(diverge); - | ^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { diverge(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(diverge); +LL + if let Ok(x_field) = x.field { diverge(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:47:5 + --> tests/ui/result_map_unit_fn_fixable.rs:45:5 | LL | x.field.map(|value| x.do_result_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { x.do_result_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| x.do_result_nothing(value + captured)); +LL + if let Ok(value) = x.field { x.do_result_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:50:5 + --> tests/ui/result_map_unit_fn_fixable.rs:48:5 | LL | x.field.map(|value| { x.do_result_plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { x.do_result_plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { x.do_result_plus_one(value + captured); }); +LL + if let Ok(value) = x.field { x.do_result_plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:54:5 + --> tests/ui/result_map_unit_fn_fixable.rs:52:5 | LL | x.field.map(|value| do_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| do_nothing(value + captured)); +LL + if let Ok(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:57:5 + --> tests/ui/result_map_unit_fn_fixable.rs:55:5 | LL | x.field.map(|value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured) }); +LL + if let Ok(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:60:5 + --> tests/ui/result_map_unit_fn_fixable.rs:58:5 | LL | x.field.map(|value| { do_nothing(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured); }); +LL + if let Ok(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:63:5 + --> tests/ui/result_map_unit_fn_fixable.rs:61:5 | LL | x.field.map(|value| { { do_nothing(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { do_nothing(value + captured); } }); +LL + if let Ok(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:67:5 + --> tests/ui/result_map_unit_fn_fixable.rs:65:5 | LL | x.field.map(|value| diverge(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| diverge(value + captured)); +LL + if let Ok(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:70:5 + --> tests/ui/result_map_unit_fn_fixable.rs:68:5 | LL | x.field.map(|value| { diverge(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured) }); +LL + if let Ok(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:73:5 + --> tests/ui/result_map_unit_fn_fixable.rs:71:5 | LL | x.field.map(|value| { diverge(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured); }); +LL + if let Ok(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:76:5 + --> tests/ui/result_map_unit_fn_fixable.rs:74:5 | LL | x.field.map(|value| { { diverge(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { diverge(value + captured); } }); +LL + if let Ok(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:82:5 + --> tests/ui/result_map_unit_fn_fixable.rs:80:5 | LL | x.field.map(|value| { let y = plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { let y = plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { let y = plus_one(value + captured); }); +LL + if let Ok(value) = x.field { let y = plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:85:5 + --> tests/ui/result_map_unit_fn_fixable.rs:83:5 | LL | x.field.map(|value| { plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { plus_one(value + captured); }); +LL + if let Ok(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:88:5 + --> tests/ui/result_map_unit_fn_fixable.rs:86:5 | LL | x.field.map(|value| { { plus_one(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { plus_one(value + captured); } }); +LL + if let Ok(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:92:5 + --> tests/ui/result_map_unit_fn_fixable.rs:90:5 | LL | x.field.map(|ref value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(ref value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|ref value| { do_nothing(value + captured) }); +LL + if let Ok(ref value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:95:5 + --> tests/ui/result_map_unit_fn_fixable.rs:93:5 + | +LL | x.field.map(|value| println!("{value:?}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| println!("{value:?}")); +LL + if let Ok(value) = x.field { println!("{value:?}") } | -LL | x.field.map(|value| println!("{:?}", value)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { println!("{:?}", value) }` error: aborting due to 18 previous errors diff --git a/tests/ui/result_map_unit_fn_unfixable.rs b/tests/ui/result_map_unit_fn_unfixable.rs index fe3d8ece39f4..e3f5c7f996da 100644 --- a/tests/ui/result_map_unit_fn_unfixable.rs +++ b/tests/ui/result_map_unit_fn_unfixable.rs @@ -1,7 +1,8 @@ +//@no-rustfix #![warn(clippy::result_map_unit_fn)] #![feature(never_type)] -#![allow(unused, clippy::unnecessary_map_on_constructor)] -//@no-rustfix +#![allow(clippy::unnecessary_map_on_constructor)] + struct HasResult { field: Result, } diff --git a/tests/ui/result_map_unit_fn_unfixable.stderr b/tests/ui/result_map_unit_fn_unfixable.stderr index a6e38d808afa..9f80ec1bbbd0 100644 --- a/tests/ui/result_map_unit_fn_unfixable.stderr +++ b/tests/ui/result_map_unit_fn_unfixable.stderr @@ -1,58 +1,86 @@ error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:23:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:24:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::result_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value) }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:28:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:29:5 | LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:34:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:35:5 + | +LL | / x.field.map(|value| { +LL | | +LL | | +LL | | do_nothing(value); +LL | | do_nothing(value) +LL | | }); + | |______^ | -LL | // x.field.map(|value| { -LL | || -LL | || -LL | || do_nothing(value); -LL | || do_nothing(value) -LL | || }); - | ||______^- help: try: `if let Ok(value) = x.field { ... }` - | |______| +help: use `if let` instead + | +LL - x.field.map(|value| { +LL - +LL - +LL - do_nothing(value); +LL - do_nothing(value) +LL - }); +LL + if let Ok(value) = x.field { ... } | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:40:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:41:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value); }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:46:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:47:5 | LL | "12".parse::().map(diverge); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(a) = "12".parse::() { diverge(a) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - "12".parse::().map(diverge); +LL + if let Ok(a) = "12".parse::() { diverge(a) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:54:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:55:5 | LL | y.map(do_nothing); - | ^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(_y) = y { do_nothing(_y) }` + | ^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - y.map(do_nothing); +LL + if let Ok(_y) = y { do_nothing(_y) } + | error: aborting due to 6 previous errors diff --git a/tests/ui/reversed_empty_ranges_fixable.fixed b/tests/ui/reversed_empty_ranges_fixable.fixed index ba5059bbaa37..6c866aceed4f 100644 --- a/tests/ui/reversed_empty_ranges_fixable.fixed +++ b/tests/ui/reversed_empty_ranges_fixable.fixed @@ -6,9 +6,9 @@ const ANSWER: i32 = 42; fn main() { // These should be linted: - (21..=42).rev().for_each(|x| println!("{}", x)); + ((21..=42).rev()).for_each(|x| println!("{}", x)); //~^ reversed_empty_ranges - let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::>(); + let _ = ((21..ANSWER).rev()).filter(|x| x % 2 == 0).take(10).collect::>(); //~^ reversed_empty_ranges for _ in (-42..=-21).rev() {} diff --git a/tests/ui/reversed_empty_ranges_fixable.stderr b/tests/ui/reversed_empty_ranges_fixable.stderr index 3fadc4c169f1..f3d3ac298cec 100644 --- a/tests/ui/reversed_empty_ranges_fixable.stderr +++ b/tests/ui/reversed_empty_ranges_fixable.stderr @@ -1,27 +1,27 @@ error: this range is empty so it will yield no values - --> tests/ui/reversed_empty_ranges_fixable.rs:9:5 + --> tests/ui/reversed_empty_ranges_fixable.rs:9:6 | LL | (42..=21).for_each(|x| println!("{}", x)); - | ^^^^^^^^^ + | ^^^^^^^ | = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::reversed_empty_ranges)]` help: consider using the following if you are attempting to iterate over this range in reverse | LL - (42..=21).for_each(|x| println!("{}", x)); -LL + (21..=42).rev().for_each(|x| println!("{}", x)); +LL + ((21..=42).rev()).for_each(|x| println!("{}", x)); | error: this range is empty so it will yield no values - --> tests/ui/reversed_empty_ranges_fixable.rs:11:13 + --> tests/ui/reversed_empty_ranges_fixable.rs:11:14 | LL | let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); - | ^^^^^^^^^^^^ + | ^^^^^^^^^^ | help: consider using the following if you are attempting to iterate over this range in reverse | LL - let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); -LL + let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::>(); +LL + let _ = ((21..ANSWER).rev()).filter(|x| x % 2 == 0).take(10).collect::>(); | error: this range is empty so it will yield no values diff --git a/tests/ui/reversed_empty_ranges_loops_fixable.fixed b/tests/ui/reversed_empty_ranges_loops_fixable.fixed index 55080da8a137..9fb46c9533cd 100644 --- a/tests/ui/reversed_empty_ranges_loops_fixable.fixed +++ b/tests/ui/reversed_empty_ranges_loops_fixable.fixed @@ -34,7 +34,7 @@ fn main() { println!("{}", i); } - for i in (0..10).rev().map(|x| x * 2) { + for i in ((0..10).rev()).map(|x| x * 2) { //~^ reversed_empty_ranges println!("{}", i); } diff --git a/tests/ui/reversed_empty_ranges_loops_fixable.stderr b/tests/ui/reversed_empty_ranges_loops_fixable.stderr index eadd9d3675e1..775d109296a1 100644 --- a/tests/ui/reversed_empty_ranges_loops_fixable.stderr +++ b/tests/ui/reversed_empty_ranges_loops_fixable.stderr @@ -37,15 +37,15 @@ LL + for i in (0..MAX_LEN).rev() { | error: this range is empty so it will yield no values - --> tests/ui/reversed_empty_ranges_loops_fixable.rs:37:14 + --> tests/ui/reversed_empty_ranges_loops_fixable.rs:37:15 | LL | for i in (10..0).map(|x| x * 2) { - | ^^^^^^^ + | ^^^^^ | help: consider using the following if you are attempting to iterate over this range in reverse | LL - for i in (10..0).map(|x| x * 2) { -LL + for i in (0..10).rev().map(|x| x * 2) { +LL + for i in ((0..10).rev()).map(|x| x * 2) { | error: this range is empty so it will yield no values diff --git a/tests/ui/search_is_some.rs b/tests/ui/search_is_some.rs index 802d27449abf..0a1e0b07d1eb 100644 --- a/tests/ui/search_is_some.rs +++ b/tests/ui/search_is_some.rs @@ -8,31 +8,6 @@ use option_helpers::IteratorFalsePositives; //@no-rustfix #[rustfmt::skip] fn main() { - let v = vec![3, 2, 1, 0, -1, -2, -3]; - let y = &&42; - - - // Check `find().is_some()`, multi-line case. - let _ = v.iter().find(|&x| { - //~^ search_is_some - *x < 0 - } - ).is_some(); - - // Check `position().is_some()`, multi-line case. - let _ = v.iter().position(|&x| { - //~^ search_is_some - x < 0 - } - ).is_some(); - - // Check `rposition().is_some()`, multi-line case. - let _ = v.iter().rposition(|&x| { - //~^ search_is_some - x < 0 - } - ).is_some(); - // Check that we don't lint if the caller is not an `Iterator` or string let falsepos = IteratorFalsePositives { foo: 0 }; let _ = falsepos.find().is_some(); @@ -49,31 +24,6 @@ fn main() { #[rustfmt::skip] fn is_none() { - let v = vec![3, 2, 1, 0, -1, -2, -3]; - let y = &&42; - - - // Check `find().is_none()`, multi-line case. - let _ = v.iter().find(|&x| { - //~^ search_is_some - *x < 0 - } - ).is_none(); - - // Check `position().is_none()`, multi-line case. - let _ = v.iter().position(|&x| { - //~^ search_is_some - x < 0 - } - ).is_none(); - - // Check `rposition().is_none()`, multi-line case. - let _ = v.iter().rposition(|&x| { - //~^ search_is_some - x < 0 - } - ).is_none(); - // Check that we don't lint if the caller is not an `Iterator` or string let falsepos = IteratorFalsePositives { foo: 0 }; let _ = falsepos.find().is_none(); @@ -87,18 +37,3 @@ fn is_none() { let _ = (0..1).find(some_closure).is_none(); //~^ search_is_some } - -#[allow(clippy::match_like_matches_macro)] -fn issue15102() { - let values = [None, Some(3)]; - let has_even = values - //~^ search_is_some - .iter() - .find(|v| match v { - Some(x) if x % 2 == 0 => true, - _ => false, - }) - .is_some(); - - println!("{has_even}"); -} diff --git a/tests/ui/search_is_some.stderr b/tests/ui/search_is_some.stderr index d5412f901110..ccbab5320ee2 100644 --- a/tests/ui/search_is_some.stderr +++ b/tests/ui/search_is_some.stderr @@ -1,109 +1,17 @@ error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:16:13 - | -LL | let _ = v.iter().find(|&x| { - | _____________^ -LL | | -LL | | *x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - = note: `-D clippy::search-is-some` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::search_is_some)]` - -error: called `is_some()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:23:13 - | -LL | let _ = v.iter().position(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: called `is_some()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:30:13 - | -LL | let _ = v.iter().rposition(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:46:20 + --> tests/ui/search_is_some.rs:21:20 | LL | let _ = (0..1).find(some_closure).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(some_closure)` - -error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:57:13 | -LL | let _ = v.iter().find(|&x| { - | _____________^ -LL | | -LL | | *x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation - -error: called `is_none()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:64:13 - | -LL | let _ = v.iter().position(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation - -error: called `is_none()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:71:13 - | -LL | let _ = v.iter().rposition(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation + = note: `-D clippy::search-is-some` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::search_is_some)]` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:87:13 + --> tests/ui/search_is_some.rs:37:13 | LL | let _ = (0..1).find(some_closure).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!(0..1).any(some_closure)` -error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:94:20 - | -LL | let has_even = values - | ____________________^ -LL | | -LL | | .iter() -LL | | .find(|v| match v { -... | -LL | | }) -LL | | .is_some(); - | |__________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: aborting due to 9 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/search_is_some_fixable_none.fixed b/tests/ui/search_is_some_fixable_none.fixed index cc4dbc919d81..720ed385fb48 100644 --- a/tests/ui/search_is_some_fixable_none.fixed +++ b/tests/ui/search_is_some_fixable_none.fixed @@ -24,14 +24,32 @@ fn main() { let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0); //~^ search_is_some let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1); + // Check `find().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|x| { + *x < 0 // + }); // Check `position().is_none()`, single-line case. let _ = !v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `position().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|&x| { + x < 0 // + }); // Check `rposition().is_none()`, single-line case. let _ = !v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `rposition().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|&x| { + x < 0 // + }); let s1 = String::from("hello world"); let s2 = String::from("world"); diff --git a/tests/ui/search_is_some_fixable_none.rs b/tests/ui/search_is_some_fixable_none.rs index fa31a9ddedc6..1cb5f9c39959 100644 --- a/tests/ui/search_is_some_fixable_none.rs +++ b/tests/ui/search_is_some_fixable_none.rs @@ -27,14 +27,38 @@ fn main() { //~^ search_is_some .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1) .is_none(); + // Check `find().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .find(|&x| { + *x < 0 // + }) + .is_none(); // Check `position().is_none()`, single-line case. let _ = v.iter().position(|&x| x < 0).is_none(); //~^ search_is_some + // Check `position().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .position(|&x| { + x < 0 // + }) + .is_none(); // Check `rposition().is_none()`, single-line case. let _ = v.iter().rposition(|&x| x < 0).is_none(); //~^ search_is_some + // Check `rposition().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .rposition(|&x| { + x < 0 // + }) + .is_none(); let s1 = String::from("hello world"); let s2 = String::from("world"); diff --git a/tests/ui/search_is_some_fixable_none.stderr b/tests/ui/search_is_some_fixable_none.stderr index b079cf7ea361..909248357169 100644 --- a/tests/ui/search_is_some_fixable_none.stderr +++ b/tests/ui/search_is_some_fixable_none.stderr @@ -59,92 +59,158 @@ LL | | .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains( LL | | .is_none(); | |__________________^ help: consider using: `!(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)` +error: called `is_none()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_none.rs:31:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .find(|&x| { +LL | | *x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|x| { +LL + *x < 0 // +LL ~ }); + | + error: called `is_none()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some_fixable_none.rs:32:13 + --> tests/ui/search_is_some_fixable_none.rs:40:13 | LL | let _ = v.iter().position(|&x| x < 0).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|&x| x < 0)` +error: called `is_none()` after searching an `Iterator` with `position` + --> tests/ui/search_is_some_fixable_none.rs:43:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .position(|&x| { +LL | | x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|&x| { +LL + x < 0 // +LL ~ }); + | + error: called `is_none()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some_fixable_none.rs:36:13 + --> tests/ui/search_is_some_fixable_none.rs:52:13 | LL | let _ = v.iter().rposition(|&x| x < 0).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|&x| x < 0)` +error: called `is_none()` after searching an `Iterator` with `rposition` + --> tests/ui/search_is_some_fixable_none.rs:55:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .rposition(|&x| { +LL | | x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|&x| { +LL + x < 0 // +LL ~ }); + | + error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:43:13 + --> tests/ui/search_is_some_fixable_none.rs:67:13 | LL | let _ = "hello world".find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:45:13 + --> tests/ui/search_is_some_fixable_none.rs:69:13 | LL | let _ = "hello world".find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:47:13 + --> tests/ui/search_is_some_fixable_none.rs:71:13 | LL | let _ = "hello world".find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains(&s2[2..])` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:50:13 + --> tests/ui/search_is_some_fixable_none.rs:74:13 | LL | let _ = s1.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:52:13 + --> tests/ui/search_is_some_fixable_none.rs:76:13 | LL | let _ = s1.find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:54:13 + --> tests/ui/search_is_some_fixable_none.rs:78:13 | LL | let _ = s1.find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains(&s2[2..])` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:57:13 + --> tests/ui/search_is_some_fixable_none.rs:81:13 | LL | let _ = s1[2..].find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:59:13 + --> tests/ui/search_is_some_fixable_none.rs:83:13 | LL | let _ = s1[2..].find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:61:13 + --> tests/ui/search_is_some_fixable_none.rs:85:13 | LL | let _ = s1[2..].find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains(&s2[2..])` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:78:25 + --> tests/ui/search_is_some_fixable_none.rs:102:25 | LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_none()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!filter_hand.iter().any(|cc| c == &cc)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:95:30 + --> tests/ui/search_is_some_fixable_none.rs:119:30 | LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_none()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!filter_hand.iter().any(|cc| c == cc)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:107:17 + --> tests/ui/search_is_some_fixable_none.rs:131:17 | LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.foo == 1 && v.bar == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:111:17 + --> tests/ui/search_is_some_fixable_none.rs:135:17 | LL | let _ = vfoo | _________________^ @@ -162,55 +228,55 @@ LL ~ .iter().any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2); | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:120:17 + --> tests/ui/search_is_some_fixable_none.rs:144:17 | LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|a| a[0] == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:127:17 + --> tests/ui/search_is_some_fixable_none.rs:151:17 | LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|sub| sub[1..4].len() == 3)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:146:17 + --> tests/ui/search_is_some_fixable_none.rs:170:17 | LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `![ppx].iter().any(|ppp_x: &&u32| please(ppp_x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:148:17 + --> tests/ui/search_is_some_fixable_none.rs:172:17 | LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `![String::from("Hey hey")].iter().any(|s| s.len() == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:152:17 + --> tests/ui/search_is_some_fixable_none.rs:176:17 | LL | let _ = v.iter().find(|x| deref_enough(**x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| deref_enough(*x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:154:17 + --> tests/ui/search_is_some_fixable_none.rs:178:17 | LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x: &u32| deref_enough(*x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:158:17 + --> tests/ui/search_is_some_fixable_none.rs:182:17 | LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| arg_no_deref(&x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:161:17 + --> tests/ui/search_is_some_fixable_none.rs:185:17 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x: &u32| arg_no_deref(&x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:182:17 + --> tests/ui/search_is_some_fixable_none.rs:206:17 | LL | let _ = vfoo | _________________^ @@ -228,25 +294,25 @@ LL ~ .iter().any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:199:17 + --> tests/ui/search_is_some_fixable_none.rs:223:17 | LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.inner[0].bar == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:205:17 + --> tests/ui/search_is_some_fixable_none.rs:229:17 | LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|x| (**x)[0] == 9)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:219:17 + --> tests/ui/search_is_some_fixable_none.rs:243:17 | LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.by_ref(&v.bar))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:224:17 + --> tests/ui/search_is_some_fixable_none.rs:248:17 | LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)] | _________________^ @@ -264,106 +330,106 @@ LL ~ .iter().any(|&&(&x, ref y)| x == *y); | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:247:17 + --> tests/ui/search_is_some_fixable_none.rs:271:17 | LL | let _ = v.iter().find(|s| s[0].is_empty()).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|s| s[0].is_empty())` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:249:17 + --> tests/ui/search_is_some_fixable_none.rs:273:17 | LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|s| test_string_1(&s[0]))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:259:17 + --> tests/ui/search_is_some_fixable_none.rs:283:17 | LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| fp.field.is_power_of_two())` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:261:17 + --> tests/ui/search_is_some_fixable_none.rs:285:17 | LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| test_u32_1(fp.field))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:263:17 + --> tests/ui/search_is_some_fixable_none.rs:287:17 | LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| test_u32_2(*fp.field))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:280:17 + --> tests/ui/search_is_some_fixable_none.rs:304:17 | LL | let _ = v.iter().find(|x| **x == 42).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:282:17 + --> tests/ui/search_is_some_fixable_none.rs:306:17 | LL | Foo.bar(v.iter().find(|x| **x == 42).is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:288:9 + --> tests/ui/search_is_some_fixable_none.rs:312:9 | LL | v.iter().find(|x| **x == 42).is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:294:9 + --> tests/ui/search_is_some_fixable_none.rs:318:9 | LL | v.iter().find(|x| **x == 42).is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:300:17 + --> tests/ui/search_is_some_fixable_none.rs:324:17 | LL | let _ = s.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:302:17 + --> tests/ui/search_is_some_fixable_none.rs:326:17 | LL | Foo.bar(s.find("world").is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:305:17 + --> tests/ui/search_is_some_fixable_none.rs:329:17 | LL | let _ = s.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:307:17 + --> tests/ui/search_is_some_fixable_none.rs:331:17 | LL | Foo.bar(s.find("world").is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:313:17 + --> tests/ui/search_is_some_fixable_none.rs:337:17 | LL | let _ = s.find("world").is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:316:17 + --> tests/ui/search_is_some_fixable_none.rs:340:17 | LL | let _ = s.find("world").is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:322:17 + --> tests/ui/search_is_some_fixable_none.rs:346:17 | LL | let _ = s.find("world").is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:325:17 + --> tests/ui/search_is_some_fixable_none.rs:349:17 | LL | let _ = s.find("world").is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` -error: aborting due to 54 previous errors +error: aborting due to 57 previous errors diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index c7a4422f3737..1213fdcf6119 100644 --- a/tests/ui/search_is_some_fixable_some.fixed +++ b/tests/ui/search_is_some_fixable_some.fixed @@ -25,14 +25,35 @@ fn main() { //~^ search_is_some let _ = (1..3) .any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1); + // Check `find().is_some()`, multi-line case. + let _ = v + .iter() + .any(|x| { + //~^ search_is_some + *x < 0 + }); // Check `position().is_some()`, single-line case. let _ = v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `position().is_some()`, multi-line case. + let _ = v + .iter() + .any(|&x| { + //~^ search_is_some + x < 0 + }); // Check `rposition().is_some()`, single-line case. let _ = v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `rposition().is_some()`, multi-line case. + let _ = v + .iter() + .any(|&x| { + //~^ search_is_some + x < 0 + }); let s1 = String::from("hello world"); let s2 = String::from("world"); @@ -290,9 +311,19 @@ mod issue9120 { } } +#[allow(clippy::match_like_matches_macro)] fn issue15102() { let values = [None, Some(3)]; let has_even = values.iter().any(|v| matches!(&v, Some(x) if x % 2 == 0)); //~^ search_is_some println!("{has_even}"); + + let has_even = values + .iter() + .any(|v| match &v { + //~^ search_is_some + Some(x) if x % 2 == 0 => true, + _ => false, + }); + println!("{has_even}"); } diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index d6b1c67c9718..4294a39333f2 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -27,14 +27,38 @@ fn main() { .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1) //~^ search_is_some .is_some(); + // Check `find().is_some()`, multi-line case. + let _ = v + .iter() + .find(|&x| { + //~^ search_is_some + *x < 0 + }) + .is_some(); // Check `position().is_some()`, single-line case. let _ = v.iter().position(|&x| x < 0).is_some(); //~^ search_is_some + // Check `position().is_some()`, multi-line case. + let _ = v + .iter() + .position(|&x| { + //~^ search_is_some + x < 0 + }) + .is_some(); // Check `rposition().is_some()`, single-line case. let _ = v.iter().rposition(|&x| x < 0).is_some(); //~^ search_is_some + // Check `rposition().is_some()`, multi-line case. + let _ = v + .iter() + .rposition(|&x| { + //~^ search_is_some + x < 0 + }) + .is_some(); let s1 = String::from("hello world"); let s2 = String::from("world"); @@ -298,9 +322,20 @@ mod issue9120 { } } +#[allow(clippy::match_like_matches_macro)] fn issue15102() { let values = [None, Some(3)]; let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); //~^ search_is_some println!("{has_even}"); + + let has_even = values + .iter() + .find(|v| match v { + //~^ search_is_some + Some(x) if x % 2 == 0 => true, + _ => false, + }) + .is_some(); + println!("{has_even}"); } diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr index 551a670d937f..cee1eb08876b 100644 --- a/tests/ui/search_is_some_fixable_some.stderr +++ b/tests/ui/search_is_some_fixable_some.stderr @@ -58,92 +58,149 @@ LL | | LL | | .is_some(); | |__________________^ help: consider using: `any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)` +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_some.rs:33:10 + | +LL | .find(|&x| { + | __________^ +LL | | +LL | | *x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|x| { +LL + +LL + *x < 0 +LL ~ }); + | + error: called `is_some()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some_fixable_some.rs:32:22 + --> tests/ui/search_is_some_fixable_some.rs:40:22 | LL | let _ = v.iter().position(|&x| x < 0).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|&x| x < 0)` +error: called `is_some()` after searching an `Iterator` with `position` + --> tests/ui/search_is_some_fixable_some.rs:45:10 + | +LL | .position(|&x| { + | __________^ +LL | | +LL | | x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|&x| { +LL + +LL + x < 0 +LL ~ }); + | + error: called `is_some()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some_fixable_some.rs:36:22 + --> tests/ui/search_is_some_fixable_some.rs:52:22 | LL | let _ = v.iter().rposition(|&x| x < 0).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|&x| x < 0)` +error: called `is_some()` after searching an `Iterator` with `rposition` + --> tests/ui/search_is_some_fixable_some.rs:57:10 + | +LL | .rposition(|&x| { + | __________^ +LL | | +LL | | x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|&x| { +LL + +LL + x < 0 +LL ~ }); + | + error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:42:27 + --> tests/ui/search_is_some_fixable_some.rs:66:27 | LL | let _ = "hello world".find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:44:27 + --> tests/ui/search_is_some_fixable_some.rs:68:27 | LL | let _ = "hello world".find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:46:27 + --> tests/ui/search_is_some_fixable_some.rs:70:27 | LL | let _ = "hello world".find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:49:16 + --> tests/ui/search_is_some_fixable_some.rs:73:16 | LL | let _ = s1.find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:51:16 + --> tests/ui/search_is_some_fixable_some.rs:75:16 | LL | let _ = s1.find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:53:16 + --> tests/ui/search_is_some_fixable_some.rs:77:16 | LL | let _ = s1.find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:56:21 + --> tests/ui/search_is_some_fixable_some.rs:80:21 | LL | let _ = s1[2..].find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:58:21 + --> tests/ui/search_is_some_fixable_some.rs:82:21 | LL | let _ = s1[2..].find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:60:21 + --> tests/ui/search_is_some_fixable_some.rs:84:21 | LL | let _ = s1[2..].find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:77:44 + --> tests/ui/search_is_some_fixable_some.rs:101:44 | LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_some()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|cc| c == &cc)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:94:49 + --> tests/ui/search_is_some_fixable_some.rs:118:49 | LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_some()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|cc| c == cc)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:106:29 + --> tests/ui/search_is_some_fixable_some.rs:130:29 | LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.foo == 1 && v.bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:112:14 + --> tests/ui/search_is_some_fixable_some.rs:136:14 | LL | .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2) | ______________^ @@ -152,55 +209,55 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:119:29 + --> tests/ui/search_is_some_fixable_some.rs:143:29 | LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|a| a[0] == 42)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:126:29 + --> tests/ui/search_is_some_fixable_some.rs:150:29 | LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|sub| sub[1..4].len() == 3)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:145:30 + --> tests/ui/search_is_some_fixable_some.rs:169:30 | LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|ppp_x: &&u32| please(ppp_x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:147:50 + --> tests/ui/search_is_some_fixable_some.rs:171:50 | LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| s.len() == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:151:26 + --> tests/ui/search_is_some_fixable_some.rs:175:26 | LL | let _ = v.iter().find(|x| deref_enough(**x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| deref_enough(*x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:153:26 + --> tests/ui/search_is_some_fixable_some.rs:177:26 | LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| deref_enough(*x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:157:26 + --> tests/ui/search_is_some_fixable_some.rs:181:26 | LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| arg_no_deref(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:160:26 + --> tests/ui/search_is_some_fixable_some.rs:184:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:183:14 + --> tests/ui/search_is_some_fixable_some.rs:207:14 | LL | .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2) | ______________^ @@ -209,25 +266,25 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:198:29 + --> tests/ui/search_is_some_fixable_some.rs:222:29 | LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.inner[0].bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:204:29 + --> tests/ui/search_is_some_fixable_some.rs:228:29 | LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| (**x)[0] == 9)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:218:29 + --> tests/ui/search_is_some_fixable_some.rs:242:29 | LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.by_ref(&v.bar))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:225:14 + --> tests/ui/search_is_some_fixable_some.rs:249:14 | LL | .find(|&&&(&x, ref y)| x == *y) | ______________^ @@ -236,64 +293,85 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|&&(&x, ref y)| x == *y)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:246:26 + --> tests/ui/search_is_some_fixable_some.rs:270:26 | LL | let _ = v.iter().find(|s| s[0].is_empty()).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| s[0].is_empty())` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:248:26 + --> tests/ui/search_is_some_fixable_some.rs:272:26 | LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| test_string_1(&s[0]))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:258:26 + --> tests/ui/search_is_some_fixable_some.rs:282:26 | LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| fp.field.is_power_of_two())` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:260:26 + --> tests/ui/search_is_some_fixable_some.rs:284:26 | LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| test_u32_1(fp.field))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:262:26 + --> tests/ui/search_is_some_fixable_some.rs:286:26 | LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| test_u32_2(*fp.field))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:278:18 + --> tests/ui/search_is_some_fixable_some.rs:302:18 | LL | v.iter().find(|x: &&u32| func(x)).is_some() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| func(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:288:26 + --> tests/ui/search_is_some_fixable_some.rs:312:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_impl(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref_impl(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:292:26 + --> tests/ui/search_is_some_fixable_some.rs:316:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_dyn(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref_dyn(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:296:26 + --> tests/ui/search_is_some_fixable_some.rs:320:26 | LL | let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| (*arg_no_deref_dyn)(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:303:34 + --> tests/ui/search_is_some_fixable_some.rs:328:34 | LL | let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| matches!(&v, Some(x) if x % 2 == 0))` -error: aborting due to 47 previous errors +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_some.rs:334:10 + | +LL | .find(|v| match v { + | __________^ +LL | | +LL | | Some(x) if x % 2 == 0 => true, +LL | | _ => false, +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|v| match &v { +LL + +LL + Some(x) if x % 2 == 0 => true, +LL + _ => false, +LL ~ }); + | + +error: aborting due to 51 previous errors diff --git a/tests/ui/semicolon_inside_block.fixed b/tests/ui/semicolon_inside_block.fixed index 7308e78aae26..468f0a5b1e47 100644 --- a/tests/ui/semicolon_inside_block.fixed +++ b/tests/ui/semicolon_inside_block.fixed @@ -3,7 +3,8 @@ clippy::unused_unit, clippy::unnecessary_operation, clippy::no_effect, - clippy::single_element_loop + clippy::single_element_loop, + clippy::double_parens )] #![warn(clippy::semicolon_inside_block)] @@ -87,6 +88,20 @@ fn main() { unit_fn_block() } +#[rustfmt::skip] +fn issue15380() { + ( {0;0}); + + ({ + 0; + 0 + }); + + (({ 0 })) ; + + ( ( { 0 } ) ) ; +} + pub fn issue15388() { #[rustfmt::skip] {0; 0}; diff --git a/tests/ui/semicolon_inside_block.rs b/tests/ui/semicolon_inside_block.rs index 467bf4d779f2..101374af2647 100644 --- a/tests/ui/semicolon_inside_block.rs +++ b/tests/ui/semicolon_inside_block.rs @@ -3,7 +3,8 @@ clippy::unused_unit, clippy::unnecessary_operation, clippy::no_effect, - clippy::single_element_loop + clippy::single_element_loop, + clippy::double_parens )] #![warn(clippy::semicolon_inside_block)] @@ -87,6 +88,20 @@ fn main() { unit_fn_block() } +#[rustfmt::skip] +fn issue15380() { + ( {0;0}); + + ({ + 0; + 0 + }); + + (({ 0 })) ; + + ( ( { 0 } ) ) ; +} + pub fn issue15388() { #[rustfmt::skip] {0; 0}; diff --git a/tests/ui/semicolon_inside_block.stderr b/tests/ui/semicolon_inside_block.stderr index 23433f4e7ef9..2046dd1c36be 100644 --- a/tests/ui/semicolon_inside_block.stderr +++ b/tests/ui/semicolon_inside_block.stderr @@ -1,5 +1,5 @@ error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:38:5 + --> tests/ui/semicolon_inside_block.rs:39:5 | LL | { unit_fn_block() }; | ^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + { unit_fn_block(); } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:40:5 + --> tests/ui/semicolon_inside_block.rs:41:5 | LL | unsafe { unit_fn_block() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + unsafe { unit_fn_block(); } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:49:5 + --> tests/ui/semicolon_inside_block.rs:50:5 | LL | / { LL | | @@ -41,7 +41,7 @@ LL ~ } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:63:5 + --> tests/ui/semicolon_inside_block.rs:64:5 | LL | { m!(()) }; | ^^^^^^^^^^^ diff --git a/tests/ui/shadow.rs b/tests/ui/shadow.rs index 05009b2ddd41..d589bbc4affd 100644 --- a/tests/ui/shadow.rs +++ b/tests/ui/shadow.rs @@ -3,7 +3,7 @@ #![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)] #![allow( clippy::let_unit_value, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_locals )] diff --git a/tests/ui/should_impl_trait/method_list_1.stderr b/tests/ui/should_impl_trait/method_list_1.edition2015.stderr similarity index 86% rename from tests/ui/should_impl_trait/method_list_1.stderr rename to tests/ui/should_impl_trait/method_list_1.edition2015.stderr index 5609d6a21a36..0312fa8f04fa 100644 --- a/tests/ui/should_impl_trait/method_list_1.stderr +++ b/tests/ui/should_impl_trait/method_list_1.edition2015.stderr @@ -1,5 +1,5 @@ error: method `add` can be confused for the standard trait method `std::ops::Add::add` - --> tests/ui/should_impl_trait/method_list_1.rs:24:5 + --> tests/ui/should_impl_trait/method_list_1.rs:27:5 | LL | / pub fn add(self, other: T) -> T { LL | | @@ -13,7 +13,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:30:5 + --> tests/ui/should_impl_trait/method_list_1.rs:33:5 | LL | / pub fn as_mut(&mut self) -> &mut T { LL | | @@ -25,7 +25,7 @@ LL | | } = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref` - --> tests/ui/should_impl_trait/method_list_1.rs:36:5 + --> tests/ui/should_impl_trait/method_list_1.rs:39:5 | LL | / pub fn as_ref(&self) -> &T { LL | | @@ -37,7 +37,7 @@ LL | | } = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand` - --> tests/ui/should_impl_trait/method_list_1.rs:42:5 + --> tests/ui/should_impl_trait/method_list_1.rs:45:5 | LL | / pub fn bitand(self, rhs: T) -> T { LL | | @@ -49,7 +49,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor` - --> tests/ui/should_impl_trait/method_list_1.rs:48:5 + --> tests/ui/should_impl_trait/method_list_1.rs:51:5 | LL | / pub fn bitor(self, rhs: Self) -> Self { LL | | @@ -61,7 +61,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor` - --> tests/ui/should_impl_trait/method_list_1.rs:54:5 + --> tests/ui/should_impl_trait/method_list_1.rs:57:5 | LL | / pub fn bitxor(self, rhs: Self) -> Self { LL | | @@ -73,7 +73,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow` - --> tests/ui/should_impl_trait/method_list_1.rs:60:5 + --> tests/ui/should_impl_trait/method_list_1.rs:63:5 | LL | / pub fn borrow(&self) -> &str { LL | | @@ -85,7 +85,7 @@ LL | | } = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:66:5 + --> tests/ui/should_impl_trait/method_list_1.rs:69:5 | LL | / pub fn borrow_mut(&mut self) -> &mut str { LL | | @@ -97,7 +97,7 @@ LL | | } = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone` - --> tests/ui/should_impl_trait/method_list_1.rs:72:5 + --> tests/ui/should_impl_trait/method_list_1.rs:75:5 | LL | / pub fn clone(&self) -> Self { LL | | @@ -109,7 +109,7 @@ LL | | } = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp` - --> tests/ui/should_impl_trait/method_list_1.rs:78:5 + --> tests/ui/should_impl_trait/method_list_1.rs:81:5 | LL | / pub fn cmp(&self, other: &Self) -> Self { LL | | @@ -121,7 +121,7 @@ LL | | } = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name error: method `default` can be confused for the standard trait method `std::default::Default::default` - --> tests/ui/should_impl_trait/method_list_1.rs:84:5 + --> tests/ui/should_impl_trait/method_list_1.rs:87:5 | LL | / pub fn default() -> Self { LL | | @@ -133,7 +133,7 @@ LL | | } = help: consider implementing the trait `std::default::Default` or choosing a less ambiguous method name error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref` - --> tests/ui/should_impl_trait/method_list_1.rs:90:5 + --> tests/ui/should_impl_trait/method_list_1.rs:93:5 | LL | / pub fn deref(&self) -> &Self { LL | | @@ -145,7 +145,7 @@ LL | | } = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:96:5 + --> tests/ui/should_impl_trait/method_list_1.rs:99:5 | LL | / pub fn deref_mut(&mut self) -> &mut Self { LL | | @@ -157,7 +157,7 @@ LL | | } = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name error: method `div` can be confused for the standard trait method `std::ops::Div::div` - --> tests/ui/should_impl_trait/method_list_1.rs:102:5 + --> tests/ui/should_impl_trait/method_list_1.rs:105:5 | LL | / pub fn div(self, rhs: Self) -> Self { LL | | @@ -169,7 +169,7 @@ LL | | } = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop` - --> tests/ui/should_impl_trait/method_list_1.rs:108:5 + --> tests/ui/should_impl_trait/method_list_1.rs:111:5 | LL | / pub fn drop(&mut self) { LL | | diff --git a/tests/ui/should_impl_trait/method_list_1.edition2021.stderr b/tests/ui/should_impl_trait/method_list_1.edition2021.stderr new file mode 100644 index 000000000000..0312fa8f04fa --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_1.edition2021.stderr @@ -0,0 +1,184 @@ +error: method `add` can be confused for the standard trait method `std::ops::Add::add` + --> tests/ui/should_impl_trait/method_list_1.rs:27:5 + | +LL | / pub fn add(self, other: T) -> T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Add` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:33:5 + | +LL | / pub fn as_mut(&mut self) -> &mut T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name + +error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref` + --> tests/ui/should_impl_trait/method_list_1.rs:39:5 + | +LL | / pub fn as_ref(&self) -> &T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name + +error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand` + --> tests/ui/should_impl_trait/method_list_1.rs:45:5 + | +LL | / pub fn bitand(self, rhs: T) -> T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name + +error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor` + --> tests/ui/should_impl_trait/method_list_1.rs:51:5 + | +LL | / pub fn bitor(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name + +error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor` + --> tests/ui/should_impl_trait/method_list_1.rs:57:5 + | +LL | / pub fn bitxor(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name + +error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow` + --> tests/ui/should_impl_trait/method_list_1.rs:63:5 + | +LL | / pub fn borrow(&self) -> &str { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name + +error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:69:5 + | +LL | / pub fn borrow_mut(&mut self) -> &mut str { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name + +error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone` + --> tests/ui/should_impl_trait/method_list_1.rs:75:5 + | +LL | / pub fn clone(&self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name + +error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp` + --> tests/ui/should_impl_trait/method_list_1.rs:81:5 + | +LL | / pub fn cmp(&self, other: &Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name + +error: method `default` can be confused for the standard trait method `std::default::Default::default` + --> tests/ui/should_impl_trait/method_list_1.rs:87:5 + | +LL | / pub fn default() -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::default::Default` or choosing a less ambiguous method name + +error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref` + --> tests/ui/should_impl_trait/method_list_1.rs:93:5 + | +LL | / pub fn deref(&self) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name + +error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:99:5 + | +LL | / pub fn deref_mut(&mut self) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name + +error: method `div` can be confused for the standard trait method `std::ops::Div::div` + --> tests/ui/should_impl_trait/method_list_1.rs:105:5 + | +LL | / pub fn div(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name + +error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop` + --> tests/ui/should_impl_trait/method_list_1.rs:111:5 + | +LL | / pub fn drop(&mut self) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Drop` or choosing a less ambiguous method name + +error: aborting due to 15 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_1.rs b/tests/ui/should_impl_trait/method_list_1.rs index e8de0e04c0c4..bbb04c0c5aa1 100644 --- a/tests/ui/should_impl_trait/method_list_1.rs +++ b/tests/ui/should_impl_trait/method_list_1.rs @@ -1,3 +1,6 @@ +//@revisions: edition2015 edition2021 +//@[edition2015] edition:2015 +//@[edition2021] edition:2021 #![allow( clippy::missing_errors_doc, clippy::needless_pass_by_value, diff --git a/tests/ui/should_impl_trait/method_list_2.edition2015.stderr b/tests/ui/should_impl_trait/method_list_2.edition2015.stderr new file mode 100644 index 000000000000..259815908fee --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_2.edition2015.stderr @@ -0,0 +1,172 @@ +error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq` + --> tests/ui/should_impl_trait/method_list_2.rs:28:5 + | +LL | / pub fn eq(&self, other: &Self) -> bool { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str` + --> tests/ui/should_impl_trait/method_list_2.rs:40:5 + | +LL | / pub fn from_str(s: &str) -> Result { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name + +error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash` + --> tests/ui/should_impl_trait/method_list_2.rs:46:5 + | +LL | / pub fn hash(&self, state: &mut T) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name + +error: method `index` can be confused for the standard trait method `std::ops::Index::index` + --> tests/ui/should_impl_trait/method_list_2.rs:52:5 + | +LL | / pub fn index(&self, index: usize) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name + +error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut` + --> tests/ui/should_impl_trait/method_list_2.rs:58:5 + | +LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name + +error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:64:5 + | +LL | / pub fn into_iter(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + +error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul` + --> tests/ui/should_impl_trait/method_list_2.rs:70:5 + | +LL | / pub fn mul(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name + +error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg` + --> tests/ui/should_impl_trait/method_list_2.rs:76:5 + | +LL | / pub fn neg(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name + +error: method `next` can be confused for the standard trait method `std::iter::Iterator::next` + --> tests/ui/should_impl_trait/method_list_2.rs:82:5 + | +LL | / pub fn next(&mut self) -> Option { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name + +error: method `not` can be confused for the standard trait method `std::ops::Not::not` + --> tests/ui/should_impl_trait/method_list_2.rs:88:5 + | +LL | / pub fn not(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name + +error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem` + --> tests/ui/should_impl_trait/method_list_2.rs:94:5 + | +LL | / pub fn rem(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name + +error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl` + --> tests/ui/should_impl_trait/method_list_2.rs:100:5 + | +LL | / pub fn shl(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name + +error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr` + --> tests/ui/should_impl_trait/method_list_2.rs:106:5 + | +LL | / pub fn shr(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name + +error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub` + --> tests/ui/should_impl_trait/method_list_2.rs:112:5 + | +LL | / pub fn sub(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name + +error: aborting due to 14 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_2.edition2021.stderr b/tests/ui/should_impl_trait/method_list_2.edition2021.stderr new file mode 100644 index 000000000000..2f90b61e7a17 --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_2.edition2021.stderr @@ -0,0 +1,184 @@ +error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq` + --> tests/ui/should_impl_trait/method_list_2.rs:28:5 + | +LL | / pub fn eq(&self, other: &Self) -> bool { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `from_iter` can be confused for the standard trait method `std::iter::FromIterator::from_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:34:5 + | +LL | / pub fn from_iter(iter: T) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::FromIterator` or choosing a less ambiguous method name + +error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str` + --> tests/ui/should_impl_trait/method_list_2.rs:40:5 + | +LL | / pub fn from_str(s: &str) -> Result { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name + +error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash` + --> tests/ui/should_impl_trait/method_list_2.rs:46:5 + | +LL | / pub fn hash(&self, state: &mut T) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name + +error: method `index` can be confused for the standard trait method `std::ops::Index::index` + --> tests/ui/should_impl_trait/method_list_2.rs:52:5 + | +LL | / pub fn index(&self, index: usize) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name + +error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut` + --> tests/ui/should_impl_trait/method_list_2.rs:58:5 + | +LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name + +error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:64:5 + | +LL | / pub fn into_iter(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + +error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul` + --> tests/ui/should_impl_trait/method_list_2.rs:70:5 + | +LL | / pub fn mul(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name + +error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg` + --> tests/ui/should_impl_trait/method_list_2.rs:76:5 + | +LL | / pub fn neg(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name + +error: method `next` can be confused for the standard trait method `std::iter::Iterator::next` + --> tests/ui/should_impl_trait/method_list_2.rs:82:5 + | +LL | / pub fn next(&mut self) -> Option { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name + +error: method `not` can be confused for the standard trait method `std::ops::Not::not` + --> tests/ui/should_impl_trait/method_list_2.rs:88:5 + | +LL | / pub fn not(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name + +error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem` + --> tests/ui/should_impl_trait/method_list_2.rs:94:5 + | +LL | / pub fn rem(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name + +error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl` + --> tests/ui/should_impl_trait/method_list_2.rs:100:5 + | +LL | / pub fn shl(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name + +error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr` + --> tests/ui/should_impl_trait/method_list_2.rs:106:5 + | +LL | / pub fn shr(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name + +error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub` + --> tests/ui/should_impl_trait/method_list_2.rs:112:5 + | +LL | / pub fn sub(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name + +error: aborting due to 15 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_2.rs b/tests/ui/should_impl_trait/method_list_2.rs index 1f25ab3938a3..4dfbe7e0f9f4 100644 --- a/tests/ui/should_impl_trait/method_list_2.rs +++ b/tests/ui/should_impl_trait/method_list_2.rs @@ -1,3 +1,6 @@ +//@revisions: edition2015 edition2021 +//@[edition2015] edition:2015 +//@[edition2021] edition:2021 #![allow( clippy::missing_errors_doc, clippy::needless_pass_by_value, @@ -29,7 +32,7 @@ impl T { } pub fn from_iter(iter: T) -> Self { - //~^ should_implement_trait + //~[edition2021]^ should_implement_trait unimplemented!() } diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed index db5107600ee6..fe28674ec18e 100644 --- a/tests/ui/single_match.fixed +++ b/tests/ui/single_match.fixed @@ -3,7 +3,7 @@ #![allow( unused, clippy::uninlined_format_args, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_pattern_matching, clippy::manual_unwrap_or_default @@ -218,7 +218,7 @@ fn main() { }; } -fn issue_10808(bar: Option) { +fn issue10808(bar: Option) { if let Some(v) = bar { unsafe { let r = &v as *const i32; println!("{}", *r); @@ -330,6 +330,7 @@ pub struct Data([u8; 4]); const DATA: Data = Data([1, 2, 3, 4]); const CONST_I32: i32 = 1; +// https://github.com/rust-lang/rust-clippy/issues/13012 fn irrefutable_match() { println!(); //~^^^^ single_match @@ -367,7 +368,7 @@ fn irrefutable_match() { //~| NOTE: you might want to preserve the comments from inside the `match` } -fn issue_14493() { +fn issue14493() { macro_rules! mac { (some) => { Some(42) diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index a367b94c4ca6..0f558cb5f786 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -3,7 +3,7 @@ #![allow( unused, clippy::uninlined_format_args, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_pattern_matching, clippy::manual_unwrap_or_default @@ -269,7 +269,7 @@ fn main() { }; } -fn issue_10808(bar: Option) { +fn issue10808(bar: Option) { match bar { Some(v) => unsafe { let r = &v as *const i32; @@ -397,6 +397,7 @@ pub struct Data([u8; 4]); const DATA: Data = Data([1, 2, 3, 4]); const CONST_I32: i32 = 1; +// https://github.com/rust-lang/rust-clippy/issues/13012 fn irrefutable_match() { match DATA { DATA => println!(), @@ -462,7 +463,7 @@ fn irrefutable_match() { //~| NOTE: you might want to preserve the comments from inside the `match` } -fn issue_14493() { +fn issue14493() { macro_rules! mac { (some) => { Some(42) diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index 1a4edc45c928..ba8bc5af5a60 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -225,7 +225,7 @@ LL | | } | |_____^ help: try: `if &s[0..3] == b"foo" { println!() }` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:401:5 + --> tests/ui/single_match.rs:402:5 | LL | / match DATA { LL | | DATA => println!(), @@ -234,7 +234,7 @@ LL | | } | |_____^ help: try: `println!();` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:407:5 + --> tests/ui/single_match.rs:408:5 | LL | / match CONST_I32 { LL | | CONST_I32 => println!(), @@ -243,7 +243,7 @@ LL | | } | |_____^ help: try: `println!();` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:414:5 + --> tests/ui/single_match.rs:415:5 | LL | / match i { LL | | i => { @@ -263,7 +263,7 @@ LL + } | error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:423:5 + --> tests/ui/single_match.rs:424:5 | LL | / match i { LL | | i => {}, @@ -272,7 +272,7 @@ LL | | } | |_____^ help: `match` expression can be removed error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:429:5 + --> tests/ui/single_match.rs:430:5 | LL | / match i { LL | | i => (), @@ -281,7 +281,7 @@ LL | | } | |_____^ help: `match` expression can be removed error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:435:5 + --> tests/ui/single_match.rs:436:5 | LL | / match CONST_I32 { LL | | CONST_I32 => println!(), @@ -290,7 +290,7 @@ LL | | } | |_____^ help: try: `println!();` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:443:5 + --> tests/ui/single_match.rs:444:5 | LL | / match x.pop() { LL | | // bla @@ -302,7 +302,7 @@ LL | | } = note: you might want to preserve the comments from inside the `match` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:452:5 + --> tests/ui/single_match.rs:453:5 | LL | / match x.pop() { LL | | // bla @@ -322,7 +322,7 @@ LL + } | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:478:5 + --> tests/ui/single_match.rs:479:5 | LL | / match mac!(some) { LL | | Some(u) => println!("{u}"), @@ -331,7 +331,7 @@ LL | | } | |_____^ help: try: `if let Some(u) = mac!(some) { println!("{u}") }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> tests/ui/single_match.rs:486:5 + --> tests/ui/single_match.rs:487:5 | LL | / match mac!(str) { LL | | "foo" => println!("eq"), diff --git a/tests/ui/single_match_else_deref_patterns.fixed b/tests/ui/single_match_else_deref_patterns.fixed index 7a9f80630964..1743ae6bf5a7 100644 --- a/tests/ui/single_match_else_deref_patterns.fixed +++ b/tests/ui/single_match_else_deref_patterns.fixed @@ -5,7 +5,7 @@ clippy::op_ref, clippy::deref_addrof, clippy::borrow_deref_ref, - clippy::needless_if + clippy::needless_ifs )] #![deny(clippy::single_match_else)] diff --git a/tests/ui/single_match_else_deref_patterns.rs b/tests/ui/single_match_else_deref_patterns.rs index ef19c7cbde2b..d5cb8a24a609 100644 --- a/tests/ui/single_match_else_deref_patterns.rs +++ b/tests/ui/single_match_else_deref_patterns.rs @@ -5,7 +5,7 @@ clippy::op_ref, clippy::deref_addrof, clippy::borrow_deref_ref, - clippy::needless_if + clippy::needless_ifs )] #![deny(clippy::single_match_else)] diff --git a/tests/ui/starts_ends_with.fixed b/tests/ui/starts_ends_with.fixed index b4f9d5867703..f2fab9217604 100644 --- a/tests/ui/starts_ends_with.fixed +++ b/tests/ui/starts_ends_with.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if, dead_code, unused_must_use, clippy::double_ended_iterator_last)] +#![allow(clippy::needless_ifs, dead_code, unused_must_use, clippy::double_ended_iterator_last)] fn main() {} diff --git a/tests/ui/starts_ends_with.rs b/tests/ui/starts_ends_with.rs index dae04ac4a62d..1460d77a094d 100644 --- a/tests/ui/starts_ends_with.rs +++ b/tests/ui/starts_ends_with.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if, dead_code, unused_must_use, clippy::double_ended_iterator_last)] +#![allow(clippy::needless_ifs, dead_code, unused_must_use, clippy::double_ended_iterator_last)] fn main() {} diff --git a/tests/ui/suspicious_else_formatting.rs b/tests/ui/suspicious_else_formatting.rs index 072e7b27b0d0..8574cf1c20a1 100644 --- a/tests/ui/suspicious_else_formatting.rs +++ b/tests/ui/suspicious_else_formatting.rs @@ -4,7 +4,7 @@ #![allow( clippy::if_same_then_else, clippy::let_unit_value, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] diff --git a/tests/ui/suspicious_unary_op_formatting.rs b/tests/ui/suspicious_unary_op_formatting.rs index ab9bdde6cf90..19f8b231925b 100644 --- a/tests/ui/suspicious_unary_op_formatting.rs +++ b/tests/ui/suspicious_unary_op_formatting.rs @@ -1,5 +1,5 @@ #![warn(clippy::suspicious_unary_op_formatting)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[rustfmt::skip] fn main() { diff --git a/tests/ui/test_attr_in_doctest.rs b/tests/ui/test_attr_in_doctest.rs index 6cffd813b835..7d1a09024895 100644 --- a/tests/ui/test_attr_in_doctest.rs +++ b/tests/ui/test_attr_in_doctest.rs @@ -21,7 +21,6 @@ /// } /// /// #[test] -//~^ test_attr_in_doctest /// fn should_be_linted_too() { /// assert_eq!("#[test]", " /// #[test] diff --git a/tests/ui/test_attr_in_doctest.stderr b/tests/ui/test_attr_in_doctest.stderr index 166b9b417b62..a8fa53034036 100644 --- a/tests/ui/test_attr_in_doctest.stderr +++ b/tests/ui/test_attr_in_doctest.stderr @@ -1,11 +1,8 @@ error: unit tests in doctest are not executed --> tests/ui/test_attr_in_doctest.rs:6:5 | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_be_linted() { - | |_______________________^ +LL | /// #[test] + | ^^^^^^^ | = note: `-D clippy::test-attr-in-doctest` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::test_attr_in_doctest)]` @@ -13,20 +10,11 @@ LL | | /// fn should_be_linted() { error: unit tests in doctest are not executed --> tests/ui/test_attr_in_doctest.rs:16:5 | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_also_be_linted() { - | |____________________________^ +LL | /// #[test] + | ^^^^^^^ +... +LL | /// #[test] + | ^^^^^^^ -error: unit tests in doctest are not executed - --> tests/ui/test_attr_in_doctest.rs:23:5 - | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_be_linted_too() { - | |___________________________^ - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/too_long_first_doc_paragraph.stderr b/tests/ui/too_long_first_doc_paragraph.stderr index f3f182aa5422..949ada30dc97 100644 --- a/tests/ui/too_long_first_doc_paragraph.stderr +++ b/tests/ui/too_long_first_doc_paragraph.stderr @@ -42,7 +42,7 @@ LL | | /// gravida non lacinia at, rhoncus eu lacus. error: first doc comment paragraph is too long --> tests/ui/too_long_first_doc_paragraph.rs:65:1 | -LL | / /// Some function. This doc-string paragraph is too long. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lore... +LL | / /// Some function. This doc-string paragraph is too long. Lorem Ipsum is simply dummy text of the printing and typesetting industr... LL | | LL | | /// LL | | /// Here's a second paragraph. It would be preferable to put the details here. diff --git a/tests/ui/track-diagnostics-clippy.stderr b/tests/ui/track-diagnostics-clippy.stderr index d5533877b451..3ceb501463b7 100644 --- a/tests/ui/track-diagnostics-clippy.stderr +++ b/tests/ui/track-diagnostics-clippy.stderr @@ -16,7 +16,7 @@ LL | let d = 42; LL | d | ^ | - = note: -Ztrack-diagnostics: created at clippy_lints/src/returns.rs:LL:CC + = note: -Ztrack-diagnostics: created at clippy_lints/src/returns/let_and_return.rs:LL:CC = note: `-D clippy::let-and-return` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_and_return)]` help: return the expression directly diff --git a/tests/ui/transmute_ptr_to_ref.fixed b/tests/ui/transmute_ptr_to_ref.fixed index 61e3ac2fe88e..c130575df960 100644 --- a/tests/ui/transmute_ptr_to_ref.fixed +++ b/tests/ui/transmute_ptr_to_ref.fixed @@ -5,7 +5,7 @@ clippy::missing_transmute_annotations )] -unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { +fn ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { unsafe { let _: &T = &*p; //~^ transmute_ptr_to_ref @@ -37,7 +37,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { } } -fn _issue1231() { +fn issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -55,7 +55,7 @@ fn _issue1231() { //~^ transmute_ptr_to_ref } -unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { +fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { unsafe { match 0 { 0 => &*x.cast::<&u32>(), @@ -71,7 +71,7 @@ unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &' } #[clippy::msrv = "1.38"] -unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -89,7 +89,7 @@ unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } #[clippy::msrv = "1.37"] -unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -106,4 +106,33 @@ unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } } +// handle DSTs +fn issue13357(ptr: *const [i32], s_ptr: *const &str, a_s_ptr: *const [&str]) { + unsafe { + // different types, without erased regions + let _ = &*(ptr as *const [u32]); + //~^ transmute_ptr_to_ref + let _: &[u32] = &*(ptr as *const [u32]); + //~^ transmute_ptr_to_ref + + // different types, with erased regions + let _ = &*(a_s_ptr as *const [&[u8]]); + //~^ transmute_ptr_to_ref + let _: &[&[u8]] = &*(a_s_ptr as *const [&[u8]]); + //~^ transmute_ptr_to_ref + + // same type, without erased regions + let _ = &*(ptr as *const [i32]); + //~^ transmute_ptr_to_ref + let _: &[i32] = &*ptr; + //~^ transmute_ptr_to_ref + + // same type, with erased regions + let _ = &*(a_s_ptr as *const [&str]); + //~^ transmute_ptr_to_ref + let _: &[&str] = &*(a_s_ptr as *const [&str]); + //~^ transmute_ptr_to_ref + } +} + fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.rs b/tests/ui/transmute_ptr_to_ref.rs index 48e2f527b554..f79d54234a2c 100644 --- a/tests/ui/transmute_ptr_to_ref.rs +++ b/tests/ui/transmute_ptr_to_ref.rs @@ -5,7 +5,7 @@ clippy::missing_transmute_annotations )] -unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { +fn ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { unsafe { let _: &T = std::mem::transmute(p); //~^ transmute_ptr_to_ref @@ -37,7 +37,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { } } -fn _issue1231() { +fn issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -55,7 +55,7 @@ fn _issue1231() { //~^ transmute_ptr_to_ref } -unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { +fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { unsafe { match 0 { 0 => std::mem::transmute(x), @@ -71,7 +71,7 @@ unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &' } #[clippy::msrv = "1.38"] -unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -89,7 +89,7 @@ unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } #[clippy::msrv = "1.37"] -unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -106,4 +106,33 @@ unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } } +// handle DSTs +fn issue13357(ptr: *const [i32], s_ptr: *const &str, a_s_ptr: *const [&str]) { + unsafe { + // different types, without erased regions + let _ = core::mem::transmute::<_, &[u32]>(ptr); + //~^ transmute_ptr_to_ref + let _: &[u32] = core::mem::transmute(ptr); + //~^ transmute_ptr_to_ref + + // different types, with erased regions + let _ = core::mem::transmute::<_, &[&[u8]]>(a_s_ptr); + //~^ transmute_ptr_to_ref + let _: &[&[u8]] = core::mem::transmute(a_s_ptr); + //~^ transmute_ptr_to_ref + + // same type, without erased regions + let _ = core::mem::transmute::<_, &[i32]>(ptr); + //~^ transmute_ptr_to_ref + let _: &[i32] = core::mem::transmute(ptr); + //~^ transmute_ptr_to_ref + + // same type, with erased regions + let _ = core::mem::transmute::<_, &[&str]>(a_s_ptr); + //~^ transmute_ptr_to_ref + let _: &[&str] = core::mem::transmute(a_s_ptr); + //~^ transmute_ptr_to_ref + } +} + fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.stderr b/tests/ui/transmute_ptr_to_ref.stderr index 7685c345c861..3f404d295fef 100644 --- a/tests/ui/transmute_ptr_to_ref.stderr +++ b/tests/ui/transmute_ptr_to_ref.stderr @@ -43,13 +43,13 @@ error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) LL | let _: &T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` -error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<'_, u8>`) +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<'_, u8>`) --> tests/ui/transmute_ptr_to_ref.rs:46:32 | LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` -error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<'_, &u8>`) +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<'_, &u8>`) --> tests/ui/transmute_ptr_to_ref.rs:49:33 | LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; @@ -133,5 +133,53 @@ error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32 LL | _ => std::mem::transmute::<_, &&'b u32>(x), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &'b u32)` -error: aborting due to 22 previous errors +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`) + --> tests/ui/transmute_ptr_to_ref.rs:113:17 + | +LL | let _ = core::mem::transmute::<_, &[u32]>(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`) + --> tests/ui/transmute_ptr_to_ref.rs:115:25 + | +LL | let _: &[u32] = core::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`) + --> tests/ui/transmute_ptr_to_ref.rs:119:17 + | +LL | let _ = core::mem::transmute::<_, &[&[u8]]>(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`) + --> tests/ui/transmute_ptr_to_ref.rs:121:27 + | +LL | let _: &[&[u8]] = core::mem::transmute(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`) + --> tests/ui/transmute_ptr_to_ref.rs:125:17 + | +LL | let _ = core::mem::transmute::<_, &[i32]>(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [i32])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`) + --> tests/ui/transmute_ptr_to_ref.rs:127:25 + | +LL | let _: &[i32] = core::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*ptr` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`) + --> tests/ui/transmute_ptr_to_ref.rs:131:17 + | +LL | let _ = core::mem::transmute::<_, &[&str]>(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`) + --> tests/ui/transmute_ptr_to_ref.rs:133:26 + | +LL | let _: &[&str] = core::mem::transmute(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])` + +error: aborting due to 30 previous errors diff --git a/tests/ui/unchecked_duration_subtraction.fixed b/tests/ui/unchecked_duration_subtraction.fixed deleted file mode 100644 index bddffe44ac4d..000000000000 --- a/tests/ui/unchecked_duration_subtraction.fixed +++ /dev/null @@ -1,20 +0,0 @@ -#![warn(clippy::unchecked_duration_subtraction)] - -use std::time::{Duration, Instant}; - -fn main() { - let _first = Instant::now(); - let second = Duration::from_secs(3); - - let _ = _first.checked_sub(second).unwrap(); - //~^ unchecked_duration_subtraction - - let _ = Instant::now().checked_sub(Duration::from_secs(5)).unwrap(); - //~^ unchecked_duration_subtraction - - let _ = _first.checked_sub(Duration::from_secs(5)).unwrap(); - //~^ unchecked_duration_subtraction - - let _ = Instant::now().checked_sub(second).unwrap(); - //~^ unchecked_duration_subtraction -} diff --git a/tests/ui/unchecked_duration_subtraction.rs b/tests/ui/unchecked_duration_subtraction.rs deleted file mode 100644 index bb0f71239642..000000000000 --- a/tests/ui/unchecked_duration_subtraction.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![warn(clippy::unchecked_duration_subtraction)] - -use std::time::{Duration, Instant}; - -fn main() { - let _first = Instant::now(); - let second = Duration::from_secs(3); - - let _ = _first - second; - //~^ unchecked_duration_subtraction - - let _ = Instant::now() - Duration::from_secs(5); - //~^ unchecked_duration_subtraction - - let _ = _first - Duration::from_secs(5); - //~^ unchecked_duration_subtraction - - let _ = Instant::now() - second; - //~^ unchecked_duration_subtraction -} diff --git a/tests/ui/unchecked_duration_subtraction.stderr b/tests/ui/unchecked_duration_subtraction.stderr deleted file mode 100644 index be291c320e68..000000000000 --- a/tests/ui/unchecked_duration_subtraction.stderr +++ /dev/null @@ -1,29 +0,0 @@ -error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:9:13 - | -LL | let _ = _first - second; - | ^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(second).unwrap()` - | - = note: `-D clippy::unchecked-duration-subtraction` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::unchecked_duration_subtraction)]` - -error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:12:13 - | -LL | let _ = Instant::now() - Duration::from_secs(5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(Duration::from_secs(5)).unwrap()` - -error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:15:13 - | -LL | let _ = _first - Duration::from_secs(5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(Duration::from_secs(5)).unwrap()` - -error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:18:13 - | -LL | let _ = Instant::now() - second; - | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()` - -error: aborting due to 4 previous errors - diff --git a/tests/ui/unchecked_time_subtraction.fixed b/tests/ui/unchecked_time_subtraction.fixed new file mode 100644 index 000000000000..2f923fef4c25 --- /dev/null +++ b/tests/ui/unchecked_time_subtraction.fixed @@ -0,0 +1,37 @@ +#![warn(clippy::unchecked_time_subtraction)] + +use std::time::{Duration, Instant}; + +fn main() { + let _first = Instant::now(); + let second = Duration::from_secs(3); + + let _ = _first.checked_sub(second).unwrap(); + //~^ unchecked_time_subtraction + + let _ = Instant::now().checked_sub(Duration::from_secs(5)).unwrap(); + //~^ unchecked_time_subtraction + + let _ = _first.checked_sub(Duration::from_secs(5)).unwrap(); + //~^ unchecked_time_subtraction + + let _ = Instant::now().checked_sub(second).unwrap(); + //~^ unchecked_time_subtraction + + // Duration - Duration cases + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + + let _ = dur1.checked_sub(dur2).unwrap(); + //~^ unchecked_time_subtraction + + let _ = Duration::from_secs(10).checked_sub(Duration::from_secs(5)).unwrap(); + //~^ unchecked_time_subtraction + + let _ = second.checked_sub(dur1).unwrap(); + //~^ unchecked_time_subtraction + + // Duration multiplication and subtraction + let _ = (2 * dur1).checked_sub(dur2).unwrap(); + //~^ unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction.rs b/tests/ui/unchecked_time_subtraction.rs new file mode 100644 index 000000000000..cf727f62aafa --- /dev/null +++ b/tests/ui/unchecked_time_subtraction.rs @@ -0,0 +1,37 @@ +#![warn(clippy::unchecked_time_subtraction)] + +use std::time::{Duration, Instant}; + +fn main() { + let _first = Instant::now(); + let second = Duration::from_secs(3); + + let _ = _first - second; + //~^ unchecked_time_subtraction + + let _ = Instant::now() - Duration::from_secs(5); + //~^ unchecked_time_subtraction + + let _ = _first - Duration::from_secs(5); + //~^ unchecked_time_subtraction + + let _ = Instant::now() - second; + //~^ unchecked_time_subtraction + + // Duration - Duration cases + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + + let _ = dur1 - dur2; + //~^ unchecked_time_subtraction + + let _ = Duration::from_secs(10) - Duration::from_secs(5); + //~^ unchecked_time_subtraction + + let _ = second - dur1; + //~^ unchecked_time_subtraction + + // Duration multiplication and subtraction + let _ = 2 * dur1 - dur2; + //~^ unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction.stderr b/tests/ui/unchecked_time_subtraction.stderr new file mode 100644 index 000000000000..7a39712269cf --- /dev/null +++ b/tests/ui/unchecked_time_subtraction.stderr @@ -0,0 +1,53 @@ +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction.rs:9:13 + | +LL | let _ = _first - second; + | ^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(second).unwrap()` + | + = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction.rs:12:13 + | +LL | let _ = Instant::now() - Duration::from_secs(5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(Duration::from_secs(5)).unwrap()` + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction.rs:15:13 + | +LL | let _ = _first - Duration::from_secs(5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(Duration::from_secs(5)).unwrap()` + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction.rs:18:13 + | +LL | let _ = Instant::now() - second; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:25:13 + | +LL | let _ = dur1 - dur2; + | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:28:13 + | +LL | let _ = Duration::from_secs(10) - Duration::from_secs(5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::from_secs(10).checked_sub(Duration::from_secs(5)).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:31:13 + | +LL | let _ = second - dur1; + | ^^^^^^^^^^^^^ help: try: `second.checked_sub(dur1).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:35:13 + | +LL | let _ = 2 * dur1 - dur2; + | ^^^^^^^^^^^^^^^ help: try: `(2 * dur1).checked_sub(dur2).unwrap()` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/unchecked_time_subtraction_unfixable.rs b/tests/ui/unchecked_time_subtraction_unfixable.rs new file mode 100644 index 000000000000..4b6a5ca15620 --- /dev/null +++ b/tests/ui/unchecked_time_subtraction_unfixable.rs @@ -0,0 +1,22 @@ +#![warn(clippy::unchecked_time_subtraction)] +//@no-rustfix + +use std::time::{Duration, Instant}; + +fn main() { + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + let dur3 = Duration::from_secs(1); + + // Chained Duration subtraction - should lint without suggestion due to complexity + let _ = dur1 - dur2 - dur3; + //~^ unchecked_time_subtraction + //~| unchecked_time_subtraction + + // Chained Instant - Duration subtraction - should lint without suggestion due to complexity + let instant1 = Instant::now(); + + let _ = instant1 - dur2 - dur3; + //~^ unchecked_time_subtraction + //~| unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction_unfixable.stderr b/tests/ui/unchecked_time_subtraction_unfixable.stderr new file mode 100644 index 000000000000..c25c112b06ce --- /dev/null +++ b/tests/ui/unchecked_time_subtraction_unfixable.stderr @@ -0,0 +1,29 @@ +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 + | +LL | let _ = dur1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 + | +LL | let _ = dur1 - dur2 - dur3; + | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 + | +LL | let _ = instant1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 + | +LL | let _ = instant1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^ help: try: `instant1.checked_sub(dur2).unwrap()` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/unit_cmp.rs b/tests/ui/unit_cmp.rs index 0a0fe3a1990b..839987a474cd 100644 --- a/tests/ui/unit_cmp.rs +++ b/tests/ui/unit_cmp.rs @@ -3,7 +3,7 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #[derive(PartialEq)] diff --git a/tests/ui/unnecessary_clone.rs b/tests/ui/unnecessary_clone.rs deleted file mode 100644 index 7335b6f9f039..000000000000 --- a/tests/ui/unnecessary_clone.rs +++ /dev/null @@ -1,111 +0,0 @@ -// does not test any rustfixable lints -#![warn(clippy::clone_on_ref_ptr)] -#![allow(unused)] -#![allow(clippy::redundant_clone, clippy::uninlined_format_args, clippy::unnecessary_wraps)] -//@no-rustfix -use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; - -trait SomeTrait {} -struct SomeImpl; -impl SomeTrait for SomeImpl {} - -fn main() {} - -fn clone_on_ref_ptr() { - let rc = Rc::new(true); - let arc = Arc::new(true); - - let rcweak = Rc::downgrade(&rc); - let arc_weak = Arc::downgrade(&arc); - - rc.clone(); - //~^ clone_on_ref_ptr - - Rc::clone(&rc); - - arc.clone(); - //~^ clone_on_ref_ptr - - Arc::clone(&arc); - - rcweak.clone(); - //~^ clone_on_ref_ptr - - rc::Weak::clone(&rcweak); - - arc_weak.clone(); - //~^ clone_on_ref_ptr - - sync::Weak::clone(&arc_weak); - - let x = Arc::new(SomeImpl); - let _: Arc = x.clone(); - //~^ clone_on_ref_ptr -} - -fn clone_on_copy_generic(t: T) { - t.clone(); - //~^ clone_on_copy - - Some(t).clone(); - //~^ clone_on_copy -} - -mod many_derefs { - struct A; - struct B; - struct C; - struct D; - #[derive(Copy, Clone)] - struct E; - - macro_rules! impl_deref { - ($src:ident, $dst:ident) => { - impl std::ops::Deref for $src { - type Target = $dst; - fn deref(&self) -> &Self::Target { - &$dst - } - } - }; - } - - impl_deref!(A, B); - impl_deref!(B, C); - impl_deref!(C, D); - impl std::ops::Deref for D { - type Target = &'static E; - fn deref(&self) -> &Self::Target { - &&E - } - } - - fn go1() { - let a = A; - let _: E = a.clone(); - //~^ clone_on_copy - - let _: E = *****a; - } -} - -mod issue2076 { - use std::rc::Rc; - - macro_rules! try_opt { - ($expr: expr) => { - match $expr { - Some(value) => value, - None => return None, - } - }; - } - - fn func() -> Option> { - let rc = Rc::new(42); - Some(try_opt!(Some(rc)).clone()) - //~^ clone_on_ref_ptr - } -} diff --git a/tests/ui/unnecessary_clone.stderr b/tests/ui/unnecessary_clone.stderr deleted file mode 100644 index 17518e123d12..000000000000 --- a/tests/ui/unnecessary_clone.stderr +++ /dev/null @@ -1,62 +0,0 @@ -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:23:5 - | -LL | rc.clone(); - | ^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&rc)` - | - = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::clone_on_ref_ptr)]` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:28:5 - | -LL | arc.clone(); - | ^^^^^^^^^^^ help: try: `std::sync::Arc::::clone(&arc)` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:33:5 - | -LL | rcweak.clone(); - | ^^^^^^^^^^^^^^ help: try: `std::rc::Weak::::clone(&rcweak)` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:38:5 - | -LL | arc_weak.clone(); - | ^^^^^^^^^^^^^^^^ help: try: `std::sync::Weak::::clone(&arc_weak)` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:44:33 - | -LL | let _: Arc = x.clone(); - | ^^^^^^^^^ help: try: `std::sync::Arc::::clone(&x)` - -error: using `clone` on type `T` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:49:5 - | -LL | t.clone(); - | ^^^^^^^^^ help: try removing the `clone` call: `t` - | - = note: `-D clippy::clone-on-copy` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::clone_on_copy)]` - -error: using `clone` on type `Option` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:52:5 - | -LL | Some(t).clone(); - | ^^^^^^^^^^^^^^^ help: try removing the `clone` call: `Some(t)` - -error: using `clone` on type `E` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:87:20 - | -LL | let _: E = a.clone(); - | ^^^^^^^^^ help: try dereferencing it: `*****a` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:108:14 - | -LL | Some(try_opt!(Some(rc)).clone()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&try_opt!(Some(rc)))` - -error: aborting due to 9 previous errors - diff --git a/tests/ui/unnecessary_filter_map.stderr b/tests/ui/unnecessary_filter_map.stderr index a879633e10f2..8c33c08c267d 100644 --- a/tests/ui/unnecessary_filter_map.stderr +++ b/tests/ui/unnecessary_filter_map.stderr @@ -1,17 +1,17 @@ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:4:13 + --> tests/ui/unnecessary_filter_map.rs:4:20 | LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_filter_map)]` error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:7:13 + --> tests/ui/unnecessary_filter_map.rs:7:20 | LL | let _ = (0..4).filter_map(|x| { - | _____________^ + | ____________________^ LL | | LL | | LL | | if x > 1 { @@ -21,10 +21,10 @@ LL | | }); | |______^ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:15:13 + --> tests/ui/unnecessary_filter_map.rs:15:20 | LL | let _ = (0..4).filter_map(|x| match x { - | _____________^ + | ____________________^ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), @@ -32,22 +32,22 @@ LL | | }); | |______^ error: this `.filter_map(..)` can be written more simply using `.map(..)` - --> tests/ui/unnecessary_filter_map.rs:21:13 + --> tests/ui/unnecessary_filter_map.rs:21:20 | LL | let _ = (0..4).filter_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `.filter_map(..)` is unnecessary - --> tests/ui/unnecessary_filter_map.rs:28:61 + --> tests/ui/unnecessary_filter_map.rs:28:46 | LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:166:14 + --> tests/ui/unnecessary_filter_map.rs:166:33 | LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 6 previous errors diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs index 33ba7074d623..c5a8937488d9 100644 --- a/tests/ui/unnecessary_find_map.rs +++ b/tests/ui/unnecessary_find_map.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - fn main() { let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); //~^ unnecessary_find_map diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr index 3754a3d99538..8a269119df22 100644 --- a/tests/ui/unnecessary_find_map.stderr +++ b/tests/ui/unnecessary_find_map.stderr @@ -1,17 +1,17 @@ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:4:13 + --> tests/ui/unnecessary_find_map.rs:2:20 | LL | let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-find-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_find_map)]` error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:7:13 + --> tests/ui/unnecessary_find_map.rs:5:20 | LL | let _ = (0..4).find_map(|x| { - | _____________^ + | ____________________^ LL | | LL | | LL | | if x > 1 { @@ -21,10 +21,10 @@ LL | | }); | |______^ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:15:13 + --> tests/ui/unnecessary_find_map.rs:13:20 | LL | let _ = (0..4).find_map(|x| match x { - | _____________^ + | ____________________^ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), @@ -32,16 +32,16 @@ LL | | }); | |______^ error: this `.find_map(..)` can be written more simply using `.map(..).next()` - --> tests/ui/unnecessary_find_map.rs:21:13 + --> tests/ui/unnecessary_find_map.rs:19:20 | LL | let _ = (0..4).find_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:33:14 + --> tests/ui/unnecessary_find_map.rs:31:33 | LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 5 previous errors diff --git a/tests/ui/mut_reference.fixed b/tests/ui/unnecessary_mut_passed.fixed similarity index 87% rename from tests/ui/mut_reference.fixed rename to tests/ui/unnecessary_mut_passed.fixed index 03d854099e64..63bbadb01dcb 100644 --- a/tests/ui/mut_reference.fixed +++ b/tests/ui/unnecessary_mut_passed.fixed @@ -40,6 +40,7 @@ mod issue11268 { struct MyStruct; impl MyStruct { + fn takes_nothing(&self) {} fn takes_ref(&self, a: &i32) {} fn takes_refmut(&self, a: &mut i32) {} fn takes_ref_ref(&self, a: &&i32) {} @@ -168,3 +169,22 @@ fn raw_ptrs(my_struct: MyStruct) { my_struct.takes_raw_mut(&raw mut n); my_struct.takes_raw_const(a); } + +#[expect(clippy::needless_borrow)] +fn issue15722(mut my_struct: MyStruct) { + (&my_struct).takes_nothing(); + //~^ unnecessary_mut_passed + (&my_struct).takes_nothing(); + + // Only put parens around the argument that used to have it + (&my_struct).takes_ref(&42); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + (&my_struct).takes_ref((&42)); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + my_struct.takes_ref((&42)); + //~^ unnecessary_mut_passed +} diff --git a/tests/ui/mut_reference.rs b/tests/ui/unnecessary_mut_passed.rs similarity index 87% rename from tests/ui/mut_reference.rs rename to tests/ui/unnecessary_mut_passed.rs index 80e3f5069277..b719ca1871b2 100644 --- a/tests/ui/mut_reference.rs +++ b/tests/ui/unnecessary_mut_passed.rs @@ -40,6 +40,7 @@ mod issue11268 { struct MyStruct; impl MyStruct { + fn takes_nothing(&self) {} fn takes_ref(&self, a: &i32) {} fn takes_refmut(&self, a: &mut i32) {} fn takes_ref_ref(&self, a: &&i32) {} @@ -168,3 +169,22 @@ fn raw_ptrs(my_struct: MyStruct) { my_struct.takes_raw_mut(&raw mut n); my_struct.takes_raw_const(a); } + +#[expect(clippy::needless_borrow)] +fn issue15722(mut my_struct: MyStruct) { + (&mut my_struct).takes_nothing(); + //~^ unnecessary_mut_passed + (&my_struct).takes_nothing(); + + // Only put parens around the argument that used to have it + (&mut my_struct).takes_ref(&mut 42); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + (&mut my_struct).takes_ref((&mut 42)); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + my_struct.takes_ref((&mut 42)); + //~^ unnecessary_mut_passed +} diff --git a/tests/ui/unnecessary_mut_passed.stderr b/tests/ui/unnecessary_mut_passed.stderr new file mode 100644 index 000000000000..ace11027e3e2 --- /dev/null +++ b/tests/ui/unnecessary_mut_passed.stderr @@ -0,0 +1,220 @@ +error: the function `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:57:15 + | +LL | takes_ref(&mut 42); + | ^^^^^^^ + | + = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_mut_passed)]` +help: remove this `mut` + | +LL - takes_ref(&mut 42); +LL + takes_ref(&42); + | + +error: the function `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:59:19 + | +LL | takes_ref_ref(&mut &42); + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - takes_ref_ref(&mut &42); +LL + takes_ref_ref(&&42); + | + +error: the function `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:61:22 + | +LL | takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - takes_ref_refmut(&mut &mut 42); +LL + takes_ref_refmut(&&mut 42); + | + +error: the function `takes_raw_const` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:63:21 + | +LL | takes_raw_const(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - takes_raw_const(&mut 42); +LL + takes_raw_const(&42); + | + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:67:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut 42); +LL + as_ptr(&42); + | + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:70:12 + | +LL | as_ptr(&mut &42); + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut &42); +LL + as_ptr(&&42); + | + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:73:12 + | +LL | as_ptr(&mut &mut 42); + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut &mut 42); +LL + as_ptr(&&mut 42); + | + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:76:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut 42); +LL + as_ptr(&42); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:81:25 + | +LL | my_struct.takes_ref(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref(&mut 42); +LL + my_struct.takes_ref(&42); + | + +error: the method `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:83:29 + | +LL | my_struct.takes_ref_ref(&mut &42); + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref_ref(&mut &42); +LL + my_struct.takes_ref_ref(&&42); + | + +error: the method `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:85:32 + | +LL | my_struct.takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref_refmut(&mut &mut 42); +LL + my_struct.takes_ref_refmut(&&mut 42); + | + +error: the method `takes_raw_const` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:87:31 + | +LL | my_struct.takes_raw_const(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_raw_const(&mut 42); +LL + my_struct.takes_raw_const(&42); + | + +error: the method `takes_nothing` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:175:5 + | +LL | (&mut my_struct).takes_nothing(); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_nothing(); +LL + (&my_struct).takes_nothing(); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:180:5 + | +LL | (&mut my_struct).takes_ref(&mut 42); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref(&mut 42); +LL + (&my_struct).takes_ref(&mut 42); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:180:32 + | +LL | (&mut my_struct).takes_ref(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref(&mut 42); +LL + (&mut my_struct).takes_ref(&42); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:184:5 + | +LL | (&mut my_struct).takes_ref((&mut 42)); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref((&mut 42)); +LL + (&my_struct).takes_ref((&mut 42)); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:184:32 + | +LL | (&mut my_struct).takes_ref((&mut 42)); + | ^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref((&mut 42)); +LL + (&mut my_struct).takes_ref((&42)); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:188:25 + | +LL | my_struct.takes_ref((&mut 42)); + | ^^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref((&mut 42)); +LL + my_struct.takes_ref((&42)); + | + +error: aborting due to 18 previous errors + diff --git a/tests/ui/unnecessary_option_map_or_else.fixed b/tests/ui/unnecessary_option_map_or_else.fixed new file mode 100644 index 000000000000..9974ee2d08eb --- /dev/null +++ b/tests/ui/unnecessary_option_map_or_else.fixed @@ -0,0 +1,75 @@ +#![warn(clippy::unnecessary_option_map_or_else)] +#![allow( + clippy::let_and_return, + clippy::let_unit_value, + clippy::unnecessary_lazy_evaluations, + clippy::unnecessary_literal_unwrap +)] + +const fn identity(x: T) -> T { + x +} + +const fn double_it(x: i32) -> i32 { + x * 2 +} + +fn main() { + // Expected errors + // Basic scenario + let option = Some(()); + option.unwrap_or_else(|| ()); //~ ERROR: unused "map closure" when calling + + // Type ascription + let option = Some(()); + option.unwrap_or_else(|| ()); //~ ERROR: unused "map closure" when calling + + // Auto-deref + let string = String::new(); + let option = Some(&string); + let _: &str = option.unwrap_or_else(|| &string); //~ ERROR: unused "map closure" when calling + + // Temporary variable + let option = Some(()); + option.unwrap_or_else(|| ()); + + // Identity + let string = String::new(); + let option = Some(&string); + let _: &str = option.unwrap_or_else(|| &string); //~ ERROR: unused "map closure" when calling + + // Closure bound to a variable + let do_nothing = |x: String| x; + let string = String::new(); + let option = Some(string.clone()); + let _: String = option.unwrap_or_else(|| string); //~ ERROR: unused "map closure" when calling + + // Correct usages + let option = Some(()); + option.map_or_else(|| (), |_| ()); + + let option = Some(()); + option.map_or_else(|| (), |_: ()| ()); + + let string = String::new(); + let option = Some(&string); + let _: &str = option.map_or_else(|| &string, |_| &string); + + let option = Some(()); + option.map_or_else( + || (), + |_| { + let tmp = (); + tmp + }, + ); + + let num = 5; + let option = Some(num); + let _: i32 = option.map_or_else(|| 0, double_it); + + let increase = |x: i32| x + 1; + let num = 5; + let option = Some(num); + let _: i32 = option.map_or_else(|| 0, increase); +} diff --git a/tests/ui/unnecessary_option_map_or_else.rs b/tests/ui/unnecessary_option_map_or_else.rs new file mode 100644 index 000000000000..9b53f3fcd521 --- /dev/null +++ b/tests/ui/unnecessary_option_map_or_else.rs @@ -0,0 +1,82 @@ +#![warn(clippy::unnecessary_option_map_or_else)] +#![allow( + clippy::let_and_return, + clippy::let_unit_value, + clippy::unnecessary_lazy_evaluations, + clippy::unnecessary_literal_unwrap +)] + +const fn identity(x: T) -> T { + x +} + +const fn double_it(x: i32) -> i32 { + x * 2 +} + +fn main() { + // Expected errors + // Basic scenario + let option = Some(()); + option.map_or_else(|| (), |x| x); //~ ERROR: unused "map closure" when calling + + // Type ascription + let option = Some(()); + option.map_or_else(|| (), |x: ()| x); //~ ERROR: unused "map closure" when calling + + // Auto-deref + let string = String::new(); + let option = Some(&string); + let _: &str = option.map_or_else(|| &string, |x| x); //~ ERROR: unused "map closure" when calling + + // Temporary variable + let option = Some(()); + option.map_or_else( + //~^ ERROR: unused "map closure" when calling + || (), + |x| { + let tmp = x; + tmp + }, + ); + + // Identity + let string = String::new(); + let option = Some(&string); + let _: &str = option.map_or_else(|| &string, identity); //~ ERROR: unused "map closure" when calling + + // Closure bound to a variable + let do_nothing = |x: String| x; + let string = String::new(); + let option = Some(string.clone()); + let _: String = option.map_or_else(|| string, do_nothing); //~ ERROR: unused "map closure" when calling + + // Correct usages + let option = Some(()); + option.map_or_else(|| (), |_| ()); + + let option = Some(()); + option.map_or_else(|| (), |_: ()| ()); + + let string = String::new(); + let option = Some(&string); + let _: &str = option.map_or_else(|| &string, |_| &string); + + let option = Some(()); + option.map_or_else( + || (), + |_| { + let tmp = (); + tmp + }, + ); + + let num = 5; + let option = Some(num); + let _: i32 = option.map_or_else(|| 0, double_it); + + let increase = |x: i32| x + 1; + let num = 5; + let option = Some(num); + let _: i32 = option.map_or_else(|| 0, increase); +} diff --git a/tests/ui/unnecessary_option_map_or_else.stderr b/tests/ui/unnecessary_option_map_or_else.stderr new file mode 100644 index 000000000000..d90875e4efc7 --- /dev/null +++ b/tests/ui/unnecessary_option_map_or_else.stderr @@ -0,0 +1,47 @@ +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:21:5 + | +LL | option.map_or_else(|| (), |x| x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| ())` + | + = note: `-D clippy::unnecessary-option-map-or-else` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_option_map_or_else)]` + +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:25:5 + | +LL | option.map_or_else(|| (), |x: ()| x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| ())` + +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:30:19 + | +LL | let _: &str = option.map_or_else(|| &string, |x| x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| &string)` + +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:34:5 + | +LL | / option.map_or_else( +LL | | +LL | | || (), +LL | | |x| { +... | +LL | | }, +LL | | ); + | |_____^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| ())` + +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:46:19 + | +LL | let _: &str = option.map_or_else(|| &string, identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| &string)` + +error: unused "map closure" when calling `Option::map_or_else` value + --> tests/ui/unnecessary_option_map_or_else.rs:52:21 + | +LL | let _: String = option.map_or_else(|| string, do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `option.unwrap_or_else(|| string)` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/unnecessary_safety_comment.rs b/tests/ui/unnecessary_safety_comment.rs index 4440089b3633..d82a7b969080 100644 --- a/tests/ui/unnecessary_safety_comment.rs +++ b/tests/ui/unnecessary_safety_comment.rs @@ -1,5 +1,5 @@ #![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)] -#![allow(clippy::let_unit_value, clippy::missing_safety_doc, clippy::needless_if)] +#![allow(clippy::let_unit_value, clippy::missing_safety_doc, clippy::needless_ifs)] mod unsafe_items_invalid_comment { // SAFETY: diff --git a/tests/ui/unnecessary_safety_comment.stderr b/tests/ui/unnecessary_safety_comment.stderr index 732e6767c178..6ad94f986434 100644 --- a/tests/ui/unnecessary_safety_comment.stderr +++ b/tests/ui/unnecessary_safety_comment.stderr @@ -5,10 +5,10 @@ LL | const CONST: u32 = 0; | ^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:5:5 + --> tests/ui/unnecessary_safety_comment.rs:5:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` @@ -19,10 +19,10 @@ LL | static STATIC: u32 = 0; | ^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:9:5 + --> tests/ui/unnecessary_safety_comment.rs:9:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ error: struct has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:14:5 @@ -31,10 +31,10 @@ LL | struct Struct; | ^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:13:5 + --> tests/ui/unnecessary_safety_comment.rs:13:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ error: enum has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:18:5 @@ -43,10 +43,10 @@ LL | enum Enum {} | ^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:17:5 + --> tests/ui/unnecessary_safety_comment.rs:17:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ error: module has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:22:5 @@ -55,10 +55,10 @@ LL | mod module {} | ^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:21:5 + --> tests/ui/unnecessary_safety_comment.rs:21:8 | LL | // SAFETY: - | ^^^^^^^^^^ + | ^^^^^^^ error: impl has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:42:13 @@ -70,10 +70,10 @@ LL | with_safety_comment!(i32); | ------------------------- in this macro invocation | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:41:13 + --> tests/ui/unnecessary_safety_comment.rs:41:16 | LL | // Safety: unnecessary - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ = note: this error originates in the macro `with_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: expression has unnecessary safety comment @@ -83,10 +83,10 @@ LL | 24 | ^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:59:5 + --> tests/ui/unnecessary_safety_comment.rs:59:8 | LL | // SAFETY: unnecessary - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ error: statement has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:52:5 @@ -95,10 +95,10 @@ LL | let num = 42; | ^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:51:5 + --> tests/ui/unnecessary_safety_comment.rs:51:8 | LL | // SAFETY: unnecessary - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ error: statement has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:56:5 @@ -107,10 +107,10 @@ LL | if num > 24 {} | ^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui/unnecessary_safety_comment.rs:55:5 + --> tests/ui/unnecessary_safety_comment.rs:55:8 | LL | // SAFETY: unnecessary - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ error: aborting due to 9 previous errors diff --git a/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed new file mode 100644 index 000000000000..b90ae1365bbf --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed @@ -0,0 +1,13 @@ +#![warn(clippy::unnecessary_semicolon)] +#![feature(stmt_expr_attributes)] + +fn main() { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + // -- unless the feature `stmt_expr_attributes` is enabled + #[rustfmt::skip] + match 0 { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + } + //~^ unnecessary_semicolon +} diff --git a/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs new file mode 100644 index 000000000000..606c901c20d3 --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs @@ -0,0 +1,13 @@ +#![warn(clippy::unnecessary_semicolon)] +#![feature(stmt_expr_attributes)] + +fn main() { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + // -- unless the feature `stmt_expr_attributes` is enabled + #[rustfmt::skip] + match 0 { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + }; + //~^ unnecessary_semicolon +} diff --git a/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr new file mode 100644 index 000000000000..3e98a92ef299 --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr @@ -0,0 +1,11 @@ +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs:11:6 + | +LL | }; + | ^ help: remove + | + = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/unneeded_wildcard_pattern.fixed b/tests/ui/unneeded_wildcard_pattern.fixed index 7e6a493b86c1..32494435fff5 100644 --- a/tests/ui/unneeded_wildcard_pattern.fixed +++ b/tests/ui/unneeded_wildcard_pattern.fixed @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![feature(stmt_expr_attributes)] #![deny(clippy::unneeded_wildcard_pattern)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unneeded_wildcard_pattern.rs b/tests/ui/unneeded_wildcard_pattern.rs index c91383e2cca5..b3a0fb6098d6 100644 --- a/tests/ui/unneeded_wildcard_pattern.rs +++ b/tests/ui/unneeded_wildcard_pattern.rs @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![feature(stmt_expr_attributes)] #![deny(clippy::unneeded_wildcard_pattern)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnested_or_patterns.fixed b/tests/ui/unnested_or_patterns.fixed index 339d4a95084a..0391fb19b1f6 100644 --- a/tests/ui/unnested_or_patterns.fixed +++ b/tests/ui/unnested_or_patterns.fixed @@ -4,7 +4,7 @@ clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] diff --git a/tests/ui/unnested_or_patterns.rs b/tests/ui/unnested_or_patterns.rs index f5c99183b0c5..f0702668fa91 100644 --- a/tests/ui/unnested_or_patterns.rs +++ b/tests/ui/unnested_or_patterns.rs @@ -4,7 +4,7 @@ clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] diff --git a/tests/ui/unnested_or_patterns2.fixed b/tests/ui/unnested_or_patterns2.fixed index 6d601ea5e57f..2c794463457f 100644 --- a/tests/ui/unnested_or_patterns2.fixed +++ b/tests/ui/unnested_or_patterns2.fixed @@ -3,7 +3,7 @@ #![allow( clippy::cognitive_complexity, clippy::match_ref_pats, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] diff --git a/tests/ui/unnested_or_patterns2.rs b/tests/ui/unnested_or_patterns2.rs index 7e5ea0161bff..12f3d3fca464 100644 --- a/tests/ui/unnested_or_patterns2.rs +++ b/tests/ui/unnested_or_patterns2.rs @@ -3,7 +3,7 @@ #![allow( clippy::cognitive_complexity, clippy::match_ref_pats, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4..c742cc8a85ba 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | diff --git a/tests/ui/use_debug.rs b/tests/ui/use_debug.rs new file mode 100644 index 000000000000..89c127a12faf --- /dev/null +++ b/tests/ui/use_debug.rs @@ -0,0 +1,50 @@ +#![warn(clippy::use_debug)] + +use std::fmt::{Debug, Display, Formatter, Result}; + +struct Foo; + +impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{:?}", 43.1415) + //~^ use_debug + } +} + +impl Debug for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } +} + +fn main() { + print!("Hello {:?}", "World"); + //~^ use_debug + + print!("Hello {:#?}", "#orld"); + //~^ use_debug + + assert_eq!(42, 1337); + + vec![1, 2]; +} + +// don't get confused by nested impls +fn issue15942() { + struct Bar; + impl Debug for Bar { + fn fmt(&self, f: &mut Formatter) -> Result { + struct Baz; + impl Debug for Baz { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } + } + + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } + } +} diff --git a/tests/ui/use_debug.stderr b/tests/ui/use_debug.stderr new file mode 100644 index 000000000000..85168d3bc341 --- /dev/null +++ b/tests/ui/use_debug.stderr @@ -0,0 +1,23 @@ +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:9:20 + | +LL | write!(f, "{:?}", 43.1415) + | ^^^^ + | + = note: `-D clippy::use-debug` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::use_debug)]` + +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:22:19 + | +LL | print!("Hello {:?}", "World"); + | ^^^^ + +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:25:19 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/use_self.fixed b/tests/ui/use_self.fixed index cccb6bffabb7..075e31d202b0 100644 --- a/tests/ui/use_self.fixed +++ b/tests/ui/use_self.fixed @@ -530,8 +530,8 @@ mod issue7206 { impl<'a> S2> { fn new_again() -> Self { - Self::new() - //~^ use_self + S2::new() + // FIXME: ^Broken by PR #15611 } } } @@ -755,3 +755,17 @@ mod crash_check_13128 { } } } + +mod issue_13277 { + trait Foo { + type Item<'foo>; + } + struct Bar<'b> { + content: &'b str, + } + impl<'b> Foo for Option> { + // when checking whether `Option>` has a lifetime, check not only the outer + // `Option`, but also the inner `Bar<'foo>` + type Item<'foo> = Option>; + } +} diff --git a/tests/ui/use_self.rs b/tests/ui/use_self.rs index 09288677aa71..6fbba0bbc550 100644 --- a/tests/ui/use_self.rs +++ b/tests/ui/use_self.rs @@ -531,7 +531,7 @@ mod issue7206 { impl<'a> S2> { fn new_again() -> Self { S2::new() - //~^ use_self + // FIXME: ^Broken by PR #15611 } } } @@ -755,3 +755,17 @@ mod crash_check_13128 { } } } + +mod issue_13277 { + trait Foo { + type Item<'foo>; + } + struct Bar<'b> { + content: &'b str, + } + impl<'b> Foo for Option> { + // when checking whether `Option>` has a lifetime, check not only the outer + // `Option`, but also the inner `Bar<'foo>` + type Item<'foo> = Option>; + } +} diff --git a/tests/ui/use_self.stderr b/tests/ui/use_self.stderr index 781327696ac1..5f65c53ea25c 100644 --- a/tests/ui/use_self.stderr +++ b/tests/ui/use_self.stderr @@ -169,12 +169,6 @@ error: unnecessary structure name repetition LL | A::new::(submod::B {}) | ^ help: use the applicable keyword: `Self` -error: unnecessary structure name repetition - --> tests/ui/use_self.rs:533:13 - | -LL | S2::new() - | ^^ help: use the applicable keyword: `Self` - error: unnecessary structure name repetition --> tests/ui/use_self.rs:571:17 | @@ -259,5 +253,5 @@ error: unnecessary structure name repetition LL | E::A => {}, | ^ help: use the applicable keyword: `Self` -error: aborting due to 43 previous errors +error: aborting due to 42 previous errors diff --git a/tests/ui/use_self_structs.fixed b/tests/ui/use_self_structs.fixed new file mode 100644 index 000000000000..bd7bc3e0c1f6 --- /dev/null +++ b/tests/ui/use_self_structs.fixed @@ -0,0 +1,134 @@ +#![warn(clippy::use_self)] +#![allow(clippy::type_complexity)] + +fn main() {} + +struct Basic { + flag: Option>, + //~^ use_self +} + +struct BasicSelf { + okay: Option>, +} + +struct Generic<'q, T: From> { + t: &'q T, + flag: Option>, + //~^ use_self +} + +struct GenericSelf<'q, T: From> { + t: &'q T, + okay: Option>, +} + +struct MixedLifetimes<'q, T: From + 'static> { + t: &'q T, + okay: Option>>, +} + +struct ConcreteType<'q, T: From> { + t: &'q T, + okay: Option>>, +} + +struct ConcreteAndGeneric<'q, T: From> { + t: &'q T, + flag: Option>, + //~^ use_self + okay: Option>>, +} + +struct ConcreteAndGenericSelf<'q, T: From> { + t: &'q T, + okay_1: Option>, + okay_2: Option>>, +} + +macro_rules! recursive_struct { + ($name:ident) => { + struct $name { + okay: Option>, + } + }; +} + +recursive_struct!(X); +recursive_struct!(Y); +recursive_struct!(Z); + +struct Tree { + left: Option>, + //~^ use_self + right: Option>, + //~^ use_self +} + +struct TreeSelf { + left: Option>, + right: Option>, +} + +struct TreeMixed { + left: Option>, + right: Option>, + //~^ use_self +} + +struct Nested { + flag: Option>>>, + //~^ use_self +} + +struct NestedSelf { + okay: Option>>>, +} + +struct Tuple(Option>); +//~^ use_self + +struct TupleSelf(Option>); + +use std::cell::RefCell; +use std::rc::{Rc, Weak}; + +struct Containers { + flag: Vec>>>>>>, + //~^ use_self +} + +struct ContainersSelf { + okay: Vec>>>>>>, +} + +type Wrappers = Vec>>>>>>; + +struct Alias { + flag: Wrappers, + //~^ use_self +} + +struct AliasSelf { + okay: Wrappers, +} + +struct Array { + flag: [Option>; N], + //~^ use_self +} + +struct ArraySelf { + okay: [Option>; N], +} + +enum Enum { + Nil, + Cons(Box), + //~^ use_self +} + +enum EnumSelf { + Nil, + Cons(Box), +} diff --git a/tests/ui/use_self_structs.rs b/tests/ui/use_self_structs.rs new file mode 100644 index 000000000000..624f158164ab --- /dev/null +++ b/tests/ui/use_self_structs.rs @@ -0,0 +1,134 @@ +#![warn(clippy::use_self)] +#![allow(clippy::type_complexity)] + +fn main() {} + +struct Basic { + flag: Option>, + //~^ use_self +} + +struct BasicSelf { + okay: Option>, +} + +struct Generic<'q, T: From> { + t: &'q T, + flag: Option>>, + //~^ use_self +} + +struct GenericSelf<'q, T: From> { + t: &'q T, + okay: Option>, +} + +struct MixedLifetimes<'q, T: From + 'static> { + t: &'q T, + okay: Option>>, +} + +struct ConcreteType<'q, T: From> { + t: &'q T, + okay: Option>>, +} + +struct ConcreteAndGeneric<'q, T: From> { + t: &'q T, + flag: Option>>, + //~^ use_self + okay: Option>>, +} + +struct ConcreteAndGenericSelf<'q, T: From> { + t: &'q T, + okay_1: Option>, + okay_2: Option>>, +} + +macro_rules! recursive_struct { + ($name:ident) => { + struct $name { + okay: Option>, + } + }; +} + +recursive_struct!(X); +recursive_struct!(Y); +recursive_struct!(Z); + +struct Tree { + left: Option>, + //~^ use_self + right: Option>, + //~^ use_self +} + +struct TreeSelf { + left: Option>, + right: Option>, +} + +struct TreeMixed { + left: Option>, + right: Option>, + //~^ use_self +} + +struct Nested { + flag: Option>>>, + //~^ use_self +} + +struct NestedSelf { + okay: Option>>>, +} + +struct Tuple(Option>); +//~^ use_self + +struct TupleSelf(Option>); + +use std::cell::RefCell; +use std::rc::{Rc, Weak}; + +struct Containers { + flag: Vec>>>>>>, + //~^ use_self +} + +struct ContainersSelf { + okay: Vec>>>>>>, +} + +type Wrappers = Vec>>>>>>; + +struct Alias { + flag: Wrappers, + //~^ use_self +} + +struct AliasSelf { + okay: Wrappers, +} + +struct Array { + flag: [Option>>; N], + //~^ use_self +} + +struct ArraySelf { + okay: [Option>; N], +} + +enum Enum { + Nil, + Cons(Box), + //~^ use_self +} + +enum EnumSelf { + Nil, + Cons(Box), +} diff --git a/tests/ui/use_self_structs.stderr b/tests/ui/use_self_structs.stderr new file mode 100644 index 000000000000..766d1d4fd2c0 --- /dev/null +++ b/tests/ui/use_self_structs.stderr @@ -0,0 +1,77 @@ +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:7:22 + | +LL | flag: Option>, + | ^^^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::use_self)]` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:17:22 + | +LL | flag: Option>>, + | ^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:38:22 + | +LL | flag: Option>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:62:22 + | +LL | left: Option>, + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:64:23 + | +LL | right: Option>, + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:75:23 + | +LL | right: Option>, + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:80:33 + | +LL | flag: Option>>>, + | ^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:88:25 + | +LL | struct Tuple(Option>); + | ^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:97:46 + | +LL | flag: Vec>>>>>>, + | ^^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:108:20 + | +LL | flag: Wrappers, + | ^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:117:23 + | +LL | flag: [Option>>; N], + | ^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> tests/ui/use_self_structs.rs:127:14 + | +LL | Cons(Box), + | ^^^^ help: use the applicable keyword: `Self` + +error: aborting due to 12 previous errors + diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index be4fb55ddfb8..e0bc23e0788c 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -153,8 +153,18 @@ pub mod redundant_imports_issue { () => {}; } - #[expect(redundant_imports)] + #[expect(unused_imports)] pub(crate) use empty; empty!(); } + +pub mod issue15636 { + pub mod f { + #[deprecated(since = "TBD")] + pub mod deprec {} + } + + #[allow(deprecated_in_future)] + pub use f::deprec; +} diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 5a1bcf97a5b4..30a4c354b238 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -153,8 +153,18 @@ pub mod redundant_imports_issue { () => {}; } - #[expect(redundant_imports)] + #[expect(unused_imports)] pub(crate) use empty; empty!(); } + +pub mod issue15636 { + pub mod f { + #[deprecated(since = "TBD")] + pub mod deprec {} + } + + #[allow(deprecated_in_future)] + pub use f::deprec; +} diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index ad30c94f3478..9de7d2c67149 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index 505afb340009..38cd1175aa48 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs index d16506d94aa3..157c236a214c 100644 --- a/tests/ui/useless_conversion_try.rs +++ b/tests/ui/useless_conversion_try.rs @@ -1,6 +1,6 @@ #![deny(clippy::useless_conversion)] #![allow( - clippy::needless_if, + clippy::needless_ifs, clippy::unnecessary_fallible_conversions, clippy::manual_unwrap_or_default )] diff --git a/tests/ui/volatile_composites.rs b/tests/ui/volatile_composites.rs new file mode 100644 index 000000000000..e7e7dafe18af --- /dev/null +++ b/tests/ui/volatile_composites.rs @@ -0,0 +1,221 @@ +#![feature(ptr_metadata)] +#![feature(portable_simd)] +#![warn(clippy::volatile_composites)] + +use std::ptr::null_mut; + +#[repr(C)] +#[derive(Copy, Clone, Default)] +struct MyDevRegisters { + baseaddr: usize, + count: usize, +} + +#[repr(transparent)] +struct Wrapper((), T, ()); + +// Not to be confused with std::ptr::NonNull +struct NonNull(T); + +impl NonNull { + fn write_volatile(&self, _arg: &T) { + unimplemented!("Something entirely unrelated to std::ptr::NonNull"); + } +} + +fn main() { + let regs = MyDevRegisters { + baseaddr: 0xabc123, + count: 42, + }; + + const DEVICE_ADDR: *mut MyDevRegisters = 0xdead as *mut _; + + // Raw pointer methods + unsafe { + (&raw mut (*DEVICE_ADDR).baseaddr).write_volatile(regs.baseaddr); // OK + (&raw mut (*DEVICE_ADDR).count).write_volatile(regs.count); // OK + + DEVICE_ADDR.write_volatile(regs); + //~^ volatile_composites + + let _regs = MyDevRegisters { + baseaddr: (&raw const (*DEVICE_ADDR).baseaddr).read_volatile(), // OK + count: (&raw const (*DEVICE_ADDR).count).read_volatile(), // OK + }; + + let _regs = DEVICE_ADDR.read_volatile(); + //~^ volatile_composites + } + + // std::ptr functions + unsafe { + std::ptr::write_volatile(&raw mut (*DEVICE_ADDR).baseaddr, regs.baseaddr); // OK + std::ptr::write_volatile(&raw mut (*DEVICE_ADDR).count, regs.count); // OK + + std::ptr::write_volatile(DEVICE_ADDR, regs); + //~^ volatile_composites + + let _regs = MyDevRegisters { + baseaddr: std::ptr::read_volatile(&raw const (*DEVICE_ADDR).baseaddr), // OK + count: std::ptr::read_volatile(&raw const (*DEVICE_ADDR).count), // OK + }; + + let _regs = std::ptr::read_volatile(DEVICE_ADDR); + //~^ volatile_composites + } + + // core::ptr functions + unsafe { + core::ptr::write_volatile(&raw mut (*DEVICE_ADDR).baseaddr, regs.baseaddr); // OK + core::ptr::write_volatile(&raw mut (*DEVICE_ADDR).count, regs.count); // OK + + core::ptr::write_volatile(DEVICE_ADDR, regs); + //~^ volatile_composites + + let _regs = MyDevRegisters { + baseaddr: core::ptr::read_volatile(&raw const (*DEVICE_ADDR).baseaddr), // OK + count: core::ptr::read_volatile(&raw const (*DEVICE_ADDR).count), // OK + }; + + let _regs = core::ptr::read_volatile(DEVICE_ADDR); + //~^ volatile_composites + } + + // std::ptr::NonNull + unsafe { + let ptr = std::ptr::NonNull::new(DEVICE_ADDR).unwrap(); + + ptr.write_volatile(regs); + //~^ volatile_composites + + let _regs = ptr.read_volatile(); + //~^ volatile_composites + } + + // Red herring + { + let thing = NonNull("hello".to_string()); + + thing.write_volatile(&"goodbye".into()); // OK + } + + // Zero size types OK + unsafe { + struct Empty; + + (0xdead as *mut Empty).write_volatile(Empty); // OK + // Note that this is OK because Wrapper is itself ZST, not because of the repr transparent + // handling tested below. + (0xdead as *mut Wrapper).write_volatile(Wrapper((), Empty, ())); // OK + } + + // Via repr transparent newtype + unsafe { + (0xdead as *mut Wrapper).write_volatile(Wrapper((), 123, ())); // OK + (0xdead as *mut Wrapper>).write_volatile(Wrapper((), Wrapper((), 123, ()), ())); // OK + + (0xdead as *mut Wrapper).write_volatile(Wrapper((), MyDevRegisters::default(), ())); + //~^ volatile_composites + } + + // Plain type alias OK + unsafe { + type MyU64 = u64; + + (0xdead as *mut MyU64).write_volatile(123); // OK + } + + // Wide pointers are not OK as data + unsafe { + let things: &[u32] = &[1, 2, 3]; + + (0xdead as *mut *const u32).write_volatile(things.as_ptr()); // OK + + let wideptr: *const [u32] = std::ptr::from_raw_parts(things.as_ptr(), things.len()); + (0xdead as *mut *const [u32]).write_volatile(wideptr); + //~^ volatile_composites + } + + // Plain pointers and pointers with lifetimes are OK + unsafe { + let v: u32 = 123; + let rv: &u32 = &v; + + (0xdead as *mut &u32).write_volatile(rv); // OK + } + + // C-style enums are OK + unsafe { + // Bad: need some specific repr + enum PlainEnum { + A = 1, + B = 2, + C = 3, + } + + (0xdead as *mut PlainEnum).write_volatile(PlainEnum::A); + //~^ volatile_composites + + // OK + #[repr(u32)] + enum U32Enum { + A = 1, + B = 2, + C = 3, + } + + (0xdead as *mut U32Enum).write_volatile(U32Enum::A); // OK + + // OK + #[repr(C)] + enum CEnum { + A = 1, + B = 2, + C = 3, + } + (0xdead as *mut CEnum).write_volatile(CEnum::A); // OK + + // Nope + enum SumType { + A(String), + B(u32), + C, + } + (0xdead as *mut SumType).write_volatile(SumType::C); + //~^ volatile_composites + + // A repr on a complex sum type is not good enough + #[repr(C)] + enum ReprSumType { + A(String), + B(u32), + C, + } + (0xdead as *mut ReprSumType).write_volatile(ReprSumType::C); + //~^ volatile_composites + } + + // SIMD is OK + unsafe { + (0xdead as *mut std::simd::u32x4).write_volatile(std::simd::u32x4::splat(1)); // OK + } + + // Can't see through generic wrapper + unsafe { + do_device_write::(0xdead as *mut _, Default::default()); // OK + } + + let mut s = String::from("foo"); + unsafe { + std::ptr::write_volatile(&mut s, String::from("bar")); + //~^ volatile_composites + } +} + +// Generic OK +unsafe fn do_device_write(ptr: *mut T, v: T) { + unsafe { + ptr.write_volatile(v); // OK + } +} diff --git a/tests/ui/volatile_composites.stderr b/tests/ui/volatile_composites.stderr new file mode 100644 index 000000000000..1545fc913ed0 --- /dev/null +++ b/tests/ui/volatile_composites.stderr @@ -0,0 +1,89 @@ +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:39:9 + | +LL | DEVICE_ADDR.write_volatile(regs); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::volatile-composites` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::volatile_composites)]` + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:47:21 + | +LL | let _regs = DEVICE_ADDR.read_volatile(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:56:9 + | +LL | std::ptr::write_volatile(DEVICE_ADDR, regs); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:64:21 + | +LL | let _regs = std::ptr::read_volatile(DEVICE_ADDR); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:73:9 + | +LL | core::ptr::write_volatile(DEVICE_ADDR, regs); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:81:21 + | +LL | let _regs = core::ptr::read_volatile(DEVICE_ADDR); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:89:9 + | +LL | ptr.write_volatile(regs); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `MyDevRegisters` is not volatile-compatible + --> tests/ui/volatile_composites.rs:92:21 + | +LL | let _regs = ptr.read_volatile(); + | ^^^^^^^^^^^^^^^^^^^ + +error: type `Wrapper` is not volatile-compatible + --> tests/ui/volatile_composites.rs:118:9 + | +LL | (0xdead as *mut Wrapper).write_volatile(Wrapper((), MyDevRegisters::default(), ())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `*const [u32]` is not volatile-compatible + --> tests/ui/volatile_composites.rs:136:9 + | +LL | (0xdead as *mut *const [u32]).write_volatile(wideptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `main::PlainEnum` is not volatile-compatible + --> tests/ui/volatile_composites.rs:157:9 + | +LL | (0xdead as *mut PlainEnum).write_volatile(PlainEnum::A); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `main::SumType` is not volatile-compatible + --> tests/ui/volatile_composites.rs:185:9 + | +LL | (0xdead as *mut SumType).write_volatile(SumType::C); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `main::ReprSumType` is not volatile-compatible + --> tests/ui/volatile_composites.rs:195:9 + | +LL | (0xdead as *mut ReprSumType).write_volatile(ReprSumType::C); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: type `std::string::String` is not volatile-compatible + --> tests/ui/volatile_composites.rs:211:9 + | +LL | std::ptr::write_volatile(&mut s, String::from("bar")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/tests/ui/infinite_loop.rs b/tests/ui/while_immutable_condition.rs similarity index 98% rename from tests/ui/infinite_loop.rs rename to tests/ui/while_immutable_condition.rs index 8ff7f3b0c18d..5c18cd41ff79 100644 --- a/tests/ui/infinite_loop.rs +++ b/tests/ui/while_immutable_condition.rs @@ -1,3 +1,5 @@ +#![warn(clippy::while_immutable_condition)] + fn fn_val(i: i32) -> i32 { unimplemented!() } diff --git a/tests/ui/infinite_loop.stderr b/tests/ui/while_immutable_condition.stderr similarity index 76% rename from tests/ui/infinite_loop.stderr rename to tests/ui/while_immutable_condition.stderr index 04da9776c302..278b473d23ce 100644 --- a/tests/ui/infinite_loop.stderr +++ b/tests/ui/while_immutable_condition.stderr @@ -1,14 +1,15 @@ error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:20:11 + --> tests/ui/while_immutable_condition.rs:22:11 | LL | while y < 10 { | ^^^^^^ | = note: this may lead to an infinite or to a never running loop - = note: `#[deny(clippy::while_immutable_condition)]` on by default + = note: `-D clippy::while-immutable-condition` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::while_immutable_condition)]` error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:27:11 + --> tests/ui/while_immutable_condition.rs:29:11 | LL | while y < 10 && x < 3 { | ^^^^^^^^^^^^^^^ @@ -16,7 +17,7 @@ LL | while y < 10 && x < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:36:11 + --> tests/ui/while_immutable_condition.rs:38:11 | LL | while !cond { | ^^^^^ @@ -24,7 +25,7 @@ LL | while !cond { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:82:11 + --> tests/ui/while_immutable_condition.rs:84:11 | LL | while i < 3 { | ^^^^^ @@ -32,7 +33,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:89:11 + --> tests/ui/while_immutable_condition.rs:91:11 | LL | while i < 3 && j > 0 { | ^^^^^^^^^^^^^^ @@ -40,7 +41,7 @@ LL | while i < 3 && j > 0 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:95:11 + --> tests/ui/while_immutable_condition.rs:97:11 | LL | while i < 3 { | ^^^^^ @@ -48,7 +49,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:112:11 + --> tests/ui/while_immutable_condition.rs:114:11 | LL | while i < 3 { | ^^^^^ @@ -56,7 +57,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:119:11 + --> tests/ui/while_immutable_condition.rs:121:11 | LL | while i < 3 { | ^^^^^ @@ -64,7 +65,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:187:15 + --> tests/ui/while_immutable_condition.rs:189:15 | LL | while self.count < n { | ^^^^^^^^^^^^^^ @@ -72,7 +73,7 @@ LL | while self.count < n { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:197:11 + --> tests/ui/while_immutable_condition.rs:199:11 | LL | while y < 10 { | ^^^^^^ @@ -82,7 +83,7 @@ LL | while y < 10 { = help: rewrite it as `if cond { loop { } }` error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:206:11 + --> tests/ui/while_immutable_condition.rs:208:11 | LL | while y < 10 { | ^^^^^^ diff --git a/tests/ui/while_let_loop.rs b/tests/ui/while_let_loop.rs index 95062c9f46c7..f28c504742fd 100644 --- a/tests/ui/while_let_loop.rs +++ b/tests/ui/while_let_loop.rs @@ -22,6 +22,19 @@ fn main() { break; } + loop { + //~^ while_let_loop + let Some(_x) = y else { break }; + } + + loop { + // no error, else branch does something other than break + let Some(_x) = y else { + let _z = 1; + break; + }; + } + loop { //~^ while_let_loop diff --git a/tests/ui/while_let_loop.stderr b/tests/ui/while_let_loop.stderr index ed42628a53e7..b9aee6eb42ec 100644 --- a/tests/ui/while_let_loop.stderr +++ b/tests/ui/while_let_loop.stderr @@ -17,6 +17,15 @@ error: this loop could be written as a `while let` loop | LL | / loop { LL | | +LL | | let Some(_x) = y else { break }; +LL | | } + | |_____^ help: try: `while let Some(_x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> tests/ui/while_let_loop.rs:38:5 + | +LL | / loop { +LL | | LL | | LL | | match y { ... | @@ -25,7 +34,7 @@ LL | | } | |_____^ help: try: `while let Some(_x) = y { .. }` error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:34:5 + --> tests/ui/while_let_loop.rs:47:5 | LL | / loop { LL | | @@ -37,7 +46,7 @@ LL | | } | |_____^ help: try: `while let Some(x) = y { .. }` error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:45:5 + --> tests/ui/while_let_loop.rs:58:5 | LL | / loop { LL | | @@ -48,7 +57,7 @@ LL | | } | |_____^ help: try: `while let Some(x) = y { .. }` error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:77:5 + --> tests/ui/while_let_loop.rs:90:5 | LL | / loop { LL | | @@ -68,7 +77,7 @@ LL + } | error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:167:9 + --> tests/ui/while_let_loop.rs:180:9 | LL | / loop { LL | | @@ -88,7 +97,7 @@ LL + } | error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:182:5 + --> tests/ui/while_let_loop.rs:195:5 | LL | / loop { LL | | @@ -107,7 +116,7 @@ LL + } | error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:194:5 + --> tests/ui/while_let_loop.rs:207:5 | LL | / loop { LL | | @@ -126,7 +135,7 @@ LL + } | error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:206:5 + --> tests/ui/while_let_loop.rs:219:5 | LL | / loop { LL | | @@ -137,7 +146,7 @@ LL | | } | |_____^ help: try: `while let Some(x) = Some(3) { .. }` error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:218:5 + --> tests/ui/while_let_loop.rs:231:5 | LL | / loop { LL | | @@ -156,7 +165,7 @@ LL + } | error: this loop could be written as a `while let` loop - --> tests/ui/while_let_loop.rs:230:5 + --> tests/ui/while_let_loop.rs:243:5 | LL | / loop { LL | | @@ -177,5 +186,5 @@ LL + .. LL + } | -error: aborting due to 11 previous errors +error: aborting due to 12 previous errors diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed index 29352fd468ea..ae29f3a57462 100644 --- a/tests/ui/write_literal.fixed +++ b/tests/ui/write_literal.fixed @@ -70,6 +70,55 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{{hello}}"); + //~^ write_literal + + writeln!(v, r"{{hello}}"); + //~^ write_literal + + writeln!(v, "'"); + //~^ write_literal + + writeln!(v, "\""); + //~^ write_literal + + writeln!(v, r"'"); + //~^ write_literal + + writeln!( + v, + "some hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some 1\ + 2 \\ 3", + //~^^^ write_literal + ); + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, r"\"); + //~^ write_literal + + writeln!(v, r#"\"#); + //~^ write_literal + + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "\""); diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index 928727527592..d930339e106c 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -70,6 +70,59 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{}", "{hello}"); + //~^ write_literal + + writeln!(v, r"{}", r"{hello}"); + //~^ write_literal + + writeln!(v, "{}", '\''); + //~^ write_literal + + writeln!(v, "{}", '"'); + //~^ write_literal + + writeln!(v, r"{}", '\''); + //~^ write_literal + + writeln!( + v, + "some {}", + "hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some {}\ + {} \\ {}", + "1", + "2", + "3", + //~^^^ write_literal + ); + writeln!(v, "{}", "\\"); + //~^ write_literal + + writeln!(v, r"{}", "\\"); + //~^ write_literal + + writeln!(v, r#"{}"#, "\\"); + //~^ write_literal + + writeln!(v, "{}", r"\"); + //~^ write_literal + + writeln!(v, "{}", "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "{}", r#"""#); diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index ca37406c8114..374098fa2b14 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -145,7 +145,156 @@ LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | error: literal with an empty format string - --> tests/ui/write_literal.rs:75:23 + --> tests/ui/write_literal.rs:76:23 + | +LL | writeln!(v, "{}", "{hello}"); + | ^^^^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", "{hello}"); +LL + writeln!(v, "{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:79:24 + | +LL | writeln!(v, r"{}", r"{hello}"); + | ^^^^^^^^^^ + | +help: try + | +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:82:23 + | +LL | writeln!(v, "{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", '\''); +LL + writeln!(v, "'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:85:23 + | +LL | writeln!(v, "{}", '"'); + | ^^^ + | +help: try + | +LL - writeln!(v, "{}", '"'); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:88:24 + | +LL | writeln!(v, r"{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", '\''); +LL + writeln!(v, r"'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:94:9 + | +LL | / "hello \ +LL | | +LL | | world!", + | |_______________^ + | +help: try + | +LL ~ "some hello \ +LL + +LL ~ world!", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:102:9 + | +LL | / "1", +LL | | "2", +LL | | "3", + | |___________^ + | +help: try + | +LL ~ "some 1\ +LL ~ 2 \\ 3", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:107:23 + | +LL | writeln!(v, "{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:110:24 + | +LL | writeln!(v, r"{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", "\\"); +LL + writeln!(v, r"\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:113:26 + | +LL | writeln!(v, r#"{}"#, "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r#"{}"#, "\\"); +LL + writeln!(v, r#"\"#); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:116:23 + | +LL | writeln!(v, "{}", r"\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", r"\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:119:23 + | +LL | writeln!(v, "{}", "\r"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\r"); +LL + writeln!(v, "\r"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:128:23 | LL | writeln!(v, "{}", r#"""#); | ^^^^^^ @@ -157,7 +306,7 @@ LL + writeln!(v, "\""); | error: literal with an empty format string - --> tests/ui/write_literal.rs:80:9 + --> tests/ui/write_literal.rs:133:9 | LL | / r#" LL | | @@ -182,7 +331,7 @@ LL ~ " | error: literal with an empty format string - --> tests/ui/write_literal.rs:94:55 + --> tests/ui/write_literal.rs:147:55 | LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); | ^^^ @@ -194,7 +343,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:96:52 + --> tests/ui/write_literal.rs:149:52 | LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); | ^^^ @@ -206,7 +355,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:98:49 + --> tests/ui/write_literal.rs:151:49 | LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); | ^^^ @@ -218,7 +367,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:100:43 + --> tests/ui/write_literal.rs:153:43 | LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); | ^^^ @@ -229,5 +378,5 @@ LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 18 previous errors +error: aborting due to 30 previous errors diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs deleted file mode 100644 index f896782aaf3b..000000000000 --- a/tests/ui/write_literal_2.rs +++ /dev/null @@ -1,65 +0,0 @@ -//@no-rustfix: overlapping suggestions -#![allow(unused_must_use)] -#![warn(clippy::write_literal)] - -use std::io::Write; - -fn main() { - let mut v = Vec::new(); - - writeln!(v, "{}", "{hello}"); - //~^ write_literal - - writeln!(v, r"{}", r"{hello}"); - //~^ write_literal - - writeln!(v, "{}", '\''); - //~^ write_literal - - writeln!(v, "{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '\''); - //~^ write_literal - - writeln!( - v, - "some {}", - "hello \ - //~^ write_literal - world!", - ); - writeln!( - v, - "some {}\ - {} \\ {}", - "1", - "2", - "3", - //~^^^ write_literal - ); - writeln!(v, "{}", "\\"); - //~^ write_literal - - writeln!(v, r"{}", "\\"); - //~^ write_literal - - writeln!(v, r#"{}"#, "\\"); - //~^ write_literal - - writeln!(v, "{}", r"\"); - //~^ write_literal - - writeln!(v, "{}", "\r"); - //~^ write_literal - - // hard mode - writeln!(v, r#"{}{}"#, '#', '"'); - //~^ write_literal - - // should not lint - writeln!(v, r"{}", "\r"); -} diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr deleted file mode 100644 index 29803d6a8b18..000000000000 --- a/tests/ui/write_literal_2.stderr +++ /dev/null @@ -1,165 +0,0 @@ -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:10:23 - | -LL | writeln!(v, "{}", "{hello}"); - | ^^^^^^^^^ - | - = note: `-D clippy::write-literal` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` -help: try - | -LL - writeln!(v, "{}", "{hello}"); -LL + writeln!(v, "{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:13:24 - | -LL | writeln!(v, r"{}", r"{hello}"); - | ^^^^^^^^^^ - | -help: try - | -LL - writeln!(v, r"{}", r"{hello}"); -LL + writeln!(v, r"{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:16:23 - | -LL | writeln!(v, "{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", '\''); -LL + writeln!(v, "'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:19:23 - | -LL | writeln!(v, "{}", '"'); - | ^^^ - | -help: try - | -LL - writeln!(v, "{}", '"'); -LL + writeln!(v, "\""); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:22:24 - | -LL | writeln!(v, r"{}", '"'); - | ^^^ - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:25:24 - | -LL | writeln!(v, r"{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", '\''); -LL + writeln!(v, r"'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:31:9 - | -LL | / "hello \ -LL | | -LL | | world!", - | |_______________^ - | -help: try - | -LL ~ "some hello \ -LL + -LL ~ world!", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:39:9 - | -LL | / "1", -LL | | "2", -LL | | "3", - | |___________^ - | -help: try - | -LL ~ "some 1\ -LL ~ 2 \\ 3", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:44:23 - | -LL | writeln!(v, "{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:47:24 - | -LL | writeln!(v, r"{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", "\\"); -LL + writeln!(v, r"\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:50:26 - | -LL | writeln!(v, r#"{}"#, "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r#"{}"#, "\\"); -LL + writeln!(v, r#"\"#); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:53:23 - | -LL | writeln!(v, "{}", r"\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", r"\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:56:23 - | -LL | writeln!(v, "{}", "\r"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\r"); -LL + writeln!(v, "\r"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:60:28 - | -LL | writeln!(v, r#"{}{}"#, '#', '"'); - | ^^^^^^^^ - -error: aborting due to 14 previous errors - diff --git a/tests/ui/write_literal_unfixable.rs b/tests/ui/write_literal_unfixable.rs new file mode 100644 index 000000000000..3a5660180779 --- /dev/null +++ b/tests/ui/write_literal_unfixable.rs @@ -0,0 +1,16 @@ +//@no-rustfix +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn escaping() { + let mut v = vec![]; + + writeln!(v, r"{}", '"'); + //~^ write_literal + + // hard mode + writeln!(v, r#"{}{}"#, '#', '"'); + //~^ write_literal +} diff --git a/tests/ui/write_literal_unfixable.stderr b/tests/ui/write_literal_unfixable.stderr new file mode 100644 index 000000000000..0dd40e891893 --- /dev/null +++ b/tests/ui/write_literal_unfixable.stderr @@ -0,0 +1,17 @@ +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:10:24 + | +LL | writeln!(v, r"{}", '"'); + | ^^^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` + +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:14:28 + | +LL | writeln!(v, r#"{}{}"#, '#', '"'); + | ^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/zero_repeat_side_effects.fixed b/tests/ui/zero_repeat_side_effects.fixed index fb9d7880a4a7..b5fca36f3f08 100644 --- a/tests/ui/zero_repeat_side_effects.fixed +++ b/tests/ui/zero_repeat_side_effects.fixed @@ -1,7 +1,6 @@ #![warn(clippy::zero_repeat_side_effects)] -#![allow(clippy::unnecessary_operation)] -#![allow(clippy::useless_vec)] -#![allow(clippy::needless_late_init)] +#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)] +#![allow(clippy::no_effect)] // only fires _after_ the fix fn f() -> i32 { println!("side effect"); @@ -15,36 +14,47 @@ fn main() { // should trigger // on arrays - f(); let a: [i32; 0] = []; + f(); + let a: [i32; 0] = []; //~^ zero_repeat_side_effects let mut b; - f(); b = [] as [i32; 0]; + f(); + b = [] as [i32; 0]; //~^ zero_repeat_side_effects // on vecs // vecs dont support inferring value of consts - f(); let c: std::vec::Vec = vec![]; + f(); + let c: std::vec::Vec = vec![]; //~^ zero_repeat_side_effects let d; - f(); d = vec![] as std::vec::Vec; + f(); + d = vec![] as std::vec::Vec; //~^ zero_repeat_side_effects // for macros - println!("side effect"); let e: [(); 0] = []; + println!("side effect"); + let e: [(); 0] = []; //~^ zero_repeat_side_effects // for nested calls - { f() }; let g: [i32; 0] = []; + { f() }; + let g: [i32; 0] = []; //~^ zero_repeat_side_effects // as function param - drop({ f(); vec![] as std::vec::Vec }); + drop({ + f(); + vec![] as std::vec::Vec + }); //~^ zero_repeat_side_effects // when singled out/not part of assignment/local - { f(); vec![] as std::vec::Vec }; + f(); + vec![] as std::vec::Vec; //~^ zero_repeat_side_effects - { f(); [] as [i32; 0] }; + f(); + [] as [i32; 0]; //~^ zero_repeat_side_effects // should not trigger @@ -79,3 +89,33 @@ fn issue_13110() { const LENGTH: usize = LEN!(); let _data = [f(); LENGTH]; } + +// TODO: consider moving the defintion+impl inside `issue_14681` +// once https://github.com/rust-lang/rust/issues/146786 is fixed +#[derive(Clone, Copy)] +struct S; + +impl S { + fn new() -> Self { + println!("This is a side effect"); + S + } +} + +// should not trigger on non-function calls +fn issue_14681() { + fn foo(_s: &[Option]) {} + + foo(&[Some(0i64); 0]); + foo(&[Some(Some(0i64)); 0]); + foo(&{ + Some(f()); + [] as [std::option::Option; 0] + }); + //~^ zero_repeat_side_effects + foo(&{ + Some(Some(S::new())); + [] as [std::option::Option>; 0] + }); + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects.rs b/tests/ui/zero_repeat_side_effects.rs index 8b22ff840244..ea043d21638c 100644 --- a/tests/ui/zero_repeat_side_effects.rs +++ b/tests/ui/zero_repeat_side_effects.rs @@ -1,7 +1,6 @@ #![warn(clippy::zero_repeat_side_effects)] -#![allow(clippy::unnecessary_operation)] -#![allow(clippy::useless_vec)] -#![allow(clippy::needless_late_init)] +#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)] +#![allow(clippy::no_effect)] // only fires _after_ the fix fn f() -> i32 { println!("side effect"); @@ -79,3 +78,27 @@ fn issue_13110() { const LENGTH: usize = LEN!(); let _data = [f(); LENGTH]; } + +// TODO: consider moving the defintion+impl inside `issue_14681` +// once https://github.com/rust-lang/rust/issues/146786 is fixed +#[derive(Clone, Copy)] +struct S; + +impl S { + fn new() -> Self { + println!("This is a side effect"); + S + } +} + +// should not trigger on non-function calls +fn issue_14681() { + fn foo(_s: &[Option]) {} + + foo(&[Some(0i64); 0]); + foo(&[Some(Some(0i64)); 0]); + foo(&[Some(f()); 0]); + //~^ zero_repeat_side_effects + foo(&[Some(Some(S::new())); 0]); + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects.stderr b/tests/ui/zero_repeat_side_effects.stderr index 2dba52e2112e..49e850d03534 100644 --- a/tests/ui/zero_repeat_side_effects.stderr +++ b/tests/ui/zero_repeat_side_effects.stderr @@ -1,59 +1,142 @@ -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:18:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:17:5 | LL | let a = [f(); 0]; - | ^^^^^^^^^^^^^^^^^ help: consider using: `f(); let a: [i32; 0] = [];` + | ^^^^^^^^^^^^^^^^^ | = note: `-D clippy::zero-repeat-side-effects` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::zero_repeat_side_effects)]` +help: consider performing the side effect separately + | +LL ~ f(); +LL + let a: [i32; 0] = []; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:21:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:20:5 | LL | b = [f(); 0]; - | ^^^^^^^^^^^^ help: consider using: `f(); b = [] as [i32; 0]` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ f(); +LL ~ b = [] as [i32; 0]; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:26:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:25:5 | LL | let c = vec![f(); 0]; - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f(); let c: std::vec::Vec = vec![];` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ f(); +LL + let c: std::vec::Vec = vec![]; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:29:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:28:5 | LL | d = vec![f(); 0]; - | ^^^^^^^^^^^^^^^^ help: consider using: `f(); d = vec![] as std::vec::Vec` + | ^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ f(); +LL ~ d = vec![] as std::vec::Vec; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:33:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:32:5 | LL | let e = [println!("side effect"); 0]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `println!("side effect"); let e: [(); 0] = [];` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ println!("side effect"); +LL + let e: [(); 0] = []; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:37:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:36:5 | LL | let g = [{ f() }; 0]; - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `{ f() }; let g: [i32; 0] = [];` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ { f() }; +LL + let g: [i32; 0] = []; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:41:10 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:40:10 | LL | drop(vec![f(); 0]); - | ^^^^^^^^^^^^ help: consider using: `{ f(); vec![] as std::vec::Vec }` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ drop({ +LL + f(); +LL + vec![] as std::vec::Vec +LL ~ }); + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:45:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:44:5 | LL | vec![f(); 0]; - | ^^^^^^^^^^^^ help: consider using: `{ f(); vec![] as std::vec::Vec }` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ f(); +LL ~ vec![] as std::vec::Vec; + | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:47:5 +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:46:5 | LL | [f(); 0]; - | ^^^^^^^^ help: consider using: `{ f(); [] as [i32; 0] }` + | ^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ f(); +LL ~ [] as [i32; 0]; + | + +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:100:10 + | +LL | foo(&[Some(f()); 0]); + | ^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ foo(&{ +LL + Some(f()); +LL + [] as [std::option::Option; 0] +LL ~ }); + | + +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:102:10 + | +LL | foo(&[Some(Some(S::new())); 0]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL ~ foo(&{ +LL + Some(Some(S::new())); +LL + [] as [std::option::Option>; 0] +LL ~ }); + | -error: aborting due to 9 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/zero_repeat_side_effects_never_pattern.fixed b/tests/ui/zero_repeat_side_effects_never_pattern.fixed new file mode 100644 index 000000000000..3d037516f75c --- /dev/null +++ b/tests/ui/zero_repeat_side_effects_never_pattern.fixed @@ -0,0 +1,10 @@ +#![warn(clippy::zero_repeat_side_effects)] +#![allow(clippy::diverging_sub_expression)] +#![feature(never_type)] + +fn issue_14998() { + // nameable type thanks to `never_type` being enabled, suggest + panic!(); + let _data: [!; 0] = []; + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects_never_pattern.rs b/tests/ui/zero_repeat_side_effects_never_pattern.rs new file mode 100644 index 000000000000..3dc1929bcdc7 --- /dev/null +++ b/tests/ui/zero_repeat_side_effects_never_pattern.rs @@ -0,0 +1,9 @@ +#![warn(clippy::zero_repeat_side_effects)] +#![allow(clippy::diverging_sub_expression)] +#![feature(never_type)] + +fn issue_14998() { + // nameable type thanks to `never_type` being enabled, suggest + let _data = [panic!(); 0]; + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects_never_pattern.stderr b/tests/ui/zero_repeat_side_effects_never_pattern.stderr new file mode 100644 index 000000000000..280955740cc4 --- /dev/null +++ b/tests/ui/zero_repeat_side_effects_never_pattern.stderr @@ -0,0 +1,16 @@ +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects_never_pattern.rs:7:5 + | +LL | let _data = [panic!(); 0]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::zero-repeat-side-effects` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::zero_repeat_side_effects)]` +help: consider performing the side effect separately + | +LL ~ panic!(); +LL + let _data: [!; 0] = []; + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/zero_repeat_side_effects_unfixable.rs b/tests/ui/zero_repeat_side_effects_unfixable.rs new file mode 100644 index 000000000000..82f0884056ab --- /dev/null +++ b/tests/ui/zero_repeat_side_effects_unfixable.rs @@ -0,0 +1,13 @@ +//@no-rustfix +#![warn(clippy::zero_repeat_side_effects)] +#![expect(clippy::diverging_sub_expression)] + +fn issue_14998() { + // unnameable types, don't suggest + let _data = [|| 3i32; 0]; + //~^ zero_repeat_side_effects + + // unnameable type because `never_type` is not enabled, don't suggest + let _data = [panic!(); 0]; + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects_unfixable.stderr b/tests/ui/zero_repeat_side_effects_unfixable.stderr new file mode 100644 index 000000000000..450617f3782c --- /dev/null +++ b/tests/ui/zero_repeat_side_effects_unfixable.stderr @@ -0,0 +1,20 @@ +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects_unfixable.rs:7:5 + | +LL | let _data = [|| 3i32; 0]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider performing the side effect separately + = note: `-D clippy::zero-repeat-side-effects` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::zero_repeat_side_effects)]` + +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects_unfixable.rs:11:5 + | +LL | let _data = [panic!(); 0]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider performing the side effect separately + +error: aborting due to 2 previous errors + diff --git a/triagebot.toml b/triagebot.toml index 7b19f8658c08..db951b95ef50 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -45,6 +45,9 @@ reviewed_label = "S-waiting-on-author" [autolabel."S-waiting-on-review"] new_pr = true +[autolabel."needs-fcp"] +trigger_files = ["clippy_lints/src/declared_lints.rs"] + [concern] # These labels are set when there are unresolved concerns, removed otherwise labels = ["S-waiting-on-concerns"] @@ -60,7 +63,7 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", - "flip1995", + "blyxyas", ] [assign.owners] diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 2b6ee67c37dc..b468b52aea5b 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,23 +1,12 @@ "use strict"; window.searchState = { - timeout: null, inputElem: document.getElementById("search-input"), lastSearch: '', clearInput: () => { searchState.inputElem.value = ""; searchState.filterLints(); }, - clearInputTimeout: () => { - if (searchState.timeout !== null) { - clearTimeout(searchState.timeout); - searchState.timeout = null - } - }, - resetInputTimeout: () => { - searchState.clearInputTimeout(); - setTimeout(searchState.filterLints, 50); - }, filterLints: () => { function matchesSearch(lint, terms, searchStr) { // Search by id @@ -42,8 +31,6 @@ window.searchState = { return true; } - searchState.clearInputTimeout(); - let searchStr = searchState.inputElem.value.trim().toLowerCase(); if (searchStr.startsWith("clippy::")) { searchStr = searchStr.slice(8); @@ -79,7 +66,7 @@ function handleInputChanged(event) { if (event.target !== document.activeElement) { return; } - searchState.resetInputTimeout(); + searchState.filterLints(); } function handleShortcut(ev) { @@ -149,27 +136,25 @@ function lintAnchor(event) { expandLint(id); } +const clipboardTimeouts = new Map(); function copyToClipboard(event) { event.preventDefault(); event.stopPropagation(); const clipboard = event.target; - let resetClipboardTimeout = null; - const resetClipboardIcon = clipboard.innerHTML; - - function resetClipboard() { - resetClipboardTimeout = null; - clipboard.innerHTML = resetClipboardIcon; - } - navigator.clipboard.writeText("clippy::" + clipboard.parentElement.id.slice(5)); - clipboard.innerHTML = "✓"; - if (resetClipboardTimeout !== null) { - clearTimeout(resetClipboardTimeout); - } - resetClipboardTimeout = setTimeout(resetClipboard, 1000); + clipboard.textContent = "✓"; + + clearTimeout(clipboardTimeouts.get(clipboard)); + clipboardTimeouts.set( + clipboard, + setTimeout(() => { + clipboard.textContent = "📋"; + clipboardTimeouts.delete(clipboard); + }, 1000) + ); } function handleBlur(event, elementId) { @@ -487,14 +472,6 @@ function generateSettings() { setupDropdown("version-filter"); } -function generateSearch() { - searchState.inputElem.addEventListener("change", handleInputChanged); - searchState.inputElem.addEventListener("input", handleInputChanged); - searchState.inputElem.addEventListener("keydown", handleInputChanged); - searchState.inputElem.addEventListener("keyup", handleInputChanged); - searchState.inputElem.addEventListener("paste", handleInputChanged); -} - function scrollToLint(lintId) { const target = document.getElementById(lintId); if (!target) { @@ -540,6 +517,8 @@ function parseURLFilters() { } function addListeners() { + searchState.inputElem.addEventListener("input", handleInputChanged); + disableShortcutsButton.addEventListener("change", () => { disableShortcuts = disableShortcutsButton.checked; storeValue("disable-shortcuts", disableShortcuts); @@ -594,7 +573,6 @@ disableShortcutsButton.checked = disableShortcuts; addListeners(); highlightLazily(); generateSettings(); -generateSearch(); parseURLFilters(); scrollToLintByURL(); filters.filterLints();