From 7a6f7bc7a668aa309249ca7e8a4275f7fedce173 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 27 May 2025 16:16:18 +0200 Subject: [PATCH 01/19] Update clippy source code to changes on `source_span_for_markdown_range` --- clippy_lints/src/doc/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index e0fc2fd93474..d38588bb799a 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -765,8 +765,8 @@ impl Fragments<'_> { /// get the span for the markdown range. Note that this function is not cheap, use it with /// caution. #[must_use] - fn span(&self, cx: &LateContext<'_>, range: Range) -> Option { - source_span_for_markdown_range(cx.tcx, self.doc, &range, self.fragments) + fn span(self, cx: &LateContext<'_>, range: Range) -> Option { + source_span_for_markdown_range(cx.tcx, self.doc, &range, self.fragments).map(|(sp, _)| sp) } } From 4f3c17486eafadd4ccebdf5230b27a285c9a110d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 11 Mar 2025 12:08:45 +0000 Subject: [PATCH 02/19] Change const trait bound syntax from ~const to [const] --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- tests/ui/assign_ops.fixed | 2 +- tests/ui/assign_ops.rs | 2 +- tests/ui/trait_duplication_in_bounds.fixed | 4 ++-- tests/ui/trait_duplication_in_bounds.rs | 4 ++-- tests/ui/trait_duplication_in_bounds.stderr | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index e629012b187c..328fe3d428b8 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -436,7 +436,7 @@ fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx> // FIXME(const_trait_impl, fee1-dead) revert to const destruct once it works again #[expect(unused)] fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool { - // If this doesn't need drop at all, then don't select `~const Destruct`. + // If this doesn't need drop at all, then don't select `[const] Destruct`. if !ty.needs_drop(tcx, body.typing_env(tcx)) { return false; } diff --git a/tests/ui/assign_ops.fixed b/tests/ui/assign_ops.fixed index 3bc6885d7c3e..99beea850a25 100644 --- a/tests/ui/assign_ops.fixed +++ b/tests/ui/assign_ops.fixed @@ -91,7 +91,7 @@ mod issue14871 { impl const NumberConstants for T where - T: Number + ~const core::ops::Add, + T: Number + [const] core::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; diff --git a/tests/ui/assign_ops.rs b/tests/ui/assign_ops.rs index f1f8f9daff95..900d5ad38e03 100644 --- a/tests/ui/assign_ops.rs +++ b/tests/ui/assign_ops.rs @@ -91,7 +91,7 @@ mod issue14871 { impl const NumberConstants for T where - T: Number + ~const core::ops::Add, + T: Number + [const] core::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; diff --git a/tests/ui/trait_duplication_in_bounds.fixed b/tests/ui/trait_duplication_in_bounds.fixed index 666ff78b2189..cf52ecf2f032 100644 --- a/tests/ui/trait_duplication_in_bounds.fixed +++ b/tests/ui/trait_duplication_in_bounds.fixed @@ -169,9 +169,9 @@ where // #13476 #[const_trait] trait ConstTrait {} -const fn const_trait_bounds_good() {} +const fn const_trait_bounds_good() {} -const fn const_trait_bounds_bad() {} +const fn const_trait_bounds_bad() {} //~^ trait_duplication_in_bounds fn projections() diff --git a/tests/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index a1a86fe058e6..955562f08dc3 100644 --- a/tests/ui/trait_duplication_in_bounds.rs +++ b/tests/ui/trait_duplication_in_bounds.rs @@ -169,9 +169,9 @@ where // #13476 #[const_trait] trait ConstTrait {} -const fn const_trait_bounds_good() {} +const fn const_trait_bounds_good() {} -const fn const_trait_bounds_bad() {} +const fn const_trait_bounds_bad() {} //~^ trait_duplication_in_bounds fn projections() diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index d76b4e458480..ab31721ef515 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -61,8 +61,8 @@ LL | fn bad_trait_object(arg0: &(dyn Any + Send + Send)) { error: these bounds contain repeated elements --> tests/ui/trait_duplication_in_bounds.rs:174:36 | -LL | const fn const_trait_bounds_bad() {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `~const ConstTrait` +LL | const fn const_trait_bounds_bad() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[const] ConstTrait` error: these where clauses contain repeated elements --> tests/ui/trait_duplication_in_bounds.rs:181:8 From 074ccaf3c0818c1c7162babe8e7874f1fa551e18 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 27 Jun 2025 12:20:57 +0200 Subject: [PATCH 03/19] Merge commit 'c5dbd1de07e0407b9687619a868384d6de06253f' into clippy-subtree-update --- .github/ISSUE_TEMPLATE/new_lint.yml | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 4 + .github/workflows/feature_freeze.yml | 25 ++ CHANGELOG.md | 91 +++++- Cargo.toml | 3 +- book/src/README.md | 4 + book/src/SUMMARY.md | 1 + book/src/development/adding_lints.md | 3 + book/src/development/feature_freeze.md | 55 ++++ book/src/lint_configuration.md | 24 +- clippy_config/Cargo.toml | 2 +- clippy_config/src/conf.rs | 25 +- clippy_dev/src/lint.rs | 4 +- clippy_dev/src/release.rs | 1 + clippy_dev/src/serve.rs | 2 +- clippy_dev/src/update_lints.rs | 301 ++++++++++-------- clippy_dev/src/utils.rs | 15 - clippy_lints/Cargo.toml | 3 +- clippy_lints/src/attrs/inline_always.rs | 4 +- clippy_lints/src/attrs/mod.rs | 2 +- clippy_lints/src/attrs/utils.rs | 12 +- clippy_lints/src/borrow_deref_ref.rs | 15 +- clippy_lints/src/casts/cast_sign_loss.rs | 2 +- clippy_lints/src/collapsible_if.rs | 127 ++++++-- clippy_lints/src/copies.rs | 18 +- clippy_lints/src/declare_clippy_lint.rs | 168 ---------- clippy_lints/src/declared_lints.rs | 4 +- clippy_lints/src/disallowed_macros.rs | 3 + clippy_lints/src/disallowed_methods.rs | 3 + clippy_lints/src/disallowed_types.rs | 3 + clippy_lints/src/doc/broken_link.rs | 83 +++++ .../src/doc/doc_suspicious_footnotes.rs | 6 +- clippy_lints/src/doc/mod.rs | 50 ++- clippy_lints/src/doc/needless_doctest_main.rs | 27 +- clippy_lints/src/empty_line_after.rs | 64 +++- clippy_lints/src/eta_reduction.rs | 92 +++--- clippy_lints/src/exhaustive_items.rs | 2 +- clippy_lints/src/floating_point_arithmetic.rs | 3 +- clippy_lints/src/functions/must_use.rs | 53 ++- clippy_lints/src/if_not_else.rs | 11 +- clippy_lints/src/inline_fn_without_body.rs | 10 +- clippy_lints/src/lib.rs | 126 +------- clippy_lints/src/loops/same_item_push.rs | 15 +- clippy_lints/src/manual_let_else.rs | 1 - clippy_lints/src/matches/manual_ok_err.rs | 20 +- clippy_lints/src/matches/match_wild_enum.rs | 14 +- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 11 +- clippy_lints/src/missing_inline.rs | 2 +- .../src/needless_borrows_for_generic_args.rs | 12 +- .../src/needless_parens_on_range_literals.rs | 1 - clippy_lints/src/needless_pass_by_value.rs | 6 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 5 +- clippy_lints/src/non_copy_const.rs | 2 +- clippy_lints/src/operators/identity_op.rs | 76 ++++- .../src/operators/manual_is_multiple_of.rs | 66 ++++ clippy_lints/src/operators/mod.rs | 33 ++ clippy_lints/src/pass_by_ref_or_value.rs | 4 +- clippy_lints/src/question_mark.rs | 1 + clippy_lints/src/question_mark_used.rs | 1 - clippy_lints/src/read_zero_byte_vec.rs | 3 +- clippy_lints/src/return_self_not_must_use.rs | 5 +- .../src/single_component_path_imports.rs | 29 +- .../src/undocumented_unsafe_blocks.rs | 45 ++- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/consts.rs | 15 + clippy_utils/src/diagnostics.rs | 6 +- clippy_utils/src/lib.rs | 22 +- clippy_utils/src/msrvs.rs | 3 +- clippy_utils/src/qualify_min_const_fn.rs | 15 + clippy_utils/src/sugg.rs | 22 +- clippy_utils/src/sym.rs | 5 +- clippy_utils/src/ty/mod.rs | 24 +- declare_clippy_lint/Cargo.toml | 10 + declare_clippy_lint/src/lib.rs | 280 ++++++++++++++++ lintcheck/src/main.rs | 2 +- rust-toolchain.toml | 2 +- src/driver.rs | 8 +- src/main.rs | 2 +- tests/compile-test.rs | 6 +- tests/dogfood.rs | 1 + .../collapsible_if/collapsible_else_if.fixed | 50 +++ .../collapsible_if/collapsible_else_if.rs | 55 ++++ .../collapsible_if/collapsible_else_if.stderr | 105 ++++++ tests/ui/borrow_deref_ref.fixed | 47 +++ tests/ui/borrow_deref_ref.rs | 47 +++ tests/ui/borrow_deref_ref.stderr | 32 +- tests/ui/borrow_interior_mutable_const.rs | 16 + tests/ui/box_default.fixed | 2 +- tests/ui/box_default.rs | 2 +- .../branches_sharing_code/shared_at_bottom.rs | 24 ++ .../shared_at_bottom.stderr | 32 +- tests/ui/collapsible_else_if.fixed | 18 ++ tests/ui/collapsible_else_if.rs | 18 ++ tests/ui/collapsible_if.fixed | 9 + tests/ui/collapsible_if.rs | 9 + tests/ui/doc/needless_doctest_main.rs | 112 ++++++- tests/ui/doc/needless_doctest_main.stderr | 36 +++ tests/ui/doc_broken_link.rs | 72 +++++ tests/ui/doc_broken_link.stderr | 29 ++ .../empty_line_after/outer_attribute.1.fixed | 9 + .../empty_line_after/outer_attribute.2.fixed | 9 + tests/ui/empty_line_after/outer_attribute.rs | 10 + .../empty_line_after/outer_attribute.stderr | 13 +- tests/ui/eta.fixed | 18 ++ tests/ui/eta.rs | 18 ++ tests/ui/exhaustive_items.fixed | 7 + tests/ui/exhaustive_items.rs | 7 + tests/ui/exhaustive_items.stderr | 10 +- tests/ui/identity_op.fixed | 46 +++ tests/ui/identity_op.rs | 46 +++ tests/ui/identity_op.stderr | 44 ++- tests/ui/infinite_iter.rs | 2 +- tests/ui/infinite_iter.stderr | 4 +- tests/ui/iter_kv_map.fixed | 20 +- tests/ui/iter_kv_map.rs | 20 +- tests/ui/iter_kv_map.stderr | 64 ++-- tests/ui/let_unit.fixed | 2 +- tests/ui/let_unit.rs | 2 +- tests/ui/let_unit.stderr | 2 +- tests/ui/manual_contains.fixed | 2 +- tests/ui/manual_contains.rs | 2 +- tests/ui/manual_find_fixable.fixed | 4 +- tests/ui/manual_find_fixable.rs | 4 +- tests/ui/manual_find_fixable.stderr | 4 +- tests/ui/manual_is_multiple_of.fixed | 25 ++ tests/ui/manual_is_multiple_of.rs | 25 ++ tests/ui/manual_is_multiple_of.stderr | 41 +++ tests/ui/manual_is_variant_and.fixed | 8 +- tests/ui/manual_is_variant_and.rs | 8 +- tests/ui/manual_is_variant_and.stderr | 12 +- tests/ui/manual_ok_err.fixed | 24 ++ tests/ui/manual_ok_err.rs | 36 +++ tests/ui/manual_ok_err.stderr | 32 +- .../ui/missing_const_for_fn/const_trait.fixed | 2 +- tests/ui/missing_const_for_fn/const_trait.rs | 2 +- .../missing_const_for_fn/could_be_const.fixed | 57 ++++ .../ui/missing_const_for_fn/could_be_const.rs | 57 ++++ .../could_be_const.stderr | 72 ++++- tests/ui/nonminimal_bool.stderr | 4 +- tests/ui/or_fun_call.fixed | 30 ++ tests/ui/or_fun_call.rs | 30 ++ tests/ui/or_fun_call.stderr | 96 +++--- tests/ui/question_mark.fixed | 12 + tests/ui/question_mark.rs | 12 + .../ui/unnecessary_os_str_debug_formatting.rs | 13 + ...unnecessary_os_str_debug_formatting.stderr | 11 +- tests/ui/wildcard_enum_match_arm.fixed | 29 ++ tests/ui/wildcard_enum_match_arm.rs | 29 ++ tests/ui/wildcard_enum_match_arm.stderr | 14 +- tests/versioncheck.rs | 1 + triagebot.toml | 3 + util/versions.py | 6 +- 154 files changed, 3163 insertions(+), 848 deletions(-) create mode 100644 .github/workflows/feature_freeze.yml create mode 100644 book/src/development/feature_freeze.md delete mode 100644 clippy_lints/src/declare_clippy_lint.rs create mode 100644 clippy_lints/src/doc/broken_link.rs create mode 100644 clippy_lints/src/operators/manual_is_multiple_of.rs create mode 100644 declare_clippy_lint/Cargo.toml create mode 100644 declare_clippy_lint/src/lib.rs create mode 100644 tests/ui-toml/collapsible_if/collapsible_else_if.fixed create mode 100644 tests/ui-toml/collapsible_if/collapsible_else_if.rs create mode 100644 tests/ui-toml/collapsible_if/collapsible_else_if.stderr create mode 100644 tests/ui/doc/needless_doctest_main.stderr create mode 100644 tests/ui/doc_broken_link.rs create mode 100644 tests/ui/doc_broken_link.stderr create mode 100644 tests/ui/manual_is_multiple_of.fixed create mode 100644 tests/ui/manual_is_multiple_of.rs create mode 100644 tests/ui/manual_is_multiple_of.stderr diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index b49493edce1b..464740640e0c 100644 --- a/.github/ISSUE_TEMPLATE/new_lint.yml +++ b/.github/ISSUE_TEMPLATE/new_lint.yml @@ -1,5 +1,7 @@ name: New lint suggestion -description: Suggest a new Clippy lint. +description: | + Suggest a new Clippy lint (currently not accepting new lints) + Check out the Clippy book for more information about the feature freeze. labels: ["A-lint"] body: - type: markdown diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9e49f60892d2..83bfd8e9c686 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,6 +32,10 @@ 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/feature_freeze.yml b/.github/workflows/feature_freeze.yml new file mode 100644 index 000000000000..a5f8d4bc145c --- /dev/null +++ b/.github/workflows/feature_freeze.yml @@ -0,0 +1,25 @@ +name: Feature freeze check + +on: + pull_request: + paths: + - 'clippy_lints/src/declared_lints.rs' + +jobs: + auto-comment: + runs-on: ubuntu-latest + + steps: + - name: Check PR Changes + id: pr-changes + run: echo "::set-output name=changes::${{ toJson(github.event.pull_request.changed_files) }}" + + - name: Create Comment + if: steps.pr-changes.outputs.changes != '[]' + run: | + # Use GitHub API to create a comment on the PR + PR_NUMBER=${{ github.event.pull_request.number }} + COMMENT="**Seems that you are trying to add a new lint!**\nWe 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 August 1st and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team" + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" + curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cfe89ad3787..a92fbdc767bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,94 @@ document. ## Unreleased / Beta / In Rust Nightly -[1e5237f4...master](https://github.com/rust-lang/rust-clippy/compare/1e5237f4...master) +[03a5b6b9...master](https://github.com/rust-lang/rust-clippy/compare/03a5b6b9...master) + +## Rust 1.88 + +Current stable, released 2025-06-26 + +[View all 126 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-03-21T10%3A30%3A57Z..2025-05-01T08%3A03%3A26Z+base%3Amaster) + +### New Lints + +* Added [`swap_with_temporary`] to `complexity` [#14046](https://github.com/rust-lang/rust-clippy/pull/14046) +* Added [`redundant_test_prefix`] to `restriction` [#13710](https://github.com/rust-lang/rust-clippy/pull/13710) +* Added [`manual_dangling_ptr`] to `style` [#14107](https://github.com/rust-lang/rust-clippy/pull/14107) +* Added [`char_indices_as_byte_indices`] to `correctness` [#13435](https://github.com/rust-lang/rust-clippy/pull/13435) +* Added [`manual_abs_diff`] to `complexity` [#14482](https://github.com/rust-lang/rust-clippy/pull/14482) +* Added [`ignore_without_reason`] to `pedantic` [#13931](https://github.com/rust-lang/rust-clippy/pull/13931) + +### Moves and Deprecations + +* Moved [`uninlined_format_args`] to `style` (from `pedantic`) + [#14160](https://github.com/rust-lang/rust-clippy/pull/14160) +* [`match_on_vec_items`] deprecated in favor of [`indexing_slicing`] + [#14217](https://github.com/rust-lang/rust-clippy/pull/14217) +* Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`, + `transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc) + [#14703](https://github.com/rust-lang/rust-clippy/pull/14703) + +### Enhancements + +* Configuration renamed from `lint-inconsistent-struct-field-initializers` + to `check-inconsistent-struct-field-initializers` + [#14280](https://github.com/rust-lang/rust-clippy/pull/14280) +* Paths in `disallowed_*` configurations are now validated + [#14397](https://github.com/rust-lang/rust-clippy/pull/14397) +* [`borrow_as_ptr`] now lints implicit casts as well + [#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 + 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) +* [`wildcard_imports`] now lints on `pub use` when `warn_on_all_wildcard_imports` is enabled + [#14182](https://github.com/rust-lang/rust-clippy/pull/14182) +* [`collapsible_if`] now recognizes the `let_chains` feature + [#14481](https://github.com/rust-lang/rust-clippy/pull/14481) +* [`match_single_binding`] now allows macros in scrutinee and patterns + [#14635](https://github.com/rust-lang/rust-clippy/pull/14635) +* [`needless_borrow`] does not contradict the compiler's + `dangerous_implicit_autorefs` lint even though the references + are not mandatory + [#14810](https://github.com/rust-lang/rust-clippy/pull/14810) + +### False Positive Fixes + +* [`double_ended_iterator_last`] and [`needless_collect`] fixed FP when iter has side effects + [#14490](https://github.com/rust-lang/rust-clippy/pull/14490) +* [`mut_from_ref`] fixed FP where lifetimes nested in types were not considered + [#14471](https://github.com/rust-lang/rust-clippy/pull/14471) +* [`redundant_clone`] fixed FP in overlapping lifetime + [#14237](https://github.com/rust-lang/rust-clippy/pull/14237) +* [`map_entry`] fixed FP where lint would trigger without insert calls present + [#14568](https://github.com/rust-lang/rust-clippy/pull/14568) +* [`iter_cloned_collect`] fixed FP with custom `From`/`IntoIterator` impl + [#14473](https://github.com/rust-lang/rust-clippy/pull/14473) +* [`shadow_unrelated`] fixed FP in destructuring assignments + [#14381](https://github.com/rust-lang/rust-clippy/pull/14381) +* [`redundant_clone`] fixed FP on enum cast + [#14395](https://github.com/rust-lang/rust-clippy/pull/14395) +* [`collapsible_if`] fixed FP on block stmt before expr + [#14730](https://github.com/rust-lang/rust-clippy/pull/14730) + +### ICE Fixes + +* [`missing_const_for_fn`] fix ICE with `-Z validate-mir` compilation option + [#14776](https://github.com/rust-lang/rust-clippy/pull/14776) + +### Documentation Improvements + +* [`missing_asserts_for_indexing`] improved documentation and examples + [#14108](https://github.com/rust-lang/rust-clippy/pull/14108) + +### Others + +* We're testing with edition 2024 now + [#14602](https://github.com/rust-lang/rust-clippy/pull/14602) +* Don't warn about unloaded crates in `clippy.toml` disallowed paths + [#14733](https://github.com/rust-lang/rust-clippy/pull/14733) ## Rust 1.87 @@ -5729,6 +5816,7 @@ Released 2018-09-13 [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression +[`doc_broken_link`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_broken_link [`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks [`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg [`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation @@ -5967,6 +6055,7 @@ Released 2018-09-13 [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check [`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite [`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite +[`manual_is_multiple_of`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of [`manual_is_power_of_two`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two [`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and [`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else diff --git a/Cargo.toml b/Cargo.toml index 13cf82a062b5..1278427b5a76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.89" +version = "0.1.90" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -24,6 +24,7 @@ path = "src/driver.rs" clippy_config = { path = "clippy_config" } clippy_lints = { path = "clippy_lints" } clippy_utils = { path = "clippy_utils" } +declare_clippy_lint = { path = "declare_clippy_lint" } rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } clippy_lints_internal = { path = "clippy_lints_internal", optional = true } tempfile = { version = "3.20", optional = true } diff --git a/book/src/README.md b/book/src/README.md index 5d2c3972b060..db73b49ecc24 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,5 +1,9 @@ # 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 39fe7358ed87..b66c3481e493 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -13,6 +13,7 @@ - [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 2b89e94cf8f4..a42a29837446 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -1,5 +1,8 @@ # 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. diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md new file mode 100644 index 000000000000..260cb136cc07 --- /dev/null +++ b/book/src/development/feature_freeze.md @@ -0,0 +1,55 @@ +# 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/lint_configuration.md b/book/src/lint_configuration.md index 7c850b4b023a..e9b7f42a1831 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -488,6 +488,13 @@ The maximum cognitive complexity a function can have ## `disallowed-macros` The list of disallowed macros, written as fully qualified paths. +**Fields:** +- `path` (required): the fully qualified path to the macro that should be disallowed +- `reason` (optional): explanation why this macro is disallowed +- `replacement` (optional): suggested alternative macro +- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + if the path doesn't exist, instead of emitting an error + **Default Value:** `[]` --- @@ -498,6 +505,13 @@ The list of disallowed macros, written as fully qualified paths. ## `disallowed-methods` The list of disallowed methods, written as fully qualified paths. +**Fields:** +- `path` (required): the fully qualified path to the method that should be disallowed +- `reason` (optional): explanation why this method is disallowed +- `replacement` (optional): suggested alternative method +- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + if the path doesn't exist, instead of emitting an error + **Default Value:** `[]` --- @@ -520,6 +534,13 @@ default configuration of Clippy. By default, any configuration will replace the ## `disallowed-types` The list of disallowed types, written as fully qualified paths. +**Fields:** +- `path` (required): the fully qualified path to the type that should be disallowed +- `reason` (optional): explanation why this type is disallowed +- `replacement` (optional): suggested alternative type +- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + if the path doesn't exist, instead of emitting an error + **Default Value:** `[]` --- @@ -651,13 +672,14 @@ The maximum size of the `Err`-variant in a `Result` returned from a function ## `lint-commented-code` -Whether collapsible `if` chains are linted if they contain comments inside the parts +Whether collapsible `if` and `else if` chains are linted if they contain comments inside the parts that would be collapsed. **Default Value:** `false` --- **Affected lints:** +* [`collapsible_else_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if) * [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 0606245f990c..858366c8a5c4 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.89" +version = "0.1.90" edition = "2024" publish = false diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 87158cec42b2..841facdca06d 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -575,10 +575,24 @@ define_Conf! { #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)] cyclomatic_complexity_threshold: u64 = 25, /// The list of disallowed macros, written as fully qualified paths. + /// + /// **Fields:** + /// - `path` (required): the fully qualified path to the macro that should be disallowed + /// - `reason` (optional): explanation why this macro is disallowed + /// - `replacement` (optional): suggested alternative macro + /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + /// if the path doesn't exist, instead of emitting an error #[disallowed_paths_allow_replacements = true] #[lints(disallowed_macros)] disallowed_macros: Vec = Vec::new(), /// The list of disallowed methods, written as fully qualified paths. + /// + /// **Fields:** + /// - `path` (required): the fully qualified path to the method that should be disallowed + /// - `reason` (optional): explanation why this method is disallowed + /// - `replacement` (optional): suggested alternative method + /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + /// if the path doesn't exist, instead of emitting an error #[disallowed_paths_allow_replacements = true] #[lints(disallowed_methods)] disallowed_methods: Vec = Vec::new(), @@ -588,6 +602,13 @@ define_Conf! { #[lints(disallowed_names)] disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(), /// The list of disallowed types, written as fully qualified paths. + /// + /// **Fields:** + /// - `path` (required): the fully qualified path to the type that should be disallowed + /// - `reason` (optional): explanation why this type is disallowed + /// - `replacement` (optional): suggested alternative type + /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry + /// if the path doesn't exist, instead of emitting an error #[disallowed_paths_allow_replacements = true] #[lints(disallowed_types)] disallowed_types: Vec = Vec::new(), @@ -641,9 +662,9 @@ define_Conf! { /// The maximum size of the `Err`-variant in a `Result` returned from a function #[lints(result_large_err)] large_error_threshold: u64 = 128, - /// Whether collapsible `if` chains are linted if they contain comments inside the parts + /// Whether collapsible `if` and `else if` chains are linted if they contain comments inside the parts /// that would be collapsed. - #[lints(collapsible_if)] + #[lints(collapsible_else_if, collapsible_if)] lint_commented_code: bool = false, /// Whether to suggest reordering constructor fields when initializers are present. /// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index e0e036757d56..0d66f167a386 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -13,7 +13,7 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator if is_file { exit_if_err( - Command::new(env::var("CARGO").unwrap_or("cargo".into())) + Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) .args(["run", "--bin", "clippy-driver", "--"]) .args(["-L", "./target/debug"]) .args(["-Z", "no-codegen"]) @@ -26,7 +26,7 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator ); } else { exit_if_err( - Command::new(env::var("CARGO").unwrap_or("cargo".into())) + Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) .arg("build") .status(), ); diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 62c1bee81850..15392dd1d292 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -5,6 +5,7 @@ static CARGO_TOML_FILES: &[&str] = &[ "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", "clippy_utils/Cargo.toml", + "declare_clippy_lint/Cargo.toml", "Cargo.toml", ]; diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index a2d1236629fd..498ffeba9d67 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -28,7 +28,7 @@ pub fn run(port: u16, lint: Option) -> ! { .map(mtime); if times.iter().any(|&time| index_time < time) { - Command::new(env::var("CARGO").unwrap_or("cargo".into())) + Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) .arg("collect-metadata") .spawn() .unwrap() diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 3b827cc5603e..5f6e874ffe25 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -2,11 +2,11 @@ use crate::utils::{ ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn, }; use itertools::Itertools; -use rustc_lexer::{LiteralKind, TokenKind, tokenize}; use std::collections::HashSet; use std::fmt::Write; +use std::fs; use std::ops::Range; -use std::path::{Path, PathBuf}; +use std::path::{self, Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ @@ -37,123 +37,164 @@ pub fn generate_lint_files( deprecated: &[DeprecatedLint], renamed: &[RenamedLint], ) { - FileUpdater::default().update_files_checked( + let mut updater = FileUpdater::default(); + updater.update_file_checked( "cargo dev update_lints", update_mode, - &mut [ - ( - "README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); - }), - ), - ( - "book/src/README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); - }), - ), - ( - "CHANGELOG.md", - &mut update_text_region_fn( - "\n", - "", - |dst| { - for lint in lints - .iter() - .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() - { - writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); - } - }, - ), - ), - ( - "clippy_lints/src/lib.rs", - &mut update_text_region_fn( - "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", - "// end lints modules, do not remove this comment, it's used in `update_lints`", - |dst| { - for lint_mod in lints.iter().map(|l| &l.module).sorted().dedup() { - writeln!(dst, "mod {lint_mod};").unwrap(); - } - }, - ), - ), - ("clippy_lints/src/declared_lints.rs", &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - dst.push_str("pub static LINTS: &[&crate::LintInfo] = &[\n"); - for (module_name, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() { - writeln!(dst, " crate::{module_name}::{lint_name}_INFO,").unwrap(); + "README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); + }), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "book/src/README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); + }), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "CHANGELOG.md", + &mut update_text_region_fn( + "\n", + "", + |dst| { + for lint in lints + .iter() + .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() + { + writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); } - dst.push_str("];\n"); - UpdateStatus::from_changed(src != dst) - }), - ("clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - let mut searcher = RustSearcher::new(src); - assert!( - searcher.find_token(Token::Ident("declare_with_version")) - && searcher.find_token(Token::Ident("declare_with_version")), - "error reading deprecated lints" - ); - dst.push_str(&src[..searcher.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for lint in deprecated { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.name, lint.reason, - ) - .unwrap(); - } - dst.push_str( - "]}\n\n\ + }, + ), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "clippy_lints/src/deprecated_lints.rs", + &mut |_, src, dst| { + let mut searcher = RustSearcher::new(src); + assert!( + searcher.find_token(Token::Ident("declare_with_version")) + && searcher.find_token(Token::Ident("declare_with_version")), + "error reading deprecated lints" + ); + dst.push_str(&src[..searcher.pos() as usize]); + dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); + for lint in deprecated { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", + lint.version, lint.name, lint.reason, + ) + .unwrap(); + } + dst.push_str( + "]}\n\n\ #[rustfmt::skip]\n\ declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ ", - ); - for lint in renamed { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.old_name, lint.new_name, - ) - .unwrap(); - } - dst.push_str("]}\n"); - UpdateStatus::from_changed(src != dst) - }), - ("tests/ui/deprecated.rs", &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - for lint in deprecated { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }), - ("tests/ui/rename.rs", &mut move |_, src, dst| { - let mut seen_lints = HashSet::new(); - 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) { - writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); - } - } - seen_lints.clear(); - for lint in renamed { - if seen_lints.insert(&lint.old_name) { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); - } - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }), - ], + ); + for lint in renamed { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", + lint.version, lint.old_name, lint.new_name, + ) + .unwrap(); + } + dst.push_str("]}\n"); + UpdateStatus::from_changed(src != dst) + }, ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/deprecated.rs", + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + for lint in deprecated { + writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/rename.rs", + &mut move |_, src, dst| { + let mut seen_lints = HashSet::new(); + 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) { + writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); + } + } + seen_lints.clear(); + for lint in renamed { + if seen_lints.insert(&lint.old_name) { + writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); + } + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + for (crate_name, lints) in lints.iter().into_group_map_by(|&l| { + let Some(path::Component::Normal(name)) = l.path.components().next() else { + // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` + panic!("internal error: can't read crate name from path `{}`", l.path.display()); + }; + name + }) { + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/lib.rs"), + &mut update_text_region_fn( + "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", + "// end lints modules, do not remove this comment, it's used in `update_lints`", + |dst| { + 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)) + .sorted() + .dedup() + { + writeln!(dst, "mod {lint_mod};").unwrap(); + } + }, + ), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/declared_lints.rs"), + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); + for (module_path, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() { + if module_path.is_empty() { + writeln!(dst, " crate::{lint_name}_INFO,").unwrap(); + } else { + writeln!(dst, " crate::{module_path}::{lint_name}_INFO,").unwrap(); + } + } + dst.push_str("];\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + } } fn round_to_fifty(count: usize) -> usize { @@ -187,13 +228,25 @@ pub struct RenamedLint { pub fn find_lint_decls() -> Vec { let mut lints = Vec::with_capacity(1000); let mut contents = String::new(); - for (file, module) in read_src_with_module("clippy_lints/src".as_ref()) { - parse_clippy_lint_decls( - file.path(), - File::open_read_to_cleared_string(file.path(), &mut contents), - &module, - &mut lints, - ); + 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 @@ -205,7 +258,7 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator (Vec, Vec) { /// Removes the line splices and surrounding quotes from a string literal fn parse_str_lit(s: &str) -> String { - let (s, mode) = if let Some(s) = s.strip_prefix("r") { - (s.trim_matches('#'), rustc_literal_escaper::Mode::RawStr) - } else { - (s, rustc_literal_escaper::Mode::Str) - }; + 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, |range, ch| { + rustc_literal_escaper::unescape_str(s, &mut |_, ch| { if let Ok(ch) = ch { res.push(ch); } diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index c4808b7048b0..89962a110341 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -383,21 +383,6 @@ impl FileUpdater { self.update_file_checked_inner(tool, mode, path.as_ref(), update); } - #[expect(clippy::type_complexity)] - pub fn update_files_checked( - &mut self, - tool: &str, - mode: UpdateMode, - files: &mut [( - impl AsRef, - &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus, - )], - ) { - for (path, update) in files { - self.update_file_checked_inner(tool, mode, path.as_ref(), update); - } - } - pub fn update_file( &mut self, path: impl AsRef, diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 39e4e2e365ea..c03cc99b581f 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.89" +version = "0.1.90" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -13,6 +13,7 @@ arrayvec = { version = "0.7", default-features = false } cargo_metadata = "0.18" clippy_config = { path = "../clippy_config" } clippy_utils = { path = "../clippy_utils" } +declare_clippy_lint = { path = "../declare_clippy_lint" } itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" diff --git a/clippy_lints/src/attrs/inline_always.rs b/clippy_lints/src/attrs/inline_always.rs index 58e51128a0dc..b8f93ee5e2c1 100644 --- a/clippy_lints/src/attrs/inline_always.rs +++ b/clippy_lints/src/attrs/inline_always.rs @@ -1,10 +1,10 @@ use super::INLINE_ALWAYS; use clippy_utils::diagnostics::span_lint; -use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr}; +use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr}; use rustc_hir::Attribute; use rustc_lint::LateContext; -use rustc_span::symbol::Symbol; use rustc_span::Span; +use rustc_span::symbol::Symbol; pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) { if span.from_expansion() { diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 9a1242980418..91c2dc7f3dc6 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -207,7 +207,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for usage of the `#[allow]` attribute and suggests replacing it with - /// the `#[expect]` (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) /// /// This lint only warns outer attributes (`#[allow]`), as inner attributes /// (`#![allow]`) are usually used to enable or disable lints on a global scale. diff --git a/clippy_lints/src/attrs/utils.rs b/clippy_lints/src/attrs/utils.rs index a5ce2137bffe..7b66f91f6c07 100644 --- a/clippy_lints/src/attrs/utils.rs +++ b/clippy_lints/src/attrs/utils.rs @@ -46,11 +46,13 @@ pub(super) fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> b } fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool { - block.stmts.first().map_or( - block - .expr - .as_ref() - .is_some_and(|e| is_relevant_expr(cx, typeck_results, e)), + block.stmts.first().map_or_else( + || { + block + .expr + .as_ref() + .is_some_and(|e| is_relevant_expr(cx, typeck_results, e)) + }, |stmt| match &stmt.kind { StmtKind::Let(_) => true, StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 7cde007a9b66..70c9c45a60c8 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -2,9 +2,9 @@ use crate::reference::DEREF_ADDROF; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; -use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed, is_mutable}; +use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_from_proc_macro, is_lint_allowed, is_mutable}; use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, ExprKind, UnOp}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Node, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::Mutability; use rustc_middle::ty; @@ -48,7 +48,7 @@ declare_clippy_lint! { declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]); impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) { if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..)) @@ -76,6 +76,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { && let e_ty = cx.typeck_results().expr_ty_adjusted(e) // check if the reference is coercing to a mutable reference && (!matches!(e_ty.kind(), ty::Ref(_, _, Mutability::Mut)) || is_mutable(cx, deref_target)) + // If the new borrow might be itself borrowed mutably and the original reference is not a temporary + // value, do not propose to use it directly. + && (is_expr_temporary_value(cx, deref_target) || !potentially_bound_to_mutable_ref(cx, e)) && let Some(deref_text) = deref_target.span.get_source_text(cx) { span_lint_and_then( @@ -110,3 +113,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { } } } + +/// Checks if `expr` is used as part of a `let` statement containing a `ref mut` binding. +fn potentially_bound_to_mutable_ref<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::LetStmt(let_stmt) + if let_stmt.pat.contains_explicit_ref_binding() == Some(Mutability::Mut)) +} diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 9a1ad8a74738..a70bd8861919 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -168,7 +168,7 @@ fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<' // Rust's integer pow() functions take an unsigned exponent. let exponent_val = get_const_unsigned_int_eval(cx, exponent, None); - let exponent_is_even = exponent_val.map(|val| val % 2 == 0); + let exponent_is_even = exponent_val.map(|val| val.is_multiple_of(2)); match (base_sign, exponent_is_even) { // Non-negative bases always return non-negative results, ignoring overflow. diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 7f6ecea99fb0..1854d86c53b2 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,13 +1,16 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability}; +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 rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind}; +use rustc_lexer::TokenKind; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::source_map::SourceMap; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -90,35 +93,74 @@ impl CollapsibleIf { } } - fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) { - if !block_starts_with_comment(cx, else_block) - && let Some(else_) = expr_block(else_block) + 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_.kind + && let ExprKind::If(else_if_cond, ..) = else_.kind + && !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_code) { - // Prevent "elseif" - // Check that the "else" is followed by whitespace - let up_to_else = then_span.between(else_block.span); - let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { - !c.is_whitespace() - } else { - false - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( + span_lint_and_then( cx, COLLAPSIBLE_ELSE_IF, else_block.span, "this `else { if .. }` block can be collapsed", - "collapse nested if block", - format!( - "{}{}", - if requires_space { " " } else { "" }, - snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability) - ), - applicability, + |diag| { + let up_to_else = then_span.between(else_block.span); + let else_before_if = else_.span.shrink_to_lo().with_hi(else_if_cond.span.lo() - BytePos(1)); + if self.lint_commented_code + && let Some(else_keyword_span) = + span_extract_keyword(cx.tcx.sess.source_map(), up_to_else, "else") + && let Some(else_if_keyword_span) = + span_extract_keyword(cx.tcx.sess.source_map(), else_before_if, "if") + { + let else_keyword_span = else_keyword_span.with_leading_whitespace(cx).into_span(); + let else_open_bracket = else_block.span.split_at(1).0.with_leading_whitespace(cx).into_span(); + let else_closing_bracket = { + let end = else_block.span.shrink_to_hi(); + end.with_lo(end.lo() - BytePos(1)) + .with_leading_whitespace(cx) + .into_span() + }; + let sugg = vec![ + // Remove the outer else block `else` + (else_keyword_span, String::new()), + // Replace the inner `if` by `else if` + (else_if_keyword_span, String::from("else if")), + // Remove the outer else block `{` + (else_open_bracket, String::new()), + // Remove the outer else block '}' + (else_closing_bracket, String::new()), + ]; + diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable); + return; + } + + // Prevent "elseif" + // Check that the "else" is followed by whitespace + let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { + !c.is_whitespace() + } else { + false + }; + let mut applicability = Applicability::MachineApplicable; + diag.span_suggestion( + else_block.span, + "collapse nested if block", + format!( + "{}{}", + if requires_space { " " } else { "" }, + snippet_block_with_applicability( + cx, + else_.span, + "..", + Some(else_block.span), + &mut applicability + ) + ), + applicability, + ); + }, ); } } @@ -130,7 +172,7 @@ impl CollapsibleIf { && self.eligible_condition(cx, check_inner) && let ctxt = expr.span.ctxt() && inner.span.ctxt() == ctxt - && (self.lint_commented_code || !block_starts_with_comment(cx, then)) + && !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code) { span_lint_and_then( cx, @@ -141,7 +183,7 @@ impl CollapsibleIf { let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span(); let then_closing_bracket = { let end = then.span.shrink_to_hi(); - end.with_lo(end.lo() - rustc_span::BytePos(1)) + end.with_lo(end.lo() - BytePos(1)) .with_leading_whitespace(cx) .into_span() }; @@ -179,7 +221,7 @@ impl LateLintPass<'_> for CollapsibleIf { if let Some(else_) = else_ && let ExprKind::Block(else_, None) = else_.kind { - Self::check_collapsible_else_if(cx, then.span, else_); + self.check_collapsible_else_if(cx, then.span, else_); } else if else_.is_none() && self.eligible_condition(cx, cond) && let ExprKind::Block(then, None) = then.kind @@ -190,12 +232,16 @@ impl LateLintPass<'_> for CollapsibleIf { } } -fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool { - // We trim all opening braces and whitespaces and then check if the next string is a comment. - let trimmed_block_text = snippet_block(cx, block.span, "..", None) - .trim_start_matches(|c: char| c.is_whitespace() || c == '{') - .to_owned(); - trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*") +// 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, @@ -226,3 +272,16 @@ fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> { vec![] } } + +fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option { + let snippet = sm.span_to_snippet(span).ok()?; + tokenize_with_text(&snippet) + .filter(|(t, s, _)| matches!(t, TokenKind::Ident if *s == keyword)) + .map(|(_, _, inner)| { + span.split_at(u32::try_from(inner.start).unwrap()) + .1 + .split_at(u32::try_from(inner.end - inner.start).unwrap()) + .0 + }) + .next() +} diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 5ef726638a56..27918698cd6b 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -11,7 +11,7 @@ use clippy_utils::{ use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind, intravisit}; +use rustc_hir::{BinOpKind, 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; @@ -295,7 +295,7 @@ fn lint_branches_sharing_code<'tcx>( sugg, Applicability::Unspecified, ); - if !cx.typeck_results().expr_ty(expr).is_unit() { + if is_expr_parent_assignment(cx, expr) || !cx.typeck_results().expr_ty(expr).is_unit() { diag.note("the end suggestion probably needs some adjustments to use the expression result correctly"); } } @@ -660,3 +660,17 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { ); } } + +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), .. }) + | Node::Expr(Expr { + kind: ExprKind::Assign(_, e, _), + .. + }) = parent + { + return e.hir_id == expr.hir_id; + } + + false +} diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs deleted file mode 100644 index 9f82f8767279..000000000000 --- a/clippy_lints/src/declare_clippy_lint.rs +++ /dev/null @@ -1,168 +0,0 @@ -#[macro_export] -#[allow(clippy::crate_in_macro_def)] -macro_rules! declare_clippy_lint { - (@ - $(#[doc = $lit:literal])* - pub $lint_name:ident, - $level:ident, - $lintcategory:expr, - $desc:literal, - $version_expr:expr, - $version_lit:literal - $(, $eval_always: literal)? - ) => { - rustc_session::declare_tool_lint! { - $(#[doc = $lit])* - #[clippy::version = $version_lit] - pub clippy::$lint_name, - $level, - $desc, - report_in_external_macro:true - $(, @eval_always = $eval_always)? - } - - pub(crate) static ${concat($lint_name, _INFO)}: &'static crate::LintInfo = &crate::LintInfo { - lint: &$lint_name, - category: $lintcategory, - explanation: concat!($($lit,"\n",)*), - location: concat!(file!(), "#L", line!()), - version: $version_expr - }; - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - restriction, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - style, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Warn, crate::LintCategory::Style, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - correctness, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, - Some($version), $version - $(, $eval_always)? - - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - perf, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Warn, crate::LintCategory::Perf, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - complexity, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - suspicious, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - nursery, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - pedantic, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, - Some($version), $version - $(, $eval_always)? - } - }; - ( - $(#[doc = $lit:literal])* - #[clippy::version = $version:literal] - pub $lint_name:ident, - cargo, - $desc:literal - $(, @eval_always = $eval_always: literal)? - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, - Some($version), $version - $(, $eval_always)? - } - }; -} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 1e3907d9ede0..c3f8e02b4c06 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -2,7 +2,7 @@ // Use that command to update this file and do not edit by hand. // Manual edits will be overwritten. -pub static LINTS: &[&crate::LintInfo] = &[ +pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, @@ -112,6 +112,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::disallowed_names::DISALLOWED_NAMES_INFO, crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, + crate::doc::DOC_BROKEN_LINK_INFO, crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO, crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO, crate::doc::DOC_LAZY_CONTINUATION_INFO, @@ -590,6 +591,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::operators::IMPOSSIBLE_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, crate::operators::INTEGER_DIVISION_INFO, + crate::operators::MANUAL_IS_MULTIPLE_OF_INFO, crate::operators::MANUAL_MIDPOINT_INFO, crate::operators::MISREFACTORED_ASSIGN_OP_INFO, crate::operators::MODULO_ARITHMETIC_INFO, diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 9814d4fa84f9..d55aeae98ede 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -40,6 +40,9 @@ declare_clippy_lint! { /// # When using an inline table, can add a `reason` for why the macro /// # is disallowed. /// { path = "serde::Serialize", reason = "no serializing" }, + /// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, + /// # it will be silently ignored + /// { path = "std::invalid_macro", reason = "use alternative instead", allow-invalid = true } /// ] /// ``` /// ```no_run diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index fb970e17f38f..8c067432cb4e 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -34,6 +34,9 @@ declare_clippy_lint! { /// { path = "std::vec::Vec::leak", reason = "no leaking memory" }, /// # Can also add a `replacement` that will be offered as a suggestion. /// { path = "std::sync::Mutex::new", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex::new" }, + /// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, + /// # it will be silently ignored + /// { path = "std::fs::InvalidPath", reason = "use alternative instead", allow-invalid = true }, /// ] /// ``` /// diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 7875cdd77e86..9a82327a0d7b 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -35,6 +35,9 @@ declare_clippy_lint! { /// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" }, /// # Can also add a `replacement` that will be offered as a suggestion. /// { path = "std::sync::Mutex", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex" }, + /// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, + /// # it will be silently ignored + /// { path = "std::invalid::Type", reason = "use alternative instead", allow-invalid = true } /// ] /// ``` /// diff --git a/clippy_lints/src/doc/broken_link.rs b/clippy_lints/src/doc/broken_link.rs new file mode 100644 index 000000000000..a97b807e605f --- /dev/null +++ b/clippy_lints/src/doc/broken_link.rs @@ -0,0 +1,83 @@ +use clippy_utils::diagnostics::span_lint; +use pulldown_cmark::BrokenLink as PullDownBrokenLink; +use rustc_lint::LateContext; +use rustc_resolve::rustdoc::{DocFragment, source_span_for_markdown_range}; +use rustc_span::{BytePos, Pos, Span}; + +use super::DOC_BROKEN_LINK; + +/// Scan and report broken link on documents. +/// It ignores false positives detected by `pulldown_cmark`, and only +/// warns users when the broken link is consider a URL. +// NOTE: We don't check these other cases because +// rustdoc itself will check and warn about it: +// - When a link url is broken across multiple lines in the URL path part +// - When a link tag is missing the close parenthesis character at the end. +// - When a link has whitespace within the url link. +pub fn check(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { + warn_if_broken_link(cx, bl, doc, fragments); +} + +fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { + if let Some(span) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) { + let mut len = 0; + + // grab raw link data + let (_, raw_link) = doc.split_at(bl.span.start); + + // strip off link text part + let raw_link = match raw_link.split_once(']') { + None => return, + Some((prefix, suffix)) => { + len += prefix.len() + 1; + suffix + }, + }; + + let raw_link = match raw_link.split_once('(') { + None => return, + Some((prefix, suffix)) => { + if !prefix.is_empty() { + // there is text between ']' and '(' chars, so it is not a valid link + return; + } + len += prefix.len() + 1; + suffix + }, + }; + + if raw_link.starts_with("(http") { + // reduce chances of false positive reports + // by limiting this checking only to http/https links. + return; + } + + for c in raw_link.chars() { + if c == ')' { + // it is a valid link + return; + } + + if c == '\n' { + report_broken_link(cx, span, len); + break; + } + + len += 1; + } + } +} + +fn report_broken_link(cx: &LateContext<'_>, frag_span: Span, offset: usize) { + let start = frag_span.lo(); + let end = start + BytePos::from_usize(offset); + + let span = Span::new(start, end, frag_span.ctxt(), frag_span.parent()); + + span_lint( + cx, + DOC_BROKEN_LINK, + span, + "possible broken doc link: broken across multiple lines", + ); +} diff --git a/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/clippy_lints/src/doc/doc_suspicious_footnotes.rs index d3c396869766..3330cc5defd3 100644 --- a/clippy_lints/src/doc/doc_suspicious_footnotes.rs +++ b/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -49,11 +49,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, range: Range, fragments: &F .filter(|attr| attr.span().overlaps(this_fragment.span)) .rev() .find_map(|attr| { - Some(( - attr, - attr.doc_str_and_comment_kind()?, - attr.doc_resolution_scope()?, - )) + Some((attr, attr.doc_str_and_comment_kind()?, attr.doc_resolution_scope()?)) }) .unwrap(); let (to_add, terminator) = match (doc_attr_comment_kind, attr_style) { diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index d38588bb799a..5ea55e102dfe 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -24,6 +24,7 @@ use rustc_span::edition::Edition; use std::ops::Range; use url::Url; +mod broken_link; mod doc_comment_double_space_linebreaks; mod doc_suspicious_footnotes; mod include_in_doc_without_cfg; @@ -292,6 +293,34 @@ declare_clippy_lint! { "possible typo for an intra-doc link" } +declare_clippy_lint! { + /// ### What it does + /// Checks the doc comments have unbroken links, mostly caused + /// by bad formatted links such as broken across multiple lines. + /// + /// ### Why is this bad? + /// Because documentation generated by rustdoc will be broken + /// since expected links won't be links and just text. + /// + /// ### Examples + /// This link is broken: + /// ```no_run + /// /// [example of a bad link](https:// + /// /// github.com/rust-lang/rust-clippy/) + /// pub fn do_something() {} + /// ``` + /// + /// It shouldn't be broken across multiple lines to work: + /// ```no_run + /// /// [example of a good link](https://github.com/rust-lang/rust-clippy/) + /// pub fn do_something() {} + /// ``` + #[clippy::version = "1.84.0"] + pub DOC_BROKEN_LINK, + pedantic, + "broken document link" +} + declare_clippy_lint! { /// ### What it does /// Checks for the doc comments of publicly visible @@ -656,6 +685,7 @@ impl Documentation { impl_lint_pass!(Documentation => [ DOC_LINK_CODE, DOC_LINK_WITH_QUOTES, + DOC_BROKEN_LINK, DOC_MARKDOWN, DOC_NESTED_REFDEFS, MISSING_SAFETY_DOC, @@ -786,9 +816,9 @@ struct DocHeaders { /// back in the various late lint pass methods if they need the final doc headers, like "Safety" or /// "Panics" sections. fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[Attribute]) -> Option { - /// We don't want the parser to choke on intra doc links. Since we don't - /// actually care about rendering them, just pretend that all broken links - /// point to a fake address. + // We don't want the parser to choke on intra doc links. Since we don't + // actually care about rendering them, just pretend that all broken links + // point to a fake address. #[expect(clippy::unnecessary_wraps)] // we're following a type signature fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> { Some(("fake".into(), "fake".into())) @@ -828,14 +858,12 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ return Some(DocHeaders::default()); } - let mut cb = fake_broken_link_callback; - check_for_code_clusters( cx, pulldown_cmark::Parser::new_with_broken_link_callback( &doc, main_body_opts() - Options::ENABLE_SMART_PUNCTUATION, - Some(&mut cb), + Some(&mut fake_broken_link_callback), ) .into_offset_iter(), &doc, @@ -845,9 +873,17 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ }, ); + // NOTE: check_doc uses it own cb function, + // to avoid causing duplicated diagnostics for the broken link checker. + let mut full_fake_broken_link_callback = |bl: BrokenLink<'_>| -> Option<(CowStr<'_>, CowStr<'_>)> { + broken_link::check(cx, &bl, &doc, &fragments); + Some(("fake".into(), "fake".into())) + }; + // disable smart punctuation to pick up ['link'] more easily let opts = main_body_opts() - Options::ENABLE_SMART_PUNCTUATION; - let parser = pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut cb)); + let parser = + pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut full_fake_broken_link_callback)); Some(check_doc( cx, diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index 7ba11c20f456..74283d7ba863 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -71,6 +71,7 @@ pub fn check( 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, @@ -89,9 +90,14 @@ pub fn check( // Another function was found; this case is ignored for needless_doctest_main ItemKind::Fn(fn_) => { eligible = false; - if !ignore { - get_test_spans(&item, fn_.ident, &mut test_attr_spans); + 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(..) @@ -104,7 +110,10 @@ pub fn check( }, Ok(None) => break, Err(e) => { - e.cancel(); + // 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); }, } @@ -119,6 +128,18 @@ pub fn check( 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]")) { + 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(); diff --git a/clippy_lints/src/empty_line_after.rs b/clippy_lints/src/empty_line_after.rs index 0c5f8bbf4ca5..3bd74856165d 100644 --- a/clippy_lints/src/empty_line_after.rs +++ b/clippy_lints/src/empty_line_after.rs @@ -10,7 +10,7 @@ use rustc_errors::{Applicability, Diag, SuggestionStyle}; use rustc_lexer::TokenKind; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, ExpnKind, Ident, InnerSpan, Span, SpanData, Symbol, kw}; +use rustc_span::{BytePos, ExpnKind, Ident, InnerSpan, Span, SpanData, Symbol, kw, sym}; declare_clippy_lint! { /// ### What it does @@ -129,10 +129,55 @@ struct Stop { kind: StopKind, first: usize, last: usize, + name: Option, } impl Stop { - fn convert_to_inner(&self) -> (Span, String) { + fn is_outer_attr_only(&self) -> bool { + let Some(name) = self.name else { + return false; + }; + // Check if the attribute only has effect when as an outer attribute + // The below attributes are collected from the builtin attributes of The Rust Reference + // https://doc.rust-lang.org/reference/attributes.html#r-attributes.builtin + // And the comments below are from compiler errors and warnings + matches!( + name, + // Cannot be used at crate level + sym::repr | sym::test | sym::derive | sym::automatically_derived | sym::path | sym::global_allocator | + // Only has an effect on macro definitions + sym::macro_export | + // Only be applied to trait definitions + sym::on_unimplemented | + // Only be placed on trait implementations + sym::do_not_recommend | + // Only has an effect on items + sym::ignore | sym::should_panic | sym::proc_macro | sym::proc_macro_derive | sym::proc_macro_attribute | + // Has no effect when applied to a module + sym::must_use | + // Should be applied to a foreign function or static + sym::link_name | sym::link_ordinal | sym::link_section | + // Should be applied to an `extern crate` item + sym::no_link | + // Should be applied to a free function, impl method or static + sym::export_name | sym::no_mangle | + // Should be applied to a `static` variable + sym::used | + // Should be applied to function or closure + sym::inline | + // Should be applied to a function definition + sym::cold | sym::target_feature | sym::track_caller | sym::instruction_set | + // Should be applied to a struct or enum + sym::non_exhaustive | + // Note: No any warning when it as an inner attribute, but it has no effect + sym::panic_handler + ) + } + + fn convert_to_inner(&self) -> Option<(Span, String)> { + if self.is_outer_attr_only() { + return None; + } let inner = match self.kind { // #![...] StopKind::Attr => InnerSpan::new(1, 1), @@ -140,7 +185,7 @@ impl Stop { // ^ ^ StopKind::Doc(_) => InnerSpan::new(2, 3), }; - (self.span.from_inner(inner), "!".into()) + Some((self.span.from_inner(inner), "!".into())) } fn comment_out(&self, cx: &EarlyContext<'_>, suggestions: &mut Vec<(Span, String)>) { @@ -177,6 +222,7 @@ impl Stop { }, first: file.lookup_line(file.relative_position(lo))?, last: file.lookup_line(file.relative_position(hi))?, + name: attr.name(), }) } } @@ -356,6 +402,12 @@ impl EmptyLineAfter { if let Some(parent) = self.items.iter().rev().nth(1) && (parent.kind == "module" || parent.kind == "crate") && parent.mod_items == Some(id) + && let suggestions = gaps + .iter() + .flat_map(|gap| gap.prev_chunk) + .filter_map(Stop::convert_to_inner) + .collect::>() + && !suggestions.is_empty() { let desc = if parent.kind == "module" { "parent module" @@ -367,10 +419,7 @@ impl EmptyLineAfter { StopKind::Attr => format!("if the attribute should apply to the {desc} use an inner attribute"), StopKind::Doc(_) => format!("if the comment should document the {desc} use an inner doc comment"), }, - gaps.iter() - .flat_map(|gap| gap.prev_chunk) - .map(Stop::convert_to_inner) - .collect(), + suggestions, Applicability::MaybeIncorrect, ); } @@ -425,6 +474,7 @@ impl EmptyLineAfter { first: line.line, // last doesn't need to be accurate here, we don't compare it with anything last: line.line, + name: None, }); } diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index b0077a9b05fe..0288747d6f3e 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::ty::get_type_diagnostic_name; @@ -109,14 +109,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx { let vec_crate = if is_no_std_crate(cx) { "alloc" } else { "std" }; // replace `|| vec![]` with `Vec::new` - span_lint_and_sugg( + span_lint_hir_and_then( cx, REDUNDANT_CLOSURE, + expr.hir_id, expr.span, "redundant closure", - "replace the closure with `Vec::new`", - format!("{vec_crate}::vec::Vec::new"), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + expr.span, + "replace the closure with `Vec::new`", + format!("{vec_crate}::vec::Vec::new"), + Applicability::MachineApplicable, + ); + }, ); } // skip `foo(|| macro!())` @@ -198,41 +204,48 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx // For now ignore all callee types which reference a type parameter. && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { - if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if path_to_local(callee).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, - ) { - // 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() => { - snippet = format!("&{snippet}"); - }, - _ => (), + span_lint_hir_and_then( + cx, + REDUNDANT_CLOSURE, + expr.hir_id, + expr.span, + "redundant closure", + |diag| { + if let Some(mut snippet) = snippet_opt(cx, callee.span) { + if path_to_local(callee).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, + ) { + // 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() => { + snippet = format!("&{snippet}"); + }, + _ => (), + } } + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + snippet, + Applicability::MachineApplicable, + ); } - diag.span_suggestion( - expr.span, - "replace the closure with the function itself", - snippet, - Applicability::MachineApplicable, - ); - } - }); + }, + ); } }, ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { @@ -245,9 +258,10 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx Some(span) => format!("::{}", snippet_with_applicability(cx, span, "<..>", &mut app)), None => String::new(), }; - span_lint_and_then( + span_lint_hir_and_then( cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.hir_id, expr.span, "redundant closure", |diag| { diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 1fb0e4d24d06..86d9038ec45d 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -76,7 +76,7 @@ impl LateLintPass<'_> for ExhaustiveItems { "exported enums should not be exhaustive", [].as_slice(), ), - ItemKind::Struct(_, _, v) => ( + ItemKind::Struct(_, _, v) if v.fields().iter().all(|f| f.default.is_none()) => ( EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive", v.fields(), diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 3c7e83b06972..b3c9e8607589 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -5,14 +5,13 @@ use clippy_utils::{ eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate, numeric_literal, peel_blocks, sugg, sym, }; +use rustc_ast::ast; use rustc_errors::Applicability; 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::source_map::Spanned; - -use rustc_ast::ast; use std::f32::consts as f32_consts; use std::f64::consts as f64_consts; use sugg::Sugg; diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index c0c23e217fd4..d959981a83ce 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -14,9 +14,9 @@ use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_must_use_ty; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{return_ty, trait_ref_of_method}; -use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_span::Symbol; use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_span::Symbol; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use core::ops::ControlFlow; @@ -34,7 +34,17 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if let Some((attr_span, reason)) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig); + check_needless_must_use( + cx, + sig.decl, + item.owner_id, + item.span, + fn_header_span, + *attr_span, + *reason, + attrs, + sig, + ); } else if is_public && !is_proc_macro(attrs) && !find_attr!(attrs, AttributeKind::NoMangle(..)) { check_must_use_candidate( cx, @@ -54,9 +64,20 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir_attrs(item.hir_id()); - let attr = find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason)); + let attr = + find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason)); if let Some((attr_span, reason)) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig); + check_needless_must_use( + cx, + sig.decl, + item.owner_id, + item.span, + fn_header_span, + *attr_span, + *reason, + attrs, + sig, + ); } else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id).is_none() { check_must_use_candidate( cx, @@ -77,9 +98,20 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir_attrs(item.hir_id()); - let attr = find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason)); + let attr = + find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason)); if let Some((attr_span, reason)) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig); + check_needless_must_use( + cx, + sig.decl, + item.owner_id, + item.span, + fn_header_span, + *attr_span, + *reason, + attrs, + sig, + ); } else if let hir::TraitFn::Provided(eid) = *eid { let body = cx.tcx.hir_body(eid); if attr.is_none() && is_public && !is_proc_macro(attrs) { @@ -121,12 +153,7 @@ fn check_needless_must_use( fn_header_span, "this unit-returning function has a `#[must_use]` attribute", |diag| { - diag.span_suggestion( - attr_span, - "remove the attribute", - "", - Applicability::MachineApplicable, - ); + diag.span_suggestion(attr_span, "remove the attribute", "", Applicability::MachineApplicable); }, ); } else { diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 45f9aa0a53e4..ab7a965b3672 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::consts::is_zero_integer_const; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::is_else_clause; use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet}; @@ -48,13 +48,6 @@ declare_clippy_lint! { declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); -fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { - if let Some(value) = ConstEvalCtxt::new(cx).eval_simple(expr) { - return Constant::Int(0) == value; - } - false -} - impl LateLintPass<'_> for IfNotElse { fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { if let ExprKind::If(cond, cond_inner, Some(els)) = e.kind @@ -68,7 +61,7 @@ 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_const(rhs, cx) => ( + 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`", ), diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index 617c006795be..ffe6ad14f630 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::DiagExt; -use rustc_attr_data_structures::{find_attr, AttributeKind}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -33,10 +33,10 @@ impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind && let Some(attr_span) = find_attr!(cx - .tcx - .hir_attrs(item.hir_id()), - AttributeKind::Inline(_, span) => *span - ) + .tcx + .hir_attrs(item.hir_id()), + AttributeKind::Inline(_, span) => *span + ) { span_lint_and_then( cx, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index be9142b17fe3..96a6dee58852 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -59,10 +59,10 @@ extern crate smallvec; extern crate thin_vec; #[macro_use] -mod declare_clippy_lint; +extern crate clippy_utils; #[macro_use] -extern crate clippy_utils; +extern crate declare_clippy_lint; mod utils; @@ -411,108 +411,9 @@ mod zombie_processes; use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; -use rustc_lint::{Lint, LintId}; +use rustc_lint::Lint; use utils::attr_collector::{AttrCollector, AttrStorage}; -#[derive(Default)] -struct RegistrationGroups { - all: Vec, - cargo: Vec, - complexity: Vec, - correctness: Vec, - nursery: Vec, - pedantic: Vec, - perf: Vec, - restriction: Vec, - style: Vec, - suspicious: Vec, -} - -impl RegistrationGroups { - #[rustfmt::skip] - fn register(self, store: &mut rustc_lint::LintStore) { - store.register_group(true, "clippy::all", Some("clippy_all"), self.all); - store.register_group(true, "clippy::cargo", Some("clippy_cargo"), self.cargo); - store.register_group(true, "clippy::complexity", Some("clippy_complexity"), self.complexity); - store.register_group(true, "clippy::correctness", Some("clippy_correctness"), self.correctness); - store.register_group(true, "clippy::nursery", Some("clippy_nursery"), self.nursery); - store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), self.pedantic); - store.register_group(true, "clippy::perf", Some("clippy_perf"), self.perf); - store.register_group(true, "clippy::restriction", Some("clippy_restriction"), self.restriction); - store.register_group(true, "clippy::style", Some("clippy_style"), self.style); - store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), self.suspicious); - } -} - -#[derive(Copy, Clone, Debug)] -pub(crate) enum LintCategory { - Cargo, - Complexity, - Correctness, - Nursery, - Pedantic, - Perf, - Restriction, - Style, - Suspicious, -} - -#[allow(clippy::enum_glob_use)] -use LintCategory::*; - -impl LintCategory { - fn is_all(self) -> bool { - matches!(self, Correctness | Suspicious | Style | Complexity | Perf) - } - - fn group(self, groups: &mut RegistrationGroups) -> &mut Vec { - match self { - Cargo => &mut groups.cargo, - Complexity => &mut groups.complexity, - Correctness => &mut groups.correctness, - Nursery => &mut groups.nursery, - Pedantic => &mut groups.pedantic, - Perf => &mut groups.perf, - Restriction => &mut groups.restriction, - Style => &mut groups.style, - Suspicious => &mut groups.suspicious, - } - } -} - -pub struct LintInfo { - /// Double reference to maintain pointer equality - pub lint: &'static &'static Lint, - category: LintCategory, - pub explanation: &'static str, - /// e.g. `clippy_lints/src/absolute_paths.rs#43` - pub location: &'static str, - pub version: Option<&'static str>, -} - -impl LintInfo { - /// Returns the lint name in lowercase without the `clippy::` prefix - #[allow(clippy::missing_panics_doc)] - pub fn name_lower(&self) -> String { - self.lint.name.strip_prefix("clippy::").unwrap().to_ascii_lowercase() - } - - /// Returns the name of the lint's category in lowercase (`style`, `pedantic`) - pub fn category_str(&self) -> &'static str { - match self.category { - Cargo => "cargo", - Complexity => "complexity", - Correctness => "correctness", - Nursery => "nursery", - Pedantic => "pedantic", - Perf => "perf", - Restriction => "restriction", - Style => "style", - Suspicious => "suspicious", - } - } -} - pub fn explain(name: &str) -> i32 { let target = format!("clippy::{}", name.to_ascii_uppercase()); @@ -535,30 +436,11 @@ pub fn explain(name: &str) -> i32 { } } -fn register_categories(store: &mut rustc_lint::LintStore) { - let mut groups = RegistrationGroups::default(); - - for LintInfo { lint, category, .. } in declared_lints::LINTS { - if category.is_all() { - groups.all.push(LintId::of(lint)); - } - - category.group(&mut groups).push(LintId::of(lint)); - } - - let lints: Vec<&'static Lint> = declared_lints::LINTS.iter().map(|info| *info.lint).collect(); - - store.register_lints(&lints); - groups.register(store); -} - /// Register all lints and lint groups with the rustc lint store /// /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] -pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { - register_categories(store); - +pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf) { for (old_name, new_name) in deprecated_lints::RENAMED { store.register_renamed(old_name, new_name); } diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 388034c39f52..e792edbe23e0 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -163,15 +163,14 @@ impl<'tcx> Visitor<'tcx> for SameItemPushVisitor<'_, 'tcx> { StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr), _ => {}, } + } + // Current statement is a push ...check whether another + // push had been previously done + else if self.vec_push.is_none() { + self.vec_push = vec_push_option; } else { - // Current statement is a push ...check whether another - // push had been previously done - if self.vec_push.is_none() { - self.vec_push = vec_push_option; - } else { - // There are multiple pushes ... don't lint - self.multiple_pushes = true; - } + // There are multiple pushes ... don't lint + self.multiple_pushes = true; } } } diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 0b3bec714c0e..9ff82cdcb664 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -13,7 +13,6 @@ 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_lint::{LateContext, LintContext}; - use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; use std::slice; diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index 4959908dad63..edbb556fd976 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::option_arg_ty; +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 rustc_ast::BindingMode; +use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; use rustc_hir::def::{DefKind, Res}; @@ -133,7 +133,21 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok Applicability::MachineApplicable }; let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren(); - let sugg = format!("{scrut}.{method}()"); + + 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 sugg = format!("{scrut}{prefix}.{method}()"); + // If the expression being expanded is the `if …` part of an `else if …`, it must be blockified. let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) && let ExprKind::If(_, _, Some(else_part)) = parent_expr.kind diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 24b4a6758004..70a03ff93762 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +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; @@ -116,11 +117,12 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { let format_suggestion = |variant: &VariantDef| { format!( "{}{}{}{}", - if let Some(ident) = wildcard_ident { - format!("{} @ ", ident.name) - } else { - String::new() - }, + wildcard_ident.map_or(String::new(), |ident| { + ident + .span + .get_source_text(cx) + .map_or_else(|| format!("{} @ ", ident.name), |s| format!("{s} @ ")) + }), if let CommonPrefixSearcher::Path(path_prefix) = path_prefix { let mut s = String::new(); for seg in path_prefix { @@ -138,7 +140,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { Some(CtorKind::Fn) if variant.fields.len() == 1 => "(_)", Some(CtorKind::Fn) => "(..)", Some(CtorKind::Const) => "", - None => "{ .. }", + None => " { .. }", } ) }; diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 347960e0003d..f2dabdd34387 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4426,7 +4426,7 @@ declare_clippy_lint! { /// ```no_run /// use std::io::{BufReader, Read}; /// use std::fs::File; - /// let file = BufReader::new(std::fs::File::open("./bytes.txt").unwrap()); + /// let file = BufReader::new(File::open("./bytes.txt").unwrap()); /// file.bytes(); /// ``` #[clippy::version = "1.87.0"] diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 7bdd999bbbad..2139466ce746 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -136,7 +136,7 @@ pub(super) fn check<'tcx>( fun_span: Option, ) -> bool { // (path, fn_has_argument, methods, suffix) - const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 4] = [ + const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 5] = [ (sym::BTreeEntry, false, &[sym::or_insert], "with"), (sym::HashMapEntry, false, &[sym::or_insert], "with"), ( @@ -145,16 +145,17 @@ pub(super) fn check<'tcx>( &[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or], "else", ), - (sym::Result, true, &[sym::or, sym::unwrap_or], "else"), + (sym::Option, false, &[sym::get_or_insert], "with"), + (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), ]; 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, poss, suffix)) = - KNOW_TYPES.iter().find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0)) - && poss.contains(&name) + && 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; diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index f835bbb7c561..25c95d234363 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use rustc_attr_data_structures::{find_attr, AttributeKind}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_hir as hir; use rustc_hir::Attribute; use rustc_lint::{LateContext, LateLintPass, LintContext}; diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 8d2f8029112f..17d251a7bbb9 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -161,7 +161,7 @@ fn path_has_args(p: &QPath<'_>) -> bool { /// - `Copy` itself, or /// - the only use of a mutable reference, or /// - not a variable (created by a function call) -#[expect(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments, clippy::too_many_lines)] fn needless_borrow_count<'tcx>( cx: &LateContext<'tcx>, possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, @@ -232,11 +232,11 @@ fn needless_borrow_count<'tcx>( let mut args_with_referent_ty = callee_args.to_vec(); let mut check_reference_and_referent = |reference: &Expr<'tcx>, referent: &Expr<'tcx>| { - if let ExprKind::Field(base, _) = &referent.kind { - let base_ty = cx.typeck_results().expr_ty(base); - if drop_trait_def_id.is_some_and(|id| implements_trait(cx, base_ty, id, &[])) { - return false; - } + if let ExprKind::Field(base, _) = &referent.kind + && let base_ty = cx.typeck_results().expr_ty(base) + && drop_trait_def_id.is_some_and(|id| implements_trait(cx, base_ty, id, &[])) + { + return false; } let referent_ty = cx.typeck_results().expr_ty(referent); diff --git a/clippy_lints/src/needless_parens_on_range_literals.rs b/clippy_lints/src/needless_parens_on_range_literals.rs index 8a62106377c5..021a11593f3a 100644 --- a/clippy_lints/src/needless_parens_on_range_literals.rs +++ b/clippy_lints/src/needless_parens_on_range_literals.rs @@ -5,7 +5,6 @@ use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; - use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 9aede1dec934..c97ecce75b46 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -124,8 +124,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { // Note that we do not want to deal with qualified predicates here. match pred.kind().no_bound_vars() { Some(ty::ClauseKind::Trait(pred)) - if pred.def_id() != sized_trait && pred.def_id() != meta_sized_trait - => Some(pred), + if pred.def_id() != sized_trait && pred.def_id() != meta_sized_trait => + { + Some(pred) + }, _ => None, } }) diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 0159c5d2ac1b..dee8efeb2910 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_abi::ExternAbi; +use rustc_attr_data_structures::AttributeKind; use rustc_errors::Applicability; -use rustc_hir::{Item, ItemKind}; +use rustc_hir::{Attribute, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::{BytePos, Pos}; -use rustc_attr_data_structures::AttributeKind; -use rustc_hir::Attribute; declare_clippy_lint! { /// ### What it does diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index a27c6aa75e36..5f10e1968f1d 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -617,7 +617,7 @@ impl<'tcx> NonCopyConst<'tcx> { // Then a type check. Note we only check the type here as the result // gets cached. - let ty = EarlyBinder::bind(typeck.expr_ty(src_expr)).instantiate(tcx, init_args); + let ty = typeck.expr_ty(src_expr); // Normalized as we need to check if this is an array later. let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); if self.is_ty_freeze(tcx, typing_env, ty).is_freeze() { diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index e1fd09549a4b..3efbb8963587 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -1,12 +1,13 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt, integer_const, is_zero_integer_const}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{clip, peel_hir_expr_refs, unsext}; +use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext}; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, Node}; +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; +use rustc_span::{Span, kw}; use super::IDENTITY_OP; @@ -17,7 +18,7 @@ pub(crate) fn check<'tcx>( left: &'tcx Expr<'_>, right: &'tcx Expr<'_>, ) { - if !is_allowed(cx, op, left, right) { + if !is_allowed(cx, expr, op, left, right) { return; } @@ -165,14 +166,27 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) Parens::Needed } -fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool { +fn is_allowed<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + cmp: BinOpKind, + left: &Expr<'tcx>, + right: &Expr<'tcx>, +) -> bool { + // Exclude case where the left or right side is associated function call returns a type which is + // `Self` that is not given explicitly, and the expression is not a let binding's init + // expression and the let binding has a type annotation, or a function's return value. + if (is_assoc_fn_without_type_instance(cx, left) || is_assoc_fn_without_type_instance(cx, right)) + && !is_expr_used_with_type_annotation(cx, expr) + { + return false; + } + // 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() // `1 << 0` is a common pattern in bit manipulation code - && !(cmp == BinOpKind::Shl - && ConstEvalCtxt::new(cx).eval_simple(right) == Some(Constant::Int(0)) - && ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1))) + && !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1)) } fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { @@ -234,3 +248,47 @@ fn span_ineffective_operation( applicability, ); } + +fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + match expr_use_ctxt(cx, expr).use_node(cx) { + ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(), + ExprUseNode::Return(_) => true, + _ => false, + } +} + +/// Check if the expression is an associated function without a type instance. +/// Example: +/// ``` +/// trait Def { +/// fn def() -> Self; +/// } +/// impl Def for usize { +/// fn def() -> Self { +/// 0 +/// } +/// } +/// fn test() { +/// let _ = 0usize + &Default::default(); +/// let _ = 0usize + &Def::def(); +/// } +/// ``` +fn is_assoc_fn_without_type_instance<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool { + if let ExprKind::Call(func, _) = peel_hir_expr_refs(expr).0.kind + && let ExprKind::Path(QPath::Resolved( + // If it's not None, don't need to go further. + None, + Path { + res: Res::Def(DefKind::AssocFn, def_id), + .. + }, + )) = func.kind + && let output_ty = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder().output() + && let ty::Param(ty::ParamTy { + name: kw::SelfUpper, .. + }) = output_ty.kind() + { + return true; + } + false +} diff --git a/clippy_lints/src/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs new file mode 100644 index 000000000000..821178a43158 --- /dev/null +++ b/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -0,0 +1,66 @@ +use clippy_utils::consts::is_zero_integer_const; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; +use rustc_ast::BinOpKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MANUAL_IS_MULTIPLE_OF; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, + 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 ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind + && operand_op.node == BinOpKind::Rem + { + let mut app = Applicability::MachineApplicable; + let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app); + span_lint_and_sugg( + cx, + MANUAL_IS_MULTIPLE_OF, + expr.span, + "manual implementation of `.is_multiple_of()`", + "replace with", + format!( + "{}{}.is_multiple_of({divisor})", + if op == BinOpKind::Eq { "" } else { "!" }, + Sugg::hir_with_applicability(cx, operand_left, "_", &mut app).maybe_paren() + ), + app, + ); + } +} + +// 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>, + 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) + { + lhs + } else if matches!(rhs.kind, ExprKind::Binary(..)) + && matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt) + && is_zero_integer_const(cx, lhs) + { + rhs + } else { + return None; + }; + + matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand) +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 2f4e8e995886..bdbbb3475cd5 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -11,6 +11,7 @@ mod float_cmp; mod float_equality_without_abs; mod identity_op; mod integer_division; +mod manual_is_multiple_of; mod manual_midpoint; mod misrefactored_assign_op; mod modulo_arithmetic; @@ -830,12 +831,42 @@ declare_clippy_lint! { "manual implementation of `midpoint` which can overflow" } +declare_clippy_lint! { + /// ### What it does + /// Checks for manual implementation of `.is_multiple_of()` on + /// unsigned integer types. + /// + /// ### Why is this bad? + /// `a.is_multiple_of(b)` is a clearer way to check for divisibility + /// of `a` by `b`. This expression can never panic. + /// + /// ### Example + /// ```no_run + /// # let (a, b) = (3u64, 4u64); + /// if a % b == 0 { + /// println!("{a} is divisible by {b}"); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # let (a, b) = (3u64, 4u64); + /// if a.is_multiple_of(b) { + /// println!("{a} is divisible by {b}"); + /// } + /// ``` + #[clippy::version = "1.89.0"] + pub MANUAL_IS_MULTIPLE_OF, + complexity, + "manual implementation of `.is_multiple_of()`" +} + pub struct Operators { arithmetic_context: numeric_arithmetic::Context, verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool, msrv: Msrv, } + impl Operators { pub fn new(conf: &'static Conf) -> Self { Self { @@ -874,6 +905,7 @@ impl_lint_pass!(Operators => [ NEEDLESS_BITWISE_BOOL, SELF_ASSIGNMENT, MANUAL_MIDPOINT, + MANUAL_IS_MULTIPLE_OF, ]); impl<'tcx> LateLintPass<'tcx> for Operators { @@ -891,6 +923,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { identity_op::check(cx, e, op.node, lhs, rhs); 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); } self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); bit_mask::check(cx, e, op.node, lhs, rhs); diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index e18bdfb34ac8..b8005dfd6f8e 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -3,10 +3,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{is_self, is_self_ty}; -use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr}; -use rustc_data_structures::fx::FxHashSet; use core::ops::ControlFlow; use rustc_abi::ExternAbi; +use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr}; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index c02e5e0621c9..de12a25b03df 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -142,6 +142,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { && let Some(ret) = find_let_else_ret_expression(els) && let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret) && !span_contains_comment(cx.tcx.sess.source_map(), els.span) + && !span_contains_cfg(cx, els.span) { let mut applicability = Applicability::MaybeIncorrect; let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); diff --git a/clippy_lints/src/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs index 96ea485d7693..7bbbd0d25acf 100644 --- a/clippy_lints/src/question_mark_used.rs +++ b/clippy_lints/src/question_mark_used.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_then; - use clippy_utils::macros::span_is_local; use rustc_hir::{Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index 6b1dc864fb7a..acd840401c6b 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -3,11 +3,10 @@ use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; use clippy_utils::{get_enclosing_block, sym}; -use hir::{Expr, ExprKind, HirId, LetStmt, PatKind, PathSegment, QPath, StmtKind}; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr}; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, LetStmt, PatKind, PathSegment, QPath, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 1b304dc57680..25929b853af8 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_must_use_ty; use clippy_utils::{nth_arg, return_ty}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::{Span}; -use rustc_attr_data_structures::AttributeKind; -use rustc_attr_data_structures::find_attr; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 62939912304b..38cf7e3822a1 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -219,22 +219,21 @@ impl SingleComponentPathImports { } } } - } else { - // keep track of `use self::some_module` usages - if segments[0].ident.name == kw::SelfLower { - // simple case such as `use self::module::SomeStruct` - if segments.len() > 1 { - imports_reused_with_self.push(segments[1].ident.name); - return; - } + } + // keep track of `use self::some_module` usages + else if segments[0].ident.name == kw::SelfLower { + // simple case such as `use self::module::SomeStruct` + if segments.len() > 1 { + imports_reused_with_self.push(segments[1].ident.name); + return; + } - // nested case such as `use self::{module1::Struct1, module2::Struct2}` - if let UseTreeKind::Nested { items, .. } = &use_tree.kind { - for tree in items { - let segments = &tree.0.prefix.segments; - if !segments.is_empty() { - imports_reused_with_self.push(segments[0].ident.name); - } + // nested case such as `use self::{module1::Struct1, module2::Struct2}` + if let UseTreeKind::Nested { items, .. } = &use_tree.kind { + for tree in items { + let segments = &tree.0.prefix.segments; + if !segments.is_empty() { + imports_reused_with_self.push(segments[0].ident.name); } } } diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index a2938c86c76a..92427473a8ee 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -606,32 +606,31 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span let ctxt = span.ctxt(); if ctxt == SyntaxContext::root() { HasSafetyComment::Maybe - } else { - // From a macro expansion. Get the text from the start of the macro declaration to start of the - // unsafe block. - // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; } - // ^--------------------------------------------^ - if let Ok(unsafe_line) = source_map.lookup_line(span.lo()) - && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo()) - && Arc::ptr_eq(&unsafe_line.sf, ¯o_line.sf) - && let Some(src) = unsafe_line.sf.src.as_deref() - { - if macro_line.line < unsafe_line.line { - match 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, - } - } else { - HasSafetyComment::No + } + // From a macro expansion. Get the text from the start of the macro declaration to start of the + // unsafe block. + // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; } + // ^--------------------------------------------^ + else if let Ok(unsafe_line) = source_map.lookup_line(span.lo()) + && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo()) + && Arc::ptr_eq(&unsafe_line.sf, ¯o_line.sf) + && let Some(src) = unsafe_line.sf.src.as_deref() + { + if macro_line.line < unsafe_line.line { + match 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, } } else { - // Problem getting source text. Pretend a comment was found. - HasSafetyComment::Maybe + HasSafetyComment::No } + } else { + // Problem getting source text. Pretend a comment was found. + HasSafetyComment::Maybe } } diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 615c0995e8b1..73291aa8cdf7 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.89" +version = "0.1.90" 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 1aa16e3943c4..649748d1534b 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-06-12 +nightly-2025-06-26 ``` diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 1ec5d11384f5..aaa071fd5c93 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -958,3 +958,18 @@ fn field_of_struct<'tcx>( 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) { + Some(value) + } else { + None + } +} + +/// Check if `expr` evaluates to an integer constant of 0. +#[inline] +pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + integer_const(cx, expr) == Some(0) +} diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index cd2098a89891..dc240dd067b1 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -109,7 +109,7 @@ pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into( }); } -/// Like `span_lint` but with a `note` section instead of a `help` message. +/// Like [`span_lint`] but with a `note` section instead of a `help` message. /// /// The `note` message is presented separately from the main lint message /// and is attached to a specific span: @@ -226,7 +226,7 @@ pub fn span_lint_and_note( }); } -/// Like `span_lint` but allows to add notes, help and suggestions using a closure. +/// Like [`span_lint`] but allows to add notes, help and suggestions using a closure. /// /// If you need to customize your lint output a lot, use this function. /// If you change the signature, remember to update the internal lint `CollapsibleCalls` diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 913589319fcc..a8b33418c8c0 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -122,7 +122,7 @@ use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; use rustc_span::{InnerSpan, Span}; -use source::walk_span_to_context; +use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; @@ -1886,10 +1886,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => None, }; - did.is_some_and(|did| find_attr!( - cx.tcx.get_all_attrs(did), - AttributeKind::MustUse { ..} - )) + did.is_some_and(|did| find_attr!(cx.tcx.get_all_attrs(did), AttributeKind::MustUse { .. })) } /// Checks if a function's body represents the identity function. Looks for bodies of the form: @@ -2713,7 +2710,7 @@ impl<'tcx> ExprUseNode<'tcx> { } /// Gets the context an expression's value is used in. -pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> { +pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtxt<'tcx> { let mut adjustments = [].as_slice(); let mut is_ty_unified = false; let mut moved_before_use = false; @@ -2790,6 +2787,19 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool { }); } +/// Checks whether a given span has any significant token. A significant token is a non-whitespace +/// token, including comments unless `skip_comments` is set. +/// This is useful to determine if there are any actual code tokens in the span that are omitted in +/// the late pass, such as platform-specific code. +pub fn span_contains_non_whitespace(cx: &impl source::HasSession, span: Span, skip_comments: bool) -> bool { + matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)| + match token { + TokenKind::Whitespace => false, + TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments, + _ => true, + } + )) +} /// Returns all the comments a given span contains /// /// Comments are returned wrapped with their relevant delimiters diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index a5e66ad463bb..7a0bef1a9bbb 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -24,7 +24,7 @@ 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 } + 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF } 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 } @@ -42,6 +42,7 @@ msrv_aliases! { 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN } + 1,61,0 { CONST_FN_TRAIT_BOUND } 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 } diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index e629012b187c..be93f275fab5 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -32,6 +32,21 @@ pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Ms for local in &body.local_decls { check_ty(cx, local.ty, local.source_info.span, msrv)?; } + if !msrv.meets(cx, msrvs::CONST_FN_TRAIT_BOUND) + && let Some(sized_did) = cx.tcx.lang_items().sized_trait() + && let Some(meta_sized_did) = cx.tcx.lang_items().meta_sized_trait() + && cx.tcx.param_env(def_id).caller_bounds().iter().any(|bound| { + bound.as_trait_clause().is_some_and(|clause| { + let did = clause.def_id(); + did != sized_did && did != meta_sized_did + }) + }) + { + return Err(( + body.span, + "non-`Sized` trait clause before `const_fn_trait_bound` is stabilized".into(), + )); + } // impl trait is gone in MIR, so check the return type manually check_ty( cx, diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 6974e6512e2c..7a24d07fa1df 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -494,7 +494,17 @@ impl Display for ParenHelper { /// operators have the same /// precedence. pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> { - Sugg::MaybeParen(format!("{op}{}", expr.maybe_paren()).into()) + // If the `expr` starts with `op` already, do not add wrap it in + // parentheses. + let expr = if let Sugg::MaybeParen(ref sugg) = expr + && !has_enclosing_paren(sugg) + && sugg.starts_with(op) + { + expr + } else { + expr.maybe_paren() + }; + Sugg::MaybeParen(format!("{op}{expr}").into()) } /// Builds the string for ` ` adding parenthesis when necessary. @@ -1016,6 +1026,16 @@ mod test { let sugg = Sugg::BinOp(AssocOp::Binary(ast::BinOpKind::Add), "(1 + 1)".into(), "(1 + 1)".into()); assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_paren().to_string()); } + + #[test] + fn unop_parenthesize() { + let sugg = Sugg::NonParen("x".into()).mut_addr(); + assert_eq!("&mut x", sugg.to_string()); + let sugg = sugg.mut_addr(); + assert_eq!("&mut &mut x", sugg.to_string()); + assert_eq!("(&mut &mut x)", sugg.maybe_paren().to_string()); + } + #[test] fn not_op() { use ast::BinOpKind::{Add, And, Eq, Ge, Gt, Le, Lt, Ne, Or}; diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 3b58dba5628f..8a8218c6976f 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -46,7 +46,6 @@ generate! { DOUBLE_QUOTE: "\"", Deserialize, EarlyLintPass, - ErrorKind, IntoIter, Itertools, LF: "\n", @@ -65,7 +64,6 @@ generate! { RegexBuilder, RegexSet, Start, - Step, Symbol, SyntaxContext, TBD, @@ -158,7 +156,6 @@ generate! { from_ne_bytes, from_ptr, from_raw, - from_ref, from_str, from_str_radix, fs, @@ -166,6 +163,7 @@ generate! { futures_util, get, get_mut, + get_or_insert, get_or_insert_with, get_unchecked, get_unchecked_mut, @@ -216,7 +214,6 @@ generate! { max_by_key, max_value, maximum, - mem, min, min_by, min_by_key, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 782b079ce093..bffbcf073ab0 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -6,6 +6,7 @@ use core::ops::ControlFlow; use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_ast::ast::Mutability; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; @@ -20,8 +21,8 @@ use rustc_middle::traits::EvaluationResult; 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, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, + GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; @@ -31,8 +32,6 @@ use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::assert_matches::debug_assert_matches; use std::collections::hash_map::Entry; use std::iter; -use rustc_attr_data_structures::find_attr; -use rustc_attr_data_structures::AttributeKind; use crate::path_res; use crate::paths::{PathNS, lookup_path_str}; @@ -328,14 +327,8 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { // Returns whether the type has #[must_use] attribute pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind() { - ty::Adt(adt, _) => find_attr!( - cx.tcx.get_all_attrs(adt.did()), - AttributeKind::MustUse { ..} - ), - ty::Foreign(did) => find_attr!( - cx.tcx.get_all_attrs(*did), - AttributeKind::MustUse { ..} - ), + ty::Adt(adt, _) => find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::MustUse { .. }), + ty::Foreign(did) => find_attr!(cx.tcx.get_all_attrs(*did), AttributeKind::MustUse { .. }), ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => { // for the Array case we don't need to care for the len == 0 case // because we don't want to lint functions returning empty arrays @@ -345,7 +338,10 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty::Alias(ty::Opaque, AliasTy { def_id, .. }) => { for (predicate, _) in cx.tcx.explicit_item_self_bounds(def_id).skip_binder() { if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() - && find_attr!(cx.tcx.get_all_attrs(trait_predicate.trait_ref.def_id), AttributeKind::MustUse { ..}) + && find_attr!( + cx.tcx.get_all_attrs(trait_predicate.trait_ref.def_id), + AttributeKind::MustUse { .. } + ) { return true; } @@ -355,7 +351,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { 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 { ..}) + && find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { .. }) { return true; } diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml new file mode 100644 index 000000000000..bd6b4dfdee4d --- /dev/null +++ b/declare_clippy_lint/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "declare_clippy_lint" +version = "0.1.90" +edition = "2024" +repository = "https://github.com/rust-lang/rust-clippy" +license = "MIT OR Apache-2.0" + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/declare_clippy_lint/src/lib.rs b/declare_clippy_lint/src/lib.rs new file mode 100644 index 000000000000..f7d9c64bfbd0 --- /dev/null +++ b/declare_clippy_lint/src/lib.rs @@ -0,0 +1,280 @@ +#![feature(macro_metavar_expr_concat, rustc_private)] + +extern crate rustc_lint; + +use rustc_lint::{Lint, LintId, LintStore}; + +// Needed by `declare_clippy_lint!`. +pub extern crate rustc_session; + +#[derive(Default)] +pub struct LintListBuilder { + lints: Vec<&'static Lint>, + all: Vec, + cargo: Vec, + complexity: Vec, + correctness: Vec, + nursery: Vec, + pedantic: Vec, + perf: Vec, + restriction: Vec, + style: Vec, + suspicious: Vec, +} +impl LintListBuilder { + pub fn insert(&mut self, lints: &[&LintInfo]) { + #[allow(clippy::enum_glob_use)] + use LintCategory::*; + + self.lints.extend(lints.iter().map(|&x| x.lint)); + for &&LintInfo { lint, category, .. } in lints { + let (all, cat) = match category { + Complexity => (Some(&mut self.all), &mut self.complexity), + Correctness => (Some(&mut self.all), &mut self.correctness), + Perf => (Some(&mut self.all), &mut self.perf), + Style => (Some(&mut self.all), &mut self.style), + Suspicious => (Some(&mut self.all), &mut self.suspicious), + Cargo => (None, &mut self.cargo), + Nursery => (None, &mut self.nursery), + Pedantic => (None, &mut self.pedantic), + Restriction => (None, &mut self.restriction), + }; + if let Some(all) = all { + all.push(LintId::of(lint)); + } + cat.push(LintId::of(lint)); + } + } + + pub fn register(self, store: &mut LintStore) { + store.register_lints(&self.lints); + store.register_group(true, "clippy::all", Some("clippy_all"), self.all); + store.register_group(true, "clippy::cargo", Some("clippy_cargo"), self.cargo); + store.register_group(true, "clippy::complexity", Some("clippy_complexity"), self.complexity); + store.register_group( + true, + "clippy::correctness", + Some("clippy_correctness"), + self.correctness, + ); + store.register_group(true, "clippy::nursery", Some("clippy_nursery"), self.nursery); + store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), self.pedantic); + store.register_group(true, "clippy::perf", Some("clippy_perf"), self.perf); + store.register_group( + true, + "clippy::restriction", + Some("clippy_restriction"), + self.restriction, + ); + store.register_group(true, "clippy::style", Some("clippy_style"), self.style); + store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), self.suspicious); + } +} + +#[derive(Copy, Clone, Debug)] +pub enum LintCategory { + Cargo, + Complexity, + Correctness, + Nursery, + Pedantic, + Perf, + Restriction, + Style, + Suspicious, +} +impl LintCategory { + #[must_use] + pub fn name(self) -> &'static str { + match self { + Self::Cargo => "cargo", + Self::Complexity => "complexity", + Self::Correctness => "correctness", + Self::Nursery => "nursery", + Self::Pedantic => "pedantic", + Self::Perf => "perf", + Self::Restriction => "restriction", + Self::Style => "style", + Self::Suspicious => "suspicious", + } + } +} + +pub struct LintInfo { + pub lint: &'static Lint, + pub category: LintCategory, + pub explanation: &'static str, + /// e.g. `clippy_lints/src/absolute_paths.rs#43` + pub location: &'static str, + pub version: &'static str, +} + +impl LintInfo { + /// Returns the lint name in lowercase without the `clippy::` prefix + #[must_use] + #[expect(clippy::missing_panics_doc)] + pub fn name_lower(&self) -> String { + self.lint.name.strip_prefix("clippy::").unwrap().to_ascii_lowercase() + } +} + +#[macro_export] +macro_rules! declare_clippy_lint_inner { + ( + $(#[doc = $docs:literal])* + #[clippy::version = $version:literal] + $vis:vis $lint_name:ident, + $level:ident, + $category:ident, + $desc:literal + $(, @eval_always = $eval_always:literal)? + ) => { + $crate::rustc_session::declare_tool_lint! { + $(#[doc = $docs])* + #[clippy::version = $version] + $vis clippy::$lint_name, + $level, + $desc, + report_in_external_macro:true + $(, @eval_always = $eval_always)? + } + + pub(crate) static ${concat($lint_name, _INFO)}: &'static $crate::LintInfo = &$crate::LintInfo { + lint: $lint_name, + category: $crate::LintCategory::$category, + explanation: concat!($($docs,"\n",)*), + location: concat!(file!(), "#L", line!()), + version: $version, + }; + }; +} + +#[macro_export] +macro_rules! declare_clippy_lint { + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + correctness, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Deny, + Correctness, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + complexity, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Warn, + Complexity, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + perf, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Warn, + Perf, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + style, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Warn, + Style, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + suspicious, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Warn, + Suspicious, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + cargo, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Allow, + Cargo, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + nursery, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Allow, + Nursery, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + pedantic, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Allow, + Pedantic, + $($rest)* + } + }; + ( + $(#[$($meta:tt)*])* + $vis:vis $lint_name:ident, + restriction, + $($rest:tt)* + ) => { + $crate::declare_clippy_lint_inner! { + $(#[$($meta)*])* + $vis $lint_name, + Allow, + Restriction, + $($rest)* + } + }; +} diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 841838314328..eb390eecbcca 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -45,7 +45,7 @@ use rayon::prelude::*; #[must_use] pub fn target_dir() -> String { - env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned()) + env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".to_owned()) } fn lintcheck_sources() -> String { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3fc5a1224a8d..124756a36009 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-06-12" +channel = "nightly-2025-06-26" # 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 426ba870f5f6..c4076cbaa77b 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -19,6 +19,7 @@ extern crate rustc_span; extern crate tikv_jemalloc_sys as jemalloc_sys; use clippy_utils::sym; +use declare_clippy_lint::LintListBuilder; use rustc_interface::interface; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; @@ -156,8 +157,13 @@ impl rustc_driver::Callbacks for ClippyCallbacks { (previous)(sess, lint_store); } + let mut list_builder = LintListBuilder::default(); + list_builder.insert(clippy_lints::declared_lints::LINTS); + list_builder.register(lint_store); + let conf = clippy_config::Conf::read(sess, &conf_path); - clippy_lints::register_lints(lint_store, conf); + clippy_lints::register_lint_passes(lint_store, conf); + #[cfg(feature = "internal")] clippy_lints_internal::register_lints(lint_store); })); diff --git a/src/main.rs b/src/main.rs index c9853e53f3b3..3c2eec1f05b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,7 @@ impl ClippyCmd { } fn into_std_cmd(self) -> Command { - let mut cmd = Command::new(env::var("CARGO").unwrap_or("cargo".into())); + let mut cmd = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())); let clippy_args: String = self .clippy_args .iter() diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 99a01257a7b6..cefe654fef68 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -7,9 +7,9 @@ use askama::filters::Safe; use cargo_metadata::Message; use cargo_metadata::diagnostic::{Applicability, Diagnostic}; use clippy_config::ClippyConfiguration; -use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; +use declare_clippy_lint::LintInfo; use pulldown_cmark::{Options, Parser, html}; use serde::Deserialize; use test_utils::IS_RUSTC_TEST_SUITE; @@ -568,10 +568,10 @@ impl LintMetadata { Self { id: name, id_location: Some(lint.location), - group: lint.category_str(), + group: lint.category.name(), level: lint.lint.default_level.as_str(), docs, - version: lint.version.unwrap(), + version: lint.version, applicability, } } diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 4ac2bd532851..389616801fca 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -40,6 +40,7 @@ fn dogfood() { "clippy_lints", "clippy_utils", "clippy_config", + "declare_clippy_lint", "lintcheck", "rustc_tools_util", ] { diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed new file mode 100644 index 000000000000..0dc0fc230c8d --- /dev/null +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed @@ -0,0 +1,50 @@ +#![allow(clippy::eq_op, clippy::nonminimal_bool)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let (x, y) = ("hello", "world"); + + if x == "hello" { + todo!() + } + // Comment must be kept + else if y == "world" { + println!("Hello world!"); + } + //~^^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } // Inner comment + else if y == "world" { + println!("Hello world!"); + } + //~^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } + /* Inner comment */ + else if y == "world" { + println!("Hello world!"); + } + //~^^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } /* Inner comment */ + else if y == "world" { + println!("Hello world!"); + } + //~^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } /* This should not be removed */ /* So does this */ + // Comment must be kept + else if y == "world" { + println!("Hello world!"); + } + //~^^^^^^ collapsible_else_if +} diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.rs b/tests/ui-toml/collapsible_if/collapsible_else_if.rs new file mode 100644 index 000000000000..8344c122f16c --- /dev/null +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.rs @@ -0,0 +1,55 @@ +#![allow(clippy::eq_op, clippy::nonminimal_bool)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let (x, y) = ("hello", "world"); + + if x == "hello" { + todo!() + } else { + // Comment must be kept + if y == "world" { + println!("Hello world!"); + } + } + //~^^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } else { // Inner comment + if y == "world" { + println!("Hello world!"); + } + } + //~^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } else { + /* Inner comment */ + if y == "world" { + println!("Hello world!"); + } + } + //~^^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } else { /* Inner comment */ + if y == "world" { + println!("Hello world!"); + } + } + //~^^^^^ collapsible_else_if + + if x == "hello" { + todo!() + } /* This should not be removed */ else /* So does this */ { + // Comment must be kept + if y == "world" { + println!("Hello world!"); + } + } + //~^^^^^^ collapsible_else_if +} diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.stderr b/tests/ui-toml/collapsible_if/collapsible_else_if.stderr new file mode 100644 index 000000000000..0ffe5f0a960d --- /dev/null +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.stderr @@ -0,0 +1,105 @@ +error: this `else { if .. }` block can be collapsed + --> tests/ui-toml/collapsible_if/collapsible_else_if.rs:10:12 + | +LL | } else { + | ____________^ +LL | | // Comment must be kept +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::collapsible-else-if` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::collapsible_else_if)]` +help: collapse nested if block + | +LL ~ } +LL | // Comment must be kept +LL ~ else if y == "world" { +LL | println!("Hello world!"); +LL ~ } + | + +error: this `else { if .. }` block can be collapsed + --> tests/ui-toml/collapsible_if/collapsible_else_if.rs:20:12 + | +LL | } else { // Inner comment + | ____________^ +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ } // Inner comment +LL ~ else if y == "world" { +LL | println!("Hello world!"); +LL ~ } + | + +error: this `else { if .. }` block can be collapsed + --> tests/ui-toml/collapsible_if/collapsible_else_if.rs:29:12 + | +LL | } else { + | ____________^ +LL | | /* Inner comment */ +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ } +LL | /* Inner comment */ +LL ~ else if y == "world" { +LL | println!("Hello world!"); +LL ~ } + | + +error: this `else { if .. }` block can be collapsed + --> tests/ui-toml/collapsible_if/collapsible_else_if.rs:39:12 + | +LL | } else { /* Inner comment */ + | ____________^ +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ } /* Inner comment */ +LL ~ else if y == "world" { +LL | println!("Hello world!"); +LL ~ } + | + +error: this `else { if .. }` block can be collapsed + --> tests/ui-toml/collapsible_if/collapsible_else_if.rs:48:64 + | +LL | } /* This should not be removed */ else /* So does this */ { + | ________________________________________________________________^ +LL | | // Comment must be kept +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ } /* This should not be removed */ /* So does this */ +LL | // Comment must be kept +LL ~ else if y == "world" { +LL | println!("Hello world!"); +LL ~ } + | + +error: aborting due to 5 previous errors + diff --git a/tests/ui/borrow_deref_ref.fixed b/tests/ui/borrow_deref_ref.fixed index 765dd75fceb9..6d06fcc3037a 100644 --- a/tests/ui/borrow_deref_ref.fixed +++ b/tests/ui/borrow_deref_ref.fixed @@ -124,3 +124,50 @@ mod issue_11346 { //~^ borrow_deref_ref } } + +fn issue_14934() { + let x: &'static str = "x"; + let y = "y".to_string(); + { + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*x; // Do not lint + *x = &*y; + } + { + let mut x = x; + //~^ borrow_deref_ref + x = &*y; + } + { + #[expect(clippy::toplevel_ref_arg, clippy::needless_borrow)] + let ref x = x; + //~^ borrow_deref_ref + } + { + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = std::convert::identity(x); + //~^ borrow_deref_ref + *x = &*y; + } + { + #[derive(Clone)] + struct S(&'static str); + let s = S("foo"); + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*s.0; // Do not lint + *x = "bar"; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = s.clone().0; + //~^ borrow_deref_ref + *x = "bar"; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*std::convert::identity(&s).0; + *x = "bar"; + } + { + let y = &1; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = { y }; + //~^ borrow_deref_ref + } +} diff --git a/tests/ui/borrow_deref_ref.rs b/tests/ui/borrow_deref_ref.rs index 8ee66bfa881a..b43f4c93bf2b 100644 --- a/tests/ui/borrow_deref_ref.rs +++ b/tests/ui/borrow_deref_ref.rs @@ -124,3 +124,50 @@ mod issue_11346 { //~^ borrow_deref_ref } } + +fn issue_14934() { + let x: &'static str = "x"; + let y = "y".to_string(); + { + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*x; // Do not lint + *x = &*y; + } + { + let mut x = &*x; + //~^ borrow_deref_ref + x = &*y; + } + { + #[expect(clippy::toplevel_ref_arg, clippy::needless_borrow)] + let ref x = &*x; + //~^ borrow_deref_ref + } + { + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*std::convert::identity(x); + //~^ borrow_deref_ref + *x = &*y; + } + { + #[derive(Clone)] + struct S(&'static str); + let s = S("foo"); + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*s.0; // Do not lint + *x = "bar"; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*s.clone().0; + //~^ borrow_deref_ref + *x = "bar"; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = &*std::convert::identity(&s).0; + *x = "bar"; + } + { + let y = &1; + #[expect(clippy::toplevel_ref_arg)] + let ref mut x = { &*y }; + //~^ borrow_deref_ref + } +} diff --git a/tests/ui/borrow_deref_ref.stderr b/tests/ui/borrow_deref_ref.stderr index 3d55da25b9b2..3a1f968b4be1 100644 --- a/tests/ui/borrow_deref_ref.stderr +++ b/tests/ui/borrow_deref_ref.stderr @@ -25,5 +25,35 @@ error: deref on an immutable reference LL | (&*s).foo(); | ^^^^^ help: if you would like to reborrow, try removing `&*`: `s` -error: aborting due to 4 previous errors +error: deref on an immutable reference + --> tests/ui/borrow_deref_ref.rs:137:21 + | +LL | let mut x = &*x; + | ^^^ help: if you would like to reborrow, try removing `&*`: `x` + +error: deref on an immutable reference + --> tests/ui/borrow_deref_ref.rs:143:21 + | +LL | let ref x = &*x; + | ^^^ help: if you would like to reborrow, try removing `&*`: `x` + +error: deref on an immutable reference + --> tests/ui/borrow_deref_ref.rs:148:25 + | +LL | let ref mut x = &*std::convert::identity(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you would like to reborrow, try removing `&*`: `std::convert::identity(x)` + +error: deref on an immutable reference + --> tests/ui/borrow_deref_ref.rs:160:25 + | +LL | let ref mut x = &*s.clone().0; + | ^^^^^^^^^^^^^ help: if you would like to reborrow, try removing `&*`: `s.clone().0` + +error: deref on an immutable reference + --> tests/ui/borrow_deref_ref.rs:170:27 + | +LL | let ref mut x = { &*y }; + | ^^^ help: if you would like to reborrow, try removing `&*`: `y` + +error: aborting due to 9 previous errors diff --git a/tests/ui/borrow_interior_mutable_const.rs b/tests/ui/borrow_interior_mutable_const.rs index 0f439f789150..674450a73ad2 100644 --- a/tests/ui/borrow_interior_mutable_const.rs +++ b/tests/ui/borrow_interior_mutable_const.rs @@ -218,4 +218,20 @@ fn main() { let _ = &S::VALUE.1; //~ borrow_interior_mutable_const let _ = &S::VALUE.2; } + { + pub struct Foo(pub Entry, pub T); + + pub struct Entry(pub Cell<[u32; N]>); + + impl Entry { + const INIT: Self = Self(Cell::new([42; N])); + } + + impl Foo { + pub fn make_foo(v: T) -> Self { + // Used to ICE due to incorrect instantiation. + Foo(Entry::INIT, v) + } + } + } } diff --git a/tests/ui/box_default.fixed b/tests/ui/box_default.fixed index 80000f5de4fd..ed00494433b9 100644 --- a/tests/ui/box_default.fixed +++ b/tests/ui/box_default.fixed @@ -126,7 +126,7 @@ fn issue_10381() { impl Bar for Foo {} fn maybe_get_bar(i: u32) -> Option> { - if i % 2 == 0 { + if i.is_multiple_of(2) { Some(Box::new(Foo::default())) } else { None diff --git a/tests/ui/box_default.rs b/tests/ui/box_default.rs index 4681016d7cd3..801d92f5c290 100644 --- a/tests/ui/box_default.rs +++ b/tests/ui/box_default.rs @@ -126,7 +126,7 @@ fn issue_10381() { impl Bar for Foo {} fn maybe_get_bar(i: u32) -> Option> { - if i % 2 == 0 { + if i.is_multiple_of(2) { Some(Box::new(Foo::default())) } else { None diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.rs b/tests/ui/branches_sharing_code/shared_at_bottom.rs index 922d30443fcc..fa322dc28a78 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_bottom.rs @@ -276,3 +276,27 @@ mod issue14873 { } } } + +fn issue15004() { + let a = 12u32; + let b = 13u32; + let mut c = 8u32; + + let mut result = if b > a { + c += 1; + 0 + } else { + c += 2; + 0 + //~^ branches_sharing_code + }; + + result = if b > a { + c += 1; + 1 + } else { + c += 2; + 1 + //~^ branches_sharing_code + }; +} diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_bottom.stderr index f437db8b7331..1c470fb0da5e 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -172,5 +172,35 @@ LL ~ } LL + let y = 1; | -error: aborting due to 10 previous errors +error: all if blocks contain the same code at the end + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:290:5 + | +LL | / 0 +LL | | +LL | | }; + | |_____^ + | + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if + | +LL ~ } +LL ~ 0; + | + +error: all if blocks contain the same code at the end + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:299:5 + | +LL | / 1 +LL | | +LL | | }; + | |_____^ + | + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if + | +LL ~ } +LL ~ 1; + | + +error: aborting due to 12 previous errors diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index 9f530ad670a0..fed75244c6f7 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -86,3 +86,21 @@ fn issue_7318() { }else if false {} //~^^^ collapsible_else_if } + +fn issue14799() { + use std::ops::ControlFlow; + + let c: ControlFlow<_, ()> = ControlFlow::Break(Some(42)); + if let ControlFlow::Break(Some(_)) = c { + todo!(); + } else { + #[cfg(target_os = "freebsd")] + todo!(); + + if let ControlFlow::Break(None) = c { + todo!(); + } else { + todo!(); + } + } +} diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index 2c646cd1d4da..e50e781fb698 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -102,3 +102,21 @@ fn issue_7318() { } //~^^^ collapsible_else_if } + +fn issue14799() { + use std::ops::ControlFlow; + + let c: ControlFlow<_, ()> = ControlFlow::Break(Some(42)); + if let ControlFlow::Break(Some(_)) = c { + todo!(); + } else { + #[cfg(target_os = "freebsd")] + todo!(); + + if let ControlFlow::Break(None) = c { + todo!(); + } else { + todo!(); + } + } +} diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index b553182a4454..77bc791ea8e9 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -154,3 +154,12 @@ fn issue14722() { None }; } + +fn issue14799() { + if true { + #[cfg(target_os = "freebsd")] + todo!(); + + if true {} + }; +} diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index f5998457ca6c..d30df157d5eb 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -164,3 +164,12 @@ fn issue14722() { None }; } + +fn issue14799() { + if true { + #[cfg(target_os = "freebsd")] + todo!(); + + if true {} + }; +} diff --git a/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs index 633a435ca5ed..8c3217624d44 100644 --- a/tests/ui/doc/needless_doctest_main.rs +++ b/tests/ui/doc/needless_doctest_main.rs @@ -1,5 +1,3 @@ -//@ check-pass - #![warn(clippy::needless_doctest_main)] //! issue 10491: //! ```rust,no_test @@ -19,4 +17,114 @@ /// ``` fn foo() {} +#[rustfmt::skip] +/// Description +/// ```rust +/// fn main() { +//~^ error: needless `fn main` in doctest +/// let a = 0; +/// } +/// ``` +fn mulpipulpi() {} + +#[rustfmt::skip] +/// With a `#[no_main]` +/// ```rust +/// #[no_main] +/// fn a() { +/// let _ = 0; +/// } +/// ``` +fn pulpimulpi() {} + +// Without a `#[no_main]` attribute +/// ```rust +/// fn a() { +/// let _ = 0; +/// } +/// ``` +fn plumilupi() {} + +#[rustfmt::skip] +/// Additional function, shouldn't trigger +/// ```rust +/// fn additional_function() { +/// let _ = 0; +/// // Thus `fn main` is actually relevant! +/// } +/// fn main() { +/// let _ = 0; +/// } +/// ``` +fn mlupipupi() {} + +#[rustfmt::skip] +/// Additional function AFTER main, shouldn't trigger +/// ```rust +/// fn main() { +/// let _ = 0; +/// } +/// fn additional_function() { +/// let _ = 0; +/// // Thus `fn main` is actually relevant! +/// } +/// ``` +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 +/// let _ = 0; +/// } +/// ``` +fn plumpiplupi() {} + +/// 4 Functions, this should not lint because there are several function +/// +/// ```rust +/// fn a() {let _ = 0; } +/// fn b() {let _ = 0; } +/// fn main() { let _ = 0; } +/// fn d() { let _ = 0; } +/// ``` +fn pulmipulmip() {} + +/// 3 Functions but main is first, should also not lint +/// +///```rust +/// fn main() { let _ = 0; } +/// fn b() { let _ = 0; } +/// fn c() { let _ = 0; } +/// ``` +fn pmuplimulip() {} + fn main() {} + +fn issue8244() -> Result<(), ()> { + //! ```compile_fail + //! fn test() -> Result< {} + //! ``` + Ok(()) +} + +/// # Examples +/// +/// ``` +/// use std::error::Error; +/// fn main() -> Result<(), Box/* > */ { +/// } +/// ``` +fn issue15041() {} diff --git a/tests/ui/doc/needless_doctest_main.stderr b/tests/ui/doc/needless_doctest_main.stderr new file mode 100644 index 000000000000..dd5474ccb85a --- /dev/null +++ b/tests/ui/doc/needless_doctest_main.stderr @@ -0,0 +1,36 @@ +error: needless `fn main` in doctest + --> tests/ui/doc/needless_doctest_main.rs:23:5 + | +LL | /// fn main() { + | _____^ +LL | | +LL | | /// let a = 0; +LL | | /// } + | |_____^ + | + = 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 + | +LL | /// fn main() { + | _____^ +LL | | +LL | | /// // Hi! +LL | | /// let _ = 0; +LL | | /// } + | |_____^ + +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 + diff --git a/tests/ui/doc_broken_link.rs b/tests/ui/doc_broken_link.rs new file mode 100644 index 000000000000..7d9c0ef13b3c --- /dev/null +++ b/tests/ui/doc_broken_link.rs @@ -0,0 +1,72 @@ +#![warn(clippy::doc_broken_link)] + +fn main() {} + +pub struct FakeType {} + +/// This might be considered a link false positive +/// and should be ignored by this lint rule: +/// Example of referencing some code with brackets [FakeType]. +pub fn doc_ignore_link_false_positive_1() {} + +/// This might be considered a link false positive +/// and should be ignored by this lint rule: +/// [`FakeType`]. Continue text after brackets, +/// then (something in +/// parenthesis). +pub fn doc_ignore_link_false_positive_2() {} + +/// Test valid link, whole link single line. +/// [doc valid link](https://test.fake/doc_valid_link) +pub fn doc_valid_link() {} + +/// Test valid link, whole link single line but it has special chars such as brackets and +/// parenthesis. [doc invalid link url invalid char](https://test.fake/doc_valid_link_url_invalid_char?foo[bar]=1&bar(foo)=2) +pub fn doc_valid_link_url_invalid_char() {} + +/// Test valid link, text tag broken across multiple lines. +/// [doc valid link broken +/// text](https://test.fake/doc_valid_link_broken_text) +pub fn doc_valid_link_broken_text() {} + +/// Test valid link, url tag broken across multiple lines, but +/// the whole url part in a single line. +/// [doc valid link broken url tag two lines first](https://test.fake/doc_valid_link_broken_url_tag_two_lines_first +/// ) +pub fn doc_valid_link_broken_url_tag_two_lines_first() {} + +/// Test valid link, url tag broken across multiple lines, but +/// the whole url part in a single line. +/// [doc valid link broken url tag two lines second]( +/// https://test.fake/doc_valid_link_broken_url_tag_two_lines_second) +pub fn doc_valid_link_broken_url_tag_two_lines_second() {} + +/// Test valid link, url tag broken across multiple lines, but +/// the whole url part in a single line, but the closing pharentesis +/// in a third line. +/// [doc valid link broken url tag three lines]( +/// https://test.fake/doc_valid_link_broken_url_tag_three_lines +/// ) +pub fn doc_valid_link_broken_url_tag_three_lines() {} + +/// Test invalid link, url part broken across multiple lines. +/// [doc invalid link broken url scheme part](https:// +/// test.fake/doc_invalid_link_broken_url_scheme_part) +//~^^ ERROR: possible broken doc link: broken across multiple lines +pub fn doc_invalid_link_broken_url_scheme_part() {} + +/// Test invalid link, url part broken across multiple lines. +/// [doc invalid link broken url host part](https://test +/// .fake/doc_invalid_link_broken_url_host_part) +//~^^ ERROR: possible broken doc link: broken across multiple lines +pub fn doc_invalid_link_broken_url_host_part() {} + +/// Test invalid link, for multiple urls in the same block of comment. +/// There is a [fist link - invalid](https://test +/// .fake) then it continues +//~^^ ERROR: possible broken doc link: broken across multiple lines +/// with a [second link - valid](https://test.fake/doc_valid_link) and another [third link - invalid](https://test +/// .fake). It ends with another +//~^^ ERROR: possible broken doc link: broken across multiple lines +/// line of comment. +pub fn doc_multiple_invalid_link_broken_url() {} diff --git a/tests/ui/doc_broken_link.stderr b/tests/ui/doc_broken_link.stderr new file mode 100644 index 000000000000..179ed97635ee --- /dev/null +++ b/tests/ui/doc_broken_link.stderr @@ -0,0 +1,29 @@ +error: possible broken doc link: broken across multiple lines + --> tests/ui/doc_broken_link.rs:53:5 + | +LL | /// [doc invalid link broken url scheme part](https:// + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::doc-broken-link` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_broken_link)]` + +error: possible broken doc link: broken across multiple lines + --> tests/ui/doc_broken_link.rs:59:5 + | +LL | /// [doc invalid link broken url host part](https://test + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible broken doc link: broken across multiple lines + --> tests/ui/doc_broken_link.rs:65:16 + | +LL | /// There is a [fist link - invalid](https://test + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible broken doc link: broken across multiple lines + --> tests/ui/doc_broken_link.rs:68:80 + | +LL | /// with a [second link - valid](https://test.fake/doc_valid_link) and another [third link - invalid](https://test + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/empty_line_after/outer_attribute.1.fixed b/tests/ui/empty_line_after/outer_attribute.1.fixed index 36d80a2c95bf..e36e3c2aea6a 100644 --- a/tests/ui/empty_line_after/outer_attribute.1.fixed +++ b/tests/ui/empty_line_after/outer_attribute.1.fixed @@ -105,4 +105,13 @@ second line ")] pub struct Args; +mod issue_14980 { + //~v empty_line_after_outer_attr + #[repr(align(536870912))] + enum Aligned { + Zero = 0, + One = 1, + } +} + fn main() {} diff --git a/tests/ui/empty_line_after/outer_attribute.2.fixed b/tests/ui/empty_line_after/outer_attribute.2.fixed index 0e8e4129e858..b0908fc72147 100644 --- a/tests/ui/empty_line_after/outer_attribute.2.fixed +++ b/tests/ui/empty_line_after/outer_attribute.2.fixed @@ -108,4 +108,13 @@ second line ")] pub struct Args; +mod issue_14980 { + //~v empty_line_after_outer_attr + #[repr(align(536870912))] + enum Aligned { + Zero = 0, + One = 1, + } +} + fn main() {} diff --git a/tests/ui/empty_line_after/outer_attribute.rs b/tests/ui/empty_line_after/outer_attribute.rs index 1295088ac00e..4ae113c68f52 100644 --- a/tests/ui/empty_line_after/outer_attribute.rs +++ b/tests/ui/empty_line_after/outer_attribute.rs @@ -116,4 +116,14 @@ second line ")] pub struct Args; +mod issue_14980 { + //~v empty_line_after_outer_attr + #[repr(align(536870912))] + + enum Aligned { + Zero = 0, + One = 1, + } +} + fn main() {} diff --git a/tests/ui/empty_line_after/outer_attribute.stderr b/tests/ui/empty_line_after/outer_attribute.stderr index 519ba6e67615..331bc7c8856d 100644 --- a/tests/ui/empty_line_after/outer_attribute.stderr +++ b/tests/ui/empty_line_after/outer_attribute.stderr @@ -111,5 +111,16 @@ LL | pub fn isolated_comment() {} | = help: if the empty lines are unintentional, remove them -error: aborting due to 9 previous errors +error: empty line after outer attribute + --> tests/ui/empty_line_after/outer_attribute.rs:121:5 + | +LL | / #[repr(align(536870912))] +LL | | + | |_^ +LL | enum Aligned { + | ------------ the attribute applies to this enum + | + = help: if the empty line is unintentional, remove it + +error: aborting due to 10 previous errors diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 0ba631fda051..c93b83f53ecb 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -543,3 +543,21 @@ mod issue_13073 { //~^ redundant_closure } } + +fn issue_14789() { + _ = Some(1u8).map( + #[expect(clippy::redundant_closure)] + |a| foo(a), + ); + + _ = Some("foo").map( + #[expect(clippy::redundant_closure_for_method_calls)] + |s| s.to_owned(), + ); + + let _: Vec = None.map_or_else( + #[expect(clippy::redundant_closure)] + || vec![], + std::convert::identity, + ); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 4d8b29d450c5..273c8b21f4ad 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -543,3 +543,21 @@ mod issue_13073 { //~^ redundant_closure } } + +fn issue_14789() { + _ = Some(1u8).map( + #[expect(clippy::redundant_closure)] + |a| foo(a), + ); + + _ = Some("foo").map( + #[expect(clippy::redundant_closure_for_method_calls)] + |s| s.to_owned(), + ); + + let _: Vec = None.map_or_else( + #[expect(clippy::redundant_closure)] + || vec![], + std::convert::identity, + ); +} diff --git a/tests/ui/exhaustive_items.fixed b/tests/ui/exhaustive_items.fixed index 79c74aeefbd8..3b2f33dbd2ce 100644 --- a/tests/ui/exhaustive_items.fixed +++ b/tests/ui/exhaustive_items.fixed @@ -1,3 +1,4 @@ +#![feature(default_field_values)] #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] #![allow(unused)] @@ -90,3 +91,9 @@ pub mod structs { pub bar: String, } } + +pub mod issue14992 { + pub struct A { + pub a: isize = 42, + } +} diff --git a/tests/ui/exhaustive_items.rs b/tests/ui/exhaustive_items.rs index 4e851f4c492e..b0a6a7170766 100644 --- a/tests/ui/exhaustive_items.rs +++ b/tests/ui/exhaustive_items.rs @@ -1,3 +1,4 @@ +#![feature(default_field_values)] #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] #![allow(unused)] @@ -87,3 +88,9 @@ pub mod structs { pub bar: String, } } + +pub mod issue14992 { + pub struct A { + pub a: isize = 42, + } +} diff --git a/tests/ui/exhaustive_items.stderr b/tests/ui/exhaustive_items.stderr index c92c8a9efaae..55928fa458d3 100644 --- a/tests/ui/exhaustive_items.stderr +++ b/tests/ui/exhaustive_items.stderr @@ -1,5 +1,5 @@ error: exported enums should not be exhaustive - --> tests/ui/exhaustive_items.rs:9:5 + --> tests/ui/exhaustive_items.rs:10:5 | LL | / pub enum Exhaustive { LL | | @@ -11,7 +11,7 @@ LL | | } | |_____^ | note: the lint level is defined here - --> tests/ui/exhaustive_items.rs:1:9 + --> tests/ui/exhaustive_items.rs:2:9 | LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL ~ pub enum Exhaustive { | error: exported enums should not be exhaustive - --> tests/ui/exhaustive_items.rs:19:5 + --> tests/ui/exhaustive_items.rs:20:5 | LL | / pub enum ExhaustiveWithAttrs { LL | | @@ -40,7 +40,7 @@ LL ~ pub enum ExhaustiveWithAttrs { | error: exported structs should not be exhaustive - --> tests/ui/exhaustive_items.rs:55:5 + --> tests/ui/exhaustive_items.rs:56:5 | LL | / pub struct Exhaustive { LL | | @@ -50,7 +50,7 @@ LL | | } | |_____^ | note: the lint level is defined here - --> tests/ui/exhaustive_items.rs:1:35 + --> tests/ui/exhaustive_items.rs:2:35 | LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index a1b556029987..4e14e1a5e33f 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -312,3 +312,49 @@ fn issue_13470() { let _: u64 = 1u64 + ((x as i32 + y as i32) as u64); //~^ identity_op } + +fn issue_14932() { + let _ = 0usize + &Default::default(); // no error + + 0usize + &Default::default(); // no error + + ::default(); + //~^ identity_op + + let _ = usize::default(); + //~^ identity_op + + let _n: usize = Default::default(); + //~^ identity_op +} + +// Expr's type can be inferred by the function's return type +fn issue_14932_2() -> usize { + Default::default() + //~^ identity_op +} + +trait Def { + fn def() -> Self; +} + +impl Def for usize { + fn def() -> Self { + 0 + } +} + +fn issue_14932_3() { + let _ = 0usize + &Def::def(); // no error + + 0usize + &Def::def(); // no error + + ::def(); + //~^ identity_op + + let _ = usize::def(); + //~^ identity_op + + let _n: usize = Def::def(); + //~^ identity_op +} diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index f603e1078e4e..ebbef5723ffb 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -312,3 +312,49 @@ fn issue_13470() { let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); //~^ identity_op } + +fn issue_14932() { + let _ = 0usize + &Default::default(); // no error + + 0usize + &Default::default(); // no error + + 0usize + &::default(); + //~^ identity_op + + let _ = 0usize + &usize::default(); + //~^ identity_op + + let _n: usize = 0usize + &Default::default(); + //~^ identity_op +} + +// Expr's type can be inferred by the function's return type +fn issue_14932_2() -> usize { + 0usize + &Default::default() + //~^ identity_op +} + +trait Def { + fn def() -> Self; +} + +impl Def for usize { + fn def() -> Self { + 0 + } +} + +fn issue_14932_3() { + let _ = 0usize + &Def::def(); // no error + + 0usize + &Def::def(); // no error + + 0usize + &::def(); + //~^ identity_op + + let _ = 0usize + &usize::def(); + //~^ identity_op + + let _n: usize = 0usize + &Def::def(); + //~^ identity_op +} diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 8f9c2b603c49..24fa5db08ce5 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -379,5 +379,47 @@ error: this operation has no effect LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `((x as i32 + y as i32) as u64)` -error: aborting due to 63 previous errors +error: this operation has no effect + --> tests/ui/identity_op.rs:321:5 + | +LL | 0usize + &::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `::default()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:324:13 + | +LL | let _ = 0usize + &usize::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::default()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:327:21 + | +LL | let _n: usize = 0usize + &Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:333:5 + | +LL | 0usize + &Default::default() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:352:5 + | +LL | 0usize + &::def(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `::def()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:355:13 + | +LL | let _ = 0usize + &usize::def(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::def()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:358:21 + | +LL | let _n: usize = 0usize + &Def::def(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Def::def()` + +error: aborting due to 70 previous errors diff --git a/tests/ui/infinite_iter.rs b/tests/ui/infinite_iter.rs index 002a791a6579..701a86534ba0 100644 --- a/tests/ui/infinite_iter.rs +++ b/tests/ui/infinite_iter.rs @@ -38,7 +38,7 @@ fn infinite_iters() { //~^ infinite_iter // infinite iter - (0_u64..).filter(|x| x % 2 == 0).last(); + (0_u64..).filter(|x| x.is_multiple_of(2)).last(); //~^ infinite_iter // not an infinite, because ranges are double-ended diff --git a/tests/ui/infinite_iter.stderr b/tests/ui/infinite_iter.stderr index 47133a2ea62e..b9e7c008f93e 100644 --- a/tests/ui/infinite_iter.stderr +++ b/tests/ui/infinite_iter.stderr @@ -42,8 +42,8 @@ LL | (0_usize..).flat_map(|x| 0..x).product::(); error: infinite iteration detected --> tests/ui/infinite_iter.rs:41:5 | -LL | (0_u64..).filter(|x| x % 2 == 0).last(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | (0_u64..).filter(|x| x.is_multiple_of(2)).last(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: possible infinite iteration detected --> tests/ui/infinite_iter.rs:53:5 diff --git a/tests/ui/iter_kv_map.fixed b/tests/ui/iter_kv_map.fixed index 874f749b33d0..b18dda358877 100644 --- a/tests/ui/iter_kv_map.fixed +++ b/tests/ui/iter_kv_map.fixed @@ -30,15 +30,19 @@ fn main() { let _ = map.clone().values().collect::>(); //~^ iter_kv_map - let _ = map.keys().filter(|x| *x % 2 == 0).count(); + let _ = map.keys().filter(|x| x.is_multiple_of(2)).count(); //~^ iter_kv_map // Don't lint - let _ = map.iter().filter(|(_, val)| *val % 2 == 0).map(|(key, _)| key).count(); + let _ = map + .iter() + .filter(|(_, val)| val.is_multiple_of(2)) + .map(|(key, _)| key) + .count(); let _ = map.iter().map(get_key).collect::>(); // Linting the following could be an improvement to the lint - // map.iter().filter_map(|(_, val)| (val % 2 == 0).then(val * 17)).count(); + // map.iter().filter_map(|(_, val)| (val.is_multiple_of(2)).then(val * 17)).count(); // Lint let _ = map.keys().map(|key| key * 9).count(); @@ -84,15 +88,19 @@ fn main() { let _ = map.clone().values().collect::>(); //~^ iter_kv_map - let _ = map.keys().filter(|x| *x % 2 == 0).count(); + let _ = map.keys().filter(|x| x.is_multiple_of(2)).count(); //~^ iter_kv_map // Don't lint - let _ = map.iter().filter(|(_, val)| *val % 2 == 0).map(|(key, _)| key).count(); + let _ = map + .iter() + .filter(|(_, val)| val.is_multiple_of(2)) + .map(|(key, _)| key) + .count(); let _ = map.iter().map(get_key).collect::>(); // Linting the following could be an improvement to the lint - // map.iter().filter_map(|(_, val)| (val % 2 == 0).then(val * 17)).count(); + // map.iter().filter_map(|(_, val)| (val.is_multiple_of(2)).then(val * 17)).count(); // Lint let _ = map.keys().map(|key| key * 9).count(); diff --git a/tests/ui/iter_kv_map.rs b/tests/ui/iter_kv_map.rs index f570e3c32cb6..729e4e8a266c 100644 --- a/tests/ui/iter_kv_map.rs +++ b/tests/ui/iter_kv_map.rs @@ -30,15 +30,19 @@ fn main() { let _ = map.clone().iter().map(|(_, val)| val).collect::>(); //~^ iter_kv_map - let _ = map.iter().map(|(key, _)| key).filter(|x| *x % 2 == 0).count(); + let _ = map.iter().map(|(key, _)| key).filter(|x| x.is_multiple_of(2)).count(); //~^ iter_kv_map // Don't lint - let _ = map.iter().filter(|(_, val)| *val % 2 == 0).map(|(key, _)| key).count(); + let _ = map + .iter() + .filter(|(_, val)| val.is_multiple_of(2)) + .map(|(key, _)| key) + .count(); let _ = map.iter().map(get_key).collect::>(); // Linting the following could be an improvement to the lint - // map.iter().filter_map(|(_, val)| (val % 2 == 0).then(val * 17)).count(); + // map.iter().filter_map(|(_, val)| (val.is_multiple_of(2)).then(val * 17)).count(); // Lint let _ = map.iter().map(|(key, _value)| key * 9).count(); @@ -86,15 +90,19 @@ fn main() { let _ = map.clone().iter().map(|(_, val)| val).collect::>(); //~^ iter_kv_map - let _ = map.iter().map(|(key, _)| key).filter(|x| *x % 2 == 0).count(); + let _ = map.iter().map(|(key, _)| key).filter(|x| x.is_multiple_of(2)).count(); //~^ iter_kv_map // Don't lint - let _ = map.iter().filter(|(_, val)| *val % 2 == 0).map(|(key, _)| key).count(); + let _ = map + .iter() + .filter(|(_, val)| val.is_multiple_of(2)) + .map(|(key, _)| key) + .count(); let _ = map.iter().map(get_key).collect::>(); // Linting the following could be an improvement to the lint - // map.iter().filter_map(|(_, val)| (val % 2 == 0).then(val * 17)).count(); + // map.iter().filter_map(|(_, val)| (val.is_multiple_of(2)).then(val * 17)).count(); // Lint let _ = map.iter().map(|(key, _value)| key * 9).count(); diff --git a/tests/ui/iter_kv_map.stderr b/tests/ui/iter_kv_map.stderr index 31ee76c25b7a..8f73541f5033 100644 --- a/tests/ui/iter_kv_map.stderr +++ b/tests/ui/iter_kv_map.stderr @@ -52,29 +52,29 @@ LL | let _ = map.clone().iter().map(|(_, val)| val).collect::>(); error: iterating on a map's keys --> tests/ui/iter_kv_map.rs:33:13 | -LL | let _ = map.iter().map(|(key, _)| key).filter(|x| *x % 2 == 0).count(); +LL | let _ = map.iter().map(|(key, _)| key).filter(|x| x.is_multiple_of(2)).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:44:13 + --> tests/ui/iter_kv_map.rs:48:13 | LL | let _ = map.iter().map(|(key, _value)| key * 9).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys().map(|key| key * 9)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:46:13 + --> tests/ui/iter_kv_map.rs:50:13 | LL | let _ = map.iter().map(|(_key, value)| value * 17).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().map(|value| value * 17)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:50:13 + --> tests/ui/iter_kv_map.rs:54:13 | LL | let _ = map.clone().into_iter().map(|(_, ref val)| ref_acceptor(val)).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values().map(|ref val| ref_acceptor(val))` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:54:13 + --> tests/ui/iter_kv_map.rs:58:13 | LL | let _ = map | _____________^ @@ -97,85 +97,85 @@ LL + }) | error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:65:13 + --> tests/ui/iter_kv_map.rs:69:13 | LL | let _ = map.clone().into_iter().map(|(_, mut val)| val).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:70:13 + --> tests/ui/iter_kv_map.rs:74:13 | LL | let _ = map.iter().map(|(key, _)| key).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:72:13 + --> tests/ui/iter_kv_map.rs:76:13 | LL | let _ = map.iter().map(|(_, value)| value).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:74:13 + --> tests/ui/iter_kv_map.rs:78:13 | LL | let _ = map.iter().map(|(_, v)| v + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().map(|v| v + 2)` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:77:13 + --> tests/ui/iter_kv_map.rs:81:13 | LL | let _ = map.clone().into_iter().map(|(key, _)| key).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_keys()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:79:13 + --> tests/ui/iter_kv_map.rs:83:13 | LL | let _ = map.clone().into_iter().map(|(key, _)| key + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_keys().map(|key| key + 2)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:82:13 + --> tests/ui/iter_kv_map.rs:86:13 | LL | let _ = map.clone().into_iter().map(|(_, val)| val).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:84:13 + --> tests/ui/iter_kv_map.rs:88:13 | LL | let _ = map.clone().into_iter().map(|(_, val)| val + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values().map(|val| val + 2)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:87:13 + --> tests/ui/iter_kv_map.rs:91:13 | LL | let _ = map.clone().iter().map(|(_, val)| val).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().values()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:89:13 + --> tests/ui/iter_kv_map.rs:93:13 | -LL | let _ = map.iter().map(|(key, _)| key).filter(|x| *x % 2 == 0).count(); +LL | let _ = map.iter().map(|(key, _)| key).filter(|x| x.is_multiple_of(2)).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:100:13 + --> tests/ui/iter_kv_map.rs:108:13 | LL | let _ = map.iter().map(|(key, _value)| key * 9).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys().map(|key| key * 9)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:102:13 + --> tests/ui/iter_kv_map.rs:110:13 | LL | let _ = map.iter().map(|(_key, value)| value * 17).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().map(|value| value * 17)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:106:13 + --> tests/ui/iter_kv_map.rs:114:13 | LL | let _ = map.clone().into_iter().map(|(_, ref val)| ref_acceptor(val)).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values().map(|ref val| ref_acceptor(val))` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:110:13 + --> tests/ui/iter_kv_map.rs:118:13 | LL | let _ = map | _____________^ @@ -198,73 +198,73 @@ LL + }) | error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:121:13 + --> tests/ui/iter_kv_map.rs:129:13 | LL | let _ = map.clone().into_iter().map(|(_, mut val)| val).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:137:13 + --> tests/ui/iter_kv_map.rs:145:13 | LL | let _ = map.iter().map(|(key, _)| key).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:140:13 + --> tests/ui/iter_kv_map.rs:148:13 | LL | let _ = map.iter().map(|(_, value)| value).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:143:13 + --> tests/ui/iter_kv_map.rs:151:13 | LL | let _ = map.iter().map(|(_, v)| v + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().map(|v| v + 2)` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:152:13 + --> tests/ui/iter_kv_map.rs:160:13 | LL | let _ = map.clone().into_iter().map(|(key, _)| key).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_keys()` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:155:13 + --> tests/ui/iter_kv_map.rs:163:13 | LL | let _ = map.clone().into_iter().map(|(key, _)| key + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_keys().map(|key| key + 2)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:158:13 + --> tests/ui/iter_kv_map.rs:166:13 | LL | let _ = map.clone().into_iter().map(|(_, val)| val).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:161:13 + --> tests/ui/iter_kv_map.rs:169:13 | LL | let _ = map.clone().into_iter().map(|(_, val)| val + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.clone().into_values().map(|val| val + 2)` error: iterating on a map's keys - --> tests/ui/iter_kv_map.rs:164:13 + --> tests/ui/iter_kv_map.rs:172:13 | LL | let _ = map.iter().map(|(key, _)| key).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.keys()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:167:13 + --> tests/ui/iter_kv_map.rs:175:13 | LL | let _ = map.iter().map(|(_, value)| value).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values()` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:170:13 + --> tests/ui/iter_kv_map.rs:178:13 | LL | let _ = map.iter().map(|(_, v)| v + 2).collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().map(|v| v + 2)` error: iterating on a map's values - --> tests/ui/iter_kv_map.rs:185:13 + --> tests/ui/iter_kv_map.rs:193:13 | LL | let _ = map.as_ref().iter().map(|(_, v)| v).copied().collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.as_ref().values()` diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 5e7a2ad37a84..304eacecd942 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -61,7 +61,7 @@ fn multiline_sugg() { //~^ let_unit_value .into_iter() .map(|i| i * 2) - .filter(|i| i % 2 == 0) + .filter(|i| i.is_multiple_of(2)) .map(|_| ()) .next() .unwrap(); diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 7b06f6940121..a02cb346ff99 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -61,7 +61,7 @@ fn multiline_sugg() { //~^ let_unit_value .into_iter() .map(|i| i * 2) - .filter(|i| i % 2 == 0) + .filter(|i| i.is_multiple_of(2)) .map(|_| ()) .next() .unwrap(); diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index d7d01d304cad..d743110c99dd 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -25,7 +25,7 @@ LL ~ v LL + LL + .into_iter() LL + .map(|i| i * 2) -LL + .filter(|i| i % 2 == 0) +LL + .filter(|i| i.is_multiple_of(2)) LL + .map(|_| ()) LL + .next() LL + .unwrap(); diff --git a/tests/ui/manual_contains.fixed b/tests/ui/manual_contains.fixed index d26c948a7817..18171f0b2b40 100644 --- a/tests/ui/manual_contains.fixed +++ b/tests/ui/manual_contains.fixed @@ -58,7 +58,7 @@ fn should_not_lint() { let vec: Vec = vec![1, 2, 3, 4, 5, 6]; let values = &vec[..]; - let _ = values.iter().any(|&v| v % 2 == 0); + let _ = values.iter().any(|&v| v.is_multiple_of(2)); let _ = values.iter().any(|&v| v * 2 == 6); let _ = values.iter().any(|&v| v == v); let _ = values.iter().any(|&v| 4 == 4); diff --git a/tests/ui/manual_contains.rs b/tests/ui/manual_contains.rs index fe67d2ee5d5c..918f4d6b8dd7 100644 --- a/tests/ui/manual_contains.rs +++ b/tests/ui/manual_contains.rs @@ -58,7 +58,7 @@ fn should_not_lint() { let vec: Vec = vec![1, 2, 3, 4, 5, 6]; let values = &vec[..]; - let _ = values.iter().any(|&v| v % 2 == 0); + let _ = values.iter().any(|&v| v.is_multiple_of(2)); let _ = values.iter().any(|&v| v * 2 == 6); let _ = values.iter().any(|&v| v == v); let _ = values.iter().any(|&v| 4 == 4); diff --git a/tests/ui/manual_find_fixable.fixed b/tests/ui/manual_find_fixable.fixed index 01b3ebacbebc..c69b0cb11e3c 100644 --- a/tests/ui/manual_find_fixable.fixed +++ b/tests/ui/manual_find_fixable.fixed @@ -11,7 +11,7 @@ fn lookup(n: u32) -> Option { } fn with_pat(arr: Vec<(u32, u32)>) -> Option { - arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0) + arr.into_iter().map(|(a, _)| a).find(|&a| a.is_multiple_of(2)) } struct Data { @@ -63,7 +63,7 @@ fn with_side_effects(arr: Vec) -> Option { fn with_else(arr: Vec) -> Option { for el in arr { - if el % 2 == 0 { + if el.is_multiple_of(2) { return Some(el); } else { println!("{}", el); diff --git a/tests/ui/manual_find_fixable.rs b/tests/ui/manual_find_fixable.rs index ce62a4beba1c..db7092f020c1 100644 --- a/tests/ui/manual_find_fixable.rs +++ b/tests/ui/manual_find_fixable.rs @@ -19,7 +19,7 @@ fn lookup(n: u32) -> Option { fn with_pat(arr: Vec<(u32, u32)>) -> Option { for (a, _) in arr { //~^ manual_find - if a % 2 == 0 { + if a.is_multiple_of(2) { return Some(a); } } @@ -111,7 +111,7 @@ fn with_side_effects(arr: Vec) -> Option { fn with_else(arr: Vec) -> Option { for el in arr { - if el % 2 == 0 { + if el.is_multiple_of(2) { return Some(el); } else { println!("{}", el); diff --git a/tests/ui/manual_find_fixable.stderr b/tests/ui/manual_find_fixable.stderr index 020635d90bb5..0c05c0d2c440 100644 --- a/tests/ui/manual_find_fixable.stderr +++ b/tests/ui/manual_find_fixable.stderr @@ -17,11 +17,11 @@ error: manual implementation of `Iterator::find` | LL | / for (a, _) in arr { LL | | -LL | | if a % 2 == 0 { +LL | | if a.is_multiple_of(2) { LL | | return Some(a); ... | LL | | None - | |________^ help: replace with an iterator: `arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0)` + | |________^ help: replace with an iterator: `arr.into_iter().map(|(a, _)| a).find(|&a| a.is_multiple_of(2))` error: manual implementation of `Iterator::find` --> tests/ui/manual_find_fixable.rs:34:5 diff --git a/tests/ui/manual_is_multiple_of.fixed b/tests/ui/manual_is_multiple_of.fixed new file mode 100644 index 000000000000..6735b99f298c --- /dev/null +++ b/tests/ui/manual_is_multiple_of.fixed @@ -0,0 +1,25 @@ +//@aux-build: proc_macros.rs +#![warn(clippy::manual_is_multiple_of)] + +fn main() {} + +#[clippy::msrv = "1.87"] +fn f(a: u64, b: u64) { + let _ = a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = (a + 1).is_multiple_of(b + 1); //~ manual_is_multiple_of + let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = !(a + 1).is_multiple_of(b + 1); //~ manual_is_multiple_of + + let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of + + proc_macros::external! { + let a: u64 = 23424; + let _ = a % 4096 == 0; + } +} + +#[clippy::msrv = "1.86"] +fn g(a: u64, b: u64) { + let _ = a % b == 0; +} diff --git a/tests/ui/manual_is_multiple_of.rs b/tests/ui/manual_is_multiple_of.rs new file mode 100644 index 000000000000..00b638e4fd9f --- /dev/null +++ b/tests/ui/manual_is_multiple_of.rs @@ -0,0 +1,25 @@ +//@aux-build: proc_macros.rs +#![warn(clippy::manual_is_multiple_of)] + +fn main() {} + +#[clippy::msrv = "1.87"] +fn f(a: u64, b: u64) { + let _ = a % b == 0; //~ manual_is_multiple_of + let _ = (a + 1) % (b + 1) == 0; //~ manual_is_multiple_of + let _ = a % b != 0; //~ manual_is_multiple_of + let _ = (a + 1) % (b + 1) != 0; //~ manual_is_multiple_of + + let _ = a % b > 0; //~ manual_is_multiple_of + let _ = 0 < a % b; //~ manual_is_multiple_of + + proc_macros::external! { + let a: u64 = 23424; + let _ = a % 4096 == 0; + } +} + +#[clippy::msrv = "1.86"] +fn g(a: u64, b: u64) { + let _ = a % b == 0; +} diff --git a/tests/ui/manual_is_multiple_of.stderr b/tests/ui/manual_is_multiple_of.stderr new file mode 100644 index 000000000000..0b1ae70c2a70 --- /dev/null +++ b/tests/ui/manual_is_multiple_of.stderr @@ -0,0 +1,41 @@ +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:8:13 + | +LL | let _ = a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)` + | + = note: `-D clippy::manual-is-multiple-of` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_is_multiple_of)]` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:9:13 + | +LL | let _ = (a + 1) % (b + 1) == 0; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `(a + 1).is_multiple_of(b + 1)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:10:13 + | +LL | let _ = a % b != 0; + | ^^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:11:13 + | +LL | let _ = (a + 1) % (b + 1) != 0; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `!(a + 1).is_multiple_of(b + 1)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:13:13 + | +LL | let _ = a % b > 0; + | ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:14:13 + | +LL | let _ = 0 < a % b; + | ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/manual_is_variant_and.fixed b/tests/ui/manual_is_variant_and.fixed index 18a72188ab59..6425f32c09c4 100644 --- a/tests/ui/manual_is_variant_and.fixed +++ b/tests/ui/manual_is_variant_and.fixed @@ -77,7 +77,7 @@ fn option_methods() { let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint // Should not lint. - let _ = Foo::(0).map(|x| x % 2 == 0) == Some(true); + let _ = Foo::(0).map(|x| x.is_multiple_of(2)) == Some(true); let _ = Some(2).map(|x| x % 2 == 0) != foo(); let _ = mac!(eq Some(2).map(|x| x % 2 == 0), Some(true)); let _ = mac!(some 2).map(|x| x % 2 == 0) == Some(true); @@ -96,11 +96,11 @@ fn result_methods() { }); let _ = res.is_ok_and(|x| x > 1); - let _ = Ok::(2).is_ok_and(|x| x % 2 == 0); + let _ = Ok::(2).is_ok_and(|x| x.is_multiple_of(2)); //~^ manual_is_variant_and - let _ = !Ok::(2).is_ok_and(|x| x % 2 == 0); + let _ = !Ok::(2).is_ok_and(|x| x.is_multiple_of(2)); //~^ manual_is_variant_and - let _ = !Ok::(2).is_ok_and(|x| x % 2 == 0); + let _ = !Ok::(2).is_ok_and(|x| x.is_multiple_of(2)); //~^ manual_is_variant_and // won't fix because the return type of the closure is not `bool` diff --git a/tests/ui/manual_is_variant_and.rs b/tests/ui/manual_is_variant_and.rs index a92f7c043695..e069e97a04dd 100644 --- a/tests/ui/manual_is_variant_and.rs +++ b/tests/ui/manual_is_variant_and.rs @@ -83,7 +83,7 @@ fn option_methods() { let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint // Should not lint. - let _ = Foo::(0).map(|x| x % 2 == 0) == Some(true); + let _ = Foo::(0).map(|x| x.is_multiple_of(2)) == Some(true); let _ = Some(2).map(|x| x % 2 == 0) != foo(); let _ = mac!(eq Some(2).map(|x| x % 2 == 0), Some(true)); let _ = mac!(some 2).map(|x| x % 2 == 0) == Some(true); @@ -105,11 +105,11 @@ fn result_methods() { //~^ manual_is_variant_and .unwrap_or_default(); - let _ = Ok::(2).map(|x| x % 2 == 0) == Ok(true); + let _ = Ok::(2).map(|x| x.is_multiple_of(2)) == Ok(true); //~^ manual_is_variant_and - let _ = Ok::(2).map(|x| x % 2 == 0) != Ok(true); + let _ = Ok::(2).map(|x| x.is_multiple_of(2)) != Ok(true); //~^ manual_is_variant_and - let _ = Ok::(2).map(|x| x % 2 == 0) != Ok(true); + let _ = Ok::(2).map(|x| x.is_multiple_of(2)) != Ok(true); //~^ manual_is_variant_and // won't fix because the return type of the closure is not `bool` diff --git a/tests/ui/manual_is_variant_and.stderr b/tests/ui/manual_is_variant_and.stderr index 1fb437a8bc74..f770319a2681 100644 --- a/tests/ui/manual_is_variant_and.stderr +++ b/tests/ui/manual_is_variant_and.stderr @@ -105,20 +105,20 @@ LL | | .unwrap_or_default(); error: called `.map() == Ok()` --> tests/ui/manual_is_variant_and.rs:108:13 | -LL | let _ = Ok::(2).map(|x| x % 2 == 0) == Ok(true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ok::(2).is_ok_and(|x| x % 2 == 0)` +LL | let _ = Ok::(2).map(|x| x.is_multiple_of(2)) == Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Ok::(2).is_ok_and(|x| x.is_multiple_of(2))` error: called `.map() != Ok()` --> tests/ui/manual_is_variant_and.rs:110:13 | -LL | let _ = Ok::(2).map(|x| x % 2 == 0) != Ok(true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!Ok::(2).is_ok_and(|x| x % 2 == 0)` +LL | let _ = Ok::(2).map(|x| x.is_multiple_of(2)) != Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!Ok::(2).is_ok_and(|x| x.is_multiple_of(2))` error: called `.map() != Ok()` --> tests/ui/manual_is_variant_and.rs:112:13 | -LL | let _ = Ok::(2).map(|x| x % 2 == 0) != Ok(true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!Ok::(2).is_ok_and(|x| x % 2 == 0)` +LL | let _ = Ok::(2).map(|x| x.is_multiple_of(2)) != Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!Ok::(2).is_ok_and(|x| x.is_multiple_of(2))` error: called `map().unwrap_or_default()` on a `Result` value --> tests/ui/manual_is_variant_and.rs:119:18 diff --git a/tests/ui/manual_ok_err.fixed b/tests/ui/manual_ok_err.fixed index e6f799aa58d6..9b70ce0df43a 100644 --- a/tests/ui/manual_ok_err.fixed +++ b/tests/ui/manual_ok_err.fixed @@ -103,3 +103,27 @@ fn issue14239() { }; //~^^^^^ manual_ok_err } + +mod issue15051 { + struct Container { + field: Result, + } + + #[allow(clippy::needless_borrow)] + fn with_addr_of(x: &Container) -> Option<&bool> { + (&x.field).as_ref().ok() + } + + fn from_fn(x: &Container) -> Option<&bool> { + let result_with_ref = || &x.field; + result_with_ref().as_ref().ok() + } + + fn result_with_ref_mut(x: &mut Container) -> &mut Result { + &mut x.field + } + + fn from_fn_mut(x: &mut Container) -> Option<&mut bool> { + result_with_ref_mut(x).as_mut().ok() + } +} diff --git a/tests/ui/manual_ok_err.rs b/tests/ui/manual_ok_err.rs index 972b2c41ee7a..dee904638245 100644 --- a/tests/ui/manual_ok_err.rs +++ b/tests/ui/manual_ok_err.rs @@ -141,3 +141,39 @@ fn issue14239() { }; //~^^^^^ manual_ok_err } + +mod issue15051 { + struct Container { + field: Result, + } + + #[allow(clippy::needless_borrow)] + fn with_addr_of(x: &Container) -> Option<&bool> { + match &x.field { + //~^ manual_ok_err + Ok(panel) => Some(panel), + Err(_) => None, + } + } + + fn from_fn(x: &Container) -> Option<&bool> { + let result_with_ref = || &x.field; + match result_with_ref() { + //~^ manual_ok_err + Ok(panel) => Some(panel), + Err(_) => None, + } + } + + fn result_with_ref_mut(x: &mut Container) -> &mut Result { + &mut x.field + } + + fn from_fn_mut(x: &mut Container) -> Option<&mut bool> { + match result_with_ref_mut(x) { + //~^ manual_ok_err + Ok(panel) => Some(panel), + Err(_) => None, + } + } +} diff --git a/tests/ui/manual_ok_err.stderr b/tests/ui/manual_ok_err.stderr index 040e170f397e..448fbffc0509 100644 --- a/tests/ui/manual_ok_err.stderr +++ b/tests/ui/manual_ok_err.stderr @@ -111,5 +111,35 @@ LL + "1".parse::().ok() LL ~ }; | -error: aborting due to 9 previous errors +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:152:9 + | +LL | / match &x.field { +LL | | +LL | | Ok(panel) => Some(panel), +LL | | Err(_) => None, +LL | | } + | |_________^ help: replace with: `(&x.field).as_ref().ok()` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:161:9 + | +LL | / match result_with_ref() { +LL | | +LL | | Ok(panel) => Some(panel), +LL | | Err(_) => None, +LL | | } + | |_________^ help: replace with: `result_with_ref().as_ref().ok()` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:173:9 + | +LL | / match result_with_ref_mut(x) { +LL | | +LL | | Ok(panel) => Some(panel), +LL | | Err(_) => None, +LL | | } + | |_________^ help: replace with: `result_with_ref_mut(x).as_mut().ok()` + +error: aborting due to 12 previous errors diff --git a/tests/ui/missing_const_for_fn/const_trait.fixed b/tests/ui/missing_const_for_fn/const_trait.fixed index 7e0d4fccaae2..f1d5579a7230 100644 --- a/tests/ui/missing_const_for_fn/const_trait.fixed +++ b/tests/ui/missing_const_for_fn/const_trait.fixed @@ -25,7 +25,7 @@ const fn can_be_const() { 0u64.method(); } -// False negative, see FIXME comment in `clipy_utils::qualify_min_const` +// False negative, see FIXME comment in `clippy_utils::qualify_min_const_fn` fn could_be_const_but_does_not_trigger(t: T) where T: const ConstTrait, diff --git a/tests/ui/missing_const_for_fn/const_trait.rs b/tests/ui/missing_const_for_fn/const_trait.rs index 439da4622d7e..d495759526d3 100644 --- a/tests/ui/missing_const_for_fn/const_trait.rs +++ b/tests/ui/missing_const_for_fn/const_trait.rs @@ -25,7 +25,7 @@ fn can_be_const() { 0u64.method(); } -// False negative, see FIXME comment in `clipy_utils::qualify_min_const` +// False negative, see FIXME comment in `clippy_utils::qualify_min_const_fn` fn could_be_const_but_does_not_trigger(t: T) where T: const ConstTrait, diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 65eb2d5938b6..95bf63ed1df6 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -221,3 +221,60 @@ const fn mut_add(x: &mut i32) { //~^ missing_const_for_fn *x += 1; } + +mod issue_15079 { + pub trait Trait {} + + pub struct Struct { + _t: Option, + } + + impl Struct { + #[clippy::msrv = "1.60"] + pub fn new_1_60() -> Self { + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub const fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } + + pub struct S2 { + _t: Option, + } + + impl S2 { + #[clippy::msrv = "1.60"] + pub const fn new_1_60() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub const fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } + + pub struct S3 { + _t: Option<&'static T>, + } + + impl S3 { + #[clippy::msrv = "1.60"] + pub const fn new_1_60() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub const fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 3690d2f799ff..8290be675462 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -221,3 +221,60 @@ fn mut_add(x: &mut i32) { //~^ missing_const_for_fn *x += 1; } + +mod issue_15079 { + pub trait Trait {} + + pub struct Struct { + _t: Option, + } + + impl Struct { + #[clippy::msrv = "1.60"] + pub fn new_1_60() -> Self { + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } + + pub struct S2 { + _t: Option, + } + + impl S2 { + #[clippy::msrv = "1.60"] + pub fn new_1_60() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } + + pub struct S3 { + _t: Option<&'static T>, + } + + impl S3 { + #[clippy::msrv = "1.60"] + pub fn new_1_60() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + + #[clippy::msrv = "1.61"] + pub fn new_1_61() -> Self { + //~^ missing_const_for_fn + Self { _t: None } + } + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 10e07d12f5a4..17cbc4312766 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -332,5 +332,75 @@ help: make the function `const` LL | const fn mut_add(x: &mut i32) { | +++++ -error: aborting due to 25 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:239:9 + | +LL | / pub fn new_1_61() -> Self { +LL | | +LL | | Self { _t: None } +LL | | } + | |_________^ + | +help: make the function `const` + | +LL | pub const fn new_1_61() -> Self { + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:251:9 + | +LL | / pub fn new_1_60() -> Self { +LL | | +LL | | Self { _t: None } +LL | | } + | |_________^ + | +help: make the function `const` + | +LL | pub const fn new_1_60() -> Self { + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:257:9 + | +LL | / pub fn new_1_61() -> Self { +LL | | +LL | | Self { _t: None } +LL | | } + | |_________^ + | +help: make the function `const` + | +LL | pub const fn new_1_61() -> Self { + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:269:9 + | +LL | / pub fn new_1_60() -> Self { +LL | | +LL | | Self { _t: None } +LL | | } + | |_________^ + | +help: make the function `const` + | +LL | pub const fn new_1_60() -> Self { + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:275:9 + | +LL | / pub fn new_1_61() -> Self { +LL | | +LL | | Self { _t: None } +LL | | } + | |_________^ + | +help: make the function `const` + | +LL | pub const fn new_1_61() -> Self { + | +++++ + +error: aborting due to 30 previous errors diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index 0e3e4cf7988e..ecb82a23da03 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -179,7 +179,7 @@ error: inequality checks against true can be replaced by a negation --> tests/ui/nonminimal_bool.rs:186:8 | LL | if !b != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!(!b)` + | ^^^^^^^^^^ help: try simplifying it as shown: `!!b` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:189:8 @@ -209,7 +209,7 @@ error: inequality checks against true can be replaced by a negation --> tests/ui/nonminimal_bool.rs:193:8 | LL | if true != !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!(!b)` + | ^^^^^^^^^^ help: try simplifying it as shown: `!!b` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:196:8 diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index a1119d75c231..34f3e0468419 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -5,6 +5,7 @@ clippy::uninlined_format_args, clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap, + clippy::unnecessary_result_map_or_else, clippy::useless_vec )] @@ -409,4 +410,33 @@ fn fn_call_in_nested_expr() { //~^ or_fun_call } +mod result_map_or { + fn g() -> i32 { + 3 + } + + fn f(n: i32) -> i32 { + n + } + + fn test_map_or() { + let x: Result = Ok(4); + let _ = x.map_or_else(|_| g(), |v| v); + //~^ or_fun_call + let _ = x.map_or_else(|_| g(), f); + //~^ or_fun_call + let _ = x.map_or(0, f); + } +} + +fn test_option_get_or_insert() { + // assume that this is slow call + fn g() -> u8 { + 99 + } + let mut x = Some(42_u8); + let _ = x.get_or_insert_with(g); + //~^ or_fun_call +} + fn main() {} diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index a7cd632bf166..dc57bd6060ac 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -5,6 +5,7 @@ clippy::uninlined_format_args, clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap, + clippy::unnecessary_result_map_or_else, clippy::useless_vec )] @@ -409,4 +410,33 @@ fn fn_call_in_nested_expr() { //~^ or_fun_call } +mod result_map_or { + fn g() -> i32 { + 3 + } + + fn f(n: i32) -> i32 { + n + } + + fn test_map_or() { + let x: Result = Ok(4); + let _ = x.map_or(g(), |v| v); + //~^ or_fun_call + let _ = x.map_or(g(), f); + //~^ or_fun_call + let _ = x.map_or(0, f); + } +} + +fn test_option_get_or_insert() { + // assume that this is slow call + fn g() -> u8 { + 99 + } + let mut x = Some(42_u8); + let _ = x.get_or_insert(g()); + //~^ or_fun_call +} + fn main() {} diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 35bda7e4d331..0f159fe8bff4 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -1,5 +1,5 @@ error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:52:22 + --> tests/ui/or_fun_call.rs:53:22 | LL | with_constructor.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(make)` @@ -8,7 +8,7 @@ LL | with_constructor.unwrap_or(make()); = help: to override `-D warnings` add `#[allow(clippy::or_fun_call)]` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:56:14 + --> tests/ui/or_fun_call.rs:57:14 | LL | with_new.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` @@ -17,199 +17,199 @@ LL | with_new.unwrap_or(Vec::new()); = help: to override `-D warnings` add `#[allow(clippy::unwrap_or_default)]` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:60:21 + --> tests/ui/or_fun_call.rs:61:21 | LL | with_const_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Vec::with_capacity(12))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:64:14 + --> tests/ui/or_fun_call.rs:65:14 | LL | with_err.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| make())` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:68:19 + --> tests/ui/or_fun_call.rs:69:19 | LL | with_err_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Vec::with_capacity(12))` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:72:24 + --> tests/ui/or_fun_call.rs:73:24 | LL | with_default_trait.unwrap_or(Default::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:76:23 + --> tests/ui/or_fun_call.rs:77:23 | 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:80:18 + --> tests/ui/or_fun_call.rs:81: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:84:18 + --> tests/ui/or_fun_call.rs:85: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:88:14 + --> tests/ui/or_fun_call.rs:89:14 | LL | with_vec.unwrap_or(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:92:21 + --> tests/ui/or_fun_call.rs:93: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:96:19 + --> tests/ui/or_fun_call.rs:97: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:100:23 + --> tests/ui/or_fun_call.rs:101:23 | LL | map_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:104:21 + --> tests/ui/or_fun_call.rs:105: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:108:25 + --> tests/ui/or_fun_call.rs:109:25 | LL | btree_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:112:21 + --> tests/ui/or_fun_call.rs:113: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:117:17 + --> tests/ui/or_fun_call.rs:118: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:122:21 + --> tests/ui/or_fun_call.rs:123: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:125:21 + --> tests/ui/or_fun_call.rs:126: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:150:35 + --> tests/ui/or_fun_call.rs:151: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:193:18 + --> tests/ui/or_fun_call.rs:194: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:201:14 + --> tests/ui/or_fun_call.rs:202: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:204:14 + --> tests/ui/or_fun_call.rs:205: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:280:25 + --> tests/ui/or_fun_call.rs:281: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:282:25 + --> tests/ui/or_fun_call.rs:283:25 | LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:314:18 + --> tests/ui/or_fun_call.rs:315: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:318:28 + --> tests/ui/or_fun_call.rs:319: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:322:27 + --> tests/ui/or_fun_call.rs:323: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:326:22 + --> tests/ui/or_fun_call.rs:327: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:330:23 + --> tests/ui/or_fun_call.rs:331: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:334:25 + --> tests/ui/or_fun_call.rs:335: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:338:25 + --> tests/ui/or_fun_call.rs:339: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:380:17 + --> tests/ui/or_fun_call.rs:381: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:385:17 + --> tests/ui/or_fun_call.rs:386: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:390:17 + --> tests/ui/or_fun_call.rs:391:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -229,22 +229,40 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:396:17 + --> tests/ui/or_fun_call.rs:397: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:401:17 + --> tests/ui/or_fun_call.rs:402: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:408:21 + --> tests/ui/or_fun_call.rs:409:21 | LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })` -error: aborting due to 38 previous errors +error: function call inside of `map_or` + --> tests/ui/or_fun_call.rs:424: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:426:19 + | +LL | let _ = x.map_or(g(), f); + | ^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), f)` + +error: function call inside of `get_or_insert` + --> tests/ui/or_fun_call.rs:438:15 + | +LL | let _ = x.get_or_insert(g()); + | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` + +error: aborting due to 41 previous errors diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 60dc1c101b6e..8d6f5fbadca5 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -453,3 +453,15 @@ fn const_in_pattern(x: Option<(i32, i32)>) -> Option<()> { None } + +fn issue_13642(x: Option) -> Option<()> { + let Some(x) = x else { + #[cfg(false)] + panic!(); + + #[cfg(true)] + return None; + }; + + None +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 99d0122a98fa..f13eee29c113 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -549,3 +549,15 @@ fn const_in_pattern(x: Option<(i32, i32)>) -> Option<()> { None } + +fn issue_13642(x: Option) -> Option<()> { + let Some(x) = x else { + #[cfg(false)] + panic!(); + + #[cfg(true)] + return None; + }; + + None +} diff --git a/tests/ui/unnecessary_os_str_debug_formatting.rs b/tests/ui/unnecessary_os_str_debug_formatting.rs index 6652efd9ae1d..66590be3d054 100644 --- a/tests/ui/unnecessary_os_str_debug_formatting.rs +++ b/tests/ui/unnecessary_os_str_debug_formatting.rs @@ -21,3 +21,16 @@ fn main() { let _: String = format!("{:?}", os_str); //~ unnecessary_debug_formatting let _: String = format!("{:?}", os_string); //~ unnecessary_debug_formatting } + +#[clippy::msrv = "1.86"] +fn msrv_1_86() { + let os_str = OsStr::new("test"); + println!("{:?}", os_str); +} + +#[clippy::msrv = "1.87"] +fn msrv_1_87() { + let os_str = OsStr::new("test"); + println!("{:?}", os_str); + //~^ unnecessary_debug_formatting +} diff --git a/tests/ui/unnecessary_os_str_debug_formatting.stderr b/tests/ui/unnecessary_os_str_debug_formatting.stderr index 382e59b04619..f04d2d5bdc82 100644 --- a/tests/ui/unnecessary_os_str_debug_formatting.stderr +++ b/tests/ui/unnecessary_os_str_debug_formatting.stderr @@ -54,5 +54,14 @@ LL | let _: String = format!("{:?}", os_string); = help: use `Display` formatting and change this to `os_string.display()` = note: switching to `Display` formatting will change how the value is shown; escaped characters will no longer be escaped and surrounding quotes will be removed -error: aborting due to 6 previous errors +error: unnecessary `Debug` formatting in `println!` args + --> tests/ui/unnecessary_os_str_debug_formatting.rs:34:22 + | +LL | println!("{:?}", os_str); + | ^^^^^^ + | + = help: use `Display` formatting and change this to `os_str.display()` + = note: switching to `Display` formatting will change how the value is shown; escaped characters will no longer be escaped and surrounding quotes will be removed + +error: aborting due to 7 previous errors diff --git a/tests/ui/wildcard_enum_match_arm.fixed b/tests/ui/wildcard_enum_match_arm.fixed index 141ff6eb2ac7..5f738a254dcd 100644 --- a/tests/ui/wildcard_enum_match_arm.fixed +++ b/tests/ui/wildcard_enum_match_arm.fixed @@ -90,6 +90,21 @@ fn main() { _ => {}, } + { + pub enum Enum { + A, + B, + C(u8), + D(u8, u8), + E { e: u8 }, + }; + match Enum::A { + Enum::A => (), + Enum::B | Enum::C(_) | Enum::D(..) | Enum::E { .. } => (), + //~^ wildcard_enum_match_arm + } + } + { #![allow(clippy::manual_non_exhaustive)] pub enum Enum { @@ -105,3 +120,17 @@ fn main() { } } } + +fn issue15091() { + enum Foo { + A, + B, + C, + } + + match Foo::A { + Foo::A => {}, + r#type @ Foo::B | r#type @ Foo::C => {}, + //~^ wildcard_enum_match_arm + } +} diff --git a/tests/ui/wildcard_enum_match_arm.rs b/tests/ui/wildcard_enum_match_arm.rs index a13684e9100b..4bc4bfdcb794 100644 --- a/tests/ui/wildcard_enum_match_arm.rs +++ b/tests/ui/wildcard_enum_match_arm.rs @@ -90,6 +90,21 @@ fn main() { _ => {}, } + { + pub enum Enum { + A, + B, + C(u8), + D(u8, u8), + E { e: u8 }, + }; + match Enum::A { + Enum::A => (), + _ => (), + //~^ wildcard_enum_match_arm + } + } + { #![allow(clippy::manual_non_exhaustive)] pub enum Enum { @@ -105,3 +120,17 @@ fn main() { } } } + +fn issue15091() { + enum Foo { + A, + B, + C, + } + + match Foo::A { + Foo::A => {}, + r#type => {}, + //~^ wildcard_enum_match_arm + } +} diff --git a/tests/ui/wildcard_enum_match_arm.stderr b/tests/ui/wildcard_enum_match_arm.stderr index 088c6b7b2841..d0929989494a 100644 --- a/tests/ui/wildcard_enum_match_arm.stderr +++ b/tests/ui/wildcard_enum_match_arm.stderr @@ -37,8 +37,20 @@ LL | _ => {}, error: wildcard match will also match any future added variants --> tests/ui/wildcard_enum_match_arm.rs:103:13 | +LL | _ => (), + | ^ help: try: `Enum::B | Enum::C(_) | Enum::D(..) | Enum::E { .. }` + +error: wildcard match will also match any future added variants + --> tests/ui/wildcard_enum_match_arm.rs:118:13 + | LL | _ => (), | ^ help: try: `Enum::B | Enum::__Private` -error: aborting due to 6 previous errors +error: wildcard match will also match any future added variants + --> tests/ui/wildcard_enum_match_arm.rs:133:9 + | +LL | r#type => {}, + | ^^^^^^ help: try: `r#type @ Foo::B | r#type @ Foo::C` + +error: aborting due to 8 previous errors diff --git a/tests/versioncheck.rs b/tests/versioncheck.rs index f6fc2354ca08..b0179387b2b8 100644 --- a/tests/versioncheck.rs +++ b/tests/versioncheck.rs @@ -27,6 +27,7 @@ fn consistent_clippy_crate_versions() { "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", "clippy_utils/Cargo.toml", + "declare_clippy_lint/Cargo.toml", ]; for path in paths { diff --git a/triagebot.toml b/triagebot.toml index 16557a4bebb8..4f370758c006 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -17,6 +17,9 @@ allow-unauthenticated = [ [issue-links] +[mentions."clippy_lints/src/doc"] +cc = ["@notriddle"] + # Prevents mentions in commits to avoid users being spammed [no-mentions] diff --git a/util/versions.py b/util/versions.py index fee0d292df16..6e06d77a7714 100755 --- a/util/versions.py +++ b/util/versions.py @@ -6,11 +6,11 @@ import os import sys def key(v): - if v == "master": - return sys.maxsize if v == "stable": - return sys.maxsize - 1 + return sys.maxsize if v == "beta": + return sys.maxsize - 1 + if v == "master": return sys.maxsize - 2 if v == "pre-1.29.0": return -1 From 4a8eec34ee888852f3f2b909c575df0bedf9f61a Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 27 Jun 2025 12:21:41 +0200 Subject: [PATCH 04/19] broken_links: Fix rustdoc API usage --- clippy_lints/src/doc/broken_link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/doc/broken_link.rs b/clippy_lints/src/doc/broken_link.rs index a97b807e605f..4af10510023d 100644 --- a/clippy_lints/src/doc/broken_link.rs +++ b/clippy_lints/src/doc/broken_link.rs @@ -19,7 +19,7 @@ pub fn check(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragm } fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { - if let Some(span) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) { + if let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) { let mut len = 0; // grab raw link data From 6709720e4c9208519874b50158150a8ce3c1afb1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 2 Jun 2025 08:59:29 +1000 Subject: [PATCH 05/19] Introduce `ByteSymbol`. It's like `Symbol` but for byte strings. The interner is now used for both `Symbol` and `ByteSymbol`. E.g. if you intern `"dog"` and `b"dog"` you'll get a `Symbol` and a `ByteSymbol` with the same index and the characters will only be stored once. The motivation for this is to eliminate the `Arc`s in `ast::LitKind`, to make `ast::LitKind` impl `Copy`, and to avoid the need to arena-allocate `ast::LitKind` in HIR. The latter change reduces peak memory by a non-trivial amount on literal-heavy benchmarks such as `deep-vector` and `tuple-stress`. `Encoder`, `Decoder`, `SpanEncoder`, and `SpanDecoder` all get some changes so that they can handle normal strings and byte strings. This change does slow down compilation of programs that use `include_bytes!` on large files, because the contents of those files are now interned (hashed). This makes `include_bytes!` more similar to `include_str!`, though `include_bytes!` contents still aren't escaped, and hashing is still much cheaper than escaping. --- clippy_lints/src/approx_const.rs | 2 +- clippy_lints/src/bool_assert_comparison.rs | 2 +- clippy_lints/src/casts/manual_dangling_ptr.rs | 2 +- clippy_lints/src/casts/unnecessary_cast.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 4 ++-- clippy_lints/src/large_include_file.rs | 2 +- clippy_lints/src/manual_ignore_case_cmp.rs | 8 ++++---- clippy_lints/src/manual_strip.rs | 2 +- clippy_lints/src/matches/match_like_matches.rs | 2 +- clippy_lints/src/matches/match_same_arms.rs | 8 +++++--- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/missing_asserts_for_indexing.rs | 2 +- clippy_lints/src/utils/author.rs | 2 +- clippy_utils/src/consts.rs | 12 +++++++----- 14 files changed, 28 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index 852e48cbcaee..5ed4c82634aa 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -74,7 +74,7 @@ impl ApproxConstant { } impl LateLintPass<'_> for ApproxConstant { - fn check_lit(&mut self, cx: &LateContext<'_>, _hir_id: HirId, lit: &Lit, _negated: bool) { + fn check_lit(&mut self, cx: &LateContext<'_>, _hir_id: HirId, lit: Lit, _negated: bool) { match lit.node { LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty { FloatTy::F16 => self.check_known_consts(cx, lit.span, s, "f16"), diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index 8f95e44bf853..581fe33ea0b3 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -42,7 +42,7 @@ fn extract_bool_lit(e: &Expr<'_>) -> Option { }) = e.kind && !e.span.from_expansion() { - Some(*b) + Some(b) } else { None } diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs index d9e88d6a401c..92910cf8adf5 100644 --- a/clippy_lints/src/casts/manual_dangling_ptr.rs +++ b/clippy_lints/src/casts/manual_dangling_ptr.rs @@ -46,7 +46,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool { match expr.kind { ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to), - ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to), + ExprKind::Lit(lit) => is_literal_aligned(cx, &lit, to), _ => false, } } diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 010f09d4c1d3..c88a0539d70e 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -243,7 +243,7 @@ fn lint_unnecessary_cast( ); } -fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> { +fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option { match expr.kind { ExprKind::Lit(lit) => Some(lit), ExprKind::Unary(UnOp::Neg, e) => { diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 784214c29af9..1507f1ed3053 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -83,7 +83,7 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } /// Check whether a passed literal has potential to cause fallback or not. - fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) { + fn check_lit(&self, lit: Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) { if !lit.span.in_external_macro(self.cx.sess().source_map()) && matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false))) && matches!( @@ -210,7 +210,7 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> { ExprKind::Lit(lit) => { let ty = self.cx.typeck_results().expr_ty(expr); - self.check_lit(lit, ty, expr.hir_id); + self.check_lit(*lit, ty, expr.hir_id); return; }, diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 621a2af1d322..8707612fbdd0 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -57,7 +57,7 @@ impl LateLintPass<'_> for LargeIncludeFile { if let ExprKind::Lit(lit) = &expr.kind && let len = match &lit.node { // include_bytes - LitKind::ByteStr(bstr, _) => bstr.len(), + LitKind::ByteStr(bstr, _) => bstr.as_byte_str().len(), // include_str LitKind::Str(sym, _) => sym.as_str().len(), _ => return, diff --git a/clippy_lints/src/manual_ignore_case_cmp.rs b/clippy_lints/src/manual_ignore_case_cmp.rs index 57c03fbb2ed2..f7d9ec1fae8e 100644 --- a/clippy_lints/src/manual_ignore_case_cmp.rs +++ b/clippy_lints/src/manual_ignore_case_cmp.rs @@ -41,12 +41,12 @@ declare_clippy_lint! { declare_lint_pass!(ManualIgnoreCaseCmp => [MANUAL_IGNORE_CASE_CMP]); -enum MatchType<'a, 'b> { +enum MatchType<'a> { ToAscii(bool, Ty<'a>), - Literal(&'b LitKind), + Literal(LitKind), } -fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> { +fn get_ascii_type<'a>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'_>) -> Option<(Span, MatchType<'a>)> { if let MethodCall(path, expr, _, _) = kind { let is_lower = match path.ident.name { sym::to_ascii_lowercase => true, @@ -63,7 +63,7 @@ fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) - return Some((expr.span, ToAscii(is_lower, ty_raw))); } } else if let Lit(expr) = kind { - return Some((expr.span, Literal(&expr.node))); + return Some((expr.span, Literal(expr.node))); } None } diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 9e911e61f196..6bf43a1c6d47 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -184,7 +184,7 @@ fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'t .. }) = expr.kind { - constant_length(cx, pattern).is_some_and(|length| *n == length) + constant_length(cx, pattern).is_some_and(|length| n == length) } else { len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg)) } diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index f14b69d91ce4..5816da5695eb 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -159,7 +159,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option { node: LitKind::Bool(b), .. }) = exp.kind { - Some(*b) + Some(b) } else { None } diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index dbb29ee776b1..ede68f309413 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -12,7 +12,7 @@ use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatExp use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; -use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use rustc_span::{ByteSymbol, ErrorGuaranteed, Span, Symbol}; use super::MATCH_SAME_ARMS; @@ -193,7 +193,7 @@ enum NormalizedPat<'a> { Or(&'a [Self]), Path(Option), LitStr(Symbol), - LitBytes(&'a [u8]), + LitBytes(ByteSymbol), LitInt(u128), LitBool(bool), Range(PatRange), @@ -332,7 +332,9 @@ impl<'a> NormalizedPat<'a> { // TODO: Handle negative integers. They're currently treated as a wild match. PatExprKind::Lit { lit, negated: false } => match lit.node { LitKind::Str(sym, _) => Self::LitStr(sym), - LitKind::ByteStr(ref bytes, _) | LitKind::CStr(ref bytes, _) => Self::LitBytes(bytes), + LitKind::ByteStr(byte_sym, _) | LitKind::CStr(byte_sym, _) => { + Self::LitBytes(byte_sym) + } LitKind::Byte(val) => Self::LitInt(val.into()), LitKind::Char(val) => Self::LitInt(val.into()), LitKind::Int(val, _) => Self::LitInt(val.get()), diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index fd368024177a..9b5f138295c3 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -76,7 +76,7 @@ fn get_open_options( .. } = span { - Argument::Set(*lit) + Argument::Set(lit) } else { // The function is called with a literal which is not a boolean literal. // This is theoretically possible, but not very likely. diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index c8e3462b24ef..cf0c85990b15 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -104,7 +104,7 @@ fn len_comparison<'hir>( ) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { macro_rules! int_lit_pat { ($id:ident) => { - ExprKind::Lit(&Spanned { + ExprKind::Lit(Spanned { node: LitKind::Int(Pu128($id), _), .. }) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 3a08531cf1c9..ac92ab5a245c 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -324,7 +324,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { } } - fn lit(&self, lit: &Binding<&Lit>) { + fn lit(&self, lit: &Binding) { let kind = |kind| chain!(self, "let LitKind::{kind} = {lit}.node"); macro_rules! kind { ($($t:tt)*) => (kind(format_args!($($t)*))); diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index aaa071fd5c93..09299c869dcf 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -4,8 +4,6 @@ //! executable MIR bodies, so we have to do this instead. #![allow(clippy::float_cmp)] -use std::sync::Arc; - use crate::source::{SpanRangeExt, walk_span_to_context}; use crate::{clip, is_direct_expn_of, sext, unsext}; @@ -38,7 +36,7 @@ pub enum Constant<'tcx> { /// A `String` (e.g., "abc"). Str(String), /// A binary string (e.g., `b"abc"`). - Binary(Arc<[u8]>), + Binary(Vec), /// A single `char` (e.g., `'a'`). Char(char), /// An integer's bit representation. @@ -306,7 +304,9 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constan match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), - LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(Arc::clone(s)), + LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => { + Constant::Binary(s.as_byte_str().to_vec()) + } LitKind::Char(c) => Constant::Char(c), LitKind::Int(n, _) => Constant::Int(n.get()), LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { @@ -568,7 +568,9 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } else { match &lit.node { LitKind::Str(is, _) => Some(is.is_empty()), - LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => Some(s.is_empty()), + LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => { + Some(s.as_byte_str().is_empty()) + } _ => None, } } From 4793fd1c46f3e091b2c81e20a2c1d59f049760a3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 1 Jul 2025 15:15:44 +0200 Subject: [PATCH 06/19] Update clippy source code to make use of `TyCtxt::def_descr` instead of `ItemKind::descr` --- clippy_lints/src/undocumented_unsafe_blocks.rs | 10 ++++++++-- .../undocumented_unsafe_blocks.default.stderr | 2 +- .../undocumented_unsafe_blocks.disabled.stderr | 2 +- tests/ui/unnecessary_safety_comment.stderr | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 92427473a8ee..6cc4b589a720 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -256,7 +256,10 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { cx, UNNECESSARY_SAFETY_COMMENT, span, - format!("{} has unnecessary safety comment", item.kind.descr()), + format!( + "{} has unnecessary safety comment", + cx.tcx.def_descr(item.owner_id.to_def_id()), + ), |diag| { diag.span_help(help_span, "consider removing the safety comment"); }, @@ -274,7 +277,10 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { cx, UNNECESSARY_SAFETY_COMMENT, span, - format!("{} has unnecessary safety comment", item.kind.descr()), + format!( + "{} has unnecessary safety comment", + cx.tcx.def_descr(item.owner_id.to_def_id()), + ), |diag| { diag.span_help(help_span, "consider removing the safety comment"); }, 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 8a2f201009a9..bfc14be5421f 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 @@ -240,7 +240,7 @@ LL | unsafe impl TrailingComment for () {} // SAFETY: | = help: consider adding a safety comment on the preceding line -error: constant item has unnecessary safety comment +error: constant has unnecessary safety comment --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5 | LL | const BIG_NUMBER: i32 = 1000000; 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 e9c5e5f9f114..20cdff5fcd12 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 @@ -240,7 +240,7 @@ LL | unsafe impl TrailingComment for () {} // SAFETY: | = help: consider adding a safety comment on the preceding line -error: constant item has unnecessary safety comment +error: constant has unnecessary safety comment --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5 | LL | const BIG_NUMBER: i32 = 1000000; diff --git a/tests/ui/unnecessary_safety_comment.stderr b/tests/ui/unnecessary_safety_comment.stderr index b56e8b354931..732e6767c178 100644 --- a/tests/ui/unnecessary_safety_comment.stderr +++ b/tests/ui/unnecessary_safety_comment.stderr @@ -1,4 +1,4 @@ -error: constant item has unnecessary safety comment +error: constant has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:6:5 | LL | const CONST: u32 = 0; @@ -12,7 +12,7 @@ LL | // SAFETY: = note: `-D clippy::unnecessary-safety-comment` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` -error: static item has unnecessary safety comment +error: static has unnecessary safety comment --> tests/ui/unnecessary_safety_comment.rs:10:5 | LL | static STATIC: u32 = 0; From eeaa63f39aadd452ae4dc1710820c8e9b4883ab2 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 26 Jun 2025 02:01:38 +0000 Subject: [PATCH 07/19] Remove support for dyn* --- clippy_utils/src/qualify_min_const_fn.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 8f1ebb8ada6e..942c71ac33b8 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -175,10 +175,6 @@ fn check_rvalue<'tcx>( Rvalue::Cast(CastKind::PointerExposeProvenance, _, _) => { Err((span, "casting pointers to ints is unstable in const fn".into())) }, - Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::DynStar, _), _, _) => { - // FIXME(dyn-star) - unimplemented!() - }, Rvalue::Cast(CastKind::Transmute, _, _) => Err(( span, "transmute can attempt to turn pointers into integers, so is unstable in const fn".into(), From 8bd580b80e3c31f9c2d4115768a3ac0b71372587 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 2 Jul 2025 00:57:59 +0200 Subject: [PATCH 08/19] Add `track_caller` attributes to trace origin of Clippy lints This allows the use of `-Z track-diagnostics` to see the origin of Clippy lints emission, as is already the case for lints coming from rustc. --- clippy_utils/src/diagnostics.rs | 7 ++++++ tests/ui/track-diagnostics-clippy.rs | 22 ++++++++++++++++++ tests/ui/track-diagnostics-clippy.stderr | 29 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/ui/track-diagnostics-clippy.rs create mode 100644 tests/ui/track-diagnostics-clippy.stderr diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index dc240dd067b1..8453165818b3 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -98,6 +98,7 @@ fn validate_diag(diag: &Diag<'_, impl EmissionGuarantee>) { /// 17 | std::mem::forget(seven); /// | ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` +#[track_caller] pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: impl Into) { #[expect(clippy::disallowed_methods)] cx.span_lint(lint, sp, |diag| { @@ -143,6 +144,7 @@ pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into( cx: &T, lint: &'static Lint, @@ -203,6 +205,7 @@ pub fn span_lint_and_help( /// 10 | forget(&SomeStruct); /// | ^^^^^^^^^^^ /// ``` +#[track_caller] pub fn span_lint_and_note( cx: &T, lint: &'static Lint, @@ -244,6 +247,7 @@ pub fn span_lint_and_note( /// If you're unsure which function you should use, you can test if the `#[expect]` attribute works /// where you would expect it to. /// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead. +#[track_caller] pub fn span_lint_and_then(cx: &C, lint: &'static Lint, sp: S, msg: M, f: F) where C: LintContext, @@ -286,6 +290,7 @@ where /// Instead, use this function and also pass the `HirId` of ``, which will let /// the compiler check lint level attributes at the place of the expression and /// the `#[allow]` will work. +#[track_caller] pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: impl Into) { #[expect(clippy::disallowed_methods)] cx.tcx.node_span_lint(lint, hir_id, sp, |diag| { @@ -321,6 +326,7 @@ pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, s /// Instead, use this function and also pass the `HirId` of ``, which will let /// the compiler check lint level attributes at the place of the expression and /// the `#[allow]` will work. +#[track_caller] pub fn span_lint_hir_and_then( cx: &LateContext<'_>, lint: &'static Lint, @@ -374,6 +380,7 @@ pub fn span_lint_hir_and_then( /// = note: `-D fold-any` implied by `-D warnings` /// ``` #[cfg_attr(not(debug_assertions), expect(clippy::collapsible_span_lint_calls))] +#[track_caller] pub fn span_lint_and_sugg( cx: &T, lint: &'static Lint, diff --git a/tests/ui/track-diagnostics-clippy.rs b/tests/ui/track-diagnostics-clippy.rs new file mode 100644 index 000000000000..2e67fb65efcd --- /dev/null +++ b/tests/ui/track-diagnostics-clippy.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Z track-diagnostics +//@no-rustfix + +// Normalize the emitted location so this doesn't need +// updating everytime someone adds or removes a line. +//@normalize-stderr-test: ".rs:\d+:\d+" -> ".rs:LL:CC" + +#![warn(clippy::let_and_return, clippy::unnecessary_cast)] + +fn main() { + // Check the provenance of a lint sent through `LintContext::span_lint()` + let a = 3u32; + let b = a as u32; + //~^ unnecessary_cast + + // Check the provenance of a lint sent through `TyCtxt::node_span_lint()` + let c = { + let d = 42; + d + //~^ let_and_return + }; +} diff --git a/tests/ui/track-diagnostics-clippy.stderr b/tests/ui/track-diagnostics-clippy.stderr new file mode 100644 index 000000000000..f3aca6854174 --- /dev/null +++ b/tests/ui/track-diagnostics-clippy.stderr @@ -0,0 +1,29 @@ +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> tests/ui/track-diagnostics-clippy.rs:LL:CC + | +LL | let b = a as u32; + | ^^^^^^^^ help: try: `a` +-Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs:LL:CC + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_cast)]` + +error: returning the result of a `let` binding from a block + --> tests/ui/track-diagnostics-clippy.rs:LL:CC + | +LL | let d = 42; + | ----------- unnecessary `let` binding +LL | d + | ^ +-Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/returns.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 + | +LL ~ +LL ~ 42 + | + +error: aborting due to 2 previous errors + From 7060377bfe52e08fe1f52b45dd67f665d2944c50 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 2 Jun 2025 23:40:01 -0600 Subject: [PATCH 09/19] refactor: Make -Ztrack-diagnostics emit like a note --- tests/ui/track-diagnostics-clippy.stderr | 4 ++-- tests/ui/track-diagnostics.rs | 1 + tests/ui/track-diagnostics.stderr | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/ui/track-diagnostics-clippy.stderr b/tests/ui/track-diagnostics-clippy.stderr index f3aca6854174..9d6538112bf0 100644 --- a/tests/ui/track-diagnostics-clippy.stderr +++ b/tests/ui/track-diagnostics-clippy.stderr @@ -3,8 +3,8 @@ error: casting to the same type is unnecessary (`u32` -> `u32`) | LL | let b = a as u32; | ^^^^^^^^ help: try: `a` --Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs:LL:CC | + = note: -Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs:LL:CC = note: `-D clippy::unnecessary-cast` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_cast)]` @@ -15,8 +15,8 @@ LL | let d = 42; | ----------- unnecessary `let` binding LL | d | ^ --Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/returns.rs:LL:CC | + = note: -Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/returns.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/track-diagnostics.rs b/tests/ui/track-diagnostics.rs index 723ea23e9a63..0fbde867390d 100644 --- a/tests/ui/track-diagnostics.rs +++ b/tests/ui/track-diagnostics.rs @@ -8,5 +8,6 @@ struct A; struct B; const S: A = B; //~^ ERROR: mismatched types +//~| NOTE: created at fn main() {} diff --git a/tests/ui/track-diagnostics.stderr b/tests/ui/track-diagnostics.stderr index 83451fb658d0..45262ba618f2 100644 --- a/tests/ui/track-diagnostics.stderr +++ b/tests/ui/track-diagnostics.stderr @@ -3,7 +3,8 @@ error[E0308]: mismatched types | LL | const S: A = B; | ^ expected `A`, found `B` --Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs:LL:CC + | + = note: -Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs:LL:CC error: aborting due to 1 previous error From a4f9d7a3f3de0096780e86b7a82a8474fc14fc59 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 2 Jul 2024 09:38:49 +0000 Subject: [PATCH 10/19] Replace kw_span by full span. --- clippy_utils/src/ast_utils/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index e65914b9b5ee..e6396987cc6d 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -886,13 +886,13 @@ pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool { ( Const { ty: lt, - kw_span: _, default: ld, + span: _, }, Const { ty: rt, - kw_span: _, default: rd, + span: _, }, ) => eq_ty(lt, rt) && both(ld.as_ref(), rd.as_ref(), eq_anon_const), _ => false, From b4a0cd99950871b9e694d81a24f6e41827b3a4e5 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Thu, 3 Jul 2025 13:42:36 +0000 Subject: [PATCH 11/19] Remove names_imported_by_glob_use query. --- clippy_lints/src/wildcard_imports.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index d9dda6eadb2a..22fd15d153ab 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -130,7 +130,7 @@ impl LateLintPass<'_> for WildcardImports { } if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind && (self.warn_on_all || !self.check_exceptions(cx, item, use_path.segments)) - && let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id) + && let Some(used_imports) = cx.tcx.resolutions(()).glob_map.get(&item.owner_id.def_id) && !used_imports.is_empty() // Already handled by `unused_imports` && !used_imports.contains(&kw::Underscore) { From 9c9ee348072ef5c8f29ccd8c0496d2269ba244df Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 3 Jul 2025 09:10:49 +0200 Subject: [PATCH 12/19] Port `#[non_exhaustive]` to the new attribute parsing infrastructure --- clippy_lints/src/exhaustive_items.rs | 6 ++++-- clippy_lints/src/manual_non_exhaustive.rs | 13 +++++++------ clippy_utils/src/attrs.rs | 9 +++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 86d9038ec45d..7dda3e0fdb91 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -4,7 +4,9 @@ use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::sym; +use rustc_attr_data_structures::AttributeKind; +use rustc_attr_data_structures::find_attr; + declare_clippy_lint! { /// ### What it does @@ -85,7 +87,7 @@ impl LateLintPass<'_> for ExhaustiveItems { }; if cx.effective_visibilities.is_exported(item.owner_id.def_id) && let attrs = cx.tcx.hir_attrs(item.hir_id()) - && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)) + && !find_attr!(attrs, AttributeKind::NonExhaustive(..)) && fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public()) { span_lint_and_then(cx, lint, item.span, msg, |diag| { diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 3562b1ff5cce..51696b388800 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -4,7 +4,6 @@ use clippy_utils::is_doc_hidden; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_indent; use itertools::Itertools; -use rustc_ast::attr; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; @@ -12,7 +11,9 @@ use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; +use rustc_span::Span; +use rustc_attr_data_structures::find_attr; +use rustc_attr_data_structures::AttributeKind; declare_clippy_lint! { /// ### What it does @@ -93,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive { .then_some((v.def_id, v.span)) }); if let Ok((id, span)) = iter.exactly_one() - && !attr::contains_name(cx.tcx.hir_attrs(item.hir_id()), sym::non_exhaustive) + && !find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::NonExhaustive(..)) { self.potential_enums.push((item.owner_id.def_id, id, item.span, span)); } @@ -113,10 +114,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive { item.span, "this seems like a manual implementation of the non-exhaustive pattern", |diag| { - if let Some(non_exhaustive) = - attr::find_by_name(cx.tcx.hir_attrs(item.hir_id()), sym::non_exhaustive) + if let Some(non_exhaustive_span) = + find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::NonExhaustive(span) => *span) { - diag.span_note(non_exhaustive.span(), "the struct is already non-exhaustive"); + diag.span_note(non_exhaustive_span, "the struct is already non-exhaustive"); } else { let indent = snippet_indent(cx, item.span).unwrap_or_default(); diag.span_suggestion_verbose( diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 8a0ff5323c98..4c7a589e185b 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -7,9 +7,10 @@ use rustc_middle::ty::{AdtDef, TyCtxt}; use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; - +use rustc_attr_data_structures::find_attr; use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; +use rustc_attr_data_structures::AttributeKind; /// Deprecation status of attributes known by Clippy. pub enum DeprecationStatus { @@ -165,13 +166,13 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() - || tcx.has_attr(adt.did(), sym::non_exhaustive) + || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) || adt.variants().iter().any(|variant_def| { - variant_def.is_field_list_non_exhaustive() || tcx.has_attr(variant_def.def_id, sym::non_exhaustive) + variant_def.is_field_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..)) }) || adt .all_fields() - .any(|field_def| tcx.has_attr(field_def.did, sym::non_exhaustive)) + .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..))) } /// Checks if the given span contains a `#[cfg(..)]` attribute From 8aad0e6ad9c0a31f458c62529ccbe1708c12b560 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 4 Jul 2025 12:42:33 +0200 Subject: [PATCH 13/19] Rewrite empty attribute lint Signed-off-by: Jonathan Brouwer --- clippy_lints/src/arbitrary_source_item_ordering.rs | 2 +- clippy_lints/src/attrs/repr_attributes.rs | 2 +- clippy_lints/src/default_union_representation.rs | 2 +- clippy_utils/src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index b9ae9afe8510..8b6bfaebbe58 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -266,7 +266,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { .tcx .hir_attrs(item.hir_id()) .iter() - .any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr(..)))) + .any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr{ .. }))) { // Do not lint items with a `#[repr]` attribute as their layout may be imposed by an external API. return; diff --git a/clippy_lints/src/attrs/repr_attributes.rs b/clippy_lints/src/attrs/repr_attributes.rs index 05d8a8c26d1c..3e8808cec617 100644 --- a/clippy_lints/src/attrs/repr_attributes.rs +++ b/clippy_lints/src/attrs/repr_attributes.rs @@ -9,7 +9,7 @@ use clippy_utils::msrvs::{self, Msrv}; use super::REPR_PACKED_WITHOUT_ABI; pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute], msrv: Msrv) { - if let Some(reprs) = find_attr!(attrs, AttributeKind::Repr(r) => r) { + if let Some(reprs) = find_attr!(attrs, AttributeKind::Repr { reprs, .. } => reprs) { let packed_span = reprs .iter() .find(|(r, _)| matches!(r, ReprAttr::ReprPacked(..))) diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 615421f3a40d..9bf2144e445d 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -99,5 +99,5 @@ fn is_zst<'tcx>(cx: &LateContext<'tcx>, field: &FieldDef, args: ty::GenericArgsR fn has_c_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - find_attr!(attrs, AttributeKind::Repr(r) if r.iter().any(|(x, _)| *x == ReprAttr::ReprC)) + find_attr!(attrs, AttributeKind::Repr { reprs, .. } if reprs.iter().any(|(x, _)| *x == ReprAttr::ReprC)) } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a8b33418c8c0..c01f0ffaac9a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1761,7 +1761,7 @@ pub fn has_attr(attrs: &[hir::Attribute], symbol: Symbol) -> bool { } pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { - find_attr!(cx.tcx.hir_attrs(hir_id), AttributeKind::Repr(..)) + find_attr!(cx.tcx.hir_attrs(hir_id), AttributeKind::Repr { .. }) } pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool { From 491c8feaabff3c941a30d02c0c2a2622acf257e3 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Sun, 6 Jul 2025 13:30:42 -0700 Subject: [PATCH 14/19] clippy: migrate BareFn -> FnPtr --- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/lifetimes.rs | 4 ++-- clippy_lints/src/types/type_complexity.rs | 2 +- clippy_utils/src/ast_utils/mod.rs | 2 +- clippy_utils/src/check_proc_macro.rs | 10 +++++----- clippy_utils/src/hir_utils.rs | 14 +++++++------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 7463d7b5c3bd..5099df3fa023 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -824,7 +824,7 @@ impl TyCoercionStability { TyKind::Slice(_) | TyKind::Array(..) | TyKind::Ptr(_) - | TyKind::BareFn(_) + | TyKind::FnPtr(_) | TyKind::Pat(..) | TyKind::Never | TyKind::Tup(_) diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 8fe0c9d60f96..caf17c10484f 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -13,7 +13,7 @@ use rustc_hir::intravisit::{ walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_unambig_ty, walk_where_predicate, }; use rustc_hir::{ - AmbigArg, BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericArgs, GenericBound, GenericParam, GenericParamKind, + AmbigArg, BodyId, FnDecl, FnPtrTy, FnSig, GenericArg, GenericArgs, GenericBound, GenericParam, GenericParamKind, Generics, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeKind, LifetimeParamKind, Node, PolyTraitRef, PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereBoundPredicate, WherePredicate, WherePredicateKind, lang_items, @@ -480,7 +480,7 @@ impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> { fn visit_ty(&mut self, ty: &'tcx Ty<'_, AmbigArg>) { match ty.kind { - TyKind::BareFn(&BareFnTy { decl, .. }) => { + TyKind::FnPtr(&FnPtrTy { decl, .. }) => { let mut sub_visitor = RefVisitor::new(self.cx); sub_visitor.visit_fn_decl(decl); self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); diff --git a/clippy_lints/src/types/type_complexity.rs b/clippy_lints/src/types/type_complexity.rs index 0704653385f1..52c6fda80973 100644 --- a/clippy_lints/src/types/type_complexity.rs +++ b/clippy_lints/src/types/type_complexity.rs @@ -50,7 +50,7 @@ impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor { TyKind::Path(..) | TyKind::Slice(..) | TyKind::Tup(..) | TyKind::Array(..) => (10 * self.nest, 1), // function types bring a lot of overhead - TyKind::BareFn(bare) if bare.abi == ExternAbi::Rust => (50 * self.nest, 1), + TyKind::FnPtr(fn_ptr) if fn_ptr.abi == ExternAbi::Rust => (50 * self.nest, 1), TyKind::TraitObject(param_bounds, _) => { let has_lifetime_parameters = param_bounds.iter().any(|bound| { diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index e6396987cc6d..42254ec8e92d 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -838,7 +838,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool { (PinnedRef(ll, l), PinnedRef(rl, r)) => { both(ll.as_ref(), rl.as_ref(), |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty) }, - (BareFn(l), BareFn(r)) => { + (FnPtr(l), FnPtr(r)) => { l.safety == r.safety && eq_ext(&l.ext, &r.ext) && over(&l.generic_params, &r.generic_params, eq_generic_param) diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 407e92d88fb0..ce61fffe0def 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -372,17 +372,17 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")), TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1), TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1), - TyKind::BareFn(bare_fn) => ( - if bare_fn.safety.is_unsafe() { + TyKind::FnPtr(fn_ptr) => ( + if fn_ptr.safety.is_unsafe() { Pat::Str("unsafe") - } else if bare_fn.abi != ExternAbi::Rust { + } else if fn_ptr.abi != ExternAbi::Rust { Pat::Str("extern") } else { Pat::MultiStr(&["fn", "extern"]) }, - match bare_fn.decl.output { + match fn_ptr.decl.output { FnRetTy::DefaultReturn(_) => { - if let [.., ty] = bare_fn.decl.inputs { + if let [.., ty] = fn_ptr.decl.inputs { ty_search_pat(ty).1 } else { Pat::Str("(") diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index c37231d09312..0ca494f16e31 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1283,20 +1283,20 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); }, - TyKind::BareFn(bfn) => { - bfn.safety.hash(&mut self.s); - bfn.abi.hash(&mut self.s); - for arg in bfn.decl.inputs { + TyKind::FnPtr(fn_ptr) => { + fn_ptr.safety.hash(&mut self.s); + fn_ptr.abi.hash(&mut self.s); + for arg in fn_ptr.decl.inputs { self.hash_ty(arg); } - std::mem::discriminant(&bfn.decl.output).hash(&mut self.s); - match bfn.decl.output { + std::mem::discriminant(&fn_ptr.decl.output).hash(&mut self.s); + match fn_ptr.decl.output { FnRetTy::DefaultReturn(_) => {}, FnRetTy::Return(ty) => { self.hash_ty(ty); }, } - bfn.decl.c_variadic.hash(&mut self.s); + fn_ptr.decl.c_variadic.hash(&mut self.s); }, TyKind::Tup(ty_list) => { for ty in *ty_list { From f4e2b5c7f542f441d8bbd0cf7e1fdd9d71952431 Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Sun, 29 Jun 2025 12:11:51 +0200 Subject: [PATCH 15/19] compiler: Parse `p-` specs in datalayout string, allow definition of custom default data address space --- clippy_lints/src/casts/fn_to_numeric_cast.rs | 2 +- clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs | 2 +- clippy_lints/src/casts/utils.rs | 2 +- clippy_lints/src/enum_clike.rs | 2 +- clippy_utils/src/consts.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/casts/fn_to_numeric_cast.rs b/clippy_lints/src/casts/fn_to_numeric_cast.rs index 105477093b56..55e27a05f3c0 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -17,7 +17,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, ty::FnDef(..) | ty::FnPtr(..) => { let mut applicability = Applicability::MaybeIncorrect; - if to_nbits >= cx.tcx.data_layout.pointer_size.bits() && !cast_to.is_usize() { + if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() { let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs index 700b7d0d4266..4da79205e208 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -17,7 +17,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - if to_nbits < cx.tcx.data_layout.pointer_size.bits() { + if to_nbits < cx.tcx.data_layout.pointer_size().bits() { span_lint_and_sugg( cx, FN_TO_NUMERIC_CAST_WITH_TRUNCATION, diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 318a1646477e..d846d78b9ee7 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -5,7 +5,7 @@ use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; /// integral type. pub(super) fn int_ty_to_nbits(tcx: TyCtxt<'_>, ty: Ty<'_>) -> Option { match ty.kind() { - ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(tcx.data_layout.pointer_size.bits()), + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(tcx.data_layout.pointer_size().bits()), ty::Int(i) => i.bit_width(), ty::Uint(i) => i.bit_width(), _ => None, diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 098571a53512..c828fc57f760 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -35,7 +35,7 @@ declare_lint_pass!(UnportableVariant => [ENUM_CLIKE_UNPORTABLE_VARIANT]); impl<'tcx> LateLintPass<'tcx> for UnportableVariant { #[expect(clippy::cast_possible_wrap)] fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if cx.tcx.data_layout.pointer_size.bits() != 64 { + if cx.tcx.data_layout.pointer_size().bits() != 64 { return; } if let ItemKind::Enum(_, _, def) = &item.kind { diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 09299c869dcf..ba0376e4d406 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -918,7 +918,7 @@ fn mir_is_empty<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option Date: Sat, 21 Dec 2024 20:25:43 +0000 Subject: [PATCH 16/19] Make `Default` const and add some `const Default` impls Full list of `impl const Default` types: - () - bool - char - Cell - std::ascii::Char - usize - u8 - u16 - u32 - u64 - u128 - i8 - i16 - i32 - i64 - i128 - f16 - f32 - f64 - f128 - std::marker::PhantomData - Option - std::iter::Empty - std::ptr::Alignment - &[T] - &mut [T] - &str - &mut str - String - Vec --- tests/ui/or_fun_call.fixed | 4 ++-- tests/ui/or_fun_call.rs | 2 +- tests/ui/or_fun_call.stderr | 8 +------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 34f3e0468419..38822bdbb306 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -406,8 +406,8 @@ fn fn_call_in_nested_expr() { val: String::from("123"), }); - let _ = opt_foo.unwrap_or_else(|| Foo { val: String::default() }); - //~^ or_fun_call + // ok, `String::default()` is now `const` + let _ = opt_foo.unwrap_or(Foo { val: String::default() }); } mod result_map_or { diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index dc57bd6060ac..7e8647f221c3 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -406,8 +406,8 @@ fn fn_call_in_nested_expr() { val: String::from("123"), }); + // ok, `String::default()` is now `const` let _ = opt_foo.unwrap_or(Foo { val: String::default() }); - //~^ or_fun_call } mod result_map_or { diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 0f159fe8bff4..0d88929e131c 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -240,12 +240,6 @@ error: use of `unwrap_or` to construct default value 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:409: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:424:19 | @@ -264,5 +258,5 @@ error: function call inside of `get_or_insert` LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` -error: aborting due to 41 previous errors +error: aborting due to 40 previous errors From 5228e2846995ef24c743710877b6668fa0260ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 7 Jul 2025 23:07:32 +0000 Subject: [PATCH 17/19] Account for const stability in clippy when checking constness --- clippy_utils/src/visitors.rs | 6 ++++-- tests/ui/or_fun_call.fixed | 4 ++-- tests/ui/or_fun_call.rs | 2 +- tests/ui/or_fun_call.stderr | 8 +++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index fc6e30a98047..615a0910dfde 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,5 +1,7 @@ +use crate::msrvs::Msrv; use crate::ty::needs_ordered_drop; use crate::{get_enclosing_block, path_to_local_id}; +use crate::qualify_min_const_fn::is_stable_const_fn; use core::ops::ControlFlow; use rustc_ast::visit::{VisitorResult, try_visit}; use rustc_hir::def::{CtorKind, DefKind, Res}; @@ -343,13 +345,13 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> .cx .qpath_res(p, hir_id) .opt_def_id() - .is_some_and(|id| self.cx.tcx.is_const_fn(id)) => {}, + .is_some_and(|id| is_stable_const_fn(self.cx, id, Msrv::default())) => {}, ExprKind::MethodCall(..) if self .cx .typeck_results() .type_dependent_def_id(e.hir_id) - .is_some_and(|id| self.cx.tcx.is_const_fn(id)) => {}, + .is_some_and(|id| is_stable_const_fn(self.cx, id, Msrv::default())) => {}, ExprKind::Binary(_, lhs, rhs) if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty() && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {}, diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 38822bdbb306..34f3e0468419 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -406,8 +406,8 @@ fn fn_call_in_nested_expr() { val: String::from("123"), }); - // ok, `String::default()` is now `const` - let _ = opt_foo.unwrap_or(Foo { val: String::default() }); + let _ = opt_foo.unwrap_or_else(|| Foo { val: String::default() }); + //~^ or_fun_call } mod result_map_or { diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 7e8647f221c3..dc57bd6060ac 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -406,8 +406,8 @@ fn fn_call_in_nested_expr() { val: String::from("123"), }); - // ok, `String::default()` is now `const` let _ = opt_foo.unwrap_or(Foo { val: String::default() }); + //~^ or_fun_call } mod result_map_or { diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 0d88929e131c..0f159fe8bff4 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -240,6 +240,12 @@ error: use of `unwrap_or` to construct default value 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:409: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:424:19 | @@ -258,5 +264,5 @@ error: function call inside of `get_or_insert` LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` -error: aborting due to 40 previous errors +error: aborting due to 41 previous errors From 0a64bfd6858606fae0b112673d4a28b2d6f149f8 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 29 Jun 2025 11:40:59 +0800 Subject: [PATCH 18/19] Remove uncessary parens in closure body with unused lint --- clippy_lints/src/unused_async.rs | 2 +- clippy_utils/src/ty/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index 8ceaa3dc58ec..e67afc7f5a8b 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -169,7 +169,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { let iter = self .unused_async_fns .iter() - .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id))); + .filter(|UnusedAsyncFn { def_id, .. }| !self.async_fns_as_value.contains(def_id)); for fun in iter { span_lint_hir_and_then( diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index bffbcf073ab0..fe208c032f4c 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -889,7 +889,7 @@ impl AdtVariantInfo { .enumerate() .map(|(i, f)| (i, approx_ty_size(cx, f.ty(cx.tcx, subst)))) .collect::>(); - fields_size.sort_by(|(_, a_size), (_, b_size)| (a_size.cmp(b_size))); + fields_size.sort_by(|(_, a_size), (_, b_size)| a_size.cmp(b_size)); Self { ind: i, @@ -898,7 +898,7 @@ impl AdtVariantInfo { } }) .collect::>(); - variants_size.sort_by(|a, b| (b.size.cmp(&a.size))); + variants_size.sort_by(|a, b| b.size.cmp(&a.size)); variants_size } } From b9af6670d882c4d5f71f760c4efaceec2c849030 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 10 Jul 2025 20:01:15 +0200 Subject: [PATCH 19/19] Bump nightly version -> 2025-07-10 --- clippy_utils/README.md | 2 +- rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 649748d1534b..645b644d9f4e 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-06-26 +nightly-2025-07-10 ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 124756a36009..f46e079db3f1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-06-26" +channel = "nightly-2025-07-10" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal"