From e0c05a2bb3dfb54bca5d239658fd2a69c8443b1f Mon Sep 17 00:00:00 2001 From: blyxyas Date: Fri, 21 Mar 2025 15:41:23 +0100 Subject: [PATCH 01/75] Include temporary feature freeze in book --- book/src/README.md | 4 +++ book/src/SUMMARY.md | 1 + book/src/development/adding_lints.md | 3 ++ book/src/development/feature_freeze.md | 50 ++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 book/src/development/feature_freeze.md diff --git a/book/src/README.md b/book/src/README.md index 5d2c3972b060a..db73b49ecc24e 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 19328fdd3cd47..e36694fe646f0 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 0b9010f01071f..57911f655e7b0 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 0000000000000..afe83c1bc6a89 --- /dev/null +++ b/book/src/development/feature_freeze.md @@ -0,0 +1,50 @@ +# IMPORTANT: FEATURE FREEZE + +This is a temporary notice. + +From March 28, 2025 to June 20, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed with the +exception of already open ones. Every feature-adding PR open 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 contiue 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 +usecase 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! + +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 +disastreous 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 +the 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 From f71d14e10ca9d87230c4a845e69976451ae0227f Mon Sep 17 00:00:00 2001 From: blyxyas Date: Fri, 21 Mar 2025 16:02:22 +0100 Subject: [PATCH 02/75] Include temporary feature freeze in templates --- .github/ISSUE_TEMPLATE/new_lint.yml | 4 +++- .github/PULL_REQUEST_TEMPLATE.md | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index b49493edce1ba..464740640e0cf 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 9e49f60892d26..2d9babfa0de29 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 taht 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)* From ca3177c45884535618543c915247180c1c00b6ff Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 24 Mar 2025 00:51:56 +0100 Subject: [PATCH 03/75] Move back date 6 weeks --- book/src/development/feature_freeze.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md index afe83c1bc6a89..f173633f6c02d 100644 --- a/book/src/development/feature_freeze.md +++ b/book/src/development/feature_freeze.md @@ -2,7 +2,7 @@ This is a temporary notice. -From March 28, 2025 to June 20, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed with the +From May 9th, 2025 until the 1st of August, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed with the exception of already open ones. Every feature-adding PR open in between those dates will be moved into a milestone to be reviewed separately at another time. From dd8d9232c206fc3d7d35c1cfb6df4d95b243dfec Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 7 Apr 2025 01:56:45 +0200 Subject: [PATCH 04/75] Fix remark CI issues --- book/src/development/feature_freeze.md | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md index f173633f6c02d..d7e5a34301f2a 100644 --- a/book/src/development/feature_freeze.md +++ b/book/src/development/feature_freeze.md @@ -2,17 +2,17 @@ This is a temporary notice. -From May 9th, 2025 until the 1st of August, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed with the -exception of already open ones. Every feature-adding PR open in between those dates will be moved into a milestone -to be reviewed separately at another time. +From May 9th, 2025 until the 1st of August, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed +with the exception of already open ones. Every feature-adding PR open 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 contiue 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! +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 usecase of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a @@ -28,19 +28,19 @@ If you'd like to help, making a bugfix, making sure that it works, and opening a 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! +- 🟥 [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 -disastreous consequences. Easier to fix than ICEs +- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way. +Unacceptable, as this may have disastreous 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 +- 🟨 [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 -the false positive and making an exception for those cases. +- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just" +identifying the root of a the 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. From 27d00a53fc17de29511e501f07d5e491417fce05 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 7 Apr 2025 01:58:56 +0200 Subject: [PATCH 05/75] Fix typos in review --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- book/src/development/feature_freeze.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2d9babfa0de29..83bfd8e9c6865 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,7 +32,7 @@ order to get feedback. Delete this line and everything above before opening your PR. -Note taht we are currently not taking in new PRs that add new lints. We are in a +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. diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md index d7e5a34301f2a..ccdef8860d784 100644 --- a/book/src/development/feature_freeze.md +++ b/book/src/development/feature_freeze.md @@ -3,7 +3,7 @@ This is a temporary notice. From May 9th, 2025 until the 1st of August, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed -with the exception of already open ones. Every feature-adding PR open in between those dates will be moved into a +with the exception of 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 @@ -42,7 +42,8 @@ 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 the 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. +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 From 0bac1ca2a7d7384a28e41b6c70d810a0cd5aacff Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 20 May 2025 02:02:23 +0500 Subject: [PATCH 06/75] Add allow-invalid configuration option for disallowed_* to the documentation --- book/src/lint_configuration.md | 21 +++++++++++++++++++++ clippy_config/src/conf.rs | 21 +++++++++++++++++++++ clippy_lints/src/disallowed_macros.rs | 3 +++ clippy_lints/src/disallowed_methods.rs | 3 +++ clippy_lints/src/disallowed_types.rs | 3 +++ 5 files changed, 51 insertions(+) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 2314d1beac7e0..32ae40e17666e 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -478,6 +478,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:** `[]` --- @@ -488,6 +495,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:** `[]` --- @@ -510,6 +524,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:** `[]` --- diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 511cb84527d80..72acaf7def9fa 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -572,10 +572,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(), @@ -585,6 +599,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(), diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index fa33fef230622..a5eb5ffc97e09 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -38,6 +38,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 1382dafa931e4..d4d6e406444e9 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -33,6 +33,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 2bae82648ac76..3590703b161aa 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -34,6 +34,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 } /// ] /// ``` /// From d369dc2998e3f6b92dce967838905c7d0e34c865 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 28 May 2025 14:03:11 +0200 Subject: [PATCH 07/75] Add GHA script for feature freeze --- .github/workflows/feature-freeze.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/feature-freeze.yml diff --git a/.github/workflows/feature-freeze.yml b/.github/workflows/feature-freeze.yml new file mode 100644 index 0000000000000..1b3a2f4ca2815 --- /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\"}" \ No newline at end of file From 6f0fae374806b49749263a3102c7ff5ec35d2008 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 28 May 2025 14:05:19 +0200 Subject: [PATCH 08/75] Minor fixes and date correction to book page --- book/src/development/feature_freeze.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md index ccdef8860d784..f522be2fbd1b7 100644 --- a/book/src/development/feature_freeze.md +++ b/book/src/development/feature_freeze.md @@ -2,12 +2,12 @@ This is a temporary notice. -From May 9th, 2025 until the 1st of August, 2025 we will perform a feature freeze. Only bugfix PRs will be reviewed -with the exception of already open ones. Every feature-adding PR opened in between those dates will be moved into a +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 contiue being the state of the art linter that Clippy has become known for being. +in order to continue being the state-of-the-art linter that Clippy has become known for being. ## For contributors @@ -15,7 +15,7 @@ If you are a contributor or are planning to become one, **please do not open a l 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 -usecase of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a +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, @@ -33,14 +33,14 @@ debugging, sometimes interacting directly with the upstream compiler. Difficult 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 disastreous consequences. Easier to fix than ICEs +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 the false positive and making an exception for those cases. +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. From 5290b1ee0d746d1231fe498d6e70d05365756c87 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 28 May 2025 15:45:22 +0800 Subject: [PATCH 09/75] fix: `collapsible_else_if` FP on conditionally compiled stmt --- clippy_lints/src/collapsible_if.rs | 7 +++++-- clippy_utils/src/lib.rs | 12 +++++++++++- tests/ui/collapsible_else_if.fixed | 18 ++++++++++++++++++ tests/ui/collapsible_else_if.rs | 18 ++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 7f6ecea99fb02..aef8e03320735 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -2,12 +2,13 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, 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::span_contains_non_comment; use rustc_ast::BinOpKind; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -96,6 +97,8 @@ impl CollapsibleIf { && cx.tcx.hir_attrs(else_.hir_id).is_empty() && !else_.span.from_expansion() && let ExprKind::If(..) = else_.kind + && let up_to_if = else_block.span.until(else_.span) + && !span_contains_non_comment(cx, up_to_if.with_lo(BytePos(up_to_if.lo().0 + 1))) { // Prevent "elseif" // Check that the "else" is followed by whitespace @@ -141,7 +144,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() }; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 42136fdbc51a8..ad20209331b6a 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}; @@ -2788,6 +2788,16 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool { }); } +/// Checks whether a given span has any non-comment token. This checks for all types of token other +/// than line comment "//", block comment "/**", doc "///" "//!" and whitespace +/// 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_comment(cx: &impl source::HasSession, span: Span) -> bool { + matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)| { + !matches!(token, TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace) + })) +} + /// Returns all the comments a given span contains /// /// Comments are returned wrapped with their relevant delimiters diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index 9f530ad670a0f..fed75244c6f70 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 2c646cd1d4da1..e50e781fb6989 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!(); + } + } +} From 8964f6ed272d9dac4533fe1be97e5ff79de14532 Mon Sep 17 00:00:00 2001 From: Max Claus Nunes Date: Sat, 16 Nov 2024 13:05:47 -0300 Subject: [PATCH 10/75] Add lint for broken doc links Fix false positives on broken link detection Refactor variable names Fix doc comment about broken link lint Refactor, remove not used variable Improve broken link to catch more cases and span point to whole link Include reason why a link is considered broken Drop some checker because rustdoc already warn about them Refactor to use a single enum instead of multiple bool variables Fix lint warnings Rename function to collect broken links Warn directly instead of collecting all entries first Iterate directly rather than collecting Temporary change to confirm with code reviewer the next steps Handle broken links as part of the fake_broken_link_callback handler Simplify broken link detection without state machine usage Fix typos Add url check to reduce false positives Drop reason enum as there is only one reason Fix duplicated diagnostics Fix linter --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/doc/broken_link.rs | 83 +++++++++++++++++++++++++++++ clippy_lints/src/doc/mod.rs | 50 ++++++++++++++--- tests/ui/doc_broken_link.rs | 72 +++++++++++++++++++++++++ tests/ui/doc_broken_link.stderr | 29 ++++++++++ 6 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 clippy_lints/src/doc/broken_link.rs create mode 100644 tests/ui/doc_broken_link.rs create mode 100644 tests/ui/doc_broken_link.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 9209d11feb34c..cbbbc8bef3cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5571,6 +5571,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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index b9f9f4e4fe0ba..540c4031140a8 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -139,6 +139,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, diff --git a/clippy_lints/src/doc/broken_link.rs b/clippy_lints/src/doc/broken_link.rs new file mode 100644 index 0000000000000..a97b807e605f7 --- /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/mod.rs b/clippy_lints/src/doc/mod.rs index ab77edf1147cb..5e2f64e8f095f 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -25,6 +25,7 @@ use rustc_span::edition::Edition; use std::ops::Range; use url::Url; +mod broken_link; mod doc_comment_double_space_linebreaks; mod include_in_doc_without_cfg; mod lazy_continuation; @@ -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 @@ -625,6 +654,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, @@ -751,9 +781,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())) @@ -793,14 +823,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, @@ -810,9 +838,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/tests/ui/doc_broken_link.rs b/tests/ui/doc_broken_link.rs new file mode 100644 index 0000000000000..7d9c0ef13b3cd --- /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 0000000000000..179ed97635eec --- /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 + From 2037075b38ae2af58b1c0b6860a559892f766fa4 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 5 Jun 2025 22:03:05 +0200 Subject: [PATCH 11/75] `Sugg`: do not parenthesize a double unary operator For example, adding `*` in front of `*expression` is best shown as `**expression` rather than `*(*expression)`. This is not perfect, as it checks whether the operator is already a prefix of the expression, but it is better than it was before. For example, `&`+`&mut x` will get `&&mut x` but `&mut `+`&x` will get `&mut (&x)` as it did before this change. --- clippy_utils/src/sugg.rs | 22 +++++++++++++++++++++- tests/ui/nonminimal_bool.stderr | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 6974e6512e2ca..7a24d07fa1dfb 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/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index 0e3e4cf7988e2..ecb82a23da034 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 From 982abc48b3d8bbe003bc042b8ac0537e3d0e90ff Mon Sep 17 00:00:00 2001 From: nekename <63245705+nekename@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:37:39 +0100 Subject: [PATCH 12/75] docs: make unbuffered_bytes docs more consistent --- clippy_lints/src/methods/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 347960e0003d7..f2dabdd343875 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"] From dd0fa3d356d59654cd080cb99a7dd30246288a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 10 Jun 2025 11:09:54 +0200 Subject: [PATCH 13/75] Add jemalloc feature to Clippy --- Cargo.toml | 1 + src/driver.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 3a76c61489e24..13cf82a062b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } [features] integration = ["dep:tempfile"] internal = ["dep:clippy_lints_internal", "dep:tempfile"] +jemalloc = [] [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] diff --git a/src/driver.rs b/src/driver.rs index 37adb14169a3f..426ba870f5f65 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -13,6 +13,11 @@ extern crate rustc_interface; extern crate rustc_session; extern crate rustc_span; +// See docs in https://github.com/rust-lang/rust/blob/master/compiler/rustc/src/main.rs +// about jemalloc. +#[cfg(feature = "jemalloc")] +extern crate tikv_jemalloc_sys as jemalloc_sys; + use clippy_utils::sym; use rustc_interface::interface; use rustc_session::EarlyDiagCtxt; @@ -181,6 +186,36 @@ const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/ne #[allow(clippy::too_many_lines)] #[allow(clippy::ignored_unit_patterns)] pub fn main() { + // See docs in https://github.com/rust-lang/rust/blob/master/compiler/rustc/src/main.rs + // about jemalloc. + #[cfg(feature = "jemalloc")] + { + use std::os::raw::{c_int, c_void}; + + #[used] + static _F1: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::calloc; + #[used] + static _F2: unsafe extern "C" fn(*mut *mut c_void, usize, usize) -> c_int = jemalloc_sys::posix_memalign; + #[used] + static _F3: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::aligned_alloc; + #[used] + static _F4: unsafe extern "C" fn(usize) -> *mut c_void = jemalloc_sys::malloc; + #[used] + static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc; + #[used] + static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free; + + #[cfg(target_os = "macos")] + { + unsafe extern "C" { + fn _rjem_je_zone_register(); + } + + #[used] + static _F7: unsafe extern "C" fn() = _rjem_je_zone_register; + } + } + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); rustc_driver::init_rustc_env_logger(&early_dcx); From 4cb48c8d7577d70ea306a84dbb3840a72c447290 Mon Sep 17 00:00:00 2001 From: Boxy Date: Wed, 11 Jun 2025 15:30:15 +0100 Subject: [PATCH 14/75] stabilize gai --- tests/ui/single_range_in_vec_init.rs | 1 - tests/ui/single_range_in_vec_init.stderr | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/ui/single_range_in_vec_init.rs b/tests/ui/single_range_in_vec_init.rs index 25884450b0842..0888019e101ce 100644 --- a/tests/ui/single_range_in_vec_init.rs +++ b/tests/ui/single_range_in_vec_init.rs @@ -2,7 +2,6 @@ //@no-rustfix: overlapping suggestions #![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] #![warn(clippy::single_range_in_vec_init)] -#![feature(generic_arg_infer)] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/single_range_in_vec_init.stderr b/tests/ui/single_range_in_vec_init.stderr index a99127a7606fb..b21338e38a3cb 100644 --- a/tests/ui/single_range_in_vec_init.stderr +++ b/tests/ui/single_range_in_vec_init.stderr @@ -1,5 +1,5 @@ error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:26:5 + --> tests/ui/single_range_in_vec_init.rs:25:5 | LL | [0..200]; | ^^^^^^^^ @@ -18,7 +18,7 @@ LL + [0; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:28:5 + --> tests/ui/single_range_in_vec_init.rs:27:5 | LL | vec![0..200]; | ^^^^^^^^^^^^ @@ -35,7 +35,7 @@ LL + vec![0; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:30:5 + --> tests/ui/single_range_in_vec_init.rs:29:5 | LL | [0u8..200]; | ^^^^^^^^^^ @@ -52,7 +52,7 @@ LL + [0u8; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:32:5 + --> tests/ui/single_range_in_vec_init.rs:31:5 | LL | [0usize..200]; | ^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL + [0usize; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:34:5 + --> tests/ui/single_range_in_vec_init.rs:33:5 | LL | [0..200usize]; | ^^^^^^^^^^^^^ @@ -86,7 +86,7 @@ LL + [0; 200usize]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:36:5 + --> tests/ui/single_range_in_vec_init.rs:35:5 | LL | vec![0u8..200]; | ^^^^^^^^^^^^^^ @@ -103,7 +103,7 @@ LL + vec![0u8; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:38:5 + --> tests/ui/single_range_in_vec_init.rs:37:5 | LL | vec![0usize..200]; | ^^^^^^^^^^^^^^^^^ @@ -120,7 +120,7 @@ LL + vec![0usize; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:40:5 + --> tests/ui/single_range_in_vec_init.rs:39:5 | LL | vec![0..200usize]; | ^^^^^^^^^^^^^^^^^ @@ -137,7 +137,7 @@ LL + vec![0; 200usize]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:43:5 + --> tests/ui/single_range_in_vec_init.rs:42:5 | LL | [0..200isize]; | ^^^^^^^^^^^^^ @@ -149,7 +149,7 @@ LL + (0..200isize).collect::>(); | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:45:5 + --> tests/ui/single_range_in_vec_init.rs:44:5 | LL | vec![0..200isize]; | ^^^^^^^^^^^^^^^^^ From 7cb7e28c4de7d7fc8338039a6cdf8228e71a546d Mon Sep 17 00:00:00 2001 From: relaxcn Date: Wed, 11 Jun 2025 01:04:11 +0800 Subject: [PATCH 15/75] Fix FP of `identity_op` when encountering `Default::default()` --- clippy_lints/src/operators/identity_op.rs | 65 +++++++++++++++++++++-- tests/ui/identity_op.fixed | 17 ++++++ tests/ui/identity_op.rs | 17 ++++++ tests/ui/identity_op.stderr | 20 ++++++- 4 files changed, 114 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index e1fd09549a4b8..6460bf6d6729a 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -3,10 +3,10 @@ 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 rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, Node}; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::Span; +use rustc_span::{Span, sym}; use super::IDENTITY_OP; @@ -17,7 +17,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,7 +165,14 @@ 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(cx: &LateContext<'_>, expr: &Expr<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool { + // Exclude case where the left or right side is a call to `Default::default()` + // 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_default_call(cx, left) || is_default_call(cx, right)) && !is_expr_with_type_annotation(cx, expr.hir_id) { + 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() @@ -175,6 +182,20 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Exp && ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1))) } +/// Check if the expression is a call to `Default::default()` +fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(func, []) = peel_hir_expr_refs(expr).0.kind + && let ExprKind::Path(qpath) = func.kind + // Detect and ignore ::default() because these calls do explicitly name the type. + && let QPath::Resolved(None, _path) = qpath + && let Some(def_id) = cx.qpath_res(&qpath, func.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::default_fn, def_id) + { + return true; + } + false +} + fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { let ecx = ConstEvalCtxt::new(cx); if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) { @@ -234,3 +255,39 @@ fn span_ineffective_operation( applicability, ); } + +/// Check if the expression is a let binding's init expression and the let binding has a type +/// annotation. Also check if the expression is a function's return value. +fn is_expr_with_type_annotation(cx: &LateContext<'_>, hir_id: HirId) -> bool { + // Get the parent node of the expression + if let Some((_, parent)) = cx.tcx.hir_parent_iter(hir_id).next() { + match parent { + Node::LetStmt(local) => { + // Check if this expression is the init expression of the let binding + if let Some(init) = local.init + && init.hir_id == hir_id + { + // Check if the let binding has an explicit type annotation + return local.ty.is_some(); + } + }, + Node::Block(block) => { + // If the parent node is a block, we can make sure the expression is the last expression in the + // block. + return is_expr_with_type_annotation(cx, block.hir_id); + }, + Node::Expr(expr) => { + return is_expr_with_type_annotation(cx, expr.hir_id); + }, + Node::Item(Item { + kind: ItemKind::Fn { .. }, + .. + }) => { + // Every function has a return type, so we can return true. + return true; + }, + _ => {}, + } + } + false +} diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index a1b556029987a..2cc4a5d0c23b9 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -312,3 +312,20 @@ 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 + + let _ = usize::default(); + //~^ identity_op + + let _n: usize = Default::default(); + //~^ identity_op +} + +fn issue_14932_2() -> usize { + Default::default() + //~^ identity_op +} diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index f603e1078e4ec..da0597c7abe4b 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -312,3 +312,20 @@ 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 + + let _ = 0usize + &usize::default(); + //~^ identity_op + + let _n: usize = 0usize + &Default::default(); + //~^ identity_op +} + +fn issue_14932_2() -> usize { + 0usize + &Default::default() + //~^ identity_op +} diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 8f9c2b603c49c..9c774a313ffc3 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -379,5 +379,23 @@ 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:13 + | +LL | let _ = 0usize + &usize::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::default()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:324: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:329:5 + | +LL | 0usize + &Default::default() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()` + +error: aborting due to 66 previous errors From d03d226165aee0609fa338b940e49d12268e9ab5 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 12 Jun 2025 00:02:12 +0800 Subject: [PATCH 16/75] avoid `&mut P` in `visit_expr` etc methods --- clippy_lints/src/unnested_or_patterns.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index b839b6f56728c..bd8420917f5e6 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -128,7 +128,7 @@ fn remove_all_parens(pat: &mut P) { } impl MutVisitor for Visitor { - fn visit_pat(&mut self, pat: &mut P) { + fn visit_pat(&mut self, pat: &mut Pat) { let is_inner = mem::replace(&mut self.is_inner, true); walk_pat(self, pat); let inner = match &mut pat.kind { @@ -145,7 +145,7 @@ fn remove_all_parens(pat: &mut P) { fn insert_necessary_parens(pat: &mut P) { struct Visitor; impl MutVisitor for Visitor { - fn visit_pat(&mut self, pat: &mut P) { + fn visit_pat(&mut self, pat: &mut Pat) { use ast::BindingMode; walk_pat(self, pat); let target = match &mut pat.kind { @@ -167,7 +167,7 @@ fn unnest_or_patterns(pat: &mut P) -> bool { changed: bool, } impl MutVisitor for Visitor { - fn visit_pat(&mut self, p: &mut P) { + fn visit_pat(&mut self, p: &mut Pat) { // This is a bottom up transformation, so recurse first. walk_pat(self, p); From 97a2d5b8fe5067e18b1b09888386653b3d53b02c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 12 Jun 2025 13:44:19 +0200 Subject: [PATCH 17/75] intrinsics: rename min_align_of to align_of --- clippy_utils/src/sym.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index f417530be3672..11b085b70580c 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -76,7 +76,6 @@ generate! { Visitor, Weak, abs, - align_of, ambiguous_glob_reexports, append, arg, From 322e1393a48a65164254740680d41bdcbee6195f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 4 Jun 2025 12:35:19 +0200 Subject: [PATCH 18/75] Fix false positive of `borrow_deref_ref` If a reborrow is itself borrowed mutably, do not propose to replace it by the original reference. --- clippy_lints/src/borrow_deref_ref.rs | 15 +++++++-- tests/ui/borrow_deref_ref.fixed | 47 ++++++++++++++++++++++++++++ tests/ui/borrow_deref_ref.rs | 47 ++++++++++++++++++++++++++++ tests/ui/borrow_deref_ref.stderr | 32 ++++++++++++++++++- 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 7cde007a9b66d..70c9c45a60c89 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/tests/ui/borrow_deref_ref.fixed b/tests/ui/borrow_deref_ref.fixed index 765dd75fceb92..6d06fcc3037aa 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 8ee66bfa881ab..b43f4c93bf2b0 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 3d55da25b9b20..3a1f968b4be19 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 From 0d21f087dff7e8f6d11a1770557a32d06add7550 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 12 Jun 2025 22:16:07 +0200 Subject: [PATCH 19/75] Remove unneeded lifetime --- clippy_utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 55469e8ebc90c..ccae5f010b532 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2711,7 +2711,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; From 88d298317fbc3383fec40a43e8ee6ad99f20e338 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 13 Jun 2025 01:16:36 +0200 Subject: [PATCH 20/75] Unimplement unsized_locals --- tests/ui/large_stack_frames.rs | 8 +------- tests/ui/large_stack_frames.stderr | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/ui/large_stack_frames.rs b/tests/ui/large_stack_frames.rs index 3ed124f69ef35..132f1450b6ded 100644 --- a/tests/ui/large_stack_frames.rs +++ b/tests/ui/large_stack_frames.rs @@ -1,8 +1,7 @@ //@ normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR" //@ normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR" -#![allow(unused, incomplete_features)] +#![allow(unused)] #![warn(clippy::large_stack_frames)] -#![feature(unsized_locals)] use std::hint::black_box; @@ -11,11 +10,6 @@ fn generic() { black_box(&x); } -fn unsized_local() { - let x: dyn std::fmt::Display = *(Box::new(1) as Box); - black_box(&x); -} - struct ArrayDefault([u8; N]); impl Default for ArrayDefault { diff --git a/tests/ui/large_stack_frames.stderr b/tests/ui/large_stack_frames.stderr index 0ff49e9f5b372..79482e65c3e62 100644 --- a/tests/ui/large_stack_frames.stderr +++ b/tests/ui/large_stack_frames.stderr @@ -1,5 +1,5 @@ error: this function may allocate 250$PTR bytes on the stack - --> tests/ui/large_stack_frames.rs:27:4 + --> tests/ui/large_stack_frames.rs:21:4 | LL | fn many_small_arrays() { | ^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL | let x5 = [0u8; 500_000]; = help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]` error: this function may allocate 1000000 bytes on the stack - --> tests/ui/large_stack_frames.rs:38:4 + --> tests/ui/large_stack_frames.rs:32:4 | LL | fn large_return_value() -> ArrayDefault<1_000_000> { | ^^^^^^^^^^^^^^^^^^ ----------------------- this is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>` @@ -21,7 +21,7 @@ LL | fn large_return_value() -> ArrayDefault<1_000_000> { = note: 1000000 bytes is larger than Clippy's configured `stack-size-threshold` of 512000 error: this function may allocate 100$PTR bytes on the stack - --> tests/ui/large_stack_frames.rs:44:4 + --> tests/ui/large_stack_frames.rs:38:4 | LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) { | ^^^^^^^^^^^^ - `x` is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>` @@ -29,7 +29,7 @@ LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) { = note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000 error: this function may allocate 100$PTR bytes on the stack - --> tests/ui/large_stack_frames.rs:51:13 + --> tests/ui/large_stack_frames.rs:45:13 | LL | let f = || black_box(&[0u8; 1_000_000]); | ^^^^^^^^^^^^^^----------------^ From cbd683fe31c6bd661426b750e59016a04a37b6fc Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 13 Jun 2025 11:24:45 +0200 Subject: [PATCH 21/75] Merge commit '4ef75291b5dd6739212f1f61666d19d4e086690d' into clippy-subtree-update --- CHANGELOG.md | 5 + clippy_dev/src/update_lints.rs | 4 +- .../src/arbitrary_source_item_ordering.rs | 8 +- clippy_lints/src/attrs/deprecated_cfg_attr.rs | 2 +- .../src/attrs/duplicated_attributes.rs | 18 +- clippy_lints/src/booleans.rs | 16 +- clippy_lints/src/casts/cast_lossless.rs | 19 +- .../src/casts/cast_possible_truncation.rs | 14 +- clippy_lints/src/casts/cast_possible_wrap.rs | 10 +- clippy_lints/src/casts/cast_precision_loss.rs | 16 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 2 +- clippy_lints/src/casts/fn_to_numeric_cast.rs | 14 +- .../fn_to_numeric_cast_with_truncation.rs | 8 +- clippy_lints/src/casts/utils.rs | 29 +- clippy_lints/src/checked_conversions.rs | 60 ++-- clippy_lints/src/cloned_ref_to_slice_refs.rs | 2 +- clippy_lints/src/coerce_container_to_any.rs | 108 ++++++ clippy_lints/src/copies.rs | 51 ++- clippy_lints/src/create_dir.rs | 23 +- clippy_lints/src/ctfe.rs | 26 -- clippy_lints/src/declared_lints.rs | 5 + clippy_lints/src/default.rs | 18 +- clippy_lints/src/disallowed_names.rs | 4 +- clippy_lints/src/disallowed_types.rs | 8 +- .../src/doc/doc_suspicious_footnotes.rs | 113 ++++++ clippy_lints/src/doc/mod.rs | 40 ++- clippy_lints/src/endian_bytes.rs | 12 +- clippy_lints/src/format_args.rs | 124 ++++++- clippy_lints/src/functions/mod.rs | 2 +- clippy_lints/src/if_let_mutex.rs | 5 +- clippy_lints/src/implicit_saturating_sub.rs | 12 +- clippy_lints/src/infallible_try_from.rs | 76 ++++ clippy_lints/src/lib.rs | 12 +- clippy_lints/src/loops/manual_flatten.rs | 38 +- clippy_lints/src/manual_clamp.rs | 9 +- clippy_lints/src/manual_string_new.rs | 8 +- clippy_lints/src/match_result_ok.rs | 5 +- .../src/matches/match_single_binding.rs | 18 +- .../src/matches/match_str_case_mismatch.rs | 17 +- clippy_lints/src/methods/ip_constant.rs | 52 +++ clippy_lints/src/methods/iter_kv_map.rs | 2 +- .../methods/manual_saturating_arithmetic.rs | 6 +- clippy_lints/src/methods/mod.rs | 39 ++ clippy_lints/src/methods/needless_collect.rs | 9 +- clippy_lints/src/methods/open_options.rs | 18 +- clippy_lints/src/methods/str_splitn.rs | 8 +- clippy_lints/src/multiple_bound_locations.rs | 4 +- clippy_lints/src/non_canonical_impls.rs | 9 +- clippy_lints/src/pathbuf_init_then_push.rs | 6 +- clippy_lints/src/ptr.rs | 26 +- clippy_lints/src/read_zero_byte_vec.rs | 12 +- .../src/reserve_after_initialization.rs | 4 +- clippy_lints/src/semicolon_block.rs | 2 +- clippy_lints/src/serde_api.rs | 8 +- clippy_lints/src/std_instead_of_core.rs | 79 ++-- clippy_lints/src/strings.rs | 2 +- clippy_lints/src/swap.rs | 17 +- .../missing_transmute_annotations.rs | 2 +- clippy_lints/src/types/borrowed_box.rs | 2 +- clippy_lints/src/unit_types/unit_arg.rs | 124 +++++-- clippy_lints/src/unused_io_amount.rs | 16 +- clippy_lints/src/unused_result_ok.rs | 4 +- clippy_lints/src/unused_unit.rs | 3 +- clippy_lints/src/vec_init_then_push.rs | 4 +- clippy_lints/src/wildcard_imports.rs | 6 +- clippy_lints/src/write.rs | 64 +++- clippy_lints/src/zombie_processes.rs | 26 +- .../src/almost_standard_lint_formulation.rs | 2 +- .../src/lint_without_lint_pass.rs | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/lib.rs | 15 +- clippy_utils/src/qualify_min_const_fn.rs | 12 +- clippy_utils/src/sym.rs | 21 +- clippy_utils/src/ty/mod.rs | 37 +- rust-toolchain.toml | 2 +- tests/compile-test.rs | 8 +- tests/symbols-used.rs | 81 +++++ .../await_holding_invalid_type.rs | 1 + .../await_holding_invalid_type.stderr | 6 +- .../branches_sharing_code/shared_at_bottom.rs | 37 ++ .../shared_at_bottom.stderr | 17 +- .../ui/branches_sharing_code/shared_at_top.rs | 31 ++ .../shared_at_top.stderr | 17 +- .../shared_at_top_and_bottom.rs | 39 ++ .../shared_at_top_and_bottom.stderr | 28 +- tests/ui/cmp_null.fixed | 6 + tests/ui/cmp_null.rs | 6 + tests/ui/cmp_null.stderr | 8 +- tests/ui/coerce_container_to_any.fixed | 26 ++ tests/ui/coerce_container_to_any.rs | 26 ++ tests/ui/coerce_container_to_any.stderr | 23 ++ tests/ui/crashes/ice-14935.rs | 27 ++ tests/ui/crashes/ice-9463.rs | 7 +- tests/ui/crashes/ice-9463.stderr | 29 -- tests/ui/crashes/ice-rust-107877.rs | 1 + tests/ui/create_dir.fixed | 23 +- tests/ui/create_dir.rs | 19 + tests/ui/create_dir.stderr | 43 ++- tests/ui/disallowed_names.rs | 15 + tests/ui/disallowed_names.stderr | 28 +- tests/ui/doc_suspicious_footnotes.fixed | 186 ++++++++++ tests/ui/doc_suspicious_footnotes.rs | 162 +++++++++ tests/ui/doc_suspicious_footnotes.stderr | 179 ++++++++++ tests/ui/doc_suspicious_footnotes_include.rs | 4 + .../doc_suspicious_footnotes_include.stderr | 17 + tests/ui/doc_suspicious_footnotes_include.txt | 13 + tests/ui/format_args.fixed | 10 + tests/ui/format_args.rs | 10 + tests/ui/indexing_slicing_index.rs | 1 - tests/ui/indexing_slicing_index.stderr | 31 +- tests/ui/infallible_try_from.rs | 33 ++ tests/ui/infallible_try_from.stderr | 23 ++ tests/ui/ip_constant.fixed | 107 ++++++ tests/ui/ip_constant.rs | 127 +++++++ tests/ui/ip_constant.stderr | 338 ++++++++++++++++++ tests/ui/ip_constant_from_external.rs | 12 + tests/ui/ip_constant_from_external.stderr | 16 + tests/ui/localhost.txt | 1 + tests/ui/manual_flatten.fixed | 148 ++++++++ tests/ui/manual_flatten.rs | 39 +- tests/ui/manual_flatten.stderr | 139 ++++--- tests/ui/manual_swap_auto_fix.fixed | 11 + tests/ui/manual_swap_auto_fix.rs | 14 + tests/ui/manual_swap_auto_fix.stderr | 11 +- tests/ui/match_single_binding.fixed | 16 + tests/ui/match_single_binding.rs | 18 + tests/ui/match_single_binding.stderr | 35 +- .../ui/missing_const_for_fn/const_trait.fixed | 36 ++ tests/ui/missing_const_for_fn/const_trait.rs | 36 ++ .../missing_const_for_fn/const_trait.stderr | 17 + tests/ui/needless_lifetimes.fixed | 2 +- tests/ui/needless_lifetimes.rs | 2 +- tests/ui/non_canonical_partial_ord_impl.fixed | 16 + tests/ui/non_canonical_partial_ord_impl.rs | 18 + .../ui/non_canonical_partial_ord_impl.stderr | 15 +- tests/ui/pointer_format.rs | 66 ++++ tests/ui/pointer_format.stderr | 47 +++ tests/ui/print_literal.fixed | 11 + tests/ui/print_literal.rs | 11 + tests/ui/print_literal.stderr | 50 ++- tests/ui/semicolon_outside_block.fixed | 25 ++ tests/ui/semicolon_outside_block.rs | 25 ++ tests/ui/std_instead_of_core.fixed | 6 + tests/ui/std_instead_of_core.rs | 6 + tests/ui/unit_arg.rs | 24 ++ tests/ui/unit_arg.stderr | 23 +- tests/ui/unit_arg_empty_blocks.fixed | 34 -- tests/ui/unit_arg_empty_blocks.rs | 31 -- tests/ui/unit_arg_empty_blocks.stderr | 46 --- tests/ui/unit_arg_fixable.fixed | 78 ++++ tests/ui/unit_arg_fixable.rs | 71 ++++ tests/ui/unit_arg_fixable.stderr | 110 ++++++ tests/ui/unused_unit.edition2021.fixed | 8 +- tests/ui/unused_unit.edition2024.fixed | 8 +- tests/ui/unused_unit.rs | 8 +- tests/ui/write_literal.fixed | 12 + tests/ui/write_literal.rs | 12 + tests/ui/write_literal.stderr | 50 ++- tests/ui/zombie_processes.rs | 10 + util/gh-pages/script.js | 2 +- 160 files changed, 4109 insertions(+), 668 deletions(-) create mode 100644 clippy_lints/src/coerce_container_to_any.rs delete mode 100644 clippy_lints/src/ctfe.rs create mode 100644 clippy_lints/src/doc/doc_suspicious_footnotes.rs create mode 100644 clippy_lints/src/infallible_try_from.rs create mode 100644 clippy_lints/src/methods/ip_constant.rs create mode 100644 tests/symbols-used.rs create mode 100644 tests/ui/coerce_container_to_any.fixed create mode 100644 tests/ui/coerce_container_to_any.rs create mode 100644 tests/ui/coerce_container_to_any.stderr create mode 100644 tests/ui/crashes/ice-14935.rs delete mode 100644 tests/ui/crashes/ice-9463.stderr create mode 100644 tests/ui/doc_suspicious_footnotes.fixed create mode 100644 tests/ui/doc_suspicious_footnotes.rs create mode 100644 tests/ui/doc_suspicious_footnotes.stderr create mode 100644 tests/ui/doc_suspicious_footnotes_include.rs create mode 100644 tests/ui/doc_suspicious_footnotes_include.stderr create mode 100644 tests/ui/doc_suspicious_footnotes_include.txt create mode 100644 tests/ui/infallible_try_from.rs create mode 100644 tests/ui/infallible_try_from.stderr create mode 100644 tests/ui/ip_constant.fixed create mode 100644 tests/ui/ip_constant.rs create mode 100644 tests/ui/ip_constant.stderr create mode 100644 tests/ui/ip_constant_from_external.rs create mode 100644 tests/ui/ip_constant_from_external.stderr create mode 100644 tests/ui/localhost.txt create mode 100644 tests/ui/manual_flatten.fixed create mode 100644 tests/ui/missing_const_for_fn/const_trait.fixed create mode 100644 tests/ui/missing_const_for_fn/const_trait.rs create mode 100644 tests/ui/missing_const_for_fn/const_trait.stderr create mode 100644 tests/ui/pointer_format.rs create mode 100644 tests/ui/pointer_format.stderr delete mode 100644 tests/ui/unit_arg_empty_blocks.fixed delete mode 100644 tests/ui/unit_arg_empty_blocks.rs delete mode 100644 tests/ui/unit_arg_empty_blocks.stderr create mode 100644 tests/ui/unit_arg_fixable.fixed create mode 100644 tests/ui/unit_arg_fixable.rs create mode 100644 tests/ui/unit_arg_fixable.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a98217f625a0..0cfe89ad3787f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5685,6 +5685,7 @@ Released 2018-09-13 [`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan [`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null [`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned +[`coerce_container_to_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#coerce_container_to_any [`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity [`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 @@ -5736,6 +5737,7 @@ Released 2018-09-13 [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs [`doc_overindented_list_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_overindented_list_items +[`doc_suspicious_footnotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_suspicious_footnotes [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons [`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last [`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use @@ -5862,6 +5864,7 @@ Released 2018-09-13 [`ineffective_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_open_options [`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string [`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match +[`infallible_try_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_try_from [`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter [`infinite_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_loop [`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string @@ -5888,6 +5891,7 @@ Released 2018-09-13 [`inverted_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#inverted_saturating_sub [`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters [`io_other_error`]: https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error +[`ip_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#ip_constant [`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix [`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements [`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module @@ -6163,6 +6167,7 @@ Released 2018-09-13 [`pathbuf_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#pathbuf_init_then_push [`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch [`permissions_set_readonly_false`]: https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false +[`pointer_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointer_format [`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block [`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 320462a2c9688..08592f2521f7d 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -73,8 +73,8 @@ pub fn generate_lint_files( ( "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`", + "// 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(); diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 59a0c7c88684d..b9ae9afe85100 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -204,7 +204,7 @@ impl ArbitrarySourceItemOrdering { self.assoc_types_order ), Some(before_item.span), - format!("should be placed before `{}`", before_item.ident.as_str(),), + format!("should be placed before `{}`", before_item.ident.name), ); } @@ -216,7 +216,7 @@ impl ArbitrarySourceItemOrdering { ident.span, "incorrect ordering of items (must be alphabetically ordered)", Some(before_ident.span), - format!("should be placed before `{}`", before_ident.as_str(),), + format!("should be placed before `{}`", before_ident.name), ); } @@ -228,7 +228,7 @@ impl ArbitrarySourceItemOrdering { }; let (before_span, note) = if let Some(ident) = before_item.kind.ident() { - (ident.span, format!("should be placed before `{}`", ident.as_str(),)) + (ident.span, format!("should be placed before `{}`", ident.name)) } else { ( before_item.span, @@ -255,7 +255,7 @@ impl ArbitrarySourceItemOrdering { self.assoc_types_order ), Some(before_item.span), - format!("should be placed before `{}`", before_item.ident.as_str(),), + format!("should be placed before `{}`", before_item.ident.name), ); } } diff --git a/clippy_lints/src/attrs/deprecated_cfg_attr.rs b/clippy_lints/src/attrs/deprecated_cfg_attr.rs index 0edb50be8c778..d67a194b02062 100644 --- a/clippy_lints/src/attrs/deprecated_cfg_attr.rs +++ b/clippy_lints/src/attrs/deprecated_cfg_attr.rs @@ -61,7 +61,7 @@ pub(super) fn check_clippy(cx: &EarlyContext<'_>, attr: &Attribute) { fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) { if let Some(ident) = attr.ident() { - if ["any", "all", "not"].contains(&ident.name.as_str()) { + if matches!(ident.name, sym::any | sym::all | sym::not) { let Some(list) = attr.meta_item_list() else { return }; for item in list.iter().filter_map(|item| item.meta_item()) { check_deprecated_cfg_recursively(cx, item); diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs index a851daaede71b..c2406bcfb6477 100644 --- a/clippy_lints/src/attrs/duplicated_attributes.rs +++ b/clippy_lints/src/attrs/duplicated_attributes.rs @@ -1,9 +1,10 @@ use super::DUPLICATED_ATTRIBUTES; use clippy_utils::diagnostics::span_lint_and_then; +use itertools::Itertools; use rustc_ast::{Attribute, MetaItem}; use rustc_data_structures::fx::FxHashMap; use rustc_lint::EarlyContext; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; use std::collections::hash_map::Entry; fn emit_if_duplicated( @@ -29,7 +30,7 @@ fn check_duplicated_attr( cx: &EarlyContext<'_>, attr: &MetaItem, attr_paths: &mut FxHashMap, - parent: &mut Vec, + parent: &mut Vec, ) { if attr.span.from_expansion() { return; @@ -43,7 +44,7 @@ fn check_duplicated_attr( return; } if let Some(direct_parent) = parent.last() - && direct_parent == sym::cfg_trace.as_str() + && *direct_parent == sym::cfg_trace && [sym::all, sym::not, sym::any].contains(&name) { // FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one @@ -51,9 +52,14 @@ fn check_duplicated_attr( return; } if let Some(value) = attr.value_str() { - emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":"))); + emit_if_duplicated( + cx, + attr, + attr_paths, + format!("{}:{name}={value}", parent.iter().join(":")), + ); } else if let Some(sub_attrs) = attr.meta_item_list() { - parent.push(name.as_str().to_string()); + parent.push(name); for sub_attr in sub_attrs { if let Some(meta) = sub_attr.meta_item() { check_duplicated_attr(cx, meta, attr_paths, parent); @@ -61,7 +67,7 @@ fn check_duplicated_attr( } parent.pop(); } else { - emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":"))); + emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.iter().join(":"))); } } diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 7c6fd91ca67f7..bf43234ff50f6 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -1,10 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; -use clippy_utils::eq_expr_value; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::{eq_expr_value, sym}; use rustc_ast::ast::LitKind; use rustc_attr_data_structures::RustcVersion; use rustc_errors::Applicability; @@ -13,7 +13,7 @@ use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, SyntaxContext, sym}; +use rustc_span::{Span, Symbol, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -73,10 +73,10 @@ declare_clippy_lint! { } // For each pairs, both orders are considered. -const METHODS_WITH_NEGATION: [(Option, &str, &str); 3] = [ - (None, "is_some", "is_none"), - (None, "is_err", "is_ok"), - (Some(msrvs::IS_NONE_OR), "is_some_and", "is_none_or"), +const METHODS_WITH_NEGATION: [(Option, Symbol, Symbol); 3] = [ + (None, sym::is_some, sym::is_none), + (None, sym::is_err, sym::is_ok), + (Some(msrvs::IS_NONE_OR), sym::is_some_and, sym::is_none_or), ]; pub struct NonminimalBool { @@ -440,9 +440,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio .iter() .copied() .flat_map(|(msrv, a, b)| vec![(msrv, a, b), (msrv, b, a)]) - .find(|&(msrv, a, _)| { - a == path.ident.name.as_str() && msrv.is_none_or(|msrv| curr_msrv.meets(cx, msrv)) - }) + .find(|&(msrv, a, _)| a == path.ident.name && msrv.is_none_or(|msrv| curr_msrv.meets(cx, msrv))) .and_then(|(_, _, neg_method)| { let negated_args = args .iter() diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 0f066fae11844..c1d6cec1b62e7 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -76,19 +76,20 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M return false; } - match (cast_from.is_integral(), cast_to.is_integral()) { - (true, true) => { + match ( + utils::int_ty_to_nbits(cx.tcx, cast_from), + utils::int_ty_to_nbits(cx.tcx, cast_to), + ) { + (Some(from_nbits), Some(to_nbits)) => { let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned }, - (true, false) => { - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + (Some(from_nbits), None) => { + // FIXME: handle `f16` and `f128` let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { 32 } else { @@ -96,9 +97,7 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M }; !is_isize_or_usize(cast_from) && from_nbits < to_nbits }, - (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(cx, msrvs::FROM_BOOL) => true, - (_, _) => { - matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) - }, + (None, Some(_)) if cast_from.is_bool() && msrv.meets(cx, msrvs::FROM_BOOL) => true, + _ => matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)), } } diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 4120e5c8cb7d0..a2ecb5fb44ae0 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -91,15 +91,14 @@ pub(super) fn check( cast_to: Ty<'_>, cast_to_span: Span, ) { - let msg = match (cast_from.kind(), cast_to.is_integral()) { - (ty::Int(_) | ty::Uint(_), true) => { + let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) { + (ty::Int(_) | ty::Uint(_), Some(to_nbits)) => { let from_nbits = apply_reductions( cx, - utils::int_ty_to_nbits(cast_from, cx.tcx), + utils::int_ty_to_nbits(cx.tcx, cast_from).unwrap(), cast_expr, cast_from.is_signed(), ); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { (true, true) | (false, false) => (to_nbits < from_nbits, ""), @@ -121,7 +120,7 @@ pub(super) fn check( format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",) }, - (ty::Adt(def, _), true) if def.is_enum() => { + (ty::Adt(def, _), Some(to_nbits)) if def.is_enum() => { let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id) { @@ -132,7 +131,6 @@ pub(super) fn check( } else { (utils::enum_ty_to_nbits(*def, cx.tcx), None) }; - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); let cast_from_ptr_size = def.repr().int.is_none_or(|ty| matches!(ty, IntegerType::Pointer(_),)); let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { @@ -157,11 +155,11 @@ pub(super) fn check( format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}") }, - (ty::Float(_), true) => { + (ty::Float(_), Some(_)) => { format!("casting `{cast_from}` to `{cast_to}` may truncate the value") }, - (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => { + (ty::Float(FloatTy::F64), None) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => { "casting `f64` to `f32` may truncate the value".to_string() }, diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 504d0a267e47d..e26c03ccda933 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -17,9 +17,12 @@ enum EmitState { } pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !(cast_from.is_integral() && cast_to.is_integral()) { + let (Some(from_nbits), Some(to_nbits)) = ( + utils::int_ty_to_nbits(cx.tcx, cast_from), + utils::int_ty_to_nbits(cx.tcx, cast_to), + ) else { return; - } + }; // emit a lint if a cast is: // 1. unsigned to signed @@ -35,9 +38,6 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca return; } - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) { (true, true) => { // casts between two ptr sized integers are trivially always the same size diff --git a/clippy_lints/src/casts/cast_precision_loss.rs b/clippy_lints/src/casts/cast_precision_loss.rs index 1eb115ce6bdc0..712e38db499fe 100644 --- a/clippy_lints/src/casts/cast_precision_loss.rs +++ b/clippy_lints/src/casts/cast_precision_loss.rs @@ -7,15 +7,14 @@ use rustc_middle::ty::{self, FloatTy, Ty}; use super::{CAST_PRECISION_LOSS, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !cast_from.is_integral() || cast_to.is_integral() { + let Some(from_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_from) else { return; - } + }; - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = if cast_to.kind() == &ty::Float(FloatTy::F32) { - 32 - } else { - 64 + // FIXME: handle `f16` and `f128` + let to_nbits = match cast_to.kind() { + ty::Float(f @ (FloatTy::F32 | FloatTy::F64)) => f.bit_width(), + _ => return, }; if !(is_isize_or_usize(cast_from) || from_nbits >= to_nbits) { @@ -29,9 +28,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca let from_nbits_str = if arch_dependent { "64".to_owned() } else if is_isize_or_usize(cast_from) { + // FIXME: handle 16 bits `usize` type "32 or 64".to_owned() } else { - utils::int_ty_to_nbits(cast_from, cx.tcx).to_string() + from_nbits.to_string() }; span_lint( diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 01020f3eee21e..e4dafde0f9df7 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -61,7 +61,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { }; match parent.kind { ExprKind::MethodCall(name, self_arg, ..) if self_arg.hir_id == e.hir_id => { - if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned") + if matches!(name.ident.name, sym::read_unaligned | sym::write_unaligned) && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && let Some(def_id) = cx.tcx.impl_of_method(def_id) && cx.tcx.type_of(def_id).instantiate_identity().is_raw_ptr() diff --git a/clippy_lints/src/casts/fn_to_numeric_cast.rs b/clippy_lints/src/casts/fn_to_numeric_cast.rs index ac1a355c8d962..105477093b561 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -3,24 +3,22 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, UintTy}; +use rustc_middle::ty::{self, Ty}; use super::{FN_TO_NUMERIC_CAST, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We only want to check casts to `ty::Uint` or `ty::Int` - match cast_to.kind() { - ty::Uint(_) | ty::Int(..) => { /* continue on */ }, - _ => return, - } + let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { + return; + }; match cast_from.kind() { ty::FnDef(..) | ty::FnPtr(..) => { let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && (*cast_to.kind() != ty::Uint(UintTy::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, FN_TO_NUMERIC_CAST, 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 18e7798452ead..700b7d0d42668 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 @@ -9,16 +9,14 @@ use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We only want to check casts to `ty::Uint` or `ty::Int` - match cast_to.kind() { - ty::Uint(_) | ty::Int(..) => { /* continue on */ }, - _ => return, - } + let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { + return; + }; match cast_from.kind() { ty::FnDef(..) | ty::FnPtr(..) => { let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); if to_nbits < cx.tcx.data_layout.pointer_size.bits() { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 5ccba92a0afe8..318a1646477ed 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,27 +1,14 @@ use clippy_utils::ty::{EnumValue, read_explicit_enum_value}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; -/// Returns the size in bits of an integral type. -/// Will return 0 if the type is not an int or uint variant -pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { - match typ.kind() { - ty::Int(i) => match i { - IntTy::Isize => tcx.data_layout.pointer_size.bits(), - IntTy::I8 => 8, - IntTy::I16 => 16, - IntTy::I32 => 32, - IntTy::I64 => 64, - IntTy::I128 => 128, - }, - ty::Uint(i) => match i { - UintTy::Usize => tcx.data_layout.pointer_size.bits(), - UintTy::U8 => 8, - UintTy::U16 => 16, - UintTy::U32 => 32, - UintTy::U64 => 64, - UintTy::U128 => 128, - }, - _ => 0, +/// Returns the size in bits of an integral type, or `None` if `ty` is not an +/// 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(i) => i.bit_width(), + ty::Uint(i) => i.bit_width(), + _ => None, } } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 8ada608049c7b..9b3822f9d8f09 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -2,11 +2,12 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal}; +use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal, sym}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::Symbol; declare_clippy_lint! { /// ### What it does @@ -98,7 +99,7 @@ impl LateLintPass<'_> for CheckedConversions { struct Conversion<'a> { cvt: ConversionType, expr_to_cast: &'a Expr<'a>, - to_type: Option<&'a str>, + to_type: Option, } /// The kind of conversion that is checked @@ -150,7 +151,7 @@ impl<'a> Conversion<'a> { } /// Try to construct a new conversion if the conversion type is valid - fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option> { + fn try_new(expr_to_cast: &'a Expr<'_>, from_type: Symbol, to_type: Symbol) -> Option> { ConversionType::try_new(from_type, to_type).map(|cvt| Conversion { cvt, expr_to_cast, @@ -171,7 +172,7 @@ impl<'a> Conversion<'a> { impl ConversionType { /// Creates a conversion type if the type is allowed & conversion is valid #[must_use] - fn try_new(from: &str, to: &str) -> Option { + fn try_new(from: Symbol, to: Symbol) -> Option { if UINTS.contains(&from) { Some(Self::FromUnsigned) } else if SINTS.contains(&from) { @@ -190,7 +191,7 @@ impl ConversionType { /// Check for `expr <= (to_type::MAX as from_type)` fn check_upper_bound<'tcx>(lt: &'tcx Expr<'tcx>, gt: &'tcx Expr<'tcx>) -> Option> { - if let Some((from, to)) = get_types_from_cast(gt, INTS, "max_value", "MAX") { + if let Some((from, to)) = get_types_from_cast(gt, INTS, sym::max_value, sym::MAX) { Conversion::try_new(lt, from, to) } else { None @@ -209,7 +210,7 @@ fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> O /// Check for `expr >= (to_type::MIN as from_type)` fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { - if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") { + if let Some((from, to)) = get_types_from_cast(check, SINTS, sym::min_value, sym::MIN) { Conversion::try_new(candidate, from, to) } else { None @@ -217,15 +218,15 @@ fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Op } /// Tries to extract the from- and to-type from a cast expression -fn get_types_from_cast<'a>( - expr: &'a Expr<'_>, - types: &'a [&str], - func: &'a str, - assoc_const: &'a str, -) -> Option<(&'a str, &'a str)> { +fn get_types_from_cast( + expr: &Expr<'_>, + types: &[Symbol], + func: Symbol, + assoc_const: Symbol, +) -> Option<(Symbol, Symbol)> { // `to_type::max_value() as from_type` // or `to_type::MAX as from_type` - let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind + let call_from_cast: Option<(&Expr<'_>, Symbol)> = if let ExprKind::Cast(limit, from_type) = &expr.kind // to_type::max_value(), from_type && let TyKind::Path(from_type_path) = &from_type.kind && let Some(from_sym) = int_ty_to_sym(from_type_path) @@ -236,12 +237,12 @@ fn get_types_from_cast<'a>( }; // `from_type::from(to_type::max_value())` - let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { + let limit_from: Option<(&Expr<'_>, Symbol)> = call_from_cast.or_else(|| { if let ExprKind::Call(from_func, [limit]) = &expr.kind // `from_type::from, to_type::max_value()` // `from_type::from` && let ExprKind::Path(path) = &from_func.kind - && let Some(from_sym) = get_implementing_type(path, INTS, "from") + && let Some(from_sym) = get_implementing_type(path, INTS, sym::from) { Some((limit, from_sym)) } else { @@ -273,32 +274,41 @@ fn get_types_from_cast<'a>( } /// Gets the type which implements the called function -fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { +fn get_implementing_type(path: &QPath<'_>, candidates: &[Symbol], function: Symbol) -> Option { if let QPath::TypeRelative(ty, path) = &path - && path.ident.name.as_str() == function + && path.ident.name == function && let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind && let [int] = tp.segments { - let name = int.ident.name.as_str(); - candidates.iter().find(|c| &name == *c).copied() + candidates.iter().find(|c| int.ident.name == **c).copied() } else { None } } /// Gets the type as a string, if it is a supported integer -fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { +fn int_ty_to_sym(path: &QPath<'_>) -> Option { if let QPath::Resolved(_, path) = *path && let [ty] = path.segments { - let name = ty.ident.name.as_str(); - INTS.iter().find(|c| &name == *c).copied() + INTS.iter().find(|c| ty.ident.name == **c).copied() } else { None } } // Constants -const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"]; -const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"]; -const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"]; +const UINTS: &[Symbol] = &[sym::u8, sym::u16, sym::u32, sym::u64, sym::usize]; +const SINTS: &[Symbol] = &[sym::i8, sym::i16, sym::i32, sym::i64, sym::isize]; +const INTS: &[Symbol] = &[ + sym::u8, + sym::u16, + sym::u32, + sym::u64, + sym::usize, + sym::i8, + sym::i16, + sym::i32, + sym::i64, + sym::isize, +]; diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 6b239a1541b0d..e33a8e0fb742c 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// /// ### Why is this bad /// - /// A reference does not need to be owned in order to used as a slice. + /// A reference does not need to be owned in order to be used as a slice. /// /// ### Known problems /// diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs new file mode 100644 index 0000000000000..8c12a42ba4e4f --- /dev/null +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -0,0 +1,108 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, ExistentialPredicate, Ty, TyCtxt}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Protects against unintended coercion of references to container types to `&dyn Any` when the + /// container type dereferences to a `dyn Any` which could be directly referenced instead. + /// + /// ### Why is this bad? + /// + /// The intention is usually to get a reference to the `dyn Any` the value dereferences to, + /// rather than coercing a reference to the container itself to `&dyn Any`. + /// + /// ### Example + /// + /// Because `Box` itself implements `Any`, `&Box` + /// can be coerced to an `&dyn Any` which refers to *the `Box` itself*, rather than the + /// inner `dyn Any`. + /// ```no_run + /// # use std::any::Any; + /// let x: Box = Box::new(0u32); + /// let dyn_any_of_box: &dyn Any = &x; + /// + /// // Fails as we have a &dyn Any to the Box, not the u32 + /// assert_eq!(dyn_any_of_box.downcast_ref::(), None); + /// ``` + /// Use instead: + /// ```no_run + /// # use std::any::Any; + /// let x: Box = Box::new(0u32); + /// let dyn_any_of_u32: &dyn Any = &*x; + /// + /// // Succeeds since we have a &dyn Any to the inner u32! + /// assert_eq!(dyn_any_of_u32.downcast_ref::(), Some(&0u32)); + /// ``` + #[clippy::version = "1.88.0"] + pub COERCE_CONTAINER_TO_ANY, + suspicious, + "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" +} +declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); + +impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + // If this expression has an effective type of `&dyn Any` ... + { + let coerced_ty = cx.typeck_results().expr_ty_adjusted(e); + + let ty::Ref(_, coerced_ref_ty, _) = *coerced_ty.kind() else { + return; + }; + if !is_dyn_any(cx.tcx, coerced_ref_ty) { + return; + } + } + + let expr_ty = cx.typeck_results().expr_ty(e); + let ty::Ref(_, expr_ref_ty, _) = *expr_ty.kind() else { + return; + }; + // ... but only due to coercion ... + if is_dyn_any(cx.tcx, expr_ref_ty) { + return; + } + // ... and it also *derefs* to `dyn Any` ... + let Some((depth, target)) = clippy_utils::ty::deref_chain(cx, expr_ref_ty).enumerate().last() else { + return; + }; + if !is_dyn_any(cx.tcx, target) { + return; + } + + // ... that's probably not intended. + let (span, deref_count) = match e.kind { + // If `e` was already an `&` expression, skip `*&` in the suggestion + ExprKind::AddrOf(_, _, referent) => (referent.span, depth), + _ => (e.span, depth + 1), + }; + span_lint_and_sugg( + cx, + COERCE_CONTAINER_TO_ANY, + e.span, + format!("coercing `{expr_ty}` to `&dyn Any`"), + "consider dereferencing", + format!("&{}{}", str::repeat("*", deref_count), snippet(cx, span, "x")), + Applicability::MaybeIncorrect, + ); + } +} + +fn is_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { + let ty::Dynamic(traits, ..) = ty.kind() else { + return false; + }; + traits.iter().any(|binder| { + let ExistentialPredicate::Trait(t) = binder.skip_binder() else { + return false; + }; + tcx.is_diagnostic_item(sym::Any, t.def_id) + }) +} diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 2467fc95fd055..5ef726638a560 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -425,7 +425,9 @@ fn scan_block_for_eq<'tcx>( modifies_any_local(cx, stmt, &cond_locals) || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) }) - .map_or(block.stmts.len(), |(i, _)| i); + .map_or(block.stmts.len(), |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev()) + }); if local_needs_ordered_drop { return BlockEq { @@ -467,7 +469,9 @@ fn scan_block_for_eq<'tcx>( .is_none_or(|s| hash != hash_stmt(cx, s)) }) }) - .map_or(block.stmts.len() - start_end_eq, |(i, _)| i); + .map_or(block.stmts.len() - start_end_eq, |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, (0..i).rev().zip(block.stmts[(block.stmts.len() - i)..].iter())) + }); let moved_locals_at_start = moved_locals.len(); let mut i = end_search_start; @@ -522,6 +526,49 @@ fn scan_block_for_eq<'tcx>( } } +/// Adjusts the index for which the statements begin to differ to the closest macro callsite. This +/// avoids giving suggestions that requires splitting a macro call in half, when only a part of the +/// macro expansion is equal. +/// +/// For example, for the following macro: +/// ```rust,ignore +/// macro_rules! foo { +/// ($x:expr) => { +/// let y = 42; +/// $x; +/// }; +/// } +/// ``` +/// If the macro is called like this: +/// ```rust,ignore +/// if false { +/// let z = 42; +/// foo!(println!("Hello")); +/// } else { +/// let z = 42; +/// foo!(println!("World")); +/// } +/// ``` +/// Although the expanded `let y = 42;` is equal, the macro call should not be included in the +/// suggestion. +fn adjust_by_closest_callsite<'tcx>( + i: usize, + stmt: &'tcx Stmt<'tcx>, + mut iter: impl Iterator)>, +) -> usize { + let Some((_, first)) = iter.next() else { + return 0; + }; + + // If it is already at the boundary of a macro call, then just return. + if first.span.source_callsite() != stmt.span.source_callsite() { + return i; + } + + iter.find(|(_, stmt)| stmt.span.source_callsite() != first.span.source_callsite()) + .map_or(0, |(i, _)| i + 1) +} + fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { get_enclosing_block(cx, if_expr.hir_id).is_some_and(|block| { let ignore_span = block.span.shrink_to_lo().to(if_expr.span); diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index b43906903a0e0..695b25aeb0b7d 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -34,10 +33,12 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]); impl LateLintPass<'_> for CreateDir { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Call(func, [arg]) = expr.kind + if let ExprKind::Call(func, [_]) = expr.kind && let ExprKind::Path(ref path) = func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id) + && let QPath::Resolved(_, path) = path + && let Some(last) = path.segments.last() { span_lint_and_then( cx, @@ -45,15 +46,15 @@ impl LateLintPass<'_> for CreateDir { expr.span, "calling `std::fs::create_dir` where there may be a better way", |diag| { - let mut app = Applicability::MaybeIncorrect; - diag.span_suggestion_verbose( - expr.span, + let mut suggestions = vec![(last.ident.span.shrink_to_hi(), "_all".to_owned())]; + if path.segments.len() == 1 { + suggestions.push((path.span.shrink_to_lo(), "std::fs::".to_owned())); + } + + diag.multipart_suggestion_verbose( "consider calling `std::fs::create_dir_all` instead", - format!( - "create_dir_all({})", - snippet_with_applicability(cx, arg.span, "..", &mut app) - ), - app, + suggestions, + Applicability::MaybeIncorrect, ); }, ); diff --git a/clippy_lints/src/ctfe.rs b/clippy_lints/src/ctfe.rs deleted file mode 100644 index 7bae04a10f10b..0000000000000 --- a/clippy_lints/src/ctfe.rs +++ /dev/null @@ -1,26 +0,0 @@ -use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; - -declare_lint_pass! { - /// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). - /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran. - ClippyCtfe => [] -} - -impl<'tcx> LateLintPass<'tcx> for ClippyCtfe { - fn check_fn( - &mut self, - cx: &LateContext<'_>, - _: FnKind<'tcx>, - _: &'tcx FnDecl<'tcx>, - _: &'tcx Body<'tcx>, - _: Span, - defid: LocalDefId, - ) { - cx.tcx.ensure_ok().mir_drops_elaborated_and_const_checked(defid); // Lint - } -} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5fcb851dfebce..1e3907d9ede03 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -77,6 +77,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::cfg_not_test::CFG_NOT_TEST_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO, + crate::coerce_container_to_any::COERCE_CONTAINER_TO_ANY_INFO, crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, crate::collapsible_if::COLLAPSIBLE_IF_INFO, @@ -119,6 +120,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::doc::DOC_MARKDOWN_INFO, crate::doc::DOC_NESTED_REFDEFS_INFO, crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, + crate::doc::DOC_SUSPICIOUS_FOOTNOTES_INFO, crate::doc::EMPTY_DOCS_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO, @@ -166,6 +168,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO, crate::format::USELESS_FORMAT_INFO, crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO, + crate::format_args::POINTER_FORMAT_INFO, crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO, crate::format_args::UNINLINED_FORMAT_ARGS_INFO, crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO, @@ -211,6 +214,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::indexing_slicing::INDEXING_SLICING_INFO, crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO, crate::ineffective_open_options::INEFFECTIVE_OPEN_OPTIONS_INFO, + crate::infallible_try_from::INFALLIBLE_TRY_FROM_INFO, crate::infinite_iter::INFINITE_ITER_INFO, crate::infinite_iter::MAYBE_INFINITE_ITER_INFO, crate::inherent_impl::MULTIPLE_INHERENT_IMPL_INFO, @@ -379,6 +383,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::INSPECT_FOR_EACH_INFO, crate::methods::INTO_ITER_ON_REF_INFO, crate::methods::IO_OTHER_ERROR_INFO, + crate::methods::IP_CONSTANT_INFO, crate::methods::IS_DIGIT_ASCII_RADIX_INFO, crate::methods::ITERATOR_STEP_BY_ZERO_INFO, crate::methods::ITER_CLONED_COLLECT_INFO, diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 886c325b355fe..a48e4d2fbd57e 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_drop, is_copy}; -use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro}; +use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_expr_default, is_from_proc_macro}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::def::Res; use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind, StructTailExpr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -129,7 +128,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { // only take bindings to identifiers && let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind // only when assigning `... = Default::default()` - && is_expr_default(expr, cx) + && is_expr_default(cx, expr) && let binding_type = cx.typeck_results().node_type(binding_id) && let ty::Adt(adt, args) = *binding_type.kind() && adt.is_struct() @@ -251,19 +250,6 @@ impl<'tcx> LateLintPass<'tcx> for Default { } } -/// Checks if the given expression is the `default` method belonging to the `Default` trait. -fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool { - if let ExprKind::Call(fn_expr, []) = &expr.kind - && let ExprKind::Path(qpath) = &fn_expr.kind - && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id) - { - // right hand side of assignment is `Default::default` - cx.tcx.is_diagnostic_item(sym::default_fn, def_id) - } else { - false - } -} - /// Returns the reassigned field and the assigning expression (right-hand side of assign). fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { if let StmtKind::Semi(later_expr) = this.kind diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index f55b0cf1c5034..566aa12b08f58 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; +use clippy_utils::{is_from_proc_macro, is_in_test}; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -43,8 +43,10 @@ impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); impl<'tcx> LateLintPass<'tcx> for DisallowedNames { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if let PatKind::Binding(.., ident, _) = pat.kind + && !ident.span.from_expansion() && self.disallow.contains(&ident.name) && !is_in_test(cx.tcx, pat.hir_id) + && !is_from_proc_macro(cx, &ident) { span_lint( cx, diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 821bb25d2ceca..7875cdd77e86e 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -105,10 +105,10 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind { - if let Some(res) = path.res.type_ns { - self.check_res_emit(cx, &res, item.span); - } + if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind + && let Some(res) = path.res.type_ns + { + self.check_res_emit(cx, &res, item.span); } } diff --git a/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/clippy_lints/src/doc/doc_suspicious_footnotes.rs new file mode 100644 index 0000000000000..289b6b915d46d --- /dev/null +++ b/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -0,0 +1,113 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::token::CommentKind; +use rustc_errors::Applicability; +use rustc_hir::{AttrStyle, Attribute}; +use rustc_lint::{LateContext, LintContext}; +use rustc_resolve::rustdoc::DocFragmentKind; + +use std::ops::Range; + +use super::{DOC_SUSPICIOUS_FOOTNOTES, Fragments}; + +pub fn check(cx: &LateContext<'_>, doc: &str, range: Range, fragments: &Fragments<'_>, attrs: &[Attribute]) { + for i in doc[range.clone()] + .bytes() + .enumerate() + .filter_map(|(i, c)| if c == b'[' { Some(i) } else { None }) + { + let start = i + range.start; + if doc.as_bytes().get(start + 1) == Some(&b'^') + && let Some(end) = all_numbers_upto_brace(doc, start + 2) + && doc.as_bytes().get(end) != Some(&b':') + && doc.as_bytes().get(start - 1) != Some(&b'\\') + && let Some(this_fragment) = { + // the `doc` string contains all fragments concatenated together + // figure out which one this suspicious footnote comes from + let mut starting_position = 0; + let mut found_fragment = fragments.fragments.last(); + for fragment in fragments.fragments { + if start >= starting_position && start < starting_position + fragment.doc.as_str().len() { + found_fragment = Some(fragment); + break; + } + starting_position += fragment.doc.as_str().len(); + } + found_fragment + } + { + let span = fragments.span(cx, start..end).unwrap_or(this_fragment.span); + span_lint_and_then( + cx, + DOC_SUSPICIOUS_FOOTNOTES, + span, + "looks like a footnote ref, but has no matching footnote", + |diag| { + if this_fragment.kind == DocFragmentKind::SugaredDoc { + let (doc_attr, (_, doc_attr_comment_kind)) = attrs + .iter() + .filter(|attr| attr.span().overlaps(this_fragment.span)) + .rev() + .find_map(|attr| Some((attr, attr.doc_str_and_comment_kind()?))) + .unwrap(); + let (to_add, terminator) = match (doc_attr_comment_kind, doc_attr.style()) { + (CommentKind::Line, AttrStyle::Outer) => ("\n///\n/// ", ""), + (CommentKind::Line, AttrStyle::Inner) => ("\n//!\n//! ", ""), + (CommentKind::Block, AttrStyle::Outer) => ("\n/** ", " */"), + (CommentKind::Block, AttrStyle::Inner) => ("\n/*! ", " */"), + }; + diag.span_suggestion_verbose( + doc_attr.span().shrink_to_hi(), + "add footnote definition", + format!( + "{to_add}{label}: {terminator}", + label = &doc[start..end] + ), + Applicability::HasPlaceholders, + ); + } else { + let is_file_include = cx + .sess() + .source_map() + .span_to_snippet(this_fragment.span) + .as_ref() + .map(|vdoc| vdoc.trim()) + == Ok(doc); + if is_file_include { + // if this is a file include, then there's no quote marks + diag.span_suggestion_verbose( + this_fragment.span.shrink_to_hi(), + "add footnote definition", + format!("\n\n{label}: ", label = &doc[start..end],), + Applicability::HasPlaceholders, + ); + } else { + // otherwise, we wrap in a string + diag.span_suggestion_verbose( + this_fragment.span, + "add footnote definition", + format!( + "r#\"{doc}\n\n{label}: \"#", + doc = this_fragment.doc, + label = &doc[start..end], + ), + Applicability::HasPlaceholders, + ); + } + } + }, + ); + } + } +} + +fn all_numbers_upto_brace(text: &str, i: usize) -> Option { + for (j, c) in text.as_bytes()[i..].iter().copied().enumerate().take(64) { + if c == b']' && j != 0 { + return Some(i + j + 1); + } + if !c.is_ascii_digit() || j >= 64 { + break; + } + } + None +} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index c46dd09d60c56..e0fc2fd93474f 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -25,6 +25,7 @@ use std::ops::Range; use url::Url; mod doc_comment_double_space_linebreaks; +mod doc_suspicious_footnotes; mod include_in_doc_without_cfg; mod lazy_continuation; mod link_with_quotes; @@ -607,6 +608,37 @@ declare_clippy_lint! { "double-space used for doc comment linebreak instead of `\\`" } +declare_clippy_lint! { + /// ### What it does + /// Detects syntax that looks like a footnote reference. + /// + /// Rustdoc footnotes are compatible with GitHub-Flavored Markdown (GFM). + /// GFM does not parse a footnote reference unless its definition also + /// exists. This lint checks for footnote references with missing + /// definitions, unless it thinks you're writing a regex. + /// + /// ### Why is this bad? + /// This probably means that a footnote was meant to exist, + /// but was not written. + /// + /// ### Example + /// ```no_run + /// /// This is not a footnote[^1], because no definition exists. + /// fn my_fn() {} + /// ``` + /// Use instead: + /// ```no_run + /// /// This is a footnote[^1]. + /// /// + /// /// [^1]: defined here + /// fn my_fn() {} + /// ``` + #[clippy::version = "1.88.0"] + pub DOC_SUSPICIOUS_FOOTNOTES, + suspicious, + "looks like a link or footnote ref, but with no definition" +} + pub struct Documentation { valid_idents: FxHashSet, check_private_items: bool, @@ -638,7 +670,8 @@ impl_lint_pass!(Documentation => [ DOC_OVERINDENTED_LIST_ITEMS, TOO_LONG_FIRST_DOC_PARAGRAPH, DOC_INCLUDE_WITHOUT_CFG, - DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS + DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS, + DOC_SUSPICIOUS_FOOTNOTES, ]); impl EarlyLintPass for Documentation { @@ -825,6 +858,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ doc: &doc, fragments: &fragments, }, + attrs, )) } @@ -905,6 +939,7 @@ fn check_doc<'a, Events: Iterator, Range, + attrs: &[Attribute], ) -> DocHeaders { // true if a safety header was found let mut headers = DocHeaders::default(); @@ -1148,7 +1183,8 @@ fn check_doc<'a, Events: Iterator, Range {} diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index a7670ffce887c..75db923b1c37c 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -1,6 +1,6 @@ use crate::Lint; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_lint_allowed; +use clippy_utils::{is_lint_allowed, sym}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Ty; @@ -65,9 +65,9 @@ declare_clippy_lint! { declare_lint_pass!(EndianBytes => [HOST_ENDIAN_BYTES, LITTLE_ENDIAN_BYTES, BIG_ENDIAN_BYTES]); -const HOST_NAMES: [&str; 2] = ["from_ne_bytes", "to_ne_bytes"]; -const LITTLE_NAMES: [&str; 2] = ["from_le_bytes", "to_le_bytes"]; -const BIG_NAMES: [&str; 2] = ["from_be_bytes", "to_be_bytes"]; +const HOST_NAMES: [Symbol; 2] = [sym::from_ne_bytes, sym::to_ne_bytes]; +const LITTLE_NAMES: [Symbol; 2] = [sym::from_le_bytes, sym::to_le_bytes]; +const BIG_NAMES: [Symbol; 2] = [sym::from_be_bytes, sym::to_be_bytes]; #[derive(Clone, Debug)] enum LintKind { @@ -95,7 +95,7 @@ impl LintKind { } } - fn as_name(&self, prefix: Prefix) -> &str { + fn as_name(&self, prefix: Prefix) -> Symbol { let index = usize::from(prefix == Prefix::To); match self { @@ -133,7 +133,7 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix let le = LintKind::Little.as_name(prefix); let be = LintKind::Big.as_name(prefix); - let (lint, other_lints) = match name.as_str() { + let (lint, other_lints) = match name { name if name == ne => ((&LintKind::Host), [(&LintKind::Little), (&LintKind::Big)]), name if name == le => ((&LintKind::Little), [(&LintKind::Host), (&LintKind::Big)]), name if name == be => ((&LintKind::Big), [(&LintKind::Host), (&LintKind::Little)]), diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index a26e736c7ae35..0c39aae9ca913 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,6 +1,8 @@ +use std::collections::hash_map::Entry; + use arrayvec::ArrayVec; use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{ FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call, @@ -9,7 +11,7 @@ use clippy_utils::macros::{ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::{implements_trait, is_type_lang_item}; -use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test}; +use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test, trait_ref_of_method}; use itertools::Itertools; use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, @@ -22,10 +24,12 @@ use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; -use rustc_middle::ty::{List, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast}; use rustc_session::impl_lint_pass; use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol, sym}; +use rustc_trait_selection::infer::TyCtxtInferExt; +use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext}; declare_clippy_lint! { /// ### What it does @@ -194,12 +198,41 @@ declare_clippy_lint! { "use of a format specifier that has no effect" } +declare_clippy_lint! { + /// ### What it does + /// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers + /// or any types that have a derived `Debug` impl that recursively contains them. + /// + /// ### Why restrict this? + /// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of + /// certain data structures or functions from prying hacker eyes as an additional line of security. + /// + /// ### Known problems + /// The lint currently only looks through derived `Debug` implementations. Checking whether a manual + /// implementation prints an address is left as an exercise to the next lint implementer. + /// + /// ### Example + /// ```no_run + /// let foo = &0_u32; + /// fn bar() {} + /// println!("{:p}", foo); + /// let _ = format!("{:?}", &(bar as fn())); + /// ``` + /// + /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits + #[clippy::version = "1.88.0"] + pub POINTER_FORMAT, + restriction, + "formatting a pointer" +} + impl_lint_pass!(FormatArgs<'_> => [ FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS, UNINLINED_FORMAT_ARGS, UNNECESSARY_DEBUG_FORMATTING, UNUSED_FORMAT_SPECS, + POINTER_FORMAT, ]); #[allow(clippy::struct_field_names)] @@ -208,6 +241,8 @@ pub struct FormatArgs<'tcx> { msrv: Msrv, ignore_mixed: bool, ty_msrv_map: FxHashMap, Option>, + has_derived_debug: FxHashMap, bool>, + has_pointer_format: FxHashMap, bool>, } impl<'tcx> FormatArgs<'tcx> { @@ -218,6 +253,8 @@ impl<'tcx> FormatArgs<'tcx> { msrv: conf.msrv, ignore_mixed: conf.allow_mixed_uninlined_format_args, ty_msrv_map, + has_derived_debug: FxHashMap::default(), + has_pointer_format: FxHashMap::default(), } } } @@ -228,7 +265,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> { && is_format_macro(cx, macro_call.def_id) && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { - let linter = FormatArgsExpr { + let mut linter = FormatArgsExpr { cx, expr, macro_call: ¯o_call, @@ -236,6 +273,8 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> { ignore_mixed: self.ignore_mixed, msrv: &self.msrv, ty_msrv_map: &self.ty_msrv_map, + has_derived_debug: &mut self.has_derived_debug, + has_pointer_format: &mut self.has_pointer_format, }; linter.check_templates(); @@ -255,10 +294,12 @@ struct FormatArgsExpr<'a, 'tcx> { ignore_mixed: bool, msrv: &'a Msrv, ty_msrv_map: &'a FxHashMap, Option>, + has_derived_debug: &'a mut FxHashMap, bool>, + has_pointer_format: &'a mut FxHashMap, bool>, } impl<'tcx> FormatArgsExpr<'_, 'tcx> { - fn check_templates(&self) { + fn check_templates(&mut self) { for piece in &self.format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece && let Ok(index) = placeholder.argument.index @@ -279,6 +320,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { if placeholder.format_trait == FormatTrait::Debug { let name = self.cx.tcx.item_name(self.macro_call.def_id); self.check_unnecessary_debug_formatting(name, arg_expr); + if let Some(span) = placeholder.span + && self.has_pointer_debug(self.cx.typeck_results().expr_ty(arg_expr), 0) + { + span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); + } + } + + if placeholder.format_trait == FormatTrait::Pointer + && let Some(span) = placeholder.span + { + span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); } } } @@ -490,6 +542,16 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { && let ty = cx.typeck_results().expr_ty(value) && self.can_display_format(ty) { + // If the parent function is a method of `Debug`, we don't want to lint + // because it is likely that the user wants to use `Debug` formatting. + let parent_fn = cx.tcx.hir_get_parent_item(value.hir_id); + if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn) + && let Some(trait_def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Debug, trait_def_id) + { + return; + } + let snippet = snippet(cx.sess(), value.span, ".."); span_lint_and_then( cx, @@ -559,6 +621,58 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { false } + + fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { + let cx = self.cx; + let tcx = cx.tcx; + if !tcx.recursion_limit().value_within_limit(depth) { + return false; + } + let depth = depth + 1; + let typing_env = cx.typing_env(); + let ty = tcx.normalize_erasing_regions(typing_env, ty); + match ty.kind() { + ty::RawPtr(..) | ty::FnPtr(..) | ty::FnDef(..) => true, + ty::Ref(_, t, _) | ty::Slice(t) | ty::Array(t, _) => self.has_pointer_debug(*t, depth), + ty::Tuple(ts) => ts.iter().any(|t| self.has_pointer_debug(t, depth)), + ty::Adt(adt, args) => { + match self.has_pointer_format.entry(ty) { + Entry::Occupied(o) => return *o.get(), + Entry::Vacant(v) => v.insert(false), + }; + let derived_debug = if let Some(&known) = self.has_derived_debug.get(&ty) { + known + } else { + let Some(trait_id) = tcx.get_diagnostic_item(sym::Debug) else { + return false; + }; + let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); + let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)]); + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: trait_ref.upcast(tcx), + }; + let selection = SelectionContext::new(&infcx).select(&obligation); + let derived = if let Ok(Some(Selection::UserDefined(data))) = selection { + tcx.has_attr(data.impl_def_id, sym::automatically_derived) + } else { + false + }; + self.has_derived_debug.insert(ty, derived); + derived + }; + let pointer_debug = derived_debug + && adt.all_fields().any(|f| { + self.has_pointer_debug(tcx.normalize_erasing_regions(typing_env, f.ty(tcx, args)), depth) + }); + self.has_pointer_format.insert(ty, pointer_debug); + pointer_debug + }, + _ => false, + } + } } fn make_ty_msrv_map(tcx: TyCtxt<'_>) -> FxHashMap, Option> { diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index d0d02a382d15e..6051dc9479baf 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -303,7 +303,7 @@ declare_clippy_lint! { /// to the name of the method, when there is a field's whose name matches that of the method. /// /// ### Why is this bad? - /// It is most likely that such a method is a bug caused by a typo or by copy-pasting. + /// It is most likely that such a method is a bug caused by a typo or by copy-pasting. /// /// ### Example diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index 6444e99ae9cdd..a99118f90f88a 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{eq_expr_value, higher}; +use clippy_utils::{eq_expr_value, higher, sym}; use core::ops::ControlFlow; use rustc_errors::Diag; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::edition::Edition::Edition2024; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -94,7 +93,7 @@ fn mutex_lock_call<'tcx>( op_mutex: Option<&'tcx Expr<'_>>, ) -> ControlFlow<&'tcx Expr<'tcx>> { if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind - && path.ident.as_str() == "lock" + && path.ident.name == sym::lock && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() && is_type_diagnostic_item(cx, ty, sym::Mutex) && op_mutex.is_none_or(|op| eq_expr_value(cx, self_arg, op)) diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 0823ef53ef98a..c743501da253e 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::{ - SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, + SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, sym, }; use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; @@ -11,7 +11,7 @@ use rustc_errors::Applicability; use rustc_hir::{AssignOpKind, BinOp, BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -325,7 +325,7 @@ fn check_with_condition<'tcx>( } // Get the variable name - let var_name = ares_path.segments[0].ident.name.as_str(); + let var_name = ares_path.segments[0].ident.name; match cond_num_val.kind { ExprKind::Lit(cond_lit) => { // Check if the constant is zero @@ -337,7 +337,7 @@ fn check_with_condition<'tcx>( } }, ExprKind::Path(QPath::TypeRelative(_, name)) => { - if name.ident.as_str() == "MIN" + if name.ident.name == sym::MIN && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(const_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl @@ -348,7 +348,7 @@ fn check_with_condition<'tcx>( }, ExprKind::Call(func, []) => { if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind - && name.ident.as_str() == "min_value" + && name.ident.name == sym::min_value && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(func_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl @@ -383,7 +383,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp } } -fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: &str, expr: &Expr<'_>) { +fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: Symbol, expr: &Expr<'_>) { span_lint_and_sugg( cx, IMPLICIT_SATURATING_SUB, diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs new file mode 100644 index 0000000000000..b54c289fa7e13 --- /dev/null +++ b/clippy_lints/src/infallible_try_from.rs @@ -0,0 +1,76 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::sym; +use rustc_errors::MultiSpan; +use rustc_hir::{AssocItemKind, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds manual impls of `TryFrom` with infallible error types. + /// + /// ### Why is this bad? + /// + /// Infalliable conversions should be implemented via `From` with the blanket conversion. + /// + /// ### Example + /// ```no_run + /// use std::convert::Infallible; + /// struct MyStruct(i16); + /// impl TryFrom for MyStruct { + /// type Error = Infallible; + /// fn try_from(other: i16) -> Result { + /// Ok(Self(other.into())) + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// struct MyStruct(i16); + /// impl From for MyStruct { + /// fn from(other: i16) -> Self { + /// Self(other) + /// } + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub INFALLIBLE_TRY_FROM, + suspicious, + "TryFrom with infallible Error type" +} +declare_lint_pass!(InfallibleTryFrom => [INFALLIBLE_TRY_FROM]); + +impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + let ItemKind::Impl(imp) = item.kind else { return }; + let Some(r#trait) = imp.of_trait else { return }; + let Some(trait_def_id) = r#trait.trait_def_id() else { + return; + }; + if !cx.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id) { + return; + } + for ii in imp.items { + if ii.kind == AssocItemKind::Type { + let ii = cx.tcx.hir_impl_item(ii.id); + if ii.ident.name != sym::Error { + continue; + } + let ii_ty = ii.expect_type(); + let ii_ty_span = ii_ty.span; + let ii_ty = clippy_utils::ty::ty_from_hir_ty(cx, ii_ty); + if !ii_ty.is_inhabited_from(cx.tcx, ii.owner_id.to_def_id(), cx.typing_env()) { + let mut span = MultiSpan::from_span(cx.tcx.def_span(item.owner_id.to_def_id())); + span.push_span_label(ii_ty_span, "infallible error type"); + span_lint( + cx, + INFALLIBLE_TRY_FROM, + span, + "infallible TryFrom impl; consider implementing From, instead", + ); + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 92eb3d7a7c59d..be9142b17fe37 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -55,6 +55,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; +extern crate smallvec; extern crate thin_vec; #[macro_use] @@ -65,11 +66,10 @@ extern crate clippy_utils; mod utils; -pub mod ctfe; // Very important lint, do not remove (rust#125116) pub mod declared_lints; pub mod deprecated_lints; -// begin lints modules, do not remove this comment, it’s used in `update_lints` +// begin lints modules, do not remove this comment, it's used in `update_lints` mod absolute_paths; mod almost_complete_range; mod approx_const; @@ -95,6 +95,7 @@ mod casts; mod cfg_not_test; mod checked_conversions; mod cloned_ref_to_slice_refs; +mod coerce_container_to_any; mod cognitive_complexity; mod collapsible_if; mod collection_is_never_read; @@ -169,6 +170,7 @@ mod inconsistent_struct_constructor; mod index_refutable_slice; mod indexing_slicing; mod ineffective_open_options; +mod infallible_try_from; mod infinite_iter; mod inherent_impl; mod inherent_to_string; @@ -404,7 +406,7 @@ mod zero_div_zero; mod zero_repeat_side_effects; mod zero_sized_map_values; mod zombie_processes; -// end lints modules, do not remove this comment, it’s used in `update_lints` +// end lints modules, do not remove this comment, it's used in `update_lints` use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; @@ -583,8 +585,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { let attrs = attr_storage.clone(); store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); - store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe)); - store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))); store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|_| Box::new(utils::author::Author)); @@ -946,5 +946,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap)); store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)); store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); + store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); + store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index 81f14b7b2b01d..ddb8bb536c04e 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -2,8 +2,9 @@ use super::MANUAL_FLATTEN; use super::utils::make_iterator_snippet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet_with_applicability}; use clippy_utils::visitors::is_local_used; -use clippy_utils::{higher, is_refutable, path_to_local_id, peel_blocks_with_stmt}; +use clippy_utils::{higher, is_refutable, path_to_local_id, peel_blocks_with_stmt, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, Pat, PatKind}; @@ -39,13 +40,20 @@ pub(super) fn check<'tcx>( && msrv.meets(cx, msrvs::ITER_FLATTEN) && !is_refutable(cx, inner_pat) { + if arg.span.from_expansion() || if_then.span.from_expansion() { + return; + } let if_let_type = if some_ctor { "Some" } else { "Ok" }; // Prepare the error message let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used"); // Prepare the help message - let mut applicability = Applicability::MaybeIncorrect; + let mut applicability = if span_contains_comment(cx.sess().source_map(), body.span) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); let copied = match cx.typeck_results().expr_ty(let_expr).kind() { ty::Ref(_, inner, _) => match inner.kind() { @@ -55,20 +63,26 @@ pub(super) fn check<'tcx>( _ => "", }; - let sugg = format!("{arg_snippet}{copied}.flatten()"); + let help_msg = "try `.flatten()` and remove the `if let` statement in the for loop"; - // If suggestion is not a one-liner, it won't be shown inline within the error message. In that - // case, it will be shown in the extra `help` message at the end, which is why the first - // `help_msg` needs to refer to the correct relative position of the suggestion. - let help_msg = if sugg.contains('\n') { - "remove the `if let` statement in the for loop and then..." - } else { - "...and remove the `if let` statement in the for loop" - }; + let pat_snippet = + snippet_with_applicability(cx, inner_pat.span.source_callsite(), "_", &mut applicability).to_string(); + let body_snippet = + snippet_with_applicability(cx, if_then.span.source_callsite(), "[body]", &mut applicability).to_string(); + let suggestions = vec![ + // flatten the iterator + (arg.span, format!("{arg_snippet}{copied}.flatten()")), + (pat.span, pat_snippet), + // remove the `if let` statement + ( + body.span, + reindent_multiline(&body_snippet, true, indent_of(cx, body.span)), + ), + ]; span_lint_and_then(cx, MANUAL_FLATTEN, span, msg, |diag| { - diag.span_suggestion(arg.span, "try", sugg, applicability); diag.span_help(inner_expr.span, help_msg); + diag.multipart_suggestion("try", suggestions, applicability); }); } } diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 02afe9f0997de..42fe386d2c3c4 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -8,7 +8,7 @@ use clippy_utils::ty::implements_trait; use clippy_utils::visitors::is_const_evaluatable; use clippy_utils::{ MaybePath, eq_expr_value, is_diag_trait_item, is_in_const_context, is_trait_method, path_res, path_to_local_id, - peel_blocks, peel_blocks_with_stmt, + peel_blocks, peel_blocks_with_stmt, sym, }; use itertools::Itertools; use rustc_errors::{Applicability, Diag}; @@ -18,7 +18,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; use rustc_span::Span; -use rustc_span::symbol::sym; use std::cmp::Ordering; use std::ops::Deref; @@ -299,9 +298,9 @@ fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> O && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord)) { let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point(); - let (min, max) = match (seg_first.ident.as_str(), seg_second.ident.as_str()) { - ("min", "max") => (arg_second, arg_first), - ("max", "min") => (arg_first, arg_second), + let (min, max) = match (seg_first.ident.name, seg_second.ident.name) { + (sym::min, sym::max) => (arg_second, arg_first), + (sym::max, sym::min) => (arg_first, arg_second), _ => return None, }; Some(ClampSuggestion { diff --git a/clippy_lints/src/manual_string_new.rs b/clippy_lints/src/manual_string_new.rs index 73ee1c3c78abc..9b590e092d056 100644 --- a/clippy_lints/src/manual_string_new.rs +++ b/clippy_lints/src/manual_string_new.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sym; use rustc_ast::LitKind; use rustc_errors::Applicability::MachineApplicable; use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -89,9 +90,10 @@ fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { /// Tries to parse an expression as a method call, emitting the warning if necessary. fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, receiver: &Expr<'_>) { - let ident = path_segment.ident.as_str(); let method_arg_kind = &receiver.kind; - if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { + if matches!(path_segment.ident.name, sym::to_string | sym::to_owned | sym::into) + && is_expr_kind_empty_str(method_arg_kind) + { warn_then_suggest(cx, span); } else if let ExprKind::Call(func, [arg]) = method_arg_kind { // If our first argument is a function call itself, it could be an `unwrap`-like function. diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index 2a5fc8b660916..e0cb5d14d3c93 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,12 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{higher, is_res_lang_ctor}; +use clippy_utils::{higher, is_res_lang_ctor, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -57,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result.ok(, _) && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation - && ok_path.ident.as_str() == "ok" + && ok_path.ident.name == sym::ok && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) && let ctxt = expr.span.ctxt() diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index adda35869900d..6a76c6cedd0da 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::HirNode; use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_context}; -use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; +use clippy_utils::{is_refutable, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}; use rustc_lint::LateContext; @@ -9,6 +9,7 @@ use rustc_span::Span; use super::MATCH_SINGLE_BINDING; +#[derive(Debug)] enum AssignmentExpr { Assign { span: Span, match_span: Span }, Local { span: Span, pat_span: Span }, @@ -160,6 +161,17 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option(cx: &LateContext<'a>, match_expr: &Expr<'a>) -> bool { + let parent = cx.tcx.parent_hir_node(match_expr.hir_id); + matches!( + parent, + Node::Expr(Expr { + kind: ExprKind::Closure { .. }, + .. + }) | Node::AnonConst(..) + ) +} + fn sugg_with_curlies<'a>( cx: &LateContext<'a>, (ex, match_expr): (&Expr<'a>, &Expr<'a>), @@ -172,9 +184,7 @@ fn sugg_with_curlies<'a>( let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if let Some(parent_expr) = get_parent_expr(cx, match_expr) - && let ExprKind::Closure { .. } = parent_expr.kind - { + if expr_parent_requires_curlies(cx, match_expr) { cbrace_end = format!("\n{indent}}}"); // Fix body indent due to the closure indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 65b93a095b926..8b4c17000519c 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,6 +1,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sym; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -42,7 +43,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { type Result = ControlFlow; fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind { - let result = self.case_altered(segment.ident.as_str(), receiver); + let result = self.case_altered(segment.ident.name, receiver); if result.is_break() { return result; } @@ -53,7 +54,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow { + fn case_altered(&mut self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); @@ -66,12 +67,12 @@ impl MatchExprVisitor<'_, '_> { } } -fn get_case_method(segment_ident_str: &str) -> Option { - match segment_ident_str { - "to_lowercase" => Some(CaseMethod::LowerCase), - "to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase), - "to_uppercase" => Some(CaseMethod::UpperCase), - "to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase), +fn get_case_method(segment_ident: Symbol) -> Option { + match segment_ident { + sym::to_lowercase => Some(CaseMethod::LowerCase), + sym::to_ascii_lowercase => Some(CaseMethod::AsciiLowerCase), + sym::to_uppercase => Some(CaseMethod::UpperCase), + sym::to_ascii_uppercase => Some(CaseMethod::AsciiUppercase), _ => None, } } diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints/src/methods/ip_constant.rs new file mode 100644 index 0000000000000..83803fba6a130 --- /dev/null +++ b/clippy_lints/src/methods/ip_constant.rs @@ -0,0 +1,52 @@ +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_span::sym; +use smallvec::SmallVec; + +use super::IP_CONSTANT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { + if let ExprKind::Path(QPath::TypeRelative( + Ty { + kind: TyKind::Path(QPath::Resolved(_, func_path)), + .. + }, + p, + )) = func.kind + && p.ident.name == sym::new + && let Some(func_def_id) = func_path.res.opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(func_def_id), + Some(sym::Ipv4Addr | sym::Ipv6Addr) + ) + && let Some(args) = args + .iter() + .map(|arg| { + if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ConstEvalCtxt::new(cx).eval(arg) { + u8::try_from(constant).ok() + } else { + None + } + }) + .collect::>>() + { + let constant_name = match args.as_slice() { + [0, 0, 0, 0] | [0, 0, 0, 0, 0, 0, 0, 0] => "UNSPECIFIED", + [127, 0, 0, 1] | [0, 0, 0, 0, 0, 0, 0, 1] => "LOCALHOST", + [255, 255, 255, 255] => "BROADCAST", + _ => return, + }; + + span_lint_and_then(cx, IP_CONSTANT, expr.span, "hand-coded well-known IP address", |diag| { + diag.span_suggestion_verbose( + expr.span.with_lo(p.ident.span.lo()), + "use", + constant_name, + Applicability::MachineApplicable, + ); + }); + } +} diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index c88462129af02..cbb1b450e60fc 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -46,7 +46,7 @@ pub(super) fn check<'tcx>( if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind && let [local_ident] = path.segments - && local_ident.ident.as_str() == bound_ident.as_str() + && local_ident.ident.name == bound_ident.name { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index e2df8ce1513c8..c785b23bc9c4a 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -72,9 +72,9 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { if let hir::ExprKind::Call(func, []) = &expr.kind && let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind { - match segment.ident.as_str() { - "max_value" => return Some(MinMax::Max), - "min_value" => return Some(MinMax::Min), + match segment.ident.name { + sym::max_value => return Some(MinMax::Max), + sym::min_value => return Some(MinMax::Min), _ => {}, } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index bc1592069850a..347960e0003d7 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -37,6 +37,7 @@ mod inefficient_to_string; mod inspect_for_each; mod into_iter_on_ref; mod io_other_error; +mod ip_constant; mod is_digit_ascii_radix; mod is_empty; mod iter_cloned_collect; @@ -4528,6 +4529,42 @@ declare_clippy_lint! { "detect swap with a temporary value" } +declare_clippy_lint! { + /// ### What it does + /// Checks for IP addresses that could be replaced with predefined constants such as + /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. + /// + /// ### Why is this bad? + /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the + /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code + /// readability, make the intent explicit, and are less error-prone. + /// + /// ### Example + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + /// ``` + /// Use instead: + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::LOCALHOST; + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::LOCALHOST; + /// ``` + #[clippy::version = "1.89.0"] + pub IP_CONSTANT, + pedantic, + "hardcoded localhost IP address" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4706,6 +4743,7 @@ impl_lint_pass!(Methods => [ MANUAL_CONTAINS, IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, + IP_CONSTANT, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4738,6 +4776,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv); io_other_error::check(cx, expr, func, args, self.msrv); swap_with_temporary::check(cx, expr, func, args); + ip_constant::check(cx, expr, func, args); }, ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 2b75d6a8248a0..0075bf166cc18 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -44,11 +44,10 @@ pub(super) fn check<'tcx>( if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { let mut app = Applicability::MachineApplicable; - let name = name.ident.as_str(); let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name { - "len" => { + let sugg: String = match name.ident.name { + sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( cx.tcx.get_diagnostic_name(adt.did()), @@ -60,13 +59,13 @@ pub(super) fn check<'tcx>( return; } }, - "is_empty" + sym::is_empty if is_is_empty_sig(cx, parent.hir_id) && iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) => { "next().is_none()".into() }, - "contains" => { + sym::contains => { if is_contains_sig(cx, parent.hir_id, iter_expr) && let Some(arg) = args.first() { diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index bce314e64f053..fd368024177ae 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -1,14 +1,14 @@ use rustc_data_structures::fx::FxHashMap; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::paths; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{paths, sym}; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; +use rustc_span::Span; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS}; @@ -87,23 +87,23 @@ fn get_open_options( _ => Argument::Unknown, }; - match path.ident.as_str() { - "create" => { + match path.ident.name { + sym::create => { options.push((OpenOption::Create, argument_option, span)); }, - "create_new" => { + sym::create_new => { options.push((OpenOption::CreateNew, argument_option, span)); }, - "append" => { + sym::append => { options.push((OpenOption::Append, argument_option, span)); }, - "truncate" => { + sym::truncate => { options.push((OpenOption::Truncate, argument_option, span)); }, - "read" => { + sym::read => { options.push((OpenOption::Read, argument_option, span)); }, - "write" => { + sym::write => { options.push((OpenOption::Write, argument_option, span)); }, _ => { diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 6935ae1f391fa..6f78d6c612815 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -285,9 +285,9 @@ fn parse_iter_usage<'tcx>( let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?; let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; - match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), - ("next_tuple", []) => { + match (name.ident.name, args) { + (sym::next, []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), + (sym::next_tuple, []) => { return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did) && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind() && cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) @@ -303,7 +303,7 @@ fn parse_iter_usage<'tcx>( None }; }, - ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { + (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) { let span = if name.ident.as_str() == "nth" { e.span diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs index 4b32ba83b325e..741f38f97560f 100644 --- a/clippy_lints/src/multiple_bound_locations.rs +++ b/clippy_lints/src/multiple_bound_locations.rs @@ -47,7 +47,7 @@ impl EarlyLintPass for MultipleBoundLocations { for param in &generics.params { if !param.bounds.is_empty() { - generic_params_with_bounds.insert(param.ident.name.as_str(), param.ident.span); + generic_params_with_bounds.insert(param.ident.as_str(), param.ident.span); } } for clause in &generics.where_clause.predicates { @@ -64,7 +64,7 @@ impl EarlyLintPass for MultipleBoundLocations { }, WherePredicateKind::RegionPredicate(pred) => { if !pred.bounds.is_empty() - && let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.name.as_str()) + && let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.as_str()) { emit_lint(cx, *bound_span, pred.lifetime.ident.span); } diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 93865197ec965..04b092769666c 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -293,7 +293,14 @@ fn self_cmp_call<'tcx>( ExprKind::Call(path, [_, _]) => path_res(cx, path) .opt_def_id() .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), - ExprKind::MethodCall(_, _, [_other], ..) => { + ExprKind::MethodCall(_, recv, [_], ..) => { + let ExprKind::Path(path) = recv.kind else { + return false; + }; + if last_path_segment(&path).ident.name != kw::SelfLower { + return false; + } + // We can set this to true here no matter what as if it's a `MethodCall` and goes to the // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp` *needs_fully_qualified = true; diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs index 35caac855cf61..4ce6827cac9bb 100644 --- a/clippy_lints/src/pathbuf_init_then_push.rs +++ b/clippy_lints/src/pathbuf_init_then_push.rs @@ -1,14 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::path_to_local_id; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{path_to_local_id, sym}; use rustc_ast::{LitKind, StrStyle}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, LetStmt, PatKind, QPath, Stmt, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -177,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { && let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [arg_expr], _) = expr.kind && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "push" + && name.ident.name == sym::push { searcher.err_span = searcher.err_span.to(stmt.span); searcher.arg = Some(*arg_expr); diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 9149406642d67..94cdcf0005484 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core}; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core, sym}; use hir::LifetimeKind; use rustc_abi::ExternAbi; use rustc_errors::{Applicability, MultiSpan}; @@ -18,8 +18,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; use rustc_session::declare_lint_pass; +use rustc_span::Span; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; @@ -268,6 +268,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), _ => return check_ptr_eq(cx, expr, op.node, l, r), }; + let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; span_lint_and_sugg( cx, @@ -275,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { expr.span, "comparing with null is better expressed by the `.is_null()` method", "try", - format!("{non_null_path_snippet}.is_null()"), + format!("{invert}{non_null_path_snippet}.is_null()",), Applicability::MachineApplicable, ); } @@ -299,7 +300,7 @@ struct PtrArg<'tcx> { emission_id: HirId, span: Span, ty_name: Symbol, - method_renames: &'static [(&'static str, &'static str)], + method_renames: &'static [(Symbol, &'static str)], ref_prefix: RefPrefix, deref_ty: DerefTy<'tcx>, } @@ -385,6 +386,7 @@ impl<'tcx> DerefTy<'tcx> { } } +#[expect(clippy::too_many_lines)] fn check_fn_args<'cx, 'tcx: 'cx>( cx: &'cx LateContext<'tcx>, fn_sig: ty::FnSig<'tcx>, @@ -409,7 +411,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { Some(sym::Vec) => ( - [("clone", ".to_owned()")].as_slice(), + [(sym::clone, ".to_owned()")].as_slice(), DerefTy::Slice( name.args.and_then(|args| args.args.first()).and_then(|arg| { if let GenericArg::Type(ty) = arg { @@ -421,10 +423,14 @@ fn check_fn_args<'cx, 'tcx: 'cx>( args.type_at(0), ), ), - _ if Some(adt.did()) == cx.tcx.lang_items().string() => { - ([("clone", ".to_owned()"), ("as_str", "")].as_slice(), DerefTy::Str) - }, - Some(sym::PathBuf) => ([("clone", ".to_path_buf()"), ("as_path", "")].as_slice(), DerefTy::Path), + _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( + [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), + DerefTy::Str, + ), + Some(sym::PathBuf) => ( + [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), + DerefTy::Path, + ), Some(sym::Cow) if mutability == Mutability::Not => { if let Some((lifetime, ty)) = name.args.and_then(|args| { if let [GenericArg::Lifetime(lifetime), ty] = args.args { @@ -595,7 +601,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind && receiver.hir_id == child_id { - let name = name.ident.as_str(); + let name = name.ident.name; // Check if the method can be renamed. if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index 49b522994fbf7..6b1dc864fb7a7 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; -use clippy_utils::get_enclosing_block; 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; @@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { diag.span_suggestion( expr.span, "try", - format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")), + format!("{}.resize({len}, 0); {}", ident, snippet(cx, expr.span, "..")), applicability, ); }, @@ -106,7 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { "try", format!( "{}.resize({}, 0); {}", - ident.as_str(), + ident, snippet(cx, e.span, ".."), snippet(cx, expr.span, "..") ), @@ -142,8 +142,8 @@ impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> { if let ExprKind::MethodCall(path, receiver, args, _) = e.kind { let PathSegment { ident, .. } = *path; - match ident.as_str() { - "read" | "read_exact" => { + match ident.name { + sym::read | sym::read_exact => { let [arg] = args else { return }; if let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind @@ -155,7 +155,7 @@ impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> { return; } }, - "resize" => { + sym::resize => { // If the Vec is resized, then it's a valid read if let ExprKind::Path(QPath::Resolved(_, inner_path)) = receiver.kind && let Res::Local(res_id) = inner_path.res diff --git a/clippy_lints/src/reserve_after_initialization.rs b/clippy_lints/src/reserve_after_initialization.rs index 152d7450f5ff5..51adbbcd58bdb 100644 --- a/clippy_lints/src/reserve_after_initialization.rs +++ b/clippy_lints/src/reserve_after_initialization.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; -use clippy_utils::{is_from_proc_macro, path_to_local_id}; +use clippy_utils::{is_from_proc_macro, path_to_local_id, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, LetStmt, PatKind, QPath, Stmt, StmtKind}; @@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for ReserveAfterInitialization { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [space_hint], _) = expr.kind && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "reserve" + && name.ident.name == sym::reserve && !is_from_proc_macro(cx, expr) { self.searcher = Some(VecReserveSearcher { diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index d85f4a8fa0160..f6c128d4c5298 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -143,7 +143,7 @@ impl LateLintPass<'_> for SemicolonBlock { StmtKind::Expr(Expr { kind: ExprKind::Block(block, _), .. - }) if !block.span.from_expansion() => { + }) if !block.span.from_expansion() && stmt.span.contains(block.span) => { let Block { expr: None, stmts: [.., stmt], diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index a64b9b223786b..b36a5d6d502ea 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::paths; +use clippy_utils::{paths, sym}; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -36,9 +36,9 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi { let mut seen_str = None; let mut seen_string = None; for item in *items { - match item.ident.as_str() { - "visit_str" => seen_str = Some(item.span), - "visit_string" => seen_string = Some(item.span), + match item.ident.name { + sym::visit_str => seen_str = Some(item.span), + sym::visit_string => seen_string = Some(item.span), _ => {}, } } diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 3d39386ecf909..442b3250d865f 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -1,13 +1,13 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::Msrv; use rustc_attr_data_structures::{StabilityLevel, StableSince}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::DefId; -use rustc_hir::{HirId, Path, PathSegment}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_hir::{Block, Body, HirId, Path, PathSegment}; +use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::symbol::kw; use rustc_span::{Span, sym}; @@ -88,24 +88,35 @@ declare_clippy_lint! { } pub struct StdReexports { - // Paths which can be either a module or a macro (e.g. `std::env`) will cause this check to happen - // twice. First for the mod, second for the macro. This is used to avoid the lint reporting for the macro - // when the path could be also be used to access the module. - prev_span: Span, + lint_point: (Span, Option), msrv: Msrv, } impl StdReexports { pub fn new(conf: &'static Conf) -> Self { Self { - prev_span: Span::default(), + lint_point: Default::default(), msrv: conf.msrv, } } + + fn lint_if_finish(&mut self, cx: &LateContext<'_>, (span, item): (Span, Option)) { + if span.source_equal(self.lint_point.0) { + return; + } + + if !self.lint_point.0.is_dummy() { + emit_lints(cx, &self.lint_point); + } + + self.lint_point = (span, item); + } } impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); +type LintPoint = (&'static Lint, &'static str, &'static str); + impl<'tcx> LateLintPass<'tcx> for StdReexports { fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { if let Res::Def(_, def_id) = path.res @@ -119,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { sym::core => (STD_INSTEAD_OF_CORE, "std", "core"), sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"), _ => { - self.prev_span = first_segment.ident.span; + self.lint_if_finish(cx, (first_segment.ident.span, None)); return; }, }, @@ -127,32 +138,44 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { if cx.tcx.crate_name(def_id.krate) == sym::core { (ALLOC_INSTEAD_OF_CORE, "alloc", "core") } else { - self.prev_span = first_segment.ident.span; + self.lint_if_finish(cx, (first_segment.ident.span, None)); return; } }, _ => return, }; - if first_segment.ident.span != self.prev_span { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - lint, - first_segment.ident.span, - format!("used import from `{used_mod}` instead of `{replace_with}`"), - |diag| { - diag.span_suggestion( - first_segment.ident.span, - format!("consider importing the item from `{replace_with}`"), - replace_with.to_string(), - Applicability::MachineApplicable, - ); - }, - ); - self.prev_span = first_segment.ident.span; - } + + self.lint_if_finish(cx, (first_segment.ident.span, Some((lint, used_mod, replace_with)))); } } + + fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } +} + +fn emit_lints(cx: &LateContext<'_>, (span, item): &(Span, Option)) { + let Some((lint, used_mod, replace_with)) = item else { + return; + }; + + span_lint_and_sugg( + cx, + lint, + *span, + format!("used import from `{used_mod}` instead of `{replace_with}`"), + format!("consider importing the item from `{replace_with}`"), + (*replace_with).to_string(), + Applicability::MachineApplicable, + ); } /// Returns the first named segment of a [`Path`]. diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 73a9fe71e0018..1cda6f596f4c5 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -560,7 +560,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { && let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id) && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind - && let trim_fn_name @ ("trim" | "trim_start" | "trim_end") = path.ident.name.as_str() + && let trim_fn_name @ (sym::trim | sym::trim_start | sym::trim_end) = path.ident.name && let Some(trim_def_id) = tyckres.type_dependent_def_id(split_recv.hir_id) && is_one_of_trim_diagnostic_items(cx, trim_def_id) { diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index e3ecd6508bf9a..5ecbb56925ec6 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -3,7 +3,7 @@ use clippy_utils::source::{snippet_indent, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, std_or_core}; +use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, path_to_local, std_or_core}; use itertools::Itertools; use rustc_data_structures::fx::FxIndexSet; @@ -350,12 +350,21 @@ impl<'tcx> IndexBinding<'_, 'tcx> { format!("{lhs_snippet}{rhs_snippet}") }, ExprKind::Path(QPath::Resolved(_, path)) => { - let init = self.cx.expr_or_init(expr); - let Some(first_segment) = path.segments.first() else { return String::new(); }; - if !self.suggest_span.contains(init.span) || !self.is_used_other_than_swapping(first_segment.ident) { + + let init = self.cx.expr_or_init(expr); + + // We skip suggesting a variable binding in any of these cases: + // - Variable initialization is outside the suggestion span + // - Variable declaration is outside the suggestion span + // - Variable is not used as an index or elsewhere later + if !self.suggest_span.contains(init.span) + || path_to_local(expr) + .is_some_and(|hir_id| !self.suggest_span.contains(self.cx.tcx.hir_span(hir_id))) + || !self.is_used_other_than_swapping(first_segment.ident) + { return String::new(); } diff --git a/clippy_lints/src/transmute/missing_transmute_annotations.rs b/clippy_lints/src/transmute/missing_transmute_annotations.rs index 96286fcf73d67..08f36a2ed5d2e 100644 --- a/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -74,7 +74,7 @@ pub(super) fn check<'tcx>( last.ident.span.with_hi(path.span.hi()), "transmute used without annotations", "consider adding missing annotations", - format!("{}::<{from_ty}, {to_ty}>", last.ident.as_str()), + format!("{}::<{from_ty}, {to_ty}>", last.ident), Applicability::MaybeIncorrect, ); true diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index 004ad03e70858..4b23367645eb0 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -33,7 +33,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m let ltopt = if lt.is_anonymous() { String::new() } else { - format!("{} ", lt.ident.as_str()) + format!("{} ", lt.ident) }; if mut_ty.mutbl == Mutability::Mut { diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index 019ae16ca8591..ae6d8a1c1aa3f 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -1,9 +1,14 @@ +use std::iter; + use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_from_proc_macro; -use clippy_utils::source::{SourceText, SpanRangeExt, indent_of, reindent_multiline}; +use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::expr_type_is_certain; +use clippy_utils::{is_expr_default, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind}; use rustc_lint::LateContext; +use rustc_span::SyntaxContext; use super::{UNIT_ARG, utils}; @@ -59,7 +64,7 @@ fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { } } -fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) { +fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_recover: &[&'tcx Expr<'tcx>]) { let mut applicability = Applicability::MachineApplicable; let (singular, plural) = if args_to_recover.len() > 1 { ("", "s") @@ -100,34 +105,41 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp let arg_snippets: Vec<_> = args_to_recover .iter() - .filter_map(|arg| arg.span.get_source_text(cx)) + // If the argument is from an expansion and is a `Default::default()`, we skip it + .filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg)) + .filter_map(|arg| get_expr_snippet(cx, arg)) .collect(); - let arg_snippets_without_empty_blocks: Vec<_> = args_to_recover + + // If the argument is an empty block or `Default::default()`, we can replace it with `()`. + let arg_snippets_without_redundant_exprs: Vec<_> = args_to_recover .iter() - .filter(|arg| !is_empty_block(arg)) - .filter_map(|arg| arg.span.get_source_text(cx)) + .filter(|arg| !is_expr_default_nested(cx, arg) && (arg.span.from_expansion() || !is_empty_block(arg))) + .filter_map(|arg| get_expr_snippet_with_type_certainty(cx, arg)) .collect(); if let Some(call_snippet) = expr.span.get_source_text(cx) { - let sugg = fmt_stmts_and_call( - cx, - expr, - &call_snippet, - &arg_snippets, - &arg_snippets_without_empty_blocks, - ); - - if arg_snippets_without_empty_blocks.is_empty() { + if arg_snippets_without_redundant_exprs.is_empty() + && let suggestions = args_to_recover + .iter() + .filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg)) + .map(|arg| (arg.span.parent_callsite().unwrap_or(arg.span), "()".to_string())) + .collect::>() + && !suggestions.is_empty() + { db.multipart_suggestion( format!("use {singular}unit literal{plural} instead"), - args_to_recover - .iter() - .map(|arg| (arg.span, "()".to_string())) - .collect::>(), + suggestions, applicability, ); } else { - let plural = arg_snippets_without_empty_blocks.len() > 1; + let plural = arg_snippets_without_redundant_exprs.len() > 1; + let sugg = fmt_stmts_and_call( + cx, + expr, + &call_snippet, + arg_snippets, + arg_snippets_without_redundant_exprs, + ); let empty_or_s = if plural { "s" } else { "" }; let it_or_them = if plural { "them" } else { "it" }; db.span_suggestion( @@ -144,6 +156,55 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp ); } +fn is_expr_default_nested<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + is_expr_default(cx, expr) + || matches!(expr.kind, ExprKind::Block(block, _) + if block.expr.is_some() && is_expr_default_nested(cx, block.expr.unwrap())) +} + +enum MaybeTypeUncertain<'tcx> { + Certain(Sugg<'tcx>), + Uncertain(Sugg<'tcx>), +} + +impl From> for String { + fn from(value: MaybeTypeUncertain<'_>) -> Self { + match value { + MaybeTypeUncertain::Certain(sugg) => sugg.to_string(), + MaybeTypeUncertain::Uncertain(sugg) => format!("let _: () = {sugg}"), + } + } +} + +fn get_expr_snippet<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option> { + let mut app = Applicability::MachineApplicable; + let snip = Sugg::hir_with_context(cx, expr, SyntaxContext::root(), "..", &mut app); + if app != Applicability::MachineApplicable { + return None; + } + + Some(snip) +} + +fn get_expr_snippet_with_type_certainty<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option> { + get_expr_snippet(cx, expr).map(|snip| { + // If the type of the expression is certain, we can use it directly. + // Otherwise, we wrap it in a `let _: () = ...` to ensure the type is correct. + if !expr_type_is_certain(cx, expr) && !is_block_with_no_expr(expr) { + MaybeTypeUncertain::Uncertain(snip) + } else { + MaybeTypeUncertain::Certain(snip) + } + }) +} + +fn is_block_with_no_expr(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Block(Block { expr: None, .. }, _)) +} + fn is_empty_block(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -162,23 +223,20 @@ fn fmt_stmts_and_call( cx: &LateContext<'_>, call_expr: &Expr<'_>, call_snippet: &str, - args_snippets: &[SourceText], - non_empty_block_args_snippets: &[SourceText], + args_snippets: Vec>, + non_empty_block_args_snippets: Vec>, ) -> String { let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0); - let call_snippet_with_replacements = args_snippets - .iter() - .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1)); + let call_snippet_with_replacements = args_snippets.into_iter().fold(call_snippet.to_owned(), |acc, arg| { + acc.replacen(&arg.to_string(), "()", 1) + }); - let mut stmts_and_call = non_empty_block_args_snippets - .iter() - .map(|it| it.as_ref().to_owned()) - .collect::>(); - stmts_and_call.push(call_snippet_with_replacements); - stmts_and_call = stmts_and_call + let stmts_and_call = non_empty_block_args_snippets .into_iter() + .map(Into::into) + .chain(iter::once(call_snippet_with_replacements)) .map(|v| reindent_multiline(&v, true, Some(call_expr_indent))) - .collect(); + .collect::>(); let mut stmts_and_call_snippet = stmts_and_call.join(&format!("{}{}", ";\n", " ".repeat(call_expr_indent))); // expr is not in a block statement or result expression position, wrap in a block diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 5e1cb9e54f57a..12cc109389939 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, paths, peel_blocks}; +use clippy_utils::{is_res_lang_ctor, paths, peel_blocks, sym}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -232,8 +232,16 @@ fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { while let ExprKind::MethodCall(path, receiver, ..) = expr.kind { if matches!( - path.ident.as_str(), - "unwrap" | "expect" | "unwrap_or" | "unwrap_or_else" | "ok" | "is_ok" | "is_err" | "or_else" | "or" + path.ident.name, + sym::unwrap + | sym::expect + | sym::unwrap_or + | sym::unwrap_or_else + | sym::ok + | sym::is_ok + | sym::is_err + | sym::or_else + | sym::or ) { expr = receiver; } else { diff --git a/clippy_lints/src/unused_result_ok.rs b/clippy_lints/src/unused_result_ok.rs index 958f19d183357..f5ed10fb76094 100644 --- a/clippy_lints/src/unused_result_ok.rs +++ b/clippy_lints/src/unused_result_ok.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; +use clippy_utils::sym; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -36,7 +36,7 @@ impl LateLintPass<'_> for UnusedResultOk { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(ok_path, recv, [], ..) = expr.kind //check is expr.ok() has type Result.ok(, _) - && ok_path.ident.as_str() == "ok" + && ok_path.ident.name == sym::ok && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) && !stmt.span.in_external_macro(cx.sess().source_map()) { diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 9859ddfdf7bde..3811f0fe6b558 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -100,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { let segments = &poly.trait_ref.path.segments; if segments.len() == 1 - && ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str()) + && matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce) && let Some(args) = segments[0].args && args.parenthesized == GenericArgsParentheses::ParenSugar && let constraints = &args.constraints @@ -109,6 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind && args.span_ext.hi() != poly.span.hi() && !hir_ty.span.from_expansion() + && args.span_ext.hi() != hir_ty.span.hi() && is_unit_ty(hir_ty) { lint_unneeded_unit_return(cx, hir_ty.span, poly.span); diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index 3c23662e9d168..8d873536295dc 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{get_parent_expr, path_to_local_id}; +use clippy_utils::{get_parent_expr, path_to_local_id, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -202,7 +202,7 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [_], _) = expr.kind && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "push" + && name.ident.name == sym::push { self.searcher = Some(VecPushSearcher { err_span: searcher.err_span.to(stmt.span), diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 467811c586bf9..d9dda6eadb2a5 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -9,8 +9,8 @@ use rustc_hir::{Item, ItemKind, PathSegment, UseKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::impl_lint_pass; +use rustc_span::BytePos; use rustc_span::symbol::kw; -use rustc_span::{BytePos, sym}; declare_clippy_lint! { /// ### What it does @@ -193,9 +193,7 @@ impl WildcardImports { // Allow "...prelude::..::*" imports. // Many crates have a prelude, and it is imported as a glob by design. fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool { - segments - .iter() - .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str())) + segments.iter().any(|ps| ps.ident.as_str().contains("prelude")) } // Allow "super::*" imports in tests. diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index a8758b65c9192..d9a007635cae1 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -5,8 +5,8 @@ use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; use clippy_utils::{is_in_test, sym}; use rustc_ast::token::LitKind; use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, - FormatTrait, + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, }; use rustc_errors::Applicability; use rustc_hir::{Expr, Impl, Item, ItemKind}; @@ -556,12 +556,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { // Decrement the index of the remaining by the number of replaced positional arguments if !suggestion.is_empty() { for piece in &format_args.template { - if let Some((span, index)) = positional_arg_piece_span(piece) - && suggestion.iter().all(|(s, _)| *s != span) - { - let decrement = replaced_position.iter().filter(|i| **i < index).count(); - suggestion.push((span, format!("{{{}}}", index.saturating_sub(decrement)))); - } + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); } } @@ -574,7 +569,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { } } -/// Extract Span and its index from the given `piece`, iff it's positional argument. +/// Extract Span and its index from the given `piece`, if it's positional argument. fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { match piece { FormatArgsPiece::Placeholder(FormatPlaceholder { @@ -591,6 +586,57 @@ fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { } } +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + /// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw /// /// `r#"a"#` -> (`a`, true) diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 09f1084fe7004..6ab94a5221092 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -5,7 +5,7 @@ use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_local}; +use rustc_hir::intravisit::{Visitor, walk_block, walk_expr}; use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -69,8 +69,9 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { let mut vis = WaitFinder { cx, local_id, + create_id: expr.hir_id, body_id: cx.tcx.hir_enclosing_body_owner(expr.hir_id), - state: VisitorState::WalkUpToLocal, + state: VisitorState::WalkUpToCreate, early_return: None, missing_wait_branch: None, }; @@ -131,6 +132,7 @@ struct MaybeWait(Span); struct WaitFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, local_id: HirId, + create_id: HirId, body_id: LocalDefId, state: VisitorState, early_return: Option, @@ -141,8 +143,8 @@ struct WaitFinder<'a, 'tcx> { #[derive(PartialEq)] enum VisitorState { - WalkUpToLocal, - LocalFound, + WalkUpToCreate, + CreateFound, } #[derive(Copy, Clone)] @@ -155,19 +157,13 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; type Result = ControlFlow; - fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result { - if self.state == VisitorState::WalkUpToLocal - && let PatKind::Binding(_, pat_id, ..) = l.pat.kind - && self.local_id == pat_id - { - self.state = VisitorState::LocalFound; + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { + if ex.hir_id == self.create_id { + self.state = VisitorState::CreateFound; + return Continue(()); } - walk_local(self, l) - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { - if self.state != VisitorState::LocalFound { + if self.state != VisitorState::CreateFound { return walk_expr(self, ex); } diff --git a/clippy_lints_internal/src/almost_standard_lint_formulation.rs b/clippy_lints_internal/src/almost_standard_lint_formulation.rs index 311f76fee6b8b..7eeec84720f72 100644 --- a/clippy_lints_internal/src/almost_standard_lint_formulation.rs +++ b/clippy_lints_internal/src/almost_standard_lint_formulation.rs @@ -45,7 +45,7 @@ impl AlmostStandardFormulation { impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { let mut check_next = false; - if let ItemKind::Static(_, ty, Mutability::Not, _) = item.kind { + if let ItemKind::Static(Mutability::Not, _, ty, _) = item.kind { let lines = cx .tcx .hir_attrs(item.hir_id()) diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs index 0edeef3ab8559..45a866030b2dd 100644 --- a/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -105,7 +105,7 @@ impl_lint_pass!(LintWithoutLintPass => [ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let hir::ItemKind::Static(ident, ty, Mutability::Not, body_id) = item.kind { + if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind { if is_lint_ref_type(cx, ty) { check_invalid_clippy_version_attribute(cx, item); diff --git a/clippy_utils/README.md b/clippy_utils/README.md index efbacbd72dba5..1aa16e3943c46 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-05-31 +nightly-2025-06-12 ``` diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f6ef638e618a4..c7a2375c8df7c 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1793,10 +1793,9 @@ pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool { /// Checks if the given `DefId` matches the `libc` item. pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: Symbol) -> bool { - let path = cx.get_def_path(did); // libc is meant to be used as a flat list of names, but they're all actually defined in different // modules based on the target platform. Ignore everything but crate name and the item name. - path.first().is_some_and(|s| *s == sym::libc) && path.last().copied() == Some(name) + cx.tcx.crate_name(did.krate) == sym::libc && cx.tcx.def_path_str(did).ends_with(name.as_str()) } /// Returns the list of condition expressions and the list of blocks in a @@ -3473,3 +3472,15 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { None } } + +/// Checks if the given expression is a call to `Default::default()`. +pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if let ExprKind::Call(fn_expr, []) = &expr.kind + && let ExprKind::Path(qpath) = &fn_expr.kind + && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id) + { + cx.tcx.is_diagnostic_item(sym::default_fn, def_id) + } else { + false + } +} diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 8e16f943e9fc3..e629012b187cd 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -18,7 +18,7 @@ use rustc_middle::mir::{ }; use rustc_middle::traits::{BuiltinImplSource, ImplSource, ObligationCause}; use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::{self, GenericArgKind, TraitRef, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArgKind, Instance, TraitRef, Ty, TyCtxt}; use rustc_span::Span; use rustc_span::symbol::sym; use rustc_trait_selection::traits::{ObligationCtxt, SelectionContext}; @@ -349,7 +349,15 @@ fn check_terminator<'tcx>( } | TerminatorKind::TailCall { func, args, fn_span: _ } => { let fn_ty = func.ty(body, cx.tcx); - if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { + if let ty::FnDef(fn_def_id, fn_substs) = fn_ty.kind() { + // FIXME: when analyzing a function with generic parameters, we may not have enough information to + // resolve to an instance. However, we could check if a host effect predicate can guarantee that + // this can be made a `const` call. + let fn_def_id = match Instance::try_resolve(cx.tcx, cx.typing_env(), *fn_def_id, fn_substs) { + Ok(Some(fn_inst)) => fn_inst.def_id(), + Ok(None) => return Err((span, format!("cannot resolve instance for {func:?}").into())), + Err(_) => return Err((span, format!("error during instance resolution of {func:?}").into())), + }; if !is_stable_const_fn(cx, fn_def_id, msrv) { return Err(( span, diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index f417530be3672..a544954931bad 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -84,6 +84,7 @@ generate! { as_deref, as_deref_mut, as_mut, + as_path, assert_failed, author, borrow, @@ -121,6 +122,8 @@ generate! { copy_to, copy_to_nonoverlapping, count_ones, + create, + create_new, cycle, cyclomatic_complexity, de, @@ -132,7 +135,6 @@ generate! { enum_glob_use, enumerate, err, - error, exp, expect_err, expn_data, @@ -150,8 +152,11 @@ generate! { floor_char_boundary, fold, for_each, + from_be_bytes, from_bytes_with_nul, from_bytes_with_nul_unchecked, + from_le_bytes, + from_ne_bytes, from_ptr, from_raw, from_ref, @@ -184,8 +189,10 @@ generate! { is_err, is_file, is_none, + is_none_or, is_ok, is_some, + is_some_and, isqrt, itertools, join, @@ -252,9 +259,12 @@ generate! { powi, product, push, + read, + read_exact, read_line, read_to_end, read_to_string, + read_unaligned, redundant_pub_crate, regex, rem_euclid, @@ -323,16 +333,22 @@ generate! { then_some, to_ascii_lowercase, to_ascii_uppercase, + to_be_bytes, to_digit, + to_le_bytes, to_lowercase, + to_ne_bytes, to_os_string, to_owned, to_path_buf, to_uppercase, tokio, trim, + trim_end, trim_end_matches, + trim_start, trim_start_matches, + truncate, unreachable_pub, unsafe_removed_from_name, unused, @@ -347,12 +363,15 @@ generate! { unwrap_unchecked, unzip, utils, + visit_str, + visit_string, wake, warnings, wildcard_imports, with_capacity, wrapping_offset, write, + write_unaligned, writeln, zip, } diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 61e70b3fa0bce..1f0a0f2b02ac0 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -1137,21 +1137,38 @@ impl<'tcx> InteriorMut<'tcx> { /// Check if given type has interior mutability such as [`std::cell::Cell`] or /// [`std::cell::RefCell`] etc. and if it does, returns a chain of types that causes - /// this type to be interior mutable + /// this type to be interior mutable. False negatives may be expected for infinitely recursive + /// types, and `None` will be returned there. pub fn interior_mut_ty_chain(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx ty::List>> { + self.interior_mut_ty_chain_inner(cx, ty, 0) + } + + fn interior_mut_ty_chain_inner( + &mut self, + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + depth: usize, + ) -> Option<&'tcx ty::List>> { + if !cx.tcx.recursion_limit().value_within_limit(depth) { + return None; + } + match self.tys.entry(ty) { Entry::Occupied(o) => return *o.get(), // Temporarily insert a `None` to break cycles Entry::Vacant(v) => v.insert(None), }; + let depth = depth + 1; let chain = match *ty.kind() { - ty::RawPtr(inner_ty, _) if !self.ignore_pointers => self.interior_mut_ty_chain(cx, inner_ty), - ty::Ref(_, inner_ty, _) | ty::Slice(inner_ty) => self.interior_mut_ty_chain(cx, inner_ty), + ty::RawPtr(inner_ty, _) if !self.ignore_pointers => self.interior_mut_ty_chain_inner(cx, inner_ty, depth), + ty::Ref(_, inner_ty, _) | ty::Slice(inner_ty) => self.interior_mut_ty_chain_inner(cx, inner_ty, depth), ty::Array(inner_ty, size) if size.try_to_target_usize(cx.tcx) != Some(0) => { - self.interior_mut_ty_chain(cx, inner_ty) + self.interior_mut_ty_chain_inner(cx, inner_ty, depth) }, - ty::Tuple(fields) => fields.iter().find_map(|ty| self.interior_mut_ty_chain(cx, ty)), + ty::Tuple(fields) => fields + .iter() + .find_map(|ty| self.interior_mut_ty_chain_inner(cx, ty, depth)), ty::Adt(def, _) if def.is_unsafe_cell() => Some(ty::List::empty()), ty::Adt(def, args) => { let is_std_collection = matches!( @@ -1171,16 +1188,17 @@ impl<'tcx> InteriorMut<'tcx> { if is_std_collection || def.is_box() { // Include the types from std collections that are behind pointers internally - args.types().find_map(|ty| self.interior_mut_ty_chain(cx, ty)) + args.types() + .find_map(|ty| self.interior_mut_ty_chain_inner(cx, ty, depth)) } else if self.ignored_def_ids.contains(&def.did()) || def.is_phantom_data() { None } else { def.all_fields() - .find_map(|f| self.interior_mut_ty_chain(cx, f.ty(cx.tcx, args))) + .find_map(|f| self.interior_mut_ty_chain_inner(cx, f.ty(cx.tcx, args), depth)) } }, ty::Alias(ty::Projection, _) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) { - Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain(cx, normalized_ty), + Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain_inner(cx, normalized_ty, depth), _ => None, }, _ => None, @@ -1342,7 +1360,8 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<' mutability.is_mut() || !pointee_ty.is_freeze(cx.tcx, cx.typing_env()) }, ty::Closure(_, closure_args) => { - matches!(closure_args.types().next_back(), Some(captures) if has_non_owning_mutable_access_inner(cx, phantoms, captures)) + matches!(closure_args.types().next_back(), + Some(captures) if has_non_owning_mutable_access_inner(cx, phantoms, captures)) }, ty::Tuple(tuple_args) => tuple_args .iter() diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b6817d9a14639..3fc5a1224a8dc 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-05-31" +channel = "nightly-2025-06-12" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 78b27e2f61399..99a01257a7b69 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -273,10 +273,10 @@ fn run_ui_cargo(cx: &TestContext) { config.program.input_file_flag = CommandBuilder::cargo().input_file_flag; config.program.out_dir_flag = CommandBuilder::cargo().out_dir_flag; config.program.args = vec!["clippy".into(), "--color".into(), "never".into(), "--quiet".into()]; - config - .program - .envs - .push(("RUSTFLAGS".into(), Some("-Dwarnings".into()))); + config.program.envs.extend([ + ("RUSTFLAGS".into(), Some("-Dwarnings".into())), + ("CARGO_INCREMENTAL".into(), Some("0".into())), + ]); // We need to do this while we still have a rustc in the `program` field. config.fill_host_and_target().unwrap(); config.program.program.set_file_name(if cfg!(windows) { diff --git a/tests/symbols-used.rs b/tests/symbols-used.rs new file mode 100644 index 0000000000000..bc0456711fb2b --- /dev/null +++ b/tests/symbols-used.rs @@ -0,0 +1,81 @@ +// This test checks that all symbols defined in Clippy's `sym.rs` file +// are used in Clippy. Otherwise, it will fail with a list of symbols +// which are unused. +// +// This test is a no-op if run as part of the compiler test suite +// and will always succeed. + +use std::collections::HashSet; +use std::ffi::OsStr; +use std::fs; + +use regex::Regex; +use walkdir::{DirEntry, WalkDir}; + +const SYM_FILE: &str = "clippy_utils/src/sym.rs"; + +type Result = std::result::Result; +type AnyError = Box; + +#[test] +#[allow(clippy::case_sensitive_file_extension_comparisons)] +fn all_symbols_are_used() -> Result<()> { + if option_env!("RUSTC_TEST_SUITE").is_some() { + return Ok(()); + } + + // Load all symbols defined in `SYM_FILE`. + let content = fs::read_to_string(SYM_FILE)?; + let content = content + .split_once("generate! {") + .ok_or("cannot find symbols start")? + .1 + .split_once("\n}\n") + .ok_or("cannot find symbols end")? + .0; + let mut interned: HashSet = Regex::new(r"(?m)^ (\w+)") + .unwrap() + .captures_iter(content) + .map(|m| m[1].to_owned()) + .collect(); + + // Remove symbols used as `sym::*`. + let used_re = Regex::new(r"\bsym::(\w+)\b").unwrap(); + let rs_ext = OsStr::new("rs"); + for dir in ["clippy_lints", "clippy_lints_internal", "clippy_utils", "src"] { + for file in WalkDir::new(dir) + .into_iter() + .flatten() + .map(DirEntry::into_path) + .filter(|p| p.extension() == Some(rs_ext)) + { + for cap in used_re.captures_iter(&fs::read_to_string(file)?) { + interned.remove(&cap[1]); + } + } + } + + // Remove symbols used as part of paths. + let paths_re = Regex::new(r"path!\(([\w:]+)\)").unwrap(); + for path in [ + "clippy_utils/src/paths.rs", + "clippy_lints_internal/src/internal_paths.rs", + ] { + for cap in paths_re.captures_iter(&fs::read_to_string(path)?) { + for sym in cap[1].split("::") { + interned.remove(sym); + } + } + } + + let mut extra = interned.iter().collect::>(); + if !extra.is_empty() { + extra.sort_unstable(); + eprintln!("Unused symbols defined in {SYM_FILE}:"); + for sym in extra { + eprintln!(" - {sym}"); + } + Err(format!("extra symbols found — remove them {SYM_FILE}"))?; + } + Ok(()) +} diff --git a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs index 6a9a49324db90..4a179cd929f07 100644 --- a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs +++ b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs @@ -1,4 +1,5 @@ #![warn(clippy::await_holding_invalid_type)] +#![allow(clippy::ip_constant)] use std::net::Ipv4Addr; async fn bad() -> u32 { diff --git a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr index deb7f49db9e11..c3c88698032c8 100644 --- a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr +++ b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr @@ -1,5 +1,5 @@ error: holding a disallowed type across an await point `std::string::String` - --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:5:9 + --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:6:9 | LL | let _x = String::from("hello"); | ^^ @@ -9,13 +9,13 @@ LL | let _x = String::from("hello"); = help: to override `-D warnings` add `#[allow(clippy::await_holding_invalid_type)]` error: holding a disallowed type across an await point `std::net::Ipv4Addr` - --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:11:9 + --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:12:9 | LL | let x = Ipv4Addr::new(127, 0, 0, 1); | ^ error: holding a disallowed type across an await point `std::string::String` - --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:35:13 + --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:36:13 | LL | let _x = String::from("hi!"); | ^^ diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.rs b/tests/ui/branches_sharing_code/shared_at_bottom.rs index 06472a4f5d572..922d30443fcc2 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_bottom.rs @@ -239,3 +239,40 @@ fn fp_if_let_issue7054() { } fn main() {} + +mod issue14873 { + fn foo() -> i32 { + todo!() + } + + macro_rules! qux { + ($a:ident, $b:ident, $condition:expr) => { + if $condition { + "." + } else { + "" + }; + $a = foo(); + $b = foo(); + }; + } + + fn share_on_bottom() { + let mut a = 0; + let mut b = 0; + if false { + qux!(a, b, a == b); + } else { + qux!(a, b, a != b); + }; + + if false { + qux!(a, b, a == b); + let y = 1; + } else { + qux!(a, b, a != b); + let y = 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 648a99c65ed22..f437db8b73313 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -157,5 +157,20 @@ LL ~ if x == 17 { b = 1; a = 0x99; } else { } LL + a = 0x99; | -error: aborting due to 9 previous errors +error: all if blocks contain the same code at the end + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:274:9 + | +LL | / let y = 1; +LL | | +LL | | } + | |_________^ + | + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements after the if + | +LL ~ } +LL + let y = 1; + | + +error: aborting due to 10 previous errors diff --git a/tests/ui/branches_sharing_code/shared_at_top.rs b/tests/ui/branches_sharing_code/shared_at_top.rs index 694c67d4c85b3..dcd77679fc691 100644 --- a/tests/ui/branches_sharing_code/shared_at_top.rs +++ b/tests/ui/branches_sharing_code/shared_at_top.rs @@ -124,3 +124,34 @@ fn pf_local_with_inferred_type_issue7053() { } fn main() {} + +mod issue14873 { + fn foo() -> i32 { + todo!() + } + + macro_rules! qux { + ($a:ident, $b:ident, $condition:expr) => { + let $a: i32 = foo(); + let $b: i32 = foo(); + if $condition { "." } else { "" } + }; + } + + fn share_on_top() { + if false { + qux!(a, b, a == b); + } else { + qux!(a, b, a != b); + }; + + if false { + //~^ branches_sharing_code + let x = 1; + qux!(a, b, a == b); + } else { + let x = 1; + qux!(a, b, a != b); + } + } +} diff --git a/tests/ui/branches_sharing_code/shared_at_top.stderr b/tests/ui/branches_sharing_code/shared_at_top.stderr index d28e9c7af2967..30efb98b3f5f6 100644 --- a/tests/ui/branches_sharing_code/shared_at_top.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top.stderr @@ -125,5 +125,20 @@ note: the lint level is defined here LL | #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: all if blocks contain the same code at the start + --> tests/ui/branches_sharing_code/shared_at_top.rs:148:9 + | +LL | / if false { +LL | | +LL | | let x = 1; + | |______________________^ + | + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if + | +LL ~ let x = 1; +LL + if false { + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs index 75334f70f1f62..e848f0601e32d 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs @@ -128,3 +128,42 @@ fn added_note_for_expression_use() -> u32 { } fn main() {} + +mod issue14873 { + fn foo() -> i32 { + todo!() + } + + macro_rules! qux { + ($a:ident, $b:ident, $condition:expr) => { + let mut $a: i32 = foo(); + let mut $b: i32 = foo(); + if $condition { + "." + } else { + "" + }; + $a = foo(); + $b = foo(); + }; + } + + fn share_on_top_and_bottom() { + if false { + qux!(a, b, a == b); + } else { + qux!(a, b, a != b); + }; + + if false { + //~^ branches_sharing_code + let x = 1; + qux!(a, b, a == b); + let y = 1; + } else { + let x = 1; + qux!(a, b, a != b); + let y = 1; + } + } +} diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 2200ab450890c..40f3453edb9ae 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -159,5 +159,31 @@ LL ~ } LL + x * 4 | -error: aborting due to 5 previous errors +error: all if blocks contain the same code at both the start and the end + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:158:9 + | +LL | / if false { +LL | | +LL | | let x = 1; + | |______________________^ + | +note: this code is shared at the end + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:166:9 + | +LL | / let y = 1; +LL | | } + | |_________^ + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if + | +LL ~ let x = 1; +LL + if false { + | +help: consider moving these statements after the if + | +LL ~ } +LL + let y = 1; + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 140ddb10aeb86..04b8ec50160b3 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -33,3 +33,9 @@ fn main() { let _ = (x as *const ()).is_null(); //~^ cmp_null } + +fn issue15010() { + let f: *mut i32 = std::ptr::null_mut(); + debug_assert!(!f.is_null()); + //~^ cmp_null +} diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 16ed17765dace..6f7762e6ae831 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -33,3 +33,9 @@ fn main() { let _ = x as *const () == ptr::null(); //~^ cmp_null } + +fn issue15010() { + let f: *mut i32 = std::ptr::null_mut(); + debug_assert!(f != std::ptr::null_mut()); + //~^ cmp_null +} diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 6821846d0466d..8a75b05011193 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -31,5 +31,11 @@ error: comparing with null is better expressed by the `.is_null()` method LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` -error: aborting due to 5 previous errors +error: comparing with null is better expressed by the `.is_null()` method + --> tests/ui/cmp_null.rs:39:19 + | +LL | debug_assert!(f != std::ptr::null_mut()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` + +error: aborting due to 6 previous errors diff --git a/tests/ui/coerce_container_to_any.fixed b/tests/ui/coerce_container_to_any.fixed new file mode 100644 index 0000000000000..ae9d3ef9656f1 --- /dev/null +++ b/tests/ui/coerce_container_to_any.fixed @@ -0,0 +1,26 @@ +#![warn(clippy::coerce_container_to_any)] + +use std::any::Any; + +fn main() { + let x: Box = Box::new(()); + let ref_x = &x; + + f(&*x); + //~^ coerce_container_to_any + + f(&**ref_x); + //~^ coerce_container_to_any + + let _: &dyn Any = &*x; + //~^ coerce_container_to_any + + f(&42); + f(&Box::new(())); + f(&Box::new(Box::new(()))); + f(&**ref_x); + f(&*x); + let _: &dyn Any = &*x; +} + +fn f(_: &dyn Any) {} diff --git a/tests/ui/coerce_container_to_any.rs b/tests/ui/coerce_container_to_any.rs new file mode 100644 index 0000000000000..9948bd48e0d8d --- /dev/null +++ b/tests/ui/coerce_container_to_any.rs @@ -0,0 +1,26 @@ +#![warn(clippy::coerce_container_to_any)] + +use std::any::Any; + +fn main() { + let x: Box = Box::new(()); + let ref_x = &x; + + f(&x); + //~^ coerce_container_to_any + + f(ref_x); + //~^ coerce_container_to_any + + let _: &dyn Any = &x; + //~^ coerce_container_to_any + + f(&42); + f(&Box::new(())); + f(&Box::new(Box::new(()))); + f(&**ref_x); + f(&*x); + let _: &dyn Any = &*x; +} + +fn f(_: &dyn Any) {} diff --git a/tests/ui/coerce_container_to_any.stderr b/tests/ui/coerce_container_to_any.stderr new file mode 100644 index 0000000000000..00ab77e0ce0fc --- /dev/null +++ b/tests/ui/coerce_container_to_any.stderr @@ -0,0 +1,23 @@ +error: coercing `&std::boxed::Box` to `&dyn Any` + --> tests/ui/coerce_container_to_any.rs:9:7 + | +LL | f(&x); + | ^^ help: consider dereferencing: `&*x` + | + = note: `-D clippy::coerce-container-to-any` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::coerce_container_to_any)]` + +error: coercing `&std::boxed::Box` to `&dyn Any` + --> tests/ui/coerce_container_to_any.rs:12:7 + | +LL | f(ref_x); + | ^^^^^ help: consider dereferencing: `&**ref_x` + +error: coercing `&std::boxed::Box` to `&dyn Any` + --> tests/ui/coerce_container_to_any.rs:15:23 + | +LL | let _: &dyn Any = &x; + | ^^ help: consider dereferencing: `&*x` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/crashes/ice-14935.rs b/tests/ui/crashes/ice-14935.rs new file mode 100644 index 0000000000000..74cda9aae53a5 --- /dev/null +++ b/tests/ui/crashes/ice-14935.rs @@ -0,0 +1,27 @@ +//@check-pass +#![warn(clippy::mutable_key_type)] + +use std::marker::PhantomData; + +trait Group { + type ExposantSet: Group; +} + +struct Pow { + exposant: Box>, + _p: PhantomData, +} + +impl Pow { + fn is_zero(&self) -> bool { + false + } + fn normalize(&self) { + #[expect(clippy::if_same_then_else)] + if self.is_zero() { + } else if false { + } + } +} + +fn main() {} diff --git a/tests/ui/crashes/ice-9463.rs b/tests/ui/crashes/ice-9463.rs index 93808e0f8923b..cfa6cdac6fa05 100644 --- a/tests/ui/crashes/ice-9463.rs +++ b/tests/ui/crashes/ice-9463.rs @@ -1,8 +1,7 @@ -#![deny(arithmetic_overflow)] +//@check-pass + fn main() { let _x = -1_i32 >> -1; - //~^ ERROR: this arithmetic operation will overflow + #[expect(overflowing_literals)] let _y = 1u32 >> 10000000000000u32; - //~^ ERROR: this arithmetic operation will overflow - //~| ERROR: literal out of range } diff --git a/tests/ui/crashes/ice-9463.stderr b/tests/ui/crashes/ice-9463.stderr deleted file mode 100644 index 9a3a5e444ade8..0000000000000 --- a/tests/ui/crashes/ice-9463.stderr +++ /dev/null @@ -1,29 +0,0 @@ -error: this arithmetic operation will overflow - --> tests/ui/crashes/ice-9463.rs:3:14 - | -LL | let _x = -1_i32 >> -1; - | ^^^^^^^^^^^^ attempt to shift right by `-1_i32`, which would overflow - | -note: the lint level is defined here - --> tests/ui/crashes/ice-9463.rs:1:9 - | -LL | #![deny(arithmetic_overflow)] - | ^^^^^^^^^^^^^^^^^^^ - -error: this arithmetic operation will overflow - --> tests/ui/crashes/ice-9463.rs:5:14 - | -LL | let _y = 1u32 >> 10000000000000u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to shift right by `1316134912_u32`, which would overflow - -error: literal out of range for `u32` - --> tests/ui/crashes/ice-9463.rs:5:22 - | -LL | let _y = 1u32 >> 10000000000000u32; - | ^^^^^^^^^^^^^^^^^ - | - = note: the literal `10000000000000u32` does not fit into the type `u32` whose range is `0..=4294967295` - = note: `#[deny(overflowing_literals)]` on by default - -error: aborting due to 3 previous errors - diff --git a/tests/ui/crashes/ice-rust-107877.rs b/tests/ui/crashes/ice-rust-107877.rs index 55fe418bed123..dccb0cf8ac152 100644 --- a/tests/ui/crashes/ice-rust-107877.rs +++ b/tests/ui/crashes/ice-rust-107877.rs @@ -4,6 +4,7 @@ struct Foo; +#[allow(clippy::infallible_try_from)] impl<'a> std::convert::TryFrom<&'a String> for Foo { type Error = std::convert::Infallible; diff --git a/tests/ui/create_dir.fixed b/tests/ui/create_dir.fixed index 4a5b1b77be6be..d4b8f8b4d0793 100644 --- a/tests/ui/create_dir.fixed +++ b/tests/ui/create_dir.fixed @@ -7,12 +7,31 @@ fn create_dir() {} fn main() { // Should be warned - create_dir_all("foo"); + std::fs::create_dir_all("foo"); //~^ create_dir - create_dir_all("bar").unwrap(); + std::fs::create_dir_all("bar").unwrap(); //~^ create_dir // Shouldn't be warned create_dir(); std::fs::create_dir_all("foobar"); } + +mod issue14994 { + fn with_no_prefix() { + use std::fs::create_dir; + std::fs::create_dir_all("some/dir").unwrap(); + //~^ create_dir + } + + fn with_fs_prefix() { + use std::fs; + fs::create_dir_all("/some/dir").unwrap(); + //~^ create_dir + } + + fn with_full_prefix() { + std::fs::create_dir_all("/some/dir").unwrap(); + //~^ create_dir + } +} diff --git a/tests/ui/create_dir.rs b/tests/ui/create_dir.rs index bf185ba3a7c33..21e0bdba03be0 100644 --- a/tests/ui/create_dir.rs +++ b/tests/ui/create_dir.rs @@ -16,3 +16,22 @@ fn main() { create_dir(); std::fs::create_dir_all("foobar"); } + +mod issue14994 { + fn with_no_prefix() { + use std::fs::create_dir; + create_dir("some/dir").unwrap(); + //~^ create_dir + } + + fn with_fs_prefix() { + use std::fs; + fs::create_dir("/some/dir").unwrap(); + //~^ create_dir + } + + fn with_full_prefix() { + std::fs::create_dir("/some/dir").unwrap(); + //~^ create_dir + } +} diff --git a/tests/ui/create_dir.stderr b/tests/ui/create_dir.stderr index 51d6ac686e027..e3ca0e7255c7e 100644 --- a/tests/ui/create_dir.stderr +++ b/tests/ui/create_dir.stderr @@ -8,9 +8,8 @@ LL | std::fs::create_dir("foo"); = help: to override `-D warnings` add `#[allow(clippy::create_dir)]` help: consider calling `std::fs::create_dir_all` instead | -LL - std::fs::create_dir("foo"); -LL + create_dir_all("foo"); - | +LL | std::fs::create_dir_all("foo"); + | ++++ error: calling `std::fs::create_dir` where there may be a better way --> tests/ui/create_dir.rs:12:5 @@ -20,9 +19,41 @@ LL | std::fs::create_dir("bar").unwrap(); | help: consider calling `std::fs::create_dir_all` instead | -LL - std::fs::create_dir("bar").unwrap(); -LL + create_dir_all("bar").unwrap(); +LL | std::fs::create_dir_all("bar").unwrap(); + | ++++ + +error: calling `std::fs::create_dir` where there may be a better way + --> tests/ui/create_dir.rs:23:9 + | +LL | create_dir("some/dir").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider calling `std::fs::create_dir_all` instead + | +LL | std::fs::create_dir_all("some/dir").unwrap(); + | +++++++++ ++++ + +error: calling `std::fs::create_dir` where there may be a better way + --> tests/ui/create_dir.rs:29:9 + | +LL | fs::create_dir("/some/dir").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider calling `std::fs::create_dir_all` instead + | +LL | fs::create_dir_all("/some/dir").unwrap(); + | ++++ + +error: calling `std::fs::create_dir` where there may be a better way + --> tests/ui/create_dir.rs:34:9 + | +LL | std::fs::create_dir("/some/dir").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider calling `std::fs::create_dir_all` instead | +LL | std::fs::create_dir_all("/some/dir").unwrap(); + | ++++ -error: aborting due to 2 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/disallowed_names.rs b/tests/ui/disallowed_names.rs index 30fbdbc1fdc82..15bb67349976d 100644 --- a/tests/ui/disallowed_names.rs +++ b/tests/ui/disallowed_names.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![allow( dead_code, clippy::needless_if, @@ -9,6 +10,9 @@ )] #![warn(clippy::disallowed_names)] +extern crate proc_macros; +use proc_macros::{external, with_span}; + fn test(foo: ()) {} //~^ disallowed_names @@ -66,6 +70,17 @@ fn issue_1647_ref_mut() { //~^ disallowed_names } +pub fn issue_14958_proc_macro() { + // does not lint macro-generated code + external! { + let foo = 0; + } + with_span! { + span + let foo = 0; + } +} + #[cfg(test)] mod tests { fn issue_7305() { diff --git a/tests/ui/disallowed_names.stderr b/tests/ui/disallowed_names.stderr index 09398ebbab77f..b43d1b3ebfa3d 100644 --- a/tests/ui/disallowed_names.stderr +++ b/tests/ui/disallowed_names.stderr @@ -1,5 +1,5 @@ error: use of a disallowed/placeholder name `foo` - --> tests/ui/disallowed_names.rs:12:9 + --> tests/ui/disallowed_names.rs:16:9 | LL | fn test(foo: ()) {} | ^^^ @@ -8,79 +8,79 @@ LL | fn test(foo: ()) {} = help: to override `-D warnings` add `#[allow(clippy::disallowed_names)]` error: use of a disallowed/placeholder name `foo` - --> tests/ui/disallowed_names.rs:16:9 + --> tests/ui/disallowed_names.rs:20:9 | LL | let foo = 42; | ^^^ error: use of a disallowed/placeholder name `baz` - --> tests/ui/disallowed_names.rs:19:9 + --> tests/ui/disallowed_names.rs:23:9 | LL | let baz = 42; | ^^^ error: use of a disallowed/placeholder name `quux` - --> tests/ui/disallowed_names.rs:22:9 + --> tests/ui/disallowed_names.rs:26:9 | LL | let quux = 42; | ^^^^ error: use of a disallowed/placeholder name `foo` - --> tests/ui/disallowed_names.rs:35:10 + --> tests/ui/disallowed_names.rs:39:10 | LL | (foo, Some(baz), quux @ Some(_)) => (), | ^^^ error: use of a disallowed/placeholder name `baz` - --> tests/ui/disallowed_names.rs:35:20 + --> tests/ui/disallowed_names.rs:39:20 | LL | (foo, Some(baz), quux @ Some(_)) => (), | ^^^ error: use of a disallowed/placeholder name `quux` - --> tests/ui/disallowed_names.rs:35:26 + --> tests/ui/disallowed_names.rs:39:26 | LL | (foo, Some(baz), quux @ Some(_)) => (), | ^^^^ error: use of a disallowed/placeholder name `foo` - --> tests/ui/disallowed_names.rs:43:19 + --> tests/ui/disallowed_names.rs:47:19 | LL | fn issue_1647(mut foo: u8) { | ^^^ error: use of a disallowed/placeholder name `baz` - --> tests/ui/disallowed_names.rs:46:13 + --> tests/ui/disallowed_names.rs:50:13 | LL | let mut baz = 0; | ^^^ error: use of a disallowed/placeholder name `quux` - --> tests/ui/disallowed_names.rs:49:21 + --> tests/ui/disallowed_names.rs:53:21 | LL | if let Some(mut quux) = Some(42) {} | ^^^^ error: use of a disallowed/placeholder name `baz` - --> tests/ui/disallowed_names.rs:54:13 + --> tests/ui/disallowed_names.rs:58:13 | LL | let ref baz = 0; | ^^^ error: use of a disallowed/placeholder name `quux` - --> tests/ui/disallowed_names.rs:57:21 + --> tests/ui/disallowed_names.rs:61:21 | LL | if let Some(ref quux) = Some(42) {} | ^^^^ error: use of a disallowed/placeholder name `baz` - --> tests/ui/disallowed_names.rs:62:17 + --> tests/ui/disallowed_names.rs:66:17 | LL | let ref mut baz = 0; | ^^^ error: use of a disallowed/placeholder name `quux` - --> tests/ui/disallowed_names.rs:65:25 + --> tests/ui/disallowed_names.rs:69:25 | LL | if let Some(ref mut quux) = Some(42) {} | ^^^^ diff --git a/tests/ui/doc_suspicious_footnotes.fixed b/tests/ui/doc_suspicious_footnotes.fixed new file mode 100644 index 0000000000000..9ed3fd4ef31c7 --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes.fixed @@ -0,0 +1,186 @@ +#![warn(clippy::doc_suspicious_footnotes)] +#![allow(clippy::needless_raw_string_hashes)] +//! This is not a footnote[^1]. +//! +//! [^1]: +//~^ doc_suspicious_footnotes +//! +//! This is not a footnote[^either], but it doesn't warn. +//! +//! This is not a footnote\[^1], but it also doesn't warn. +//! +//! This is not a footnote[^1\], but it also doesn't warn. +//! +//! This is not a `footnote[^1]`, but it also doesn't warn. +//! +//! This is a footnote[^2]. +//! +//! [^2]: hello world + +/// This is not a footnote[^1]. +/// +/// [^1]: +//~^ doc_suspicious_footnotes +/// +/// This is not a footnote[^either], but it doesn't warn. +/// +/// This is not a footnote\[^1], but it also doesn't warn. +/// +/// This is not a footnote[^1\], but it also doesn't warn. +/// +/// This is not a `footnote[^1]`, but it also doesn't warn. +/// +/// This is a footnote[^2]. +/// +/// [^2]: hello world +pub fn footnotes() { + // test code goes here +} + +pub struct Foo; +#[rustfmt::skip] +impl Foo { + #[doc = r#"This is not a footnote[^1]. + +[^1]: "#] + //~^ doc_suspicious_footnotes + #[doc = r#""#] + #[doc = r#"This is not a footnote[^either], but it doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a footnote\[^1], but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a footnote[^1\], but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a `footnote[^1]`, but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is a footnote[^2]."#] + #[doc = r#""#] + #[doc = r#"[^2]: hello world"#] + pub fn footnotes() { + // test code goes here + } + #[doc = r#"This is not a footnote[^1]. + + This is not a footnote[^either], but it doesn't warn. + + This is not a footnote\[^1], but it also doesn't warn. + + This is not a footnote[^1\], but it also doesn't warn. + + This is not a `footnote[^1]`, but it also doesn't warn. + + This is a footnote[^2]. + + [^2]: hello world + + +[^1]: "#] + //~^^^^^^^^^^^^^^ doc_suspicious_footnotes + pub fn footnotes2() { + // test code goes here + } + #[cfg_attr( + not(FALSE), + doc = r#"This is not a footnote[^1]. + +This is not a footnote[^either], but it doesn't warn. + +[^1]: "# + //~^ doc_suspicious_footnotes + )] + pub fn footnotes3() { + // test code goes here + } + #[doc = "My footnote [^foot\note]"] + pub fn footnote4() { + // test code goes here + } + #[doc = "Hihi"]pub fn footnote5() { + // test code goes here + } +} + +#[doc = r#"This is not a footnote[^1]. + +[^1]: "#] +//~^ doc_suspicious_footnotes +#[doc = r""] +#[doc = r"This is not a footnote[^either], but it doesn't warn."] +#[doc = r""] +#[doc = r"This is not a footnote\[^1], but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is not a footnote[^1\], but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is not a `footnote[^1]`, but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is a footnote[^2]."] +#[doc = r""] +#[doc = r"[^2]: hello world"] +pub fn footnotes_attrs() { + // test code goes here +} + +pub mod multiline { + /*! + * This is not a footnote[^1]. //~ doc_suspicious_footnotes + * + * This is not a footnote\[^1], but it doesn't warn. + * + * This is a footnote[^2]. + * + * These give weird results, but correct ones, so it works. + * + * [^2]: hello world + */ +/*! [^1]: */ + /** + * This is not a footnote[^1]. //~ doc_suspicious_footnotes + * + * This is not a footnote\[^1], but it doesn't warn. + * + * This is a footnote[^2]. + * + * These give weird results, but correct ones, so it works. + * + * [^2]: hello world + */ +/** [^1]: */ + pub fn foo() {} +} + +/// This is not a footnote [^1] +/// +/// [^1]: +//~^ doc_suspicious_footnotes +/// +/// This one is [^2] +/// +/// [^2]: contents +#[doc = r#"This is not a footnote [^3] + +[^3]: "#] +//~^ doc_suspicious_footnotes +#[doc = ""] +#[doc = "This one is [^4]"] +#[doc = ""] +#[doc = "[^4]: contents"] +pub struct MultiFragmentFootnote; + +#[doc(inline)] +/// This is not a footnote [^5] +/// +/// [^5]: +//~^ doc_suspicious_footnotes +/// +/// This one is [^6] +/// +/// [^6]: contents +#[doc = r#"This is not a footnote [^7] + +[^7]: "#] +//~^ doc_suspicious_footnotes +#[doc = ""] +#[doc = "This one is [^8]"] +#[doc = ""] +#[doc = "[^8]: contents"] +pub use MultiFragmentFootnote as OtherInlinedFootnote; diff --git a/tests/ui/doc_suspicious_footnotes.rs b/tests/ui/doc_suspicious_footnotes.rs new file mode 100644 index 0000000000000..9a8d0dcf47548 --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes.rs @@ -0,0 +1,162 @@ +#![warn(clippy::doc_suspicious_footnotes)] +#![allow(clippy::needless_raw_string_hashes)] +//! This is not a footnote[^1]. +//~^ doc_suspicious_footnotes +//! +//! This is not a footnote[^either], but it doesn't warn. +//! +//! This is not a footnote\[^1], but it also doesn't warn. +//! +//! This is not a footnote[^1\], but it also doesn't warn. +//! +//! This is not a `footnote[^1]`, but it also doesn't warn. +//! +//! This is a footnote[^2]. +//! +//! [^2]: hello world + +/// This is not a footnote[^1]. +//~^ doc_suspicious_footnotes +/// +/// This is not a footnote[^either], but it doesn't warn. +/// +/// This is not a footnote\[^1], but it also doesn't warn. +/// +/// This is not a footnote[^1\], but it also doesn't warn. +/// +/// This is not a `footnote[^1]`, but it also doesn't warn. +/// +/// This is a footnote[^2]. +/// +/// [^2]: hello world +pub fn footnotes() { + // test code goes here +} + +pub struct Foo; +#[rustfmt::skip] +impl Foo { + #[doc = r#"This is not a footnote[^1]."#] + //~^ doc_suspicious_footnotes + #[doc = r#""#] + #[doc = r#"This is not a footnote[^either], but it doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a footnote\[^1], but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a footnote[^1\], but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is not a `footnote[^1]`, but it also doesn't warn."#] + #[doc = r#""#] + #[doc = r#"This is a footnote[^2]."#] + #[doc = r#""#] + #[doc = r#"[^2]: hello world"#] + pub fn footnotes() { + // test code goes here + } + #[doc = "This is not a footnote[^1]. + + This is not a footnote[^either], but it doesn't warn. + + This is not a footnote\\[^1], but it also doesn't warn. + + This is not a footnote[^1\\], but it also doesn't warn. + + This is not a `footnote[^1]`, but it also doesn't warn. + + This is a footnote[^2]. + + [^2]: hello world + "] + //~^^^^^^^^^^^^^^ doc_suspicious_footnotes + pub fn footnotes2() { + // test code goes here + } + #[cfg_attr( + not(FALSE), + doc = "This is not a footnote[^1].\n\nThis is not a footnote[^either], but it doesn't warn." + //~^ doc_suspicious_footnotes + )] + pub fn footnotes3() { + // test code goes here + } + #[doc = "My footnote [^foot\note]"] + pub fn footnote4() { + // test code goes here + } + #[doc = "Hihi"]pub fn footnote5() { + // test code goes here + } +} + +#[doc = r"This is not a footnote[^1]."] +//~^ doc_suspicious_footnotes +#[doc = r""] +#[doc = r"This is not a footnote[^either], but it doesn't warn."] +#[doc = r""] +#[doc = r"This is not a footnote\[^1], but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is not a footnote[^1\], but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is not a `footnote[^1]`, but it also doesn't warn."] +#[doc = r""] +#[doc = r"This is a footnote[^2]."] +#[doc = r""] +#[doc = r"[^2]: hello world"] +pub fn footnotes_attrs() { + // test code goes here +} + +pub mod multiline { + /*! + * This is not a footnote[^1]. //~ doc_suspicious_footnotes + * + * This is not a footnote\[^1], but it doesn't warn. + * + * This is a footnote[^2]. + * + * These give weird results, but correct ones, so it works. + * + * [^2]: hello world + */ + /** + * This is not a footnote[^1]. //~ doc_suspicious_footnotes + * + * This is not a footnote\[^1], but it doesn't warn. + * + * This is a footnote[^2]. + * + * These give weird results, but correct ones, so it works. + * + * [^2]: hello world + */ + pub fn foo() {} +} + +/// This is not a footnote [^1] +//~^ doc_suspicious_footnotes +/// +/// This one is [^2] +/// +/// [^2]: contents +#[doc = "This is not a footnote [^3]"] +//~^ doc_suspicious_footnotes +#[doc = ""] +#[doc = "This one is [^4]"] +#[doc = ""] +#[doc = "[^4]: contents"] +pub struct MultiFragmentFootnote; + +#[doc(inline)] +/// This is not a footnote [^5] +//~^ doc_suspicious_footnotes +/// +/// This one is [^6] +/// +/// [^6]: contents +#[doc = "This is not a footnote [^7]"] +//~^ doc_suspicious_footnotes +#[doc = ""] +#[doc = "This one is [^8]"] +#[doc = ""] +#[doc = "[^8]: contents"] +pub use MultiFragmentFootnote as OtherInlinedFootnote; diff --git a/tests/ui/doc_suspicious_footnotes.stderr b/tests/ui/doc_suspicious_footnotes.stderr new file mode 100644 index 0000000000000..4f920f37a620f --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes.stderr @@ -0,0 +1,179 @@ +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:3:27 + | +LL | //! This is not a footnote[^1]. + | ^^^^ + | + = note: `-D clippy::doc-suspicious-footnotes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_suspicious_footnotes)]` +help: add footnote definition + | +LL ~ //! This is not a footnote[^1]. +LL + //! +LL + //! [^1]: + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:18:27 + | +LL | /// This is not a footnote[^1]. + | ^^^^ + | +help: add footnote definition + | +LL ~ /// This is not a footnote[^1]. +LL + /// +LL + /// [^1]: + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:39:13 + | +LL | #[doc = r#"This is not a footnote[^1]."#] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add footnote definition + | +LL ~ #[doc = r#"This is not a footnote[^1]. +LL + +LL ~ [^1]: "#] + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:56:13 + | +LL | #[doc = "This is not a footnote[^1]. + | _____________^ +LL | | +LL | | This is not a footnote[^either], but it doesn't warn. +... | +LL | | [^2]: hello world +LL | | "] + | |_____^ + | +help: add footnote definition + | +LL ~ #[doc = r#"This is not a footnote[^1]. +LL + +LL + This is not a footnote[^either], but it doesn't warn. +LL + +LL + This is not a footnote\[^1], but it also doesn't warn. +LL + +LL + This is not a footnote[^1\], but it also doesn't warn. +LL + +LL + This is not a `footnote[^1]`, but it also doesn't warn. +LL + +LL + This is a footnote[^2]. +LL + +LL + [^2]: hello world +LL + +LL + +LL ~ [^1]: "#] + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:76:38 + | +LL | doc = "This is not a footnote[^1].\n\nThis is not a footnote[^either], but it doesn't warn." + | ^^^^ + | +help: add footnote definition + | +LL ~ doc = r#"This is not a footnote[^1]. +LL + +LL + This is not a footnote[^either], but it doesn't warn. +LL + +LL + [^1]: "# + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:91:9 + | +LL | #[doc = r"This is not a footnote[^1]."] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add footnote definition + | +LL ~ #[doc = r#"This is not a footnote[^1]. +LL + +LL ~ [^1]: "#] + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:111:30 + | +LL | * This is not a footnote[^1]. + | ^^^^ + | +help: add footnote definition + | +LL ~ */ +LL + /*! [^1]: */ + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:122:30 + | +LL | * This is not a footnote[^1]. + | ^^^^ + | +help: add footnote definition + | +LL ~ */ +LL + /** [^1]: */ + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:135:28 + | +LL | /// This is not a footnote [^1] + | ^^^^ + | +help: add footnote definition + | +LL ~ /// This is not a footnote [^1] +LL + /// +LL + /// [^1]: + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:141:33 + | +LL | #[doc = "This is not a footnote [^3]"] + | ^^^^ + | +help: add footnote definition + | +LL ~ #[doc = r#"This is not a footnote [^3] +LL + +LL ~ [^3]: "#] + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:150:28 + | +LL | /// This is not a footnote [^5] + | ^^^^ + | +help: add footnote definition + | +LL ~ /// This is not a footnote [^5] +LL + /// +LL + /// [^5]: + | + +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes.rs:156:33 + | +LL | #[doc = "This is not a footnote [^7]"] + | ^^^^ + | +help: add footnote definition + | +LL ~ #[doc = r#"This is not a footnote [^7] +LL + +LL ~ [^7]: "#] + | + +error: aborting due to 12 previous errors + diff --git a/tests/ui/doc_suspicious_footnotes_include.rs b/tests/ui/doc_suspicious_footnotes_include.rs new file mode 100644 index 0000000000000..4f75ad94eafd5 --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes_include.rs @@ -0,0 +1,4 @@ +//@ error-in-other-file: footnote +//@ no-rustfix +#![warn(clippy::doc_suspicious_footnotes)] +#![doc=include_str!("doc_suspicious_footnotes_include.txt")] diff --git a/tests/ui/doc_suspicious_footnotes_include.stderr b/tests/ui/doc_suspicious_footnotes_include.stderr new file mode 100644 index 0000000000000..74154e3f4ef35 --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes_include.stderr @@ -0,0 +1,17 @@ +error: looks like a footnote ref, but has no matching footnote + --> tests/ui/doc_suspicious_footnotes_include.txt:1:23 + | +LL | This is not a footnote[^1]. + | ^^^^ + | + = note: `-D clippy::doc-suspicious-footnotes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_suspicious_footnotes)]` +help: add footnote definition + | +LL ~ [^2]: hello world +LL + +LL + [^1]: + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/doc_suspicious_footnotes_include.txt b/tests/ui/doc_suspicious_footnotes_include.txt new file mode 100644 index 0000000000000..2a533e32c4a69 --- /dev/null +++ b/tests/ui/doc_suspicious_footnotes_include.txt @@ -0,0 +1,13 @@ +This is not a footnote[^1]. //~ doc_suspicious_footnotes + +This is not a footnote[^either], but it doesn't warn. + +This is not a footnote\[^1], but it also doesn't warn. + +This is not a footnote[^1\], but it also doesn't warn. + +This is not a `footnote[^1]`, but it also doesn't warn. + +This is a footnote[^2]. + +[^2]: hello world diff --git a/tests/ui/format_args.fixed b/tests/ui/format_args.fixed index edfdaa23ca124..3cb6c0b4d97c0 100644 --- a/tests/ui/format_args.fixed +++ b/tests/ui/format_args.fixed @@ -192,3 +192,13 @@ mod issue_9256 { print_substring("Hello, world!"); } } + +mod issue14952 { + use std::path::Path; + struct Foo(Path); + impl std::fmt::Debug for Foo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self.0) + } + } +} diff --git a/tests/ui/format_args.rs b/tests/ui/format_args.rs index 367560d577ddd..8a9c369fff355 100644 --- a/tests/ui/format_args.rs +++ b/tests/ui/format_args.rs @@ -192,3 +192,13 @@ mod issue_9256 { print_substring("Hello, world!"); } } + +mod issue14952 { + use std::path::Path; + struct Foo(Path); + impl std::fmt::Debug for Foo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self.0) + } + } +} diff --git a/tests/ui/indexing_slicing_index.rs b/tests/ui/indexing_slicing_index.rs index ab6a823500831..2510a023acd25 100644 --- a/tests/ui/indexing_slicing_index.rs +++ b/tests/ui/indexing_slicing_index.rs @@ -68,7 +68,6 @@ fn main() { // This should be linted, since `suppress-restriction-lint-in-const` default is false. const { &ARR[idx4()] }; //~^ ERROR: indexing may panic - //~| ERROR: index out of bounds let y = &x; // Ok, referencing shouldn't affect this lint. See the issue 6021 diff --git a/tests/ui/indexing_slicing_index.stderr b/tests/ui/indexing_slicing_index.stderr index 8e24b898ed5d5..c68e1d53a9342 100644 --- a/tests/ui/indexing_slicing_index.stderr +++ b/tests/ui/indexing_slicing_index.stderr @@ -9,18 +9,6 @@ LL | const REF: &i32 = &ARR[idx()]; // This should be linted, since `suppress-re = note: `-D clippy::indexing-slicing` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` -error[E0080]: index out of bounds: the length is 2 but the index is 4 - --> tests/ui/indexing_slicing_index.rs:69:14 - | -LL | const { &ARR[idx4()] }; - | ^^^^^^^^^^^ evaluation of `main::{constant#3}` failed here - -note: erroneous constant encountered - --> tests/ui/indexing_slicing_index.rs:69:5 - | -LL | const { &ARR[idx4()] }; - | ^^^^^^^^^^^^^^^^^^^^^^ - error: indexing may panic --> tests/ui/indexing_slicing_index.rs:48:5 | @@ -63,13 +51,13 @@ LL | const { &ARR[idx4()] }; = note: the suggestion might not be applicable in constant blocks error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:77:5 + --> tests/ui/indexing_slicing_index.rs:76:5 | LL | y[4]; | ^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:81:5 + --> tests/ui/indexing_slicing_index.rs:80:5 | LL | v[0]; | ^^^^ @@ -77,7 +65,7 @@ LL | v[0]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:83:5 + --> tests/ui/indexing_slicing_index.rs:82:5 | LL | v[10]; | ^^^^^ @@ -85,7 +73,7 @@ LL | v[10]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:85:5 + --> tests/ui/indexing_slicing_index.rs:84:5 | LL | v[1 << 3]; | ^^^^^^^^^ @@ -93,13 +81,13 @@ LL | v[1 << 3]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:93:5 + --> tests/ui/indexing_slicing_index.rs:92:5 | LL | x[N]; | ^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:97:5 + --> tests/ui/indexing_slicing_index.rs:96:5 | LL | v[N]; | ^^^^ @@ -107,7 +95,7 @@ LL | v[N]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:99:5 + --> tests/ui/indexing_slicing_index.rs:98:5 | LL | v[M]; | ^^^^ @@ -115,11 +103,10 @@ LL | v[M]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:103:13 + --> tests/ui/indexing_slicing_index.rs:102:13 | LL | let _ = x[4]; | ^^^^ -error: aborting due to 15 previous errors +error: aborting due to 14 previous errors -For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/infallible_try_from.rs b/tests/ui/infallible_try_from.rs new file mode 100644 index 0000000000000..6a1f12f824f55 --- /dev/null +++ b/tests/ui/infallible_try_from.rs @@ -0,0 +1,33 @@ +#![feature(never_type)] +#![warn(clippy::infallible_try_from)] + +use std::convert::Infallible; + +struct MyStruct(i32); + +impl TryFrom for MyStruct { + //~^ infallible_try_from + type Error = !; + fn try_from(other: i8) -> Result { + Ok(Self(other.into())) + } +} + +impl TryFrom for MyStruct { + //~^ infallible_try_from + type Error = Infallible; + fn try_from(other: i16) -> Result { + Ok(Self(other.into())) + } +} + +impl TryFrom for MyStruct { + type Error = i64; + fn try_from(other: i64) -> Result { + Ok(Self(i32::try_from(other).map_err(|_| other)?)) + } +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/infallible_try_from.stderr b/tests/ui/infallible_try_from.stderr new file mode 100644 index 0000000000000..705b1188489c2 --- /dev/null +++ b/tests/ui/infallible_try_from.stderr @@ -0,0 +1,23 @@ +error: infallible TryFrom impl; consider implementing From, instead + --> tests/ui/infallible_try_from.rs:8:1 + | +LL | impl TryFrom for MyStruct { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Error = !; + | - infallible error type + | + = note: `-D clippy::infallible-try-from` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::infallible_try_from)]` + +error: infallible TryFrom impl; consider implementing From, instead + --> tests/ui/infallible_try_from.rs:16:1 + | +LL | impl TryFrom for MyStruct { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Error = Infallible; + | ---------- infallible error type + +error: aborting due to 2 previous errors + diff --git a/tests/ui/ip_constant.fixed b/tests/ui/ip_constant.fixed new file mode 100644 index 0000000000000..2e3389c119385 --- /dev/null +++ b/tests/ui/ip_constant.fixed @@ -0,0 +1,107 @@ +#![warn(clippy::ip_constant)] +#![allow(dead_code)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +fn literal_test1() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::LOCALHOST; + //~^ ip_constant + let _ = Ipv6Addr::UNSPECIFIED; + //~^ ip_constant +} + +fn literal_test2() { + use std::net; + let _ = net::Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = net::Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = net::Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + let _ = net::Ipv6Addr::LOCALHOST; + //~^ ip_constant + let _ = net::Ipv6Addr::UNSPECIFIED; + //~^ ip_constant +} + +fn literal_test3() { + let _ = std::net::Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = std::net::Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = std::net::Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + let _ = std::net::Ipv6Addr::LOCALHOST; + //~^ ip_constant + let _ = std::net::Ipv6Addr::UNSPECIFIED; + //~^ ip_constant +} + +const CONST_U8_0: u8 = 0; +const CONST_U8_1: u8 = 1; +const CONST_U8_127: u8 = 127; +const CONST_U8_255: u8 = 255; + +const CONST_U16_0: u16 = 0; +const CONST_U16_1: u16 = 1; + +fn const_test1() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::LOCALHOST; + + let _ = Ipv6Addr::UNSPECIFIED; +} + +fn const_test2() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::LOCALHOST; + //~^ ip_constant + let _ = Ipv6Addr::LOCALHOST; + //~^ ip_constant +} + +macro_rules! ipv4_new { + ($a:expr, $b:expr, $c:expr, $d:expr) => { + std::net::Ipv4Addr::new($a, $b, $c, $d) + }; +} + +fn macro_test() { + let _ = ipv4_new!(127, 0, 0, 1); + // no lint + let _ = ipv4_new!(255, 255, 255, 255); + // no lint + let _ = ipv4_new!(0, 0, 0, 0); + // no lint +} + +fn main() { + // UI Test +} diff --git a/tests/ui/ip_constant.rs b/tests/ui/ip_constant.rs new file mode 100644 index 0000000000000..15e0b0551bab7 --- /dev/null +++ b/tests/ui/ip_constant.rs @@ -0,0 +1,127 @@ +#![warn(clippy::ip_constant)] +#![allow(dead_code)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +fn literal_test1() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::new(127, 0, 0, 1); + //~^ ip_constant + let _ = Ipv4Addr::new(255, 255, 255, 255); + //~^ ip_constant + let _ = Ipv4Addr::new(0, 0, 0, 0); + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + //~^ ip_constant + let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + //~^ ip_constant +} + +fn literal_test2() { + use std::net; + let _ = net::Ipv4Addr::new(127, 0, 0, 1); + //~^ ip_constant + let _ = net::Ipv4Addr::new(255, 255, 255, 255); + //~^ ip_constant + let _ = net::Ipv4Addr::new(0, 0, 0, 0); + //~^ ip_constant + + let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + //~^ ip_constant + let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + //~^ ip_constant +} + +fn literal_test3() { + let _ = std::net::Ipv4Addr::new(127, 0, 0, 1); + //~^ ip_constant + let _ = std::net::Ipv4Addr::new(255, 255, 255, 255); + //~^ ip_constant + let _ = std::net::Ipv4Addr::new(0, 0, 0, 0); + //~^ ip_constant + + let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + //~^ ip_constant + let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + //~^ ip_constant +} + +const CONST_U8_0: u8 = 0; +const CONST_U8_1: u8 = 1; +const CONST_U8_127: u8 = 127; +const CONST_U8_255: u8 = 255; + +const CONST_U16_0: u16 = 0; +const CONST_U16_1: u16 = 1; + +fn const_test1() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); + //~^ ip_constant + let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); + //~^ ip_constant + let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::new( + //~^ ip_constant + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_1, + ); + + let _ = Ipv6Addr::new( + //~^ ip_constant + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + ); +} + +fn const_test2() { + use std::net::Ipv4Addr; + let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); + //~^ ip_constant + let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); + //~^ ip_constant + let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); + //~^ ip_constant + + use std::net::Ipv6Addr; + let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); + //~^ ip_constant + let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); + //~^ ip_constant +} + +macro_rules! ipv4_new { + ($a:expr, $b:expr, $c:expr, $d:expr) => { + std::net::Ipv4Addr::new($a, $b, $c, $d) + }; +} + +fn macro_test() { + let _ = ipv4_new!(127, 0, 0, 1); + // no lint + let _ = ipv4_new!(255, 255, 255, 255); + // no lint + let _ = ipv4_new!(0, 0, 0, 0); + // no lint +} + +fn main() { + // UI Test +} diff --git a/tests/ui/ip_constant.stderr b/tests/ui/ip_constant.stderr new file mode 100644 index 0000000000000..3e984c6cb3bb7 --- /dev/null +++ b/tests/ui/ip_constant.stderr @@ -0,0 +1,338 @@ +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:8:13 + | +LL | let _ = Ipv4Addr::new(127, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::ip-constant` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::ip_constant)]` +help: use + | +LL - let _ = Ipv4Addr::new(127, 0, 0, 1); +LL + let _ = Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:10:13 + | +LL | let _ = Ipv4Addr::new(255, 255, 255, 255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(255, 255, 255, 255); +LL + let _ = Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:12:13 + | +LL | let _ = Ipv4Addr::new(0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(0, 0, 0, 0); +LL + let _ = Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:16:13 + | +LL | let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); +LL + let _ = Ipv6Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:18:13 + | +LL | let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); +LL + let _ = Ipv6Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:24:13 + | +LL | let _ = net::Ipv4Addr::new(127, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = net::Ipv4Addr::new(127, 0, 0, 1); +LL + let _ = net::Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:26:13 + | +LL | let _ = net::Ipv4Addr::new(255, 255, 255, 255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = net::Ipv4Addr::new(255, 255, 255, 255); +LL + let _ = net::Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:28:13 + | +LL | let _ = net::Ipv4Addr::new(0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = net::Ipv4Addr::new(0, 0, 0, 0); +LL + let _ = net::Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:31:13 + | +LL | let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); +LL + let _ = net::Ipv6Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:33:13 + | +LL | let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); +LL + let _ = net::Ipv6Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:38:13 + | +LL | let _ = std::net::Ipv4Addr::new(127, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = std::net::Ipv4Addr::new(127, 0, 0, 1); +LL + let _ = std::net::Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:40:13 + | +LL | let _ = std::net::Ipv4Addr::new(255, 255, 255, 255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = std::net::Ipv4Addr::new(255, 255, 255, 255); +LL + let _ = std::net::Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:42:13 + | +LL | let _ = std::net::Ipv4Addr::new(0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = std::net::Ipv4Addr::new(0, 0, 0, 0); +LL + let _ = std::net::Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:45:13 + | +LL | let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); +LL + let _ = std::net::Ipv6Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:47:13 + | +LL | let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); +LL + let _ = std::net::Ipv6Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:61:13 + | +LL | let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); +LL + let _ = Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:63:13 + | +LL | let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); +LL + let _ = Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:65:13 + | +LL | let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); +LL + let _ = Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:69:13 + | +LL | let _ = Ipv6Addr::new( + | _____________^ +LL | | +LL | | CONST_U16_0, +LL | | CONST_U16_0, +... | +LL | | CONST_U16_1, +LL | | ); + | |_____^ + | +help: use + | +LL - let _ = Ipv6Addr::new( +LL - +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_1, +LL - ); +LL + let _ = Ipv6Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:81:13 + | +LL | let _ = Ipv6Addr::new( + | _____________^ +LL | | +LL | | CONST_U16_0, +LL | | CONST_U16_0, +... | +LL | | CONST_U16_0, +LL | | ); + | |_____^ + | +help: use + | +LL - let _ = Ipv6Addr::new( +LL - +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - CONST_U16_0, +LL - ); +LL + let _ = Ipv6Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:96:13 + | +LL | let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); +LL + let _ = Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:98:13 + | +LL | let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); +LL + let _ = Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:100:13 + | +LL | let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); +LL + let _ = Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:104:13 + | +LL | let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); +LL + let _ = Ipv6Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:106:13 + | +LL | let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); +LL + let _ = Ipv6Addr::LOCALHOST; + | + +error: aborting due to 25 previous errors + diff --git a/tests/ui/ip_constant_from_external.rs b/tests/ui/ip_constant_from_external.rs new file mode 100644 index 0000000000000..7fd27022f1274 --- /dev/null +++ b/tests/ui/ip_constant_from_external.rs @@ -0,0 +1,12 @@ +//@error-in-other-file: hand-coded well-known IP address +//@no-rustfix +#![warn(clippy::ip_constant)] + +fn external_constant_test() { + let _ = include!("localhost.txt"); + // lint in external file `localhost.txt` +} + +fn main() { + external_constant_test(); +} diff --git a/tests/ui/ip_constant_from_external.stderr b/tests/ui/ip_constant_from_external.stderr new file mode 100644 index 0000000000000..99dd827d41f75 --- /dev/null +++ b/tests/ui/ip_constant_from_external.stderr @@ -0,0 +1,16 @@ +error: hand-coded well-known IP address + --> tests/ui/localhost.txt:1:1 + | +LL | std::net::Ipv4Addr::new(127, 0, 0, 1) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::ip-constant` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::ip_constant)]` +help: use + | +LL - std::net::Ipv4Addr::new(127, 0, 0, 1) +LL + std::net::Ipv4Addr::LOCALHOST + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/localhost.txt b/tests/ui/localhost.txt new file mode 100644 index 0000000000000..4502ec2b2341e --- /dev/null +++ b/tests/ui/localhost.txt @@ -0,0 +1 @@ +std::net::Ipv4Addr::new(127, 0, 0, 1) \ No newline at end of file diff --git a/tests/ui/manual_flatten.fixed b/tests/ui/manual_flatten.fixed new file mode 100644 index 0000000000000..cc1fbd2576526 --- /dev/null +++ b/tests/ui/manual_flatten.fixed @@ -0,0 +1,148 @@ +#![warn(clippy::manual_flatten)] +#![allow(clippy::useless_vec, clippy::uninlined_format_args)] + +fn main() { + // Test for loop over implicitly adjusted `Iterator` with `if let` expression + let x = vec![Some(1), Some(2), Some(3)]; + for y in x.into_iter().flatten() { + println!("{}", y); + } + + // Test for loop over implicitly adjusted `Iterator` with `if let` statement + let y: Vec> = vec![]; + for n in y.clone().into_iter().flatten() { + println!("{}", n); + } + + // Test for loop over by reference + for n in y.iter().flatten() { + println!("{}", n); + } + + // Test for loop over an implicit reference + let z = &y; + for n in z.iter().flatten() { + println!("{}", n); + } + + // Test for loop over `Iterator` with `if let` expression + let z = vec![Some(1), Some(2), Some(3)]; + let z = z.iter(); + for m in z.flatten() { + println!("{}", m); + } + + // Using the `None` variant should not trigger the lint + // Note: for an autofixable suggestion, the binding in the for loop has to take the + // name of the binding in the `if let` + let z = vec![Some(1), Some(2), Some(3)]; + for n in z { + if n.is_none() { + println!("Nada."); + } + } + + // Using the `Err` variant should not trigger the lint + for n in y.clone() { + if let Err(e) = n { + println!("Oops: {}!", e); + } + } + + // Having an else clause should not trigger the lint + for n in y.clone() { + if let Ok(n) = n { + println!("{}", n); + } else { + println!("Oops!"); + } + } + + let vec_of_ref = vec![&Some(1)]; + for n in vec_of_ref.iter().copied().flatten() { + println!("{:?}", n); + } + + let vec_of_ref = &vec_of_ref; + for n in vec_of_ref.iter().copied().flatten() { + println!("{:?}", n); + } + + let slice_of_ref = &[&Some(1)]; + for n in slice_of_ref.iter().copied().flatten() { + println!("{:?}", n); + } + + struct Test { + a: usize, + } + + let mut vec_of_struct = [Some(Test { a: 1 }), None]; + + // Usage of `if let` expression should not trigger lint + for n in vec_of_struct.iter_mut() { + if let Some(z) = n { + *n = None; + } + } + + // Using manual flatten should not trigger the lint + for n in vec![Some(1), Some(2), Some(3)].iter().flatten() { + println!("{}", n); + } + + // Using nested `Some` pattern should not trigger the lint + for n in vec![Some((1, Some(2)))] { + if let Some((_, Some(n))) = n { + println!("{}", n); + } + } + + macro_rules! inner { + ($id:ident / $new:pat => $action:expr) => { + if let Some($new) = $id { + $action; + } + }; + } + + // Usage of `if let` expression with macro should not trigger lint + for ab in [Some((1, 2)), Some((3, 4))] { + inner!(ab / (c, d) => println!("{c}-{d}")); + } + + macro_rules! args { + ($($arg:expr),*) => { + vec![$(Some($arg)),*] + }; + } + + // Usage of `if let` expression with macro should not trigger lint + for n in args!(1, 2, 3) { + if let Some(n) = n { + println!("{:?}", n); + } + } + + // This should trigger the lint, but the applicability is `MaybeIncorrect` + let z = vec![Some(1), Some(2), Some(3)]; + for n in z.into_iter().flatten() { + println!("{:?}", n); + } + + run_unformatted_tests(); +} + +#[rustfmt::skip] +fn run_unformatted_tests() { + // Skip rustfmt here on purpose so the suggestion does not fit in one line + for n in vec![ + //~^ manual_flatten + + Some(1), + Some(2), + Some(3) + ].iter().flatten() { + println!("{:?}", n); + } +} diff --git a/tests/ui/manual_flatten.rs b/tests/ui/manual_flatten.rs index f1a0053ef384d..53b4ac7d3b6cd 100644 --- a/tests/ui/manual_flatten.rs +++ b/tests/ui/manual_flatten.rs @@ -1,6 +1,6 @@ #![warn(clippy::manual_flatten)] #![allow(clippy::useless_vec, clippy::uninlined_format_args)] -//@no-rustfix + fn main() { // Test for loop over implicitly adjusted `Iterator` with `if let` expression let x = vec![Some(1), Some(2), Some(3)]; @@ -130,6 +130,43 @@ fn main() { } } + macro_rules! inner { + ($id:ident / $new:pat => $action:expr) => { + if let Some($new) = $id { + $action; + } + }; + } + + // Usage of `if let` expression with macro should not trigger lint + for ab in [Some((1, 2)), Some((3, 4))] { + inner!(ab / (c, d) => println!("{c}-{d}")); + } + + macro_rules! args { + ($($arg:expr),*) => { + vec![$(Some($arg)),*] + }; + } + + // Usage of `if let` expression with macro should not trigger lint + for n in args!(1, 2, 3) { + if let Some(n) = n { + println!("{:?}", n); + } + } + + // This should trigger the lint, but the applicability is `MaybeIncorrect` + let z = vec![Some(1), Some(2), Some(3)]; + for n in z { + //~^ manual_flatten + + if let Some(n) = n { + println!("{:?}", n); + } + // foo + } + run_unformatted_tests(); } diff --git a/tests/ui/manual_flatten.stderr b/tests/ui/manual_flatten.stderr index 9a846fe17f329..eb39ee4207141 100644 --- a/tests/ui/manual_flatten.stderr +++ b/tests/ui/manual_flatten.stderr @@ -1,10 +1,7 @@ error: unnecessary `if let` since only the `Some` variant of the iterator element is used --> tests/ui/manual_flatten.rs:7:5 | -LL | for n in x { - | ^ - help: try: `x.into_iter().flatten()` - | _____| - | | +LL | / for n in x { LL | | LL | | LL | | if let Some(y) = n { @@ -12,7 +9,7 @@ LL | | if let Some(y) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:10:9 | LL | / if let Some(y) = n { @@ -21,14 +18,17 @@ LL | | } | |_________^ = note: `-D clippy::manual-flatten` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_flatten)]` +help: try + | +LL ~ for y in x.into_iter().flatten() { +LL + println!("{}", y); +LL + } + | error: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> tests/ui/manual_flatten.rs:17:5 | -LL | for n in y.clone() { - | ^ --------- help: try: `y.clone().into_iter().flatten()` - | _____| - | | +LL | / for n in y.clone() { LL | | LL | | LL | | if let Ok(n) = n { @@ -37,21 +37,24 @@ LL | | }; LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:20:9 | LL | / if let Ok(n) = n { LL | | println!("{}", n); LL | | }; | |_________^ +help: try + | +LL ~ for n in y.clone().into_iter().flatten() { +LL + println!("{}", n); +LL + } + | error: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> tests/ui/manual_flatten.rs:26:5 | -LL | for n in &y { - | ^ -- help: try: `y.iter().flatten()` - | _____| - | | +LL | / for n in &y { LL | | LL | | LL | | if let Ok(n) = n { @@ -59,21 +62,24 @@ LL | | if let Ok(n) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:29:9 | LL | / if let Ok(n) = n { LL | | println!("{}", n); LL | | } | |_________^ +help: try + | +LL ~ for n in y.iter().flatten() { +LL + println!("{}", n); +LL + } + | error: unnecessary `if let` since only the `Ok` variant of the iterator element is used --> tests/ui/manual_flatten.rs:36:5 | -LL | for n in z { - | ^ - help: try: `z.iter().flatten()` - | _____| - | | +LL | / for n in z { LL | | LL | | LL | | if let Ok(n) = n { @@ -81,21 +87,24 @@ LL | | if let Ok(n) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:39:9 | LL | / if let Ok(n) = n { LL | | println!("{}", n); LL | | } | |_________^ +help: try + | +LL ~ for n in z.iter().flatten() { +LL + println!("{}", n); +LL + } + | error: unnecessary `if let` since only the `Some` variant of the iterator element is used --> tests/ui/manual_flatten.rs:47:5 | -LL | for n in z { - | ^ - help: try: `z.flatten()` - | _____| - | | +LL | / for n in z { LL | | LL | | LL | | if let Some(m) = n { @@ -103,21 +112,24 @@ LL | | if let Some(m) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:50:9 | LL | / if let Some(m) = n { LL | | println!("{}", m); LL | | } | |_________^ +help: try + | +LL ~ for m in z.flatten() { +LL + println!("{}", m); +LL + } + | error: unnecessary `if let` since only the `Some` variant of the iterator element is used --> tests/ui/manual_flatten.rs:82:5 | -LL | for n in &vec_of_ref { - | ^ ----------- help: try: `vec_of_ref.iter().copied().flatten()` - | _____| - | | +LL | / for n in &vec_of_ref { LL | | LL | | LL | | if let Some(n) = n { @@ -125,21 +137,24 @@ LL | | if let Some(n) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:85:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); LL | | } | |_________^ +help: try + | +LL ~ for n in vec_of_ref.iter().copied().flatten() { +LL + println!("{:?}", n); +LL + } + | error: unnecessary `if let` since only the `Some` variant of the iterator element is used --> tests/ui/manual_flatten.rs:91:5 | -LL | for n in vec_of_ref { - | ^ ---------- help: try: `vec_of_ref.iter().copied().flatten()` - | _____| - | | +LL | / for n in vec_of_ref { LL | | LL | | LL | | if let Some(n) = n { @@ -147,21 +162,24 @@ LL | | if let Some(n) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:94:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); LL | | } | |_________^ +help: try + | +LL ~ for n in vec_of_ref.iter().copied().flatten() { +LL + println!("{:?}", n); +LL + } + | error: unnecessary `if let` since only the `Some` variant of the iterator element is used --> tests/ui/manual_flatten.rs:100:5 | -LL | for n in slice_of_ref { - | ^ ------------ help: try: `slice_of_ref.iter().copied().flatten()` - | _____| - | | +LL | / for n in slice_of_ref { LL | | LL | | LL | | if let Some(n) = n { @@ -169,16 +187,47 @@ LL | | if let Some(n) = n { LL | | } | |_____^ | -help: ...and remove the `if let` statement in the for loop +help: try `.flatten()` and remove the `if let` statement in the for loop --> tests/ui/manual_flatten.rs:103:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); LL | | } | |_________^ +help: try + | +LL ~ for n in slice_of_ref.iter().copied().flatten() { +LL + println!("{:?}", n); +LL + } + | + +error: unnecessary `if let` since only the `Some` variant of the iterator element is used + --> tests/ui/manual_flatten.rs:161:5 + | +LL | / for n in z { +LL | | +LL | | +LL | | if let Some(n) = n { +... | +LL | | } + | |_____^ + | +help: try `.flatten()` and remove the `if let` statement in the for loop + --> tests/ui/manual_flatten.rs:164:9 + | +LL | / if let Some(n) = n { +LL | | println!("{:?}", n); +LL | | } + | |_________^ +help: try + | +LL ~ for n in z.into_iter().flatten() { +LL + println!("{:?}", n); +LL + } + | error: unnecessary `if let` since only the `Some` variant of the iterator element is used - --> tests/ui/manual_flatten.rs:139:5 + --> tests/ui/manual_flatten.rs:176:5 | LL | / for n in vec![ LL | | @@ -188,8 +237,8 @@ LL | | Some(1), LL | | } | |_____^ | -help: remove the `if let` statement in the for loop and then... - --> tests/ui/manual_flatten.rs:146:9 +help: try `.flatten()` and remove the `if let` statement in the for loop + --> tests/ui/manual_flatten.rs:183:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); @@ -201,7 +250,9 @@ LL | for n in vec![ ... LL | Some(3) LL ~ ].iter().flatten() { +LL + println!("{:?}", n); +LL + } | -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_swap_auto_fix.fixed b/tests/ui/manual_swap_auto_fix.fixed index 28466ff3f9b4d..6cd81bafce8bb 100644 --- a/tests/ui/manual_swap_auto_fix.fixed +++ b/tests/ui/manual_swap_auto_fix.fixed @@ -55,3 +55,14 @@ fn swap8() { let i2 = 1; v.swap(i1 + i2, i2); } + +fn issue_14931() { + let mut v = [1, 2, 3, 4]; + + let mut i1 = 0; + for i2 in 0..4 { + v.swap(i1, i2); + + i1 += 2; + } +} diff --git a/tests/ui/manual_swap_auto_fix.rs b/tests/ui/manual_swap_auto_fix.rs index c9880e651cd7c..19dabfd833f61 100644 --- a/tests/ui/manual_swap_auto_fix.rs +++ b/tests/ui/manual_swap_auto_fix.rs @@ -78,3 +78,17 @@ fn swap8() { v[i1 + i2] = v[i2]; v[i2] = tmp; } + +fn issue_14931() { + let mut v = [1, 2, 3, 4]; + + let mut i1 = 0; + for i2 in 0..4 { + let tmp = v[i1]; + //~^ manual_swap + v[i1] = v[i2]; + v[i2] = tmp; + + i1 += 2; + } +} diff --git a/tests/ui/manual_swap_auto_fix.stderr b/tests/ui/manual_swap_auto_fix.stderr index 7ab898fcc7267..a0bb32233e239 100644 --- a/tests/ui/manual_swap_auto_fix.stderr +++ b/tests/ui/manual_swap_auto_fix.stderr @@ -92,5 +92,14 @@ LL | | v[i1 + i2] = v[i2]; LL | | v[i2] = tmp; | |________________^ help: try: `v.swap(i1 + i2, i2);` -error: aborting due to 8 previous errors +error: this looks like you are swapping elements of `v` manually + --> tests/ui/manual_swap_auto_fix.rs:87:9 + | +LL | / let tmp = v[i1]; +LL | | +LL | | v[i1] = v[i2]; +LL | | v[i2] = tmp; + | |____________________^ help: try: `v.swap(i1, i2);` + +error: aborting due to 9 previous errors diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index bdf39796ebfcb..e11dea352049e 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -188,3 +188,19 @@ fn issue14634() { let id!(_a) = dbg!(b + 1); //~^^^ match_single_binding } + +mod issue14991 { + struct AnnoConstWOBlock { + inner: [(); { + let _n = 1; + 42 + }], + } + + struct AnnoConstWBlock { + inner: [(); { + let _n = 1; + 42 + }], + } +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index 419ff95d873b0..d498da30fc87c 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -249,3 +249,21 @@ fn issue14634() { }; //~^^^ match_single_binding } + +mod issue14991 { + struct AnnoConstWOBlock { + inner: [(); match 1 { + //~^ match_single_binding + _n => 42, + }], + } + + struct AnnoConstWBlock { + inner: [(); { + match 1 { + //~^ match_single_binding + _n => 42, + } + }], + } +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index bdd0134a5f1c9..f274f80c81dad 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -378,5 +378,38 @@ LL ~ let id!(b) = dbg!(3); LL + let id!(_a) = dbg!(b + 1); | -error: aborting due to 27 previous errors +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:255:21 + | +LL | inner: [(); match 1 { + | _____________________^ +LL | | +LL | | _n => 42, +LL | | }], + | |_________^ + | +help: consider using a `let` statement + | +LL ~ inner: [(); { +LL + let _n = 1; +LL + 42 +LL ~ }], + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:263:13 + | +LL | / match 1 { +LL | | +LL | | _n => 42, +LL | | } + | |_____________^ + | +help: consider using a `let` statement + | +LL ~ let _n = 1; +LL + 42 + | + +error: aborting due to 29 previous errors diff --git a/tests/ui/missing_const_for_fn/const_trait.fixed b/tests/ui/missing_const_for_fn/const_trait.fixed new file mode 100644 index 0000000000000..7e0d4fccaae28 --- /dev/null +++ b/tests/ui/missing_const_for_fn/const_trait.fixed @@ -0,0 +1,36 @@ +#![feature(const_trait_impl)] +#![warn(clippy::missing_const_for_fn)] + +// Reduced test case from https://github.com/rust-lang/rust-clippy/issues/14658 + +#[const_trait] +trait ConstTrait { + fn method(self); +} + +impl ConstTrait for u32 { + fn method(self) {} +} + +impl const ConstTrait for u64 { + fn method(self) {} +} + +fn cannot_be_const() { + 0u32.method(); +} + +//~v missing_const_for_fn +const fn can_be_const() { + 0u64.method(); +} + +// False negative, see FIXME comment in `clipy_utils::qualify_min_const` +fn could_be_const_but_does_not_trigger(t: T) +where + T: const ConstTrait, +{ + t.method(); +} + +fn main() {} diff --git a/tests/ui/missing_const_for_fn/const_trait.rs b/tests/ui/missing_const_for_fn/const_trait.rs new file mode 100644 index 0000000000000..439da4622d7e0 --- /dev/null +++ b/tests/ui/missing_const_for_fn/const_trait.rs @@ -0,0 +1,36 @@ +#![feature(const_trait_impl)] +#![warn(clippy::missing_const_for_fn)] + +// Reduced test case from https://github.com/rust-lang/rust-clippy/issues/14658 + +#[const_trait] +trait ConstTrait { + fn method(self); +} + +impl ConstTrait for u32 { + fn method(self) {} +} + +impl const ConstTrait for u64 { + fn method(self) {} +} + +fn cannot_be_const() { + 0u32.method(); +} + +//~v missing_const_for_fn +fn can_be_const() { + 0u64.method(); +} + +// False negative, see FIXME comment in `clipy_utils::qualify_min_const` +fn could_be_const_but_does_not_trigger(t: T) +where + T: const ConstTrait, +{ + t.method(); +} + +fn main() {} diff --git a/tests/ui/missing_const_for_fn/const_trait.stderr b/tests/ui/missing_const_for_fn/const_trait.stderr new file mode 100644 index 0000000000000..b994b88fac681 --- /dev/null +++ b/tests/ui/missing_const_for_fn/const_trait.stderr @@ -0,0 +1,17 @@ +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/const_trait.rs:24:1 + | +LL | / fn can_be_const() { +LL | | 0u64.method(); +LL | | } + | |_^ + | + = note: `-D clippy::missing-const-for-fn` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_const_for_fn)]` +help: make the function `const` + | +LL | const fn can_be_const() { + | +++++ + +error: aborting due to 1 previous error + diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed index ceea4480d0dd9..15ca409c95bd1 100644 --- a/tests/ui/needless_lifetimes.fixed +++ b/tests/ui/needless_lifetimes.fixed @@ -10,7 +10,7 @@ clippy::unnecessary_wraps, dyn_drop, clippy::get_first, - mismatched_lifetime_syntaxes, + mismatched_lifetime_syntaxes )] extern crate proc_macros; diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index 8432f9e6d2f18..af9649d729872 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -10,7 +10,7 @@ clippy::unnecessary_wraps, dyn_drop, clippy::get_first, - mismatched_lifetime_syntaxes, + mismatched_lifetime_syntaxes )] extern crate proc_macros; diff --git a/tests/ui/non_canonical_partial_ord_impl.fixed b/tests/ui/non_canonical_partial_ord_impl.fixed index 23dbee5a08488..6915e1984fb1e 100644 --- a/tests/ui/non_canonical_partial_ord_impl.fixed +++ b/tests/ui/non_canonical_partial_ord_impl.fixed @@ -195,3 +195,19 @@ impl PartialOrd for K { //~^ non_canonical_partial_ord_impl fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } + +// #14574, check that partial_cmp invokes other.cmp + +#[derive(Eq, PartialEq)] +struct L(u32); + +impl Ord for L { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for L { + //~^ non_canonical_partial_ord_impl + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} diff --git a/tests/ui/non_canonical_partial_ord_impl.rs b/tests/ui/non_canonical_partial_ord_impl.rs index 12f055a542b89..7ce4cdc9aec83 100644 --- a/tests/ui/non_canonical_partial_ord_impl.rs +++ b/tests/ui/non_canonical_partial_ord_impl.rs @@ -201,3 +201,21 @@ impl PartialOrd for K { Ordering::Greater.into() } } + +// #14574, check that partial_cmp invokes other.cmp + +#[derive(Eq, PartialEq)] +struct L(u32); + +impl Ord for L { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for L { + //~^ non_canonical_partial_ord_impl + fn partial_cmp(&self, other: &Self) -> Option { + Some(other.cmp(self)) + } +} diff --git a/tests/ui/non_canonical_partial_ord_impl.stderr b/tests/ui/non_canonical_partial_ord_impl.stderr index c7de968588f8b..9bd6b1f726df2 100644 --- a/tests/ui/non_canonical_partial_ord_impl.stderr +++ b/tests/ui/non_canonical_partial_ord_impl.stderr @@ -44,5 +44,18 @@ LL | || } LL | | } | |__^ -error: aborting due to 3 previous errors +error: non-canonical implementation of `partial_cmp` on an `Ord` type + --> tests/ui/non_canonical_partial_ord_impl.rs:216:1 + | +LL | / impl PartialOrd for L { +LL | | +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________- +LL | || Some(other.cmp(self)) +LL | || } + | ||_____- help: change this to: `{ Some(self.cmp(other)) }` +LL | | } + | |__^ + +error: aborting due to 4 previous errors diff --git a/tests/ui/pointer_format.rs b/tests/ui/pointer_format.rs new file mode 100644 index 0000000000000..0621f966ad111 --- /dev/null +++ b/tests/ui/pointer_format.rs @@ -0,0 +1,66 @@ +#![warn(clippy::pointer_format)] + +use core::fmt::Debug; +use core::marker::PhantomData; + +#[derive(Debug)] +struct ContainsPointerDeep { + w: WithPointer, +} + +struct ManualDebug { + ptr: *const u8, +} + +#[derive(Debug)] +struct WithPointer { + len: usize, + ptr: *const u8, +} + +impl std::fmt::Debug for ManualDebug { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("ManualDebug") + } +} + +trait Foo { + type Assoc: Foo + Debug; +} + +#[derive(Debug)] +struct S(&'static S, PhantomData); + +#[allow(unused)] +fn unbounded(s: &S) { + format!("{s:?}"); +} + +fn main() { + let m = &(main as fn()); + let g = &0; + let o = &format!("{m:p}"); + //~^ pointer_format + let _ = format!("{m:?}"); + //~^ pointer_format + println!("{g:p}"); + //~^ pointer_format + panic!("{o:p}"); + //~^ pointer_format + let answer = 42; + let x = &raw const answer; + let arr = [0u8; 8]; + let with_ptr = WithPointer { len: 8, ptr: &arr as _ }; + let _ = format!("{x:?}"); + //~^ pointer_format + print!("{with_ptr:?}"); + //~^ pointer_format + let container = ContainsPointerDeep { w: with_ptr }; + print!("{container:?}"); + //~^ pointer_format + + let no_pointer = "foo"; + println!("{no_pointer:?}"); + let manual_debug = ManualDebug { ptr: &arr as _ }; + println!("{manual_debug:?}"); +} diff --git a/tests/ui/pointer_format.stderr b/tests/ui/pointer_format.stderr new file mode 100644 index 0000000000000..21ba39b8f8cc9 --- /dev/null +++ b/tests/ui/pointer_format.stderr @@ -0,0 +1,47 @@ +error: pointer formatting detected + --> tests/ui/pointer_format.rs:42:23 + | +LL | let o = &format!("{m:p}"); + | ^^^^^ + | + = note: `-D clippy::pointer-format` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::pointer_format)]` + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:44:22 + | +LL | let _ = format!("{m:?}"); + | ^^^^^ + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:46:15 + | +LL | println!("{g:p}"); + | ^^^^^ + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:48:13 + | +LL | panic!("{o:p}"); + | ^^^^^ + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:54:22 + | +LL | let _ = format!("{x:?}"); + | ^^^^^ + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:56:13 + | +LL | print!("{with_ptr:?}"); + | ^^^^^^^^^^^^ + +error: pointer formatting detected + --> tests/ui/pointer_format.rs:59:13 + | +LL | print!("{container:?}"); + | ^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/print_literal.fixed b/tests/ui/print_literal.fixed index 24c45a4a61b99..ebfe19c700ee6 100644 --- a/tests/ui/print_literal.fixed +++ b/tests/ui/print_literal.fixed @@ -94,3 +94,14 @@ fn issue_13959() { " ); } + +fn issue_14930() { + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ print_literal + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ print_literal + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ print_literal + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ print_literal +} diff --git a/tests/ui/print_literal.rs b/tests/ui/print_literal.rs index 42ae589ca639c..8f3d9be069854 100644 --- a/tests/ui/print_literal.rs +++ b/tests/ui/print_literal.rs @@ -95,3 +95,14 @@ fn issue_13959() { "# ); } + +fn issue_14930() { + println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); + //~^ print_literal + println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); + //~^ print_literal + println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); + //~^ print_literal + println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); + //~^ print_literal +} diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr index da663000686fd..1c378017d151b 100644 --- a/tests/ui/print_literal.stderr +++ b/tests/ui/print_literal.stderr @@ -229,5 +229,53 @@ LL + bar LL ~ " | -error: aborting due to 18 previous errors +error: literal with an empty format string + --> tests/ui/print_literal.rs:100:52 + | +LL | println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); + | ^^^ + | +help: try + | +LL - println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); +LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:102:49 + | +LL | println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); + | ^^^ + | +help: try + | +LL - println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); +LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:104:46 + | +LL | println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); + | ^^^ + | +help: try + | +LL - println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); +LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:106:40 + | +LL | println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); + | ^^^ + | +help: try + | +LL - println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); +LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: aborting due to 22 previous errors diff --git a/tests/ui/semicolon_outside_block.fixed b/tests/ui/semicolon_outside_block.fixed index 52fae9a3aff13..a3be80b492846 100644 --- a/tests/ui/semicolon_outside_block.fixed +++ b/tests/ui/semicolon_outside_block.fixed @@ -96,3 +96,28 @@ fn main() { unit_fn_block() } + +fn issue14926() { + macro_rules! gen_code { + [$l:lifetime: $b:block, $b2: block $(,)?] => { + $l: loop { + $b + break $l; + } + $l: loop { + $b2 + break $l; + } + }; + } + + gen_code! { + 'root: + { + println!("Block1"); + }, + { + println!("Block2"); + }, + } +} diff --git a/tests/ui/semicolon_outside_block.rs b/tests/ui/semicolon_outside_block.rs index 5975e66fbb819..3b7bf68029f3a 100644 --- a/tests/ui/semicolon_outside_block.rs +++ b/tests/ui/semicolon_outside_block.rs @@ -96,3 +96,28 @@ fn main() { unit_fn_block() } + +fn issue14926() { + macro_rules! gen_code { + [$l:lifetime: $b:block, $b2: block $(,)?] => { + $l: loop { + $b + break $l; + } + $l: loop { + $b2 + break $l; + } + }; + } + + gen_code! { + 'root: + { + println!("Block1"); + }, + { + println!("Block2"); + }, + } +} diff --git a/tests/ui/std_instead_of_core.fixed b/tests/ui/std_instead_of_core.fixed index ab2e801eee25f..1820ade422ff5 100644 --- a/tests/ui/std_instead_of_core.fixed +++ b/tests/ui/std_instead_of_core.fixed @@ -90,3 +90,9 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: core::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::std_instead_of_core)] +#[rustfmt::skip] +fn issue14982() { + use std::{collections::HashMap, hash::Hash}; +} diff --git a/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs index f760b3561aed7..32c4933098161 100644 --- a/tests/ui/std_instead_of_core.rs +++ b/tests/ui/std_instead_of_core.rs @@ -90,3 +90,9 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: std::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::std_instead_of_core)] +#[rustfmt::skip] +fn issue14982() { + use std::{collections::HashMap, hash::Hash}; +} diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index 22a6a26dab626..4208efad6774d 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -151,3 +151,27 @@ fn main() { bad(); ok(); } + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn some_other_fn(_: &i32) {} + + macro_rules! mac { + (def) => { + Default::default() + }; + (func $f:expr) => { + $f() + }; + (nonempty_block $e:expr) => {{ + some_other_fn(&$e); + $e + }}; + } + fn_take_unit(mac!(def)); + //~^ unit_arg + fn_take_unit(mac!(func Default::default)); + //~^ unit_arg + fn_take_unit(mac!(nonempty_block Default::default())); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index 6c333d9792d4d..0dcfb02b9fa08 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -199,5 +199,26 @@ LL ~ foo(1); LL + Some(()) | -error: aborting due to 10 previous errors +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:171:5 + | +LL | fn_take_unit(mac!(def)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:173:5 + | +LL | fn_take_unit(mac!(func Default::default)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:175:5 + | +LL | fn_take_unit(mac!(nonempty_block Default::default())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: aborting due to 13 previous errors diff --git a/tests/ui/unit_arg_empty_blocks.fixed b/tests/ui/unit_arg_empty_blocks.fixed deleted file mode 100644 index b045a33608d7c..0000000000000 --- a/tests/ui/unit_arg_empty_blocks.fixed +++ /dev/null @@ -1,34 +0,0 @@ -#![warn(clippy::unit_arg)] -#![allow(unused_must_use, unused_variables)] -#![allow(clippy::no_effect, clippy::uninlined_format_args)] - -use std::fmt::Debug; - -fn foo(t: T) { - println!("{:?}", t); -} - -fn foo3(t1: T1, t2: T2, t3: T3) { - println!("{:?}, {:?}, {:?}", t1, t2, t3); -} - -fn bad() { - foo(()); - //~^ unit_arg - foo3((), 2, 2); - //~^ unit_arg - foo(0); - taking_two_units((), ()); - //~^ unit_arg - foo(0); - foo(1); - taking_three_units((), (), ()); - //~^ unit_arg -} - -fn taking_two_units(a: (), b: ()) {} -fn taking_three_units(a: (), b: (), c: ()) {} - -fn main() { - bad(); -} diff --git a/tests/ui/unit_arg_empty_blocks.rs b/tests/ui/unit_arg_empty_blocks.rs deleted file mode 100644 index ab305913f3f69..0000000000000 --- a/tests/ui/unit_arg_empty_blocks.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![warn(clippy::unit_arg)] -#![allow(unused_must_use, unused_variables)] -#![allow(clippy::no_effect, clippy::uninlined_format_args)] - -use std::fmt::Debug; - -fn foo(t: T) { - println!("{:?}", t); -} - -fn foo3(t1: T1, t2: T2, t3: T3) { - println!("{:?}, {:?}, {:?}", t1, t2, t3); -} - -fn bad() { - foo({}); - //~^ unit_arg - foo3({}, 2, 2); - //~^ unit_arg - taking_two_units({}, foo(0)); - //~^ unit_arg - taking_three_units({}, foo(0), foo(1)); - //~^ unit_arg -} - -fn taking_two_units(a: (), b: ()) {} -fn taking_three_units(a: (), b: (), c: ()) {} - -fn main() { - bad(); -} diff --git a/tests/ui/unit_arg_empty_blocks.stderr b/tests/ui/unit_arg_empty_blocks.stderr deleted file mode 100644 index 2c686d58ecc1a..0000000000000 --- a/tests/ui/unit_arg_empty_blocks.stderr +++ /dev/null @@ -1,46 +0,0 @@ -error: passing a unit value to a function - --> tests/ui/unit_arg_empty_blocks.rs:16:5 - | -LL | foo({}); - | ^^^^--^ - | | - | help: use a unit literal instead: `()` - | - = note: `-D clippy::unit-arg` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::unit_arg)]` - -error: passing a unit value to a function - --> tests/ui/unit_arg_empty_blocks.rs:18:5 - | -LL | foo3({}, 2, 2); - | ^^^^^--^^^^^^^ - | | - | help: use a unit literal instead: `()` - -error: passing unit values to a function - --> tests/ui/unit_arg_empty_blocks.rs:20:5 - | -LL | taking_two_units({}, foo(0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: move the expression in front of the call and replace it with the unit literal `()` - | -LL ~ foo(0); -LL ~ taking_two_units((), ()); - | - -error: passing unit values to a function - --> tests/ui/unit_arg_empty_blocks.rs:22:5 - | -LL | taking_three_units({}, foo(0), foo(1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: move the expressions in front of the call and replace them with the unit literal `()` - | -LL ~ foo(0); -LL + foo(1); -LL ~ taking_three_units((), (), ()); - | - -error: aborting due to 4 previous errors - diff --git a/tests/ui/unit_arg_fixable.fixed b/tests/ui/unit_arg_fixable.fixed new file mode 100644 index 0000000000000..03353a14081b2 --- /dev/null +++ b/tests/ui/unit_arg_fixable.fixed @@ -0,0 +1,78 @@ +#![warn(clippy::unit_arg)] +#![allow(unused_must_use, unused_variables)] +#![allow(clippy::no_effect, clippy::uninlined_format_args)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +fn bad() { + foo(()); + //~^ unit_arg + foo3((), 2, 2); + //~^ unit_arg + foo(0); + taking_two_units((), ()); + //~^ unit_arg + foo(0); + foo(1); + taking_three_units((), (), ()); + //~^ unit_arg +} + +fn taking_two_units(a: (), b: ()) {} +fn taking_three_units(a: (), b: (), c: ()) {} + +fn main() { + bad(); +} + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn_take_unit(()); + //~^ unit_arg + + fn some_other_fn(_: &i32) {} + + macro_rules! another_mac { + () => { + some_other_fn(&Default::default()) + }; + ($e:expr) => { + some_other_fn(&$e) + }; + } + + another_mac!(); + fn_take_unit(()); + //~^ unit_arg + another_mac!(1); + fn_take_unit(()); + //~^ unit_arg + + macro_rules! mac { + (nondef $e:expr) => { + $e + }; + (empty_block) => {{}}; + } + fn_take_unit(mac!(nondef ())); + //~^ unit_arg + mac!(empty_block); + fn_take_unit(()); + //~^ unit_arg + + fn def() -> T { + Default::default() + } + + let _: () = def(); + fn_take_unit(()); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg_fixable.rs b/tests/ui/unit_arg_fixable.rs new file mode 100644 index 0000000000000..03fd96efdf901 --- /dev/null +++ b/tests/ui/unit_arg_fixable.rs @@ -0,0 +1,71 @@ +#![warn(clippy::unit_arg)] +#![allow(unused_must_use, unused_variables)] +#![allow(clippy::no_effect, clippy::uninlined_format_args)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +fn bad() { + foo({}); + //~^ unit_arg + foo3({}, 2, 2); + //~^ unit_arg + taking_two_units({}, foo(0)); + //~^ unit_arg + taking_three_units({}, foo(0), foo(1)); + //~^ unit_arg +} + +fn taking_two_units(a: (), b: ()) {} +fn taking_three_units(a: (), b: (), c: ()) {} + +fn main() { + bad(); +} + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn_take_unit(Default::default()); + //~^ unit_arg + + fn some_other_fn(_: &i32) {} + + macro_rules! another_mac { + () => { + some_other_fn(&Default::default()) + }; + ($e:expr) => { + some_other_fn(&$e) + }; + } + + fn_take_unit(another_mac!()); + //~^ unit_arg + fn_take_unit(another_mac!(1)); + //~^ unit_arg + + macro_rules! mac { + (nondef $e:expr) => { + $e + }; + (empty_block) => {{}}; + } + fn_take_unit(mac!(nondef Default::default())); + //~^ unit_arg + fn_take_unit(mac!(empty_block)); + //~^ unit_arg + + fn def() -> T { + Default::default() + } + + fn_take_unit(def()); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg_fixable.stderr b/tests/ui/unit_arg_fixable.stderr new file mode 100644 index 0000000000000..ccd5aa8322f9a --- /dev/null +++ b/tests/ui/unit_arg_fixable.stderr @@ -0,0 +1,110 @@ +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:16:5 + | +LL | foo({}); + | ^^^^--^ + | | + | help: use a unit literal instead: `()` + | + = note: `-D clippy::unit-arg` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unit_arg)]` + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:18:5 + | +LL | foo3({}, 2, 2); + | ^^^^^--^^^^^^^ + | | + | help: use a unit literal instead: `()` + +error: passing unit values to a function + --> tests/ui/unit_arg_fixable.rs:20:5 + | +LL | taking_two_units({}, foo(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ foo(0); +LL ~ taking_two_units((), ()); + | + +error: passing unit values to a function + --> tests/ui/unit_arg_fixable.rs:22:5 + | +LL | taking_three_units({}, foo(0), foo(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call and replace them with the unit literal `()` + | +LL ~ foo(0); +LL + foo(1); +LL ~ taking_three_units((), (), ()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:35:5 + | +LL | fn_take_unit(Default::default()); + | ^^^^^^^^^^^^^------------------^ + | | + | help: use a unit literal instead: `()` + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:49:5 + | +LL | fn_take_unit(another_mac!()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ another_mac!(); +LL ~ fn_take_unit(()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:51:5 + | +LL | fn_take_unit(another_mac!(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ another_mac!(1); +LL ~ fn_take_unit(()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:60:5 + | +LL | fn_take_unit(mac!(nondef Default::default())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^------------------^^ + | | + | help: use a unit literal instead: `()` + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:62:5 + | +LL | fn_take_unit(mac!(empty_block)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ mac!(empty_block); +LL ~ fn_take_unit(()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:69:5 + | +LL | fn_take_unit(def()); + | ^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ let _: () = def(); +LL ~ fn_take_unit(()); + | + +error: aborting due to 10 previous errors + diff --git a/tests/ui/unused_unit.edition2021.fixed b/tests/ui/unused_unit.edition2021.fixed index 93dd58b8e9d7b..def8ef86e3c57 100644 --- a/tests/ui/unused_unit.edition2021.fixed +++ b/tests/ui/unused_unit.edition2021.fixed @@ -143,4 +143,10 @@ mod issue14577 { todo!() } } -} \ No newline at end of file +} + +mod pr14962 { + #[allow(unused_parens)] + type UnusedParensButNoUnit = Box; +} + diff --git a/tests/ui/unused_unit.edition2024.fixed b/tests/ui/unused_unit.edition2024.fixed index 987d901b97df7..f908b958b191e 100644 --- a/tests/ui/unused_unit.edition2024.fixed +++ b/tests/ui/unused_unit.edition2024.fixed @@ -143,4 +143,10 @@ mod issue14577 { todo!() } } -} \ No newline at end of file +} + +mod pr14962 { + #[allow(unused_parens)] + type UnusedParensButNoUnit = Box; +} + diff --git a/tests/ui/unused_unit.rs b/tests/ui/unused_unit.rs index b7645f7b6a263..7298ec40cc28b 100644 --- a/tests/ui/unused_unit.rs +++ b/tests/ui/unused_unit.rs @@ -143,4 +143,10 @@ mod issue14577 { todo!() } } -} \ No newline at end of file +} + +mod pr14962 { + #[allow(unused_parens)] + type UnusedParensButNoUnit = Box; +} + diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed index e84f768e33d0c..29352fd468ea4 100644 --- a/tests/ui/write_literal.fixed +++ b/tests/ui/write_literal.fixed @@ -87,3 +87,15 @@ fn issue_13959() { " ); } + +fn issue_14930() { + let mut v = Vec::new(); + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ write_literal + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ write_literal + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ write_literal + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + //~^ write_literal +} diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index fc29fcbede7e6..928727527592e 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -88,3 +88,15 @@ fn issue_13959() { "# ); } + +fn issue_14930() { + let mut v = Vec::new(); + writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); + //~^ write_literal + writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); + //~^ write_literal + writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); + //~^ write_literal + writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); + //~^ write_literal +} diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index d53c2a7de2e05..ca37406c81141 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -181,5 +181,53 @@ LL + bar LL ~ " | -error: aborting due to 14 previous errors +error: literal with an empty format string + --> tests/ui/write_literal.rs:94:55 + | +LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); + | ^^^ + | +help: try + | +LL - writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); +LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:96:52 + | +LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); + | ^^^ + | +help: try + | +LL - writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); +LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:98:49 + | +LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); + | ^^^ + | +help: try + | +LL - writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); +LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:100:43 + | +LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); + | ^^^ + | +help: try + | +LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); +LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); + | + +error: aborting due to 18 previous errors diff --git a/tests/ui/zombie_processes.rs b/tests/ui/zombie_processes.rs index 395f9dd2defb5..e81b5fd4f3e24 100644 --- a/tests/ui/zombie_processes.rs +++ b/tests/ui/zombie_processes.rs @@ -198,3 +198,13 @@ mod issue14677 { child.wait().unwrap(); } } + +fn issue14911() -> std::io::Result { + let (mut recv, send) = std::io::pipe()?; + let mut command = Command::new("ls") + .stdout(send.try_clone()?) + .spawn() + .expect("Could not spawn new process..."); + command.wait()?; + Ok("".into()) +} diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index fec883938d6ae..285aa34e70186 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -602,7 +602,7 @@ filters.filterLints(); updateLintCount(); function updateLintCount() { - const allLints = filters.getAllLints(); + const allLints = filters.getAllLints().filter(lint => lint.group != "deprecated"); const totalLints = allLints.length; const countElement = document.getElementById("lint-count"); From 7e25bb163cba508e6547452637d4c1315e4e5442 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 13 Jun 2025 12:55:22 +0200 Subject: [PATCH 22/75] Move COERCE_CONTAINER_TO_ANY to nursery, as it has FPs --- clippy_lints/src/coerce_container_to_any.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs index 8c12a42ba4e4f..2b659253763fd 100644 --- a/clippy_lints/src/coerce_container_to_any.rs +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -42,7 +42,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.88.0"] pub COERCE_CONTAINER_TO_ANY, - suspicious, + nursery, "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" } declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); From fea8dd28a0773a3cc19e5b21102438c5d3eb4502 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 1 Jun 2025 00:34:55 +0800 Subject: [PATCH 23/75] Lint more cases in `collapsible_else_if` --- book/src/lint_configuration.md | 3 +- clippy_config/src/conf.rs | 4 +- clippy_lints/src/collapsible_if.rs | 126 +++++++++++++----- clippy_utils/src/lib.rs | 17 ++- .../collapsible_if/collapsible_else_if.fixed | 50 +++++++ .../collapsible_if/collapsible_else_if.rs | 55 ++++++++ .../collapsible_if/collapsible_else_if.stderr | 105 +++++++++++++++ tests/ui/collapsible_if.fixed | 9 ++ tests/ui/collapsible_if.rs | 9 ++ 9 files changed, 333 insertions(+), 45 deletions(-) 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 diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 7c850b4b023a7..ab46aabb03409 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -651,13 +651,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/src/conf.rs b/clippy_config/src/conf.rs index 87158cec42b24..064825e5ab817 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -641,9 +641,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_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index aef8e03320735..1854d86c53b22 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,13 +1,15 @@ 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::span_contains_non_comment; +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::source_map::SourceMap; use rustc_span::{BytePos, Span}; declare_clippy_lint! { @@ -91,37 +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 up_to_if = else_block.span.until(else_.span) - && !span_contains_non_comment(cx, up_to_if.with_lo(BytePos(up_to_if.lo().0 + 1))) + && 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, + ); + }, ); } } @@ -133,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, @@ -182,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 @@ -193,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, @@ -229,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_utils/src/lib.rs b/clippy_utils/src/lib.rs index ad20209331b6a..19a2816b75ab5 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2788,16 +2788,19 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool { }); } -/// Checks whether a given span has any non-comment token. This checks for all types of token other -/// than line comment "//", block comment "/**", doc "///" "//!" and whitespace +/// 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_comment(cx: &impl source::HasSession, span: Span) -> bool { - matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)| { - !matches!(token, TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace) - })) +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/tests/ui-toml/collapsible_if/collapsible_else_if.fixed b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed new file mode 100644 index 0000000000000..0dc0fc230c8de --- /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 0000000000000..8344c122f16c8 --- /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 0000000000000..0ffe5f0a960d7 --- /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/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index b553182a44543..77bc791ea8e9b 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 f5998457ca6cf..d30df157d5eb3 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 {} + }; +} From ee80deb7236002419702f0d10e322496cc4b0b38 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Fri, 13 Jun 2025 22:55:06 +0800 Subject: [PATCH 24/75] Apply `collapsible_else_if` to Clippy itself --- clippy_lints/src/loops/same_item_push.rs | 15 +++---- .../src/single_component_path_imports.rs | 29 ++++++------ .../src/undocumented_unsafe_blocks.rs | 45 +++++++++---------- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 388034c39f522..e792edbe23e0b 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/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 62939912304ba..38cf7e3822a13 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 a2938c86c76a9..92427473a8ee9 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 } } From 535b0d57085c4db4eb6818b58a042ff8d1f2a918 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Mon, 9 Jun 2025 22:35:57 +0800 Subject: [PATCH 25/75] fix: `exhaustive_structs` FP on structs with default valued field --- clippy_lints/src/exhaustive_items.rs | 2 +- tests/ui/exhaustive_items.fixed | 7 +++++++ tests/ui/exhaustive_items.rs | 7 +++++++ tests/ui/exhaustive_items.stderr | 10 +++++----- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 1fb0e4d24d062..86d9038ec45d3 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/tests/ui/exhaustive_items.fixed b/tests/ui/exhaustive_items.fixed index 79c74aeefbd83..3b2f33dbd2cee 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 4e851f4c492e8..b0a6a71707667 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 c92c8a9efaaea..55928fa458d39 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)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ From 81f9f0861edc894cefc665f38439e4dc03de3133 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 12 Jun 2025 23:41:29 +0000 Subject: [PATCH 26/75] TypeVisiting binders no longer requires TypeFolding its interior --- clippy_utils/src/ty/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 1f0a0f2b02ac0..32a992ccc2d7b 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -20,7 +20,7 @@ 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, TypeFoldable, TypeSuperVisitable, + GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; @@ -853,7 +853,7 @@ pub fn for_each_top_level_late_bound_region( ControlFlow::Continue(()) } } - fn visit_binder>>(&mut self, t: &Binder<'tcx, T>) -> Self::Result { + fn visit_binder>>(&mut self, t: &Binder<'tcx, T>) -> Self::Result { self.index += 1; let res = t.super_visit_with(self); self.index -= 1; From bf1a276db3c265f1265ad1820c831386f2d5a31b Mon Sep 17 00:00:00 2001 From: relaxcn Date: Sat, 14 Jun 2025 04:58:47 +0800 Subject: [PATCH 27/75] make it support more cases --- clippy_lints/src/operators/identity_op.rs | 101 +++++++++++----------- tests/ui/identity_op.fixed | 23 +++++ tests/ui/identity_op.rs | 23 +++++ tests/ui/identity_op.stderr | 16 +++- 4 files changed, 109 insertions(+), 54 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 6460bf6d6729a..be570c4a716a4 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::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, HirId, Item, ItemKind, Node, QPath}; +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, sym}; +use rustc_span::{Span, kw}; use super::IDENTITY_OP; @@ -165,11 +166,19 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) Parens::Needed } -fn is_allowed(cx: &LateContext<'_>, expr: &Expr<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool { - // Exclude case where the left or right side is a call to `Default::default()` - // 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_default_call(cx, left) || is_default_call(cx, right)) && !is_expr_with_type_annotation(cx, expr.hir_id) { +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; } @@ -182,20 +191,6 @@ fn is_allowed(cx: &LateContext<'_>, expr: &Expr<'_>, cmp: BinOpKind, left: &Expr && ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1))) } -/// Check if the expression is a call to `Default::default()` -fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(func, []) = peel_hir_expr_refs(expr).0.kind - && let ExprKind::Path(qpath) = func.kind - // Detect and ignore ::default() because these calls do explicitly name the type. - && let QPath::Resolved(None, _path) = qpath - && let Some(def_id) = cx.qpath_res(&qpath, func.hir_id).opt_def_id() - && cx.tcx.is_diagnostic_item(sym::default_fn, def_id) - { - return true; - } - false -} - fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { let ecx = ConstEvalCtxt::new(cx); if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) { @@ -256,38 +251,40 @@ fn span_ineffective_operation( ); } -/// Check if the expression is a let binding's init expression and the let binding has a type -/// annotation. Also check if the expression is a function's return value. -fn is_expr_with_type_annotation(cx: &LateContext<'_>, hir_id: HirId) -> bool { - // Get the parent node of the expression - if let Some((_, parent)) = cx.tcx.hir_parent_iter(hir_id).next() { - match parent { - Node::LetStmt(local) => { - // Check if this expression is the init expression of the let binding - if let Some(init) = local.init - && init.hir_id == hir_id - { - // Check if the let binding has an explicit type annotation - return local.ty.is_some(); - } - }, - Node::Block(block) => { - // If the parent node is a block, we can make sure the expression is the last expression in the - // block. - return is_expr_with_type_annotation(cx, block.hir_id); - }, - Node::Expr(expr) => { - return is_expr_with_type_annotation(cx, expr.hir_id); - }, - Node::Item(Item { - kind: ItemKind::Fn { .. }, +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: +/// ``` +/// Default::default() +/// // Or +/// trait Def { +/// fn def() -> Self; +/// } +/// 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), .. - }) => { - // Every function has a return type, so we can return true. - return true; }, - _ => {}, - } + )) = 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/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 2cc4a5d0c23b9..20cb35633264b 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -325,7 +325,30 @@ fn issue_14932() { //~^ 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 + + 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 da0597c7abe4b..18e300ef8a9e7 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -325,7 +325,30 @@ fn issue_14932() { //~^ 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 + + 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 9c774a313ffc3..1fa3dd104789a 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -392,10 +392,22 @@ 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:329:5 + --> tests/ui/identity_op.rs:330:5 | LL | 0usize + &Default::default() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()` -error: aborting due to 66 previous errors +error: this operation has no effect + --> tests/ui/identity_op.rs:349:13 + | +LL | let _ = 0usize + &usize::def(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::def()` + +error: this operation has no effect + --> tests/ui/identity_op.rs:352:21 + | +LL | let _n: usize = 0usize + &Def::def(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Def::def()` + +error: aborting due to 68 previous errors From 3d8d5756635768883d0cc4555669d490f0eb756f Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 13 Jun 2025 13:55:37 -0700 Subject: [PATCH 28/75] Reduce precedence of expressions that have an outer attr --- clippy_lints/src/casts/unnecessary_cast.rs | 2 +- clippy_lints/src/dereference.rs | 10 +++++----- clippy_lints/src/loops/single_element_loop.rs | 2 +- clippy_lints/src/matches/manual_utils.rs | 2 +- clippy_lints/src/neg_multiply.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 2 +- .../transmute/transmutes_expressible_as_ptr_casts.rs | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 8e8c55cf38329..010f09d4c1d31 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -185,7 +185,7 @@ pub(super) fn check<'tcx>( Node::Expr(parent) if is_borrow_expr(cx, parent) && !is_in_allowed_macro(cx, parent) => { MaybeParenOrBlock::Block }, - Node::Expr(parent) if cast_expr.precedence() < parent.precedence() => MaybeParenOrBlock::Paren, + Node::Expr(parent) if cx.precedence(cast_expr) < cx.precedence(parent) => MaybeParenOrBlock::Paren, _ => MaybeParenOrBlock::Nothing, }; diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index cde9528cd8789..7463d7b5c3bda 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -972,7 +972,7 @@ fn report<'tcx>( "&" }; - let expr_str = if !expr_is_macro_call && is_ufcs && expr.precedence() < ExprPrecedence::Prefix { + let expr_str = if !expr_is_macro_call && is_ufcs && cx.precedence(expr) < ExprPrecedence::Prefix { Cow::Owned(format!("({expr_str})")) } else { expr_str @@ -1015,10 +1015,10 @@ fn report<'tcx>( Node::Expr(e) => match e.kind { ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => false, ExprKind::Call(..) => { - expr.precedence() < ExprPrecedence::Unambiguous + cx.precedence(expr) < ExprPrecedence::Unambiguous || matches!(expr.kind, ExprKind::Field(..)) }, - _ => expr.precedence() < e.precedence(), + _ => cx.precedence(expr) < cx.precedence(e), }, _ => false, }; @@ -1066,7 +1066,7 @@ fn report<'tcx>( Mutability::Not => "&", Mutability::Mut => "&mut ", }; - (prefix, expr.precedence() < ExprPrecedence::Prefix) + (prefix, cx.precedence(expr) < ExprPrecedence::Prefix) }, None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", false), _ => ("", false), @@ -1172,7 +1172,7 @@ impl<'tcx> Dereferencing<'tcx> { }, Some(parent) if !parent.span.from_expansion() => { // Double reference might be needed at this point. - if parent.precedence() == ExprPrecedence::Unambiguous { + if cx.precedence(parent) == ExprPrecedence::Unambiguous { // Parentheses would be needed here, don't lint. *outer_pat = None; } else { diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index 12719c4f94bfd..d66771a8b5bec 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -84,7 +84,7 @@ pub(super) fn check<'tcx>( if !prefix.is_empty() && ( // Precedence of internal expression is less than or equal to precedence of `&expr`. - arg_expression.precedence() <= ExprPrecedence::Prefix || is_range_literal(arg_expression) + cx.precedence(arg_expression) <= ExprPrecedence::Prefix || is_range_literal(arg_expression) ) { arg_snip = format!("({arg_snip})").into(); diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index d0905733ab506..dbae71bbb1b06 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -117,7 +117,7 @@ where // it's being passed by value. let scrutinee = peel_hir_expr_refs(scrutinee).0; let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app); - let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence() < ExprPrecedence::Unambiguous { + let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && cx.precedence(scrutinee) < ExprPrecedence::Unambiguous { format!("({scrutinee_str})") } else { scrutinee_str.into() diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 74c8142787ebc..442280f999821 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -64,7 +64,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { { let mut applicability = Applicability::MachineApplicable; let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability); - let suggestion = if !from_macro && exp.precedence() < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) { + let suggestion = if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) { format!("-({snip})") } else { format!("-{snip}") diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 1117dea703c2a..324a05cdcc0c8 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed)); let parent_expr = get_parent_expr(cx, expr); let needs_parens_for_prefix = - parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix); + parent_expr.is_some_and(|parent| cx.precedence(parent) > ExprPrecedence::Prefix); if expr_ty == indexed_ty { if expr_ref_count > indexed_ref_count { diff --git a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs index 0d5cf45a5e653..18897fbb5b8f0 100644 --- a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs +++ b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -44,7 +44,7 @@ pub(super) fn check<'tcx>( }; if let Node::Expr(parent) = cx.tcx.parent_hir_node(e.hir_id) - && parent.precedence() > ExprPrecedence::Cast + && cx.precedence(parent) > ExprPrecedence::Cast { sugg = format!("({sugg})"); } From 7d708a45afad8a692ac6b57a3d332c07f81c2c57 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 14 Jun 2025 14:54:37 +0800 Subject: [PATCH 29/75] fix: `needless_doctest_main` panic when doctest is invalid --- clippy_lints/src/doc/needless_doctest_main.rs | 5 ++++- tests/ui/doc/needless_doctest_main.rs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index ec4538039a918..587ea630d9583 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -105,7 +105,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); }, } diff --git a/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs index 633a435ca5ed7..21396cbc8d31a 100644 --- a/tests/ui/doc/needless_doctest_main.rs +++ b/tests/ui/doc/needless_doctest_main.rs @@ -20,3 +20,19 @@ fn foo() {} fn main() {} + +fn issue8244() -> Result<(), ()> { + //! ```compile_fail + //! fn test() -> Result< {} + //! ``` + Ok(()) +} + +/// # Examples +/// +/// ``` +/// use std::error::Error; +/// fn main() -> Result<(), Box/* > */ { +/// } +/// ``` +fn issue15041() {} From de4f8e2bc6303b78f550ebe91f29e8c1a149c6ec Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 14 Jun 2025 15:45:14 +0800 Subject: [PATCH 30/75] fix: `manual_ok_err` suggests wrongly with references --- clippy_lints/src/matches/manual_ok_err.rs | 20 +++++++++++-- tests/ui/manual_ok_err.fixed | 24 +++++++++++++++ tests/ui/manual_ok_err.rs | 36 +++++++++++++++++++++++ tests/ui/manual_ok_err.stderr | 32 +++++++++++++++++++- 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index 4959908dad635..edbb556fd9766 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/tests/ui/manual_ok_err.fixed b/tests/ui/manual_ok_err.fixed index e6f799aa58d61..9b70ce0df43a7 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 972b2c41ee7aa..dee9046382458 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 040e170f397e2..448fbffc05093 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 From 2dfab750e7b75eb0fd2dd81ddcc3b818924f2df6 Mon Sep 17 00:00:00 2001 From: relaxcn Date: Sat, 14 Jun 2025 17:24:09 +0800 Subject: [PATCH 31/75] fix ci/cd error --- clippy_lints/src/operators/identity_op.rs | 14 +++++++++---- tests/ui/identity_op.fixed | 6 ++++++ tests/ui/identity_op.rs | 6 ++++++ tests/ui/identity_op.stderr | 24 +++++++++++++++++------ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index be570c4a716a4..7e515e83cc9dc 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -262,12 +262,18 @@ fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx E /// Check if the expression is an associated function without a type instance. /// Example: /// ``` -/// Default::default() -/// // Or /// trait Def { -/// fn def() -> Self; +/// fn def() -> Self; +/// } +/// impl Def for usize { +/// fn def() -> Self { +/// 0 +/// } +/// } +/// fn test() { +/// let _ = 0usize + &Default::default(); +/// let _ = 0usize + &Def::def(); /// } -/// 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 diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 20cb35633264b..4e14e1a5e33f0 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -318,6 +318,9 @@ fn issue_14932() { 0usize + &Default::default(); // no error + ::default(); + //~^ identity_op + let _ = usize::default(); //~^ identity_op @@ -346,6 +349,9 @@ fn issue_14932_3() { 0usize + &Def::def(); // no error + ::def(); + //~^ identity_op + let _ = usize::def(); //~^ identity_op diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index 18e300ef8a9e7..ebbef5723ffb2 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -318,6 +318,9 @@ fn issue_14932() { 0usize + &Default::default(); // no error + 0usize + &::default(); + //~^ identity_op + let _ = 0usize + &usize::default(); //~^ identity_op @@ -346,6 +349,9 @@ fn issue_14932_3() { 0usize + &Def::def(); // no error + 0usize + &::def(); + //~^ identity_op + let _ = 0usize + &usize::def(); //~^ identity_op diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 1fa3dd104789a..24fa5db08ce52 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -380,34 +380,46 @@ 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: this operation has no effect - --> tests/ui/identity_op.rs:321:13 + --> 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:324:21 + --> 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:330:5 + --> 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:349:13 + --> 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:352:21 + --> 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 68 previous errors +error: aborting due to 70 previous errors From 75762c41d479e6dac162ed5f778357510cda3e45 Mon Sep 17 00:00:00 2001 From: Chris Bloodsworth Date: Mon, 16 Jun 2025 17:36:02 -0400 Subject: [PATCH 32/75] docs: add link to span_lint in diagnostics.rs. changelog: None --- clippy_utils/src/diagnostics.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index cd2098a89891d..dc240dd067b12 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` From c7ee095bd6210ccec5d56573cfa06cdc7a7f3b2d Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 3 Mar 2025 02:39:56 +0000 Subject: [PATCH 33/75] clippy: add `MetaSized` conditions Existing lints that had special-casing for `Sized` predicates ought to have these same special cases applied to `MetaSized` predicates. --- clippy_lints/src/methods/unnecessary_to_owned.rs | 2 ++ clippy_lints/src/needless_borrows_for_generic_args.rs | 2 ++ clippy_lints/src/needless_pass_by_value.rs | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index fdccf1fb33db7..769526d131bf7 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -388,9 +388,11 @@ fn check_other_call_arg<'tcx>( && let (input, n_refs) = peel_middle_ty_refs(*input) && let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input) && let Some(sized_def_id) = cx.tcx.lang_items().sized_trait() + && let Some(meta_sized_def_id) = cx.tcx.lang_items().meta_sized_trait() && let [trait_predicate] = trait_predicates .iter() .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id) + .filter(|trait_predicate| trait_predicate.def_id() != meta_sized_def_id) .collect::>()[..] && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref) && let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef) diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 2efb55b9880c9..8d2f8029112f6 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -174,6 +174,7 @@ fn needless_borrow_count<'tcx>( ) -> usize { let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait(); let sized_trait_def_id = cx.tcx.lang_items().sized_trait(); + let meta_sized_trait_def_id = cx.tcx.lang_items().meta_sized_trait(); let drop_trait_def_id = cx.tcx.lang_items().drop_trait(); let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder(); @@ -209,6 +210,7 @@ fn needless_borrow_count<'tcx>( .all(|trait_def_id| { Some(trait_def_id) == destruct_trait_def_id || Some(trait_def_id) == sized_trait_def_id + || Some(trait_def_id) == meta_sized_trait_def_id || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id) }) { diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 95623467b8153..9aede1dec9344 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -116,13 +116,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { ]; let sized_trait = need!(cx.tcx.lang_items().sized_trait()); + let meta_sized_trait = need!(cx.tcx.lang_items().meta_sized_trait()); let preds = traits::elaborate(cx.tcx, cx.param_env.caller_bounds().iter()) .filter(|p| !p.is_global()) .filter_map(|pred| { // 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 => Some(pred), + Some(ty::ClauseKind::Trait(pred)) + if pred.def_id() != sized_trait && pred.def_id() != meta_sized_trait + => Some(pred), _ => None, } }) From 765015d7e59fe32dc84507a3d6ededdbdd6801fc Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 3 Mar 2025 02:39:21 +0000 Subject: [PATCH 34/75] clippy: `{Meta,Pointee,}Sized` in non-minicore One clippy test is `no_core` and needs to have `MetaSized` and `PointeeSized` added to it. --- tests/ui/def_id_nocore.rs | 8 +++++++- tests/ui/def_id_nocore.stderr | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/ui/def_id_nocore.rs b/tests/ui/def_id_nocore.rs index 40f40f7ea0960..5c13d86227678 100644 --- a/tests/ui/def_id_nocore.rs +++ b/tests/ui/def_id_nocore.rs @@ -7,8 +7,14 @@ #[link(name = "c")] unsafe extern "C" {} +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + #[lang = "sized"] -pub trait Sized {} +pub trait Sized: MetaSized {} #[lang = "copy"] pub trait Copy {} #[lang = "freeze"] diff --git a/tests/ui/def_id_nocore.stderr b/tests/ui/def_id_nocore.stderr index 2718217313ff9..175dd0754081f 100644 --- a/tests/ui/def_id_nocore.stderr +++ b/tests/ui/def_id_nocore.stderr @@ -1,5 +1,5 @@ error: methods called `as_*` usually take `self` by reference or `self` by mutable reference - --> tests/ui/def_id_nocore.rs:27:19 + --> tests/ui/def_id_nocore.rs:33:19 | LL | pub fn as_ref(self) -> &'static str { | ^^^^ From 9ed184365363f106885ea3809d65645d97360a2b Mon Sep 17 00:00:00 2001 From: klensy Date: Tue, 17 Jun 2025 15:42:54 +0300 Subject: [PATCH 35/75] dogfood clippy::or_fun_call --- clippy_dev/src/lint.rs | 4 ++-- clippy_dev/src/serve.rs | 2 +- clippy_lints/src/attrs/utils.rs | 12 +++++++----- lintcheck/src/main.rs | 2 +- src/main.rs | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index e0e036757d565..0d66f167a386d 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/serve.rs b/clippy_dev/src/serve.rs index a2d1236629fd2..498ffeba9d67c 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_lints/src/attrs/utils.rs b/clippy_lints/src/attrs/utils.rs index a5ce2137bffeb..7b66f91f6c073 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/lintcheck/src/main.rs b/lintcheck/src/main.rs index 8418383143280..eb390eecbcca8 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/src/main.rs b/src/main.rs index c9853e53f3b38..3c2eec1f05b90 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() From 697b4f8306808bd1ed70fbcee585ef20c8e26c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 7 Mar 2025 16:08:13 +0100 Subject: [PATCH 36/75] convert entire codebase to parsed inline attrs --- clippy_lints/src/attrs/inline_always.rs | 25 ++++++++-------------- clippy_lints/src/inline_fn_without_body.rs | 12 +++++++---- clippy_lints/src/missing_inline.rs | 6 +++--- clippy_lints/src/pass_by_ref_or_value.rs | 14 ++++++------ 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/attrs/inline_always.rs b/clippy_lints/src/attrs/inline_always.rs index cb63fadb4e21c..141ba7b0636a5 100644 --- a/clippy_lints/src/attrs/inline_always.rs +++ b/clippy_lints/src/attrs/inline_always.rs @@ -1,29 +1,22 @@ use super::INLINE_ALWAYS; -use super::utils::is_word; use clippy_utils::diagnostics::span_lint; +use rustc_attr_parsing::{find_attr, AttributeKind, InlineAttr}; use rustc_hir::Attribute; use rustc_lint::LateContext; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; +use rustc_span::Span; pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) { if span.from_expansion() { return; } - for attr in attrs { - if let Some(values) = attr.meta_item_list() { - if values.len() != 1 || !attr.has_name(sym::inline) { - continue; - } - if is_word(&values[0], sym::always) { - span_lint( - cx, - INLINE_ALWAYS, - attr.span(), - format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"), - ); - } - } + if let Some(span) = find_attr!(attrs, AttributeKind::Inline(InlineAttr::Always, span) => *span) { + span_lint( + cx, + INLINE_ALWAYS, + span, + format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"), + ); } } diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index da5ca5e677218..23c427f3c20cd 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::DiagExt; +use rustc_attr_parsing::{find_attr, AttributeKind}; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -32,15 +32,19 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]); 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) = cx.tcx.hir_attrs(item.hir_id()).iter().find(|a| a.has_name(sym::inline)) + && let Some(attr_span) = find_attr!(cx + .tcx + .hir_attrs(item.hir_id()), + AttributeKind::Inline(_, span) => *span + ) { span_lint_and_then( cx, INLINE_FN_WITHOUT_BODY, - attr.span(), + attr_span, format!("use of `#[inline]` on trait method `{}` which has no body", item.ident), |diag| { - diag.suggest_remove_item(cx, attr.span(), "remove", Applicability::MachineApplicable); + diag.suggest_remove_item(cx, attr_span, "remove", Applicability::MachineApplicable); }, ); } diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 1f613171b46e8..25579c10cb97c 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint; +use rustc_attr_parsing::{find_attr, AttributeKind}; use rustc_hir as hir; use rustc_hir::Attribute; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocItemContainer; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -64,8 +65,7 @@ declare_clippy_lint! { } fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) { - let has_inline = attrs.iter().any(|a| a.has_name(sym::inline)); - if !has_inline { + if !find_attr!(attrs, AttributeKind::Inline(..)) { span_lint( cx, MISSING_INLINE_IN_PUBLIC_ITEMS, diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index dadf49b64e517..dc86143b10e2f 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_parsing::{find_attr, AttributeKind, InlineAttr}; +use rustc_data_structures::fx::FxHashSet; use core::ops::ControlFlow; use rustc_abi::ExternAbi; -use rustc_ast::attr; -use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; @@ -270,11 +270,13 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { return; } let attrs = cx.tcx.hir_attrs(hir_id); + if find_attr!(attrs, AttributeKind::Inline(InlineAttr::Always, _)) { + return; + } + for a in attrs { - if let Some(meta_items) = a.meta_item_list() - && (a.has_name(sym::proc_macro_derive) - || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))) - { + // FIXME(jdonszelmann): make part of the find_attr above + if a.has_name(sym::proc_macro_derive) { return; } } From bc2ed2ca9fce221d26083d25cf31f2925c348428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 10 Jun 2025 08:48:56 +0200 Subject: [PATCH 37/75] fix clippy --- clippy_lints/src/attrs/inline_always.rs | 2 +- clippy_lints/src/inline_fn_without_body.rs | 2 +- clippy_lints/src/missing_inline.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/attrs/inline_always.rs b/clippy_lints/src/attrs/inline_always.rs index 141ba7b0636a5..58e51128a0dcb 100644 --- a/clippy_lints/src/attrs/inline_always.rs +++ b/clippy_lints/src/attrs/inline_always.rs @@ -1,6 +1,6 @@ use super::INLINE_ALWAYS; use clippy_utils::diagnostics::span_lint; -use rustc_attr_parsing::{find_attr, AttributeKind, InlineAttr}; +use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr}; use rustc_hir::Attribute; use rustc_lint::LateContext; use rustc_span::symbol::Symbol; diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index 23c427f3c20cd..617c006795be1 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_parsing::{find_attr, AttributeKind}; +use rustc_attr_data_structures::{find_attr, AttributeKind}; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 25579c10cb97c..f835bbb7c5610 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_parsing::{find_attr, AttributeKind}; +use rustc_attr_data_structures::{find_attr, AttributeKind}; use rustc_hir as hir; use rustc_hir::Attribute; use rustc_lint::{LateContext, LateLintPass, LintContext}; diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index dc86143b10e2f..e18bdfb34ac83 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -3,7 +3,7 @@ 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_parsing::{find_attr, AttributeKind, InlineAttr}; +use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr}; use rustc_data_structures::fx::FxHashSet; use core::ops::ControlFlow; use rustc_abi::ExternAbi; From a01dd1edaf8d2774360d21a0c9a16bbfbb56fbb2 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 4 Jun 2025 14:33:31 +0200 Subject: [PATCH 38/75] Update/bless clippy tests. --- tests/ui/author/macro_in_closure.stdout | 39 +++++++++++++++---------- tests/ui/author/macro_in_loop.stdout | 35 +++++++++++++--------- tests/ui/manual_inspect.fixed | 1 - tests/ui/manual_inspect.rs | 1 - tests/ui/manual_inspect.stderr | 23 ++------------- 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/tests/ui/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout index 5f8a4ce236302..49595e2fec257 100644 --- a/tests/ui/author/macro_in_closure.stdout +++ b/tests/ui/author/macro_in_closure.stdout @@ -9,28 +9,35 @@ if let StmtKind::Let(local) = stmt.kind && let ExprKind::Call(func, args) = e.kind && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 - && let ExprKind::Call(func1, args1) = args[0].kind - && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 2 + && let ExprKind::Block(block1, None) = args[0].kind + && block1.stmts.len() == 1 + && let StmtKind::Let(local1) = block1.stmts[0].kind + && let Some(init1) = local1.init + && let ExprKind::Array(elements) = init1.kind + && elements.len() == 1 + && let ExprKind::Call(func1, args1) = elements[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind - && let ExprKind::Array(elements) = inner.kind - && elements.len() == 2 - && let ExprKind::Lit(ref lit) = elements[0].kind + && let PatKind::Binding(BindingMode::NONE, _, name, None) = local1.pat.kind + && name.as_str() == "args" + && let Some(trailing_expr) = block1.expr + && let ExprKind::Call(func2, args2) = trailing_expr.kind + && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed + && args2.len() == 2 + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind + && let ExprKind::Array(elements1) = inner1.kind + && elements1.len() == 2 + && let ExprKind::Lit(ref lit) = elements1[0].kind && let LitKind::Str(s, _) = lit.node && s.as_str() == "" - && let ExprKind::Lit(ref lit1) = elements[1].kind + && let ExprKind::Lit(ref lit1) = elements1[1].kind && let LitKind::Str(s1, _) = lit1.node && s1.as_str() == "\n" - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args1[1].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 1 - && let ExprKind::Call(func2, args2) = elements1[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed - && args2.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind && block.expr.is_none() - && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind - && name.as_str() == "print_text" + && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind + && name1.as_str() == "print_text" { // report your lint here } diff --git a/tests/ui/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout index ecc252543117a..4fc7b49464db3 100644 --- a/tests/ui/author/macro_in_loop.stdout +++ b/tests/ui/author/macro_in_loop.stdout @@ -19,25 +19,32 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && let ExprKind::Call(func, args) = e1.kind && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 - && let ExprKind::Call(func1, args1) = args[0].kind - && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 2 + && let ExprKind::Block(block2, None) = args[0].kind + && block2.stmts.len() == 1 + && let StmtKind::Let(local) = block2.stmts[0].kind + && let Some(init) = local.init + && let ExprKind::Array(elements) = init.kind + && elements.len() == 1 + && let ExprKind::Call(func1, args1) = elements[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind - && let ExprKind::Array(elements) = inner.kind - && elements.len() == 2 - && let ExprKind::Lit(ref lit2) = elements[0].kind + && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind + && name1.as_str() == "args" + && let Some(trailing_expr) = block2.expr + && let ExprKind::Call(func2, args2) = trailing_expr.kind + && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed + && args2.len() == 2 + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind + && let ExprKind::Array(elements1) = inner1.kind + && elements1.len() == 2 + && let ExprKind::Lit(ref lit2) = elements1[0].kind && let LitKind::Str(s, _) = lit2.node && s.as_str() == "" - && let ExprKind::Lit(ref lit3) = elements[1].kind + && let ExprKind::Lit(ref lit3) = elements1[1].kind && let LitKind::Str(s1, _) = lit3.node && s1.as_str() == "\n" - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args1[1].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 1 - && let ExprKind::Call(func2, args2) = elements1[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed - && args2.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind && block1.expr.is_none() && block.expr.is_none() { diff --git a/tests/ui/manual_inspect.fixed b/tests/ui/manual_inspect.fixed index 9b768dbad700c..00a19155a5167 100644 --- a/tests/ui/manual_inspect.fixed +++ b/tests/ui/manual_inspect.fixed @@ -154,7 +154,6 @@ fn main() { }); let _ = [0] - //~^ suspicious_map .into_iter() .inspect(|&x| { //~^ manual_inspect diff --git a/tests/ui/manual_inspect.rs b/tests/ui/manual_inspect.rs index e679636201e6a..b3b17139cde3b 100644 --- a/tests/ui/manual_inspect.rs +++ b/tests/ui/manual_inspect.rs @@ -165,7 +165,6 @@ fn main() { }); let _ = [0] - //~^ suspicious_map .into_iter() .map(|x| { //~^ manual_inspect diff --git a/tests/ui/manual_inspect.stderr b/tests/ui/manual_inspect.stderr index 78b085fdfca30..70c00c1f75589 100644 --- a/tests/ui/manual_inspect.stderr +++ b/tests/ui/manual_inspect.stderr @@ -157,25 +157,8 @@ LL | LL ~ println!("{}", x); | -error: this call to `map()` won't have an effect on the call to `count()` - --> tests/ui/manual_inspect.rs:167:13 - | -LL | let _ = [0] - | _____________^ -LL | | -LL | | .into_iter() -LL | | .map(|x| { -... | -LL | | }) -LL | | .count(); - | |________________^ - | - = help: make sure you did not confuse `map` with `filter`, `for_each` or `inspect` - = note: `-D clippy::suspicious-map` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` - error: using `map` over `inspect` - --> tests/ui/manual_inspect.rs:170:10 + --> tests/ui/manual_inspect.rs:169:10 | LL | .map(|x| { | ^^^ @@ -188,7 +171,7 @@ LL ~ println!("{}", x); | error: using `map` over `inspect` - --> tests/ui/manual_inspect.rs:203:30 + --> tests/ui/manual_inspect.rs:202:30 | LL | if let Some(x) = Some(1).map(|x| { println!("{x}"); | ^^^ @@ -200,5 +183,5 @@ LL | // Do not collapse code into this comment LL ~ }) { | -error: aborting due to 14 previous errors +error: aborting due to 13 previous errors From 77ef3d7e42595a646f6b684f451114723a632be9 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 18 Jun 2025 21:05:52 +0800 Subject: [PATCH 39/75] fix: `branches_sharing_code` suggests misleadingly when in assignment --- clippy_lints/src/copies.rs | 18 +++++++++-- .../branches_sharing_code/shared_at_bottom.rs | 24 ++++++++++++++ .../shared_at_bottom.stderr | 32 ++++++++++++++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 5ef726638a560..27918698cd6ba 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/tests/ui/branches_sharing_code/shared_at_bottom.rs b/tests/ui/branches_sharing_code/shared_at_bottom.rs index 922d30443fcc2..fa322dc28a78a 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 f437db8b73313..1c470fb0da5e5 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 From acd8810e77fd3ee8362ce70ce195ae7c8bd08f25 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 23 Apr 2025 20:04:35 -0400 Subject: [PATCH 40/75] Prepare to split lints into multiple crates * Move `declare_clippy_lint` to it's own crate * Move lint/group registration into the driver * Make `dev update_lints` handle multiple lint crates --- Cargo.toml | 1 + clippy_dev/src/release.rs | 1 + clippy_dev/src/update_lints.rs | 282 +++++++++++------- clippy_dev/src/utils.rs | 15 - clippy_lints/Cargo.toml | 1 + clippy_lints/src/declare_clippy_lint.rs | 168 ----------- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/floating_point_arithmetic.rs | 3 +- clippy_lints/src/lib.rs | 126 +------- clippy_lints/src/manual_let_else.rs | 1 - .../src/needless_parens_on_range_literals.rs | 1 - clippy_lints/src/question_mark_used.rs | 1 - clippy_lints/src/read_zero_byte_vec.rs | 3 +- declare_clippy_lint/Cargo.toml | 10 + declare_clippy_lint/src/lib.rs | 280 +++++++++++++++++ src/driver.rs | 8 +- tests/compile-test.rs | 6 +- tests/dogfood.rs | 1 + tests/versioncheck.rs | 1 + 19 files changed, 480 insertions(+), 431 deletions(-) delete mode 100644 clippy_lints/src/declare_clippy_lint.rs create mode 100644 declare_clippy_lint/Cargo.toml create mode 100644 declare_clippy_lint/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5584ded0a4aa8..8cd648cf9f0ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 62c1bee81850e..15392dd1d2927 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/update_lints.rs b/clippy_dev/src/update_lints.rs index 08592f2521f7d..6dbee33ffcaad 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -4,8 +4,9 @@ use crate::utils::{ use itertools::Itertools; 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\ @@ -36,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(); - } - 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(); + "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\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(); + ); + 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(); } - 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(); + } + 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) - }), - ("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(); + } + 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(); } - } - 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(); + }, + ), + ); + 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("\nfn main() {}\n"); + dst.push_str("];\n"); UpdateStatus::from_changed(src != dst) - }), - ], - ); + }, + ); + } } fn round_to_fifty(count: usize) -> usize { @@ -186,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 @@ -204,7 +258,7 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator, - &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 39e4e2e365ea3..37b554fd0d85a 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -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/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs deleted file mode 100644 index 9f82f87672794..0000000000000 --- 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 902aff8b971f5..aac30d29a9083 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, diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 3c7e83b069726..b3c9e8607589c 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/lib.rs b/clippy_lints/src/lib.rs index be9142b17fe37..96a6dee58852b 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/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 0b3bec714c0e1..9ff82cdcb6648 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/needless_parens_on_range_literals.rs b/clippy_lints/src/needless_parens_on_range_literals.rs index 8a62106377c54..021a11593f3a7 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/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs index 96ea485d76936..7bbbd0d25acf2 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 6b1dc864fb7a7..acd840401c6bb 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/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml new file mode 100644 index 0000000000000..c8a9b13e6ce93 --- /dev/null +++ b/declare_clippy_lint/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "declare_clippy_lint" +version = "0.1.89" +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 0000000000000..f7d9c64bfbd0d --- /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/src/driver.rs b/src/driver.rs index 37adb14169a3f..202c74413cf0c 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -14,6 +14,7 @@ extern crate rustc_session; extern crate rustc_span; use clippy_utils::sym; +use declare_clippy_lint::LintListBuilder; use rustc_interface::interface; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; @@ -151,8 +152,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/tests/compile-test.rs b/tests/compile-test.rs index 99a01257a7b69..cefe654fef68a 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 4ac2bd532851e..389616801fcaa 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/versioncheck.rs b/tests/versioncheck.rs index f6fc2354ca08b..b0179387b2b83 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 { From 6524bf78b6e8fb38c668d972e69d65298085de14 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 19 Jun 2025 14:44:23 +0200 Subject: [PATCH 41/75] Check MSRV before suggesting applying `const` to a function If a function environment contains trait bounds other than `Sized`, `const` cannot be used before Rust 1.61. --- clippy_utils/src/msrvs.rs | 1 + clippy_utils/src/qualify_min_const_fn.rs | 13 ++++ .../missing_const_for_fn/could_be_const.fixed | 57 +++++++++++++++ .../ui/missing_const_for_fn/could_be_const.rs | 57 +++++++++++++++ .../could_be_const.stderr | 72 ++++++++++++++++++- 5 files changed, 199 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index a5e66ad463bbb..26f6bbc2227a4 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -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 e629012b187cd..05c24d7339f4e 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -32,6 +32,19 @@ 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() + && cx.tcx.param_env(def_id).caller_bounds().iter().any(|bound| { + bound + .as_trait_clause() + .is_some_and(|clause| clause.def_id() != 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/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 65eb2d5938b6b..95bf63ed1df6f 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 3690d2f799ff4..8290be6754621 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 10e07d12f5a4c..17cbc4312766a 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 From 9c8f3e9b1140ea3cf5245727c0787169cf59561e Mon Sep 17 00:00:00 2001 From: Everett Pompeii Date: Thu, 19 Jun 2025 10:26:08 -0500 Subject: [PATCH 42/75] Fix typo Add missing word (attribute) after the `#[expect]` attribute. --- clippy_lints/src/attrs/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 9a1242980418c..91c2dc7f3dc6c 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. From ade2682b62136b8bf5ece5b84343be44e25fe375 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 19 Jun 2025 13:02:04 -0500 Subject: [PATCH 43/75] Extract Translator struct --- clippy_lints/src/doc/needless_doctest_main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index ec4538039a918..7ba11c20f456b 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -42,9 +42,8 @@ pub fn check( let mut test_attr_spans = vec![]; let filename = FileName::anon_source_code(&code); - let fallback_bundle = - rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); - let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle); + let translator = rustc_driver::default_translator(); + let emitter = HumanEmitter::new(Box::new(io::sink()), translator); let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); #[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); From 4aa93a1129d68fe8107434d53b2e9c1c68ac61c1 Mon Sep 17 00:00:00 2001 From: Max Siling Date: Thu, 19 Jun 2025 21:51:20 +0300 Subject: [PATCH 44/75] Fix `clippy::question_mark` on let-else with cfg --- clippy_lints/src/question_mark.rs | 1 + tests/ui/question_mark.fixed | 12 ++++++++++++ tests/ui/question_mark.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index c02e5e0621c9f..de12a25b03dff 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/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 60dc1c101b6e6..8d6f5fbadca56 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 99d0122a98faf..f13eee29c113c 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 +} From ac8f50473caf0b43c05e07028cae7f13d074efb5 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 19 Jun 2025 15:30:13 -0400 Subject: [PATCH 45/75] Fix `non_copy_const` ICE --- clippy_lints/src/non_copy_const.rs | 2 +- tests/ui/borrow_interior_mutable_const.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index a27c6aa75e369..5f10e1968f1da 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/tests/ui/borrow_interior_mutable_const.rs b/tests/ui/borrow_interior_mutable_const.rs index 0f439f7891507..674450a73ad23 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) + } + } + } } From ee648edd9033779312037686df9aef48ac26ff2a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 30 May 2025 02:29:06 +1000 Subject: [PATCH 46/75] Avoid some unnecessary symbol interning. - `Ident::from_str_and_span` -> `Ident::new` when the string is pre-interned. - `Ident::from_str` -> `Ident::with_dummy_span` when the string is pre-interned. - `_d` and `_e` are unused. --- clippy_lints/src/bool_assert_comparison.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index ae36bb76117d7..8f95e44bf8531 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -56,7 +56,7 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) - .and_then(|trait_id| { cx.tcx.associated_items(trait_id).find_by_ident_and_kind( cx.tcx, - Ident::from_str("Output"), + Ident::with_dummy_span(sym::Output), ty::AssocTag::Type, trait_id, ) From 95778b554d23b5f8dcf6eef861ad3de3a500ef14 Mon Sep 17 00:00:00 2001 From: relaxcn Date: Tue, 17 Jun 2025 01:13:44 +0800 Subject: [PATCH 47/75] Fix suggestion-cases-error of `empty_line_after_outer_attr` --- clippy_lints/src/empty_line_after.rs | 64 +++++++++++++++++-- .../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 +++- 5 files changed, 97 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/empty_line_after.rs b/clippy_lints/src/empty_line_after.rs index 0c5f8bbf4ca53..3bd74856165da 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/tests/ui/empty_line_after/outer_attribute.1.fixed b/tests/ui/empty_line_after/outer_attribute.1.fixed index 36d80a2c95bf6..e36e3c2aea6a2 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 0e8e4129e858c..b0908fc721476 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 1295088ac00ed..4ae113c68f529 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 519ba6e67615c..331bc7c8856dd 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 From 0fdd0b928ed391f89ba4aec6714a1ca4dd67cefd Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 25 Feb 2025 09:20:55 +0100 Subject: [PATCH 48/75] Add `integer_const()` and `is_zero_integer_const()` utility functions --- clippy_lints/src/if_not_else.rs | 11 ++--------- clippy_lints/src/operators/identity_op.rs | 6 ++---- clippy_utils/src/consts.rs | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 45f9aa0a53e47..ab7a965b3672b 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/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 7e515e83cc9dc..3efbb8963587f 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -1,4 +1,4 @@ -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::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext}; @@ -186,9 +186,7 @@ fn is_allowed<'tcx>( 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) { diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 1ec5d11384f57..aaa071fd5c931 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) +} From f25fb1adf94772bb56b1465302a9d1e4ebf0d158 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 25 Feb 2025 22:36:30 +0100 Subject: [PATCH 49/75] Use `.is_multiple_of()` in Clippy tests sources This prevents triggering the new `manual_is_multiple_of` lint on unrelated lint tests. --- tests/ui/box_default.fixed | 2 +- tests/ui/box_default.rs | 2 +- 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_variant_and.fixed | 8 ++-- tests/ui/manual_is_variant_and.rs | 8 ++-- tests/ui/manual_is_variant_and.stderr | 12 ++--- 18 files changed, 90 insertions(+), 74 deletions(-) diff --git a/tests/ui/box_default.fixed b/tests/ui/box_default.fixed index 80000f5de4fd3..ed00494433b9f 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 4681016d7cd38..801d92f5c290f 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/infinite_iter.rs b/tests/ui/infinite_iter.rs index 002a791a65793..701a86534ba00 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 47133a2ea62e1..b9e7c008f93e0 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 874f749b33d02..b18dda358877a 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 f570e3c32cb67..729e4e8a266cc 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 31ee76c25b7a5..8f73541f50334 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 5e7a2ad37a846..304eacecd9423 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 7b06f6940121d..a02cb346ff99d 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 d7d01d304cad2..d743110c99dda 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 d26c948a7817c..18171f0b2b40c 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 fe67d2ee5d5c6..918f4d6b8dd71 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 01b3ebacbebcb..c69b0cb11e3cc 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 ce62a4beba1c4..db7092f020c1e 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 020635d90bb5c..0c05c0d2c4404 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_variant_and.fixed b/tests/ui/manual_is_variant_and.fixed index 18a72188ab593..6425f32c09c42 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 a92f7c0436959..e069e97a04ddc 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 1fb437a8bc744..f770319a2681e 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 From 6ffff5f009d951098d4762af1ec449f296787fe4 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 25 Feb 2025 09:20:55 +0100 Subject: [PATCH 50/75] New lint: `manual_is_multiple_of` --- CHANGELOG.md | 1 + clippy_lints/src/casts/cast_sign_loss.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + .../src/operators/manual_is_multiple_of.rs | 66 +++++++++++++++++++ clippy_lints/src/operators/mod.rs | 33 ++++++++++ clippy_utils/src/msrvs.rs | 2 +- tests/ui/manual_is_multiple_of.fixed | 25 +++++++ tests/ui/manual_is_multiple_of.rs | 25 +++++++ tests/ui/manual_is_multiple_of.stderr | 41 ++++++++++++ 9 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 clippy_lints/src/operators/manual_is_multiple_of.rs 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/CHANGELOG.md b/CHANGELOG.md index 9f15b16b5c964..5ad1a7177eb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5968,6 +5968,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/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 9a1ad8a747386..a70bd88619195 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/declared_lints.rs b/clippy_lints/src/declared_lints.rs index aac30d29a9083..c3f8e02b4c067 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -591,6 +591,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::operators::IMPOSSIBLE_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, crate::operators::INTEGER_DIVISION_INFO, + crate::operators::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/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs new file mode 100644 index 0000000000000..821178a431588 --- /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 2f4e8e9958868..bdbbb3475cd5f 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_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 26f6bbc2227a4..7a0bef1a9bbbf 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 } diff --git a/tests/ui/manual_is_multiple_of.fixed b/tests/ui/manual_is_multiple_of.fixed new file mode 100644 index 0000000000000..6735b99f298c5 --- /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 0000000000000..00b638e4fd9f3 --- /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 0000000000000..0b1ae70c2a70f --- /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 + From 3745a3f7ab1438ebc35836405c3b4f23bd6262a6 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Tue, 10 Jun 2025 18:30:03 +0200 Subject: [PATCH 51/75] Even more optimizing documentation lints? (3/2) Avoid creating so many SessionGlobals Improve filtering and account for spacing Actually return early --- clippy_lints/src/doc/needless_doctest_main.rs | 22 ++++- tests/ui/doc/needless_doctest_main.rs | 96 ++++++++++++++++++- tests/ui/doc/needless_doctest_main.stderr | 36 +++++++ 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 tests/ui/doc/needless_doctest_main.stderr diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index ec4538039a918..ef251860494be 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -72,6 +72,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, @@ -90,9 +91,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(..) @@ -120,6 +126,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/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs index 633a435ca5ed7..3c082f0b88133 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,98 @@ /// ``` 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() {} diff --git a/tests/ui/doc/needless_doctest_main.stderr b/tests/ui/doc/needless_doctest_main.stderr new file mode 100644 index 0000000000000..dd5474ccb85af --- /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 + From 65402ab41a3b6c62824f88d94313dda882aab11f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 20 Jun 2025 17:51:23 +0200 Subject: [PATCH 52/75] clippy: replace path uses by diagnostic items --- clippy_lints/src/casts/manual_dangling_ptr.rs | 5 ++--- clippy_lints/src/manual_option_as_slice.rs | 4 ++-- clippy_lints/src/methods/io_other_error.rs | 9 ++++++--- clippy_lints/src/single_range_in_vec_init.rs | 4 ++-- clippy_lints/src/to_digit_is_some.rs | 4 ++-- clippy_lints/src/useless_concat.rs | 5 ++--- clippy_utils/src/paths.rs | 9 --------- 7 files changed, 16 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs index 61dfc0fc0425e..d9e88d6a401ca 100644 --- a/clippy_lints/src/casts/manual_dangling_ptr.rs +++ b/clippy_lints/src/casts/manual_dangling_ptr.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{expr_or_init, path_def_id, paths, std_or_core}; +use clippy_utils::{expr_or_init, is_path_diagnostic_item, std_or_core, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind}; @@ -53,8 +53,7 @@ fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool { if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind - && let Some(fun_id) = path_def_id(cx, fun) - && paths::ALIGN_OF.matches(cx, fun_id) + && is_path_diagnostic_item(cx, fun, sym::mem_align_of) && let Some(args) = path.segments.last().and_then(|seg| seg.args) && let [GenericArg::Type(generic_ty)] = args.args { diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index b55c11f2d5b69..922db174e3d49 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::msrvs::Msrv; -use clippy_utils::{is_none_arm, msrvs, paths, peel_hir_expr_refs, sym}; +use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; @@ -220,5 +220,5 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - paths::SLICE_FROM_REF.matches_path(cx, expr) + clippy_utils::is_path_diagnostic_item(cx, expr, sym::slice_from_ref) } diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints/src/methods/io_other_error.rs index ec4b9c7ae2ee6..9276261606e1e 100644 --- a/clippy_lints/src/methods/io_other_error.rs +++ b/clippy_lints/src/methods/io_other_error.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{expr_or_init, paths}; +use clippy_utils::{expr_or_init, is_path_diagnostic_item, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; @@ -10,8 +10,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args && !expr.span.from_expansion() && !error_kind.span.from_expansion() && let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind - && paths::IO_ERROR_NEW.matches_path(cx, path) - && paths::IO_ERRORKIND_OTHER_CTOR.matches_path(cx, expr_or_init(cx, error_kind)) + && is_path_diagnostic_item(cx, path, sym::io_error_new) + && let ExprKind::Path(QPath::Resolved(_, init_path)) = &expr_or_init(cx, error_kind).kind + && let [.., error_kind_ty, error_kind_variant] = init_path.segments + && cx.tcx.is_diagnostic_item(sym::io_errorkind, error_kind_ty.res.def_id()) + && error_kind_variant.ident.name == sym::Other && msrv.meets(cx, msrvs::IO_ERROR_OTHER) { span_lint_and_then( diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index 54d09ff9ee402..dda2f8cc1d00b 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -3,7 +3,7 @@ use clippy_utils::higher::VecArgs; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_no_std_crate, paths}; +use clippy_utils::{is_no_std_crate, sym}; use rustc_ast::{LitIntType, LitKind, UintTy}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr}; @@ -100,7 +100,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { && let Some(start_snippet) = start.span.get_source_text(cx) && let Some(end_snippet) = end.span.get_source_text(cx) { - let should_emit_every_value = if let Some(step_def_id) = paths::ITER_STEP.only(cx) + let should_emit_every_value = if let Some(step_def_id) = cx.tcx.get_diagnostic_item(sym::range_step) && implements_trait(cx, ty, step_def_id, &[]) { true diff --git a/clippy_lints/src/to_digit_is_some.rs b/clippy_lints/src/to_digit_is_some.rs index 7d7d74f27b3c2..3e847543e1c16 100644 --- a/clippy_lints/src/to_digit_is_some.rs +++ b/clippy_lints/src/to_digit_is_some.rs @@ -2,7 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_in_const_context, paths, sym}; +use clippy_utils::{is_in_const_context, is_path_diagnostic_item, sym}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome { } }, hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => { - if paths::CHAR_TO_DIGIT.matches_path(cx, to_digits_call) { + if is_path_diagnostic_item(cx, to_digits_call, sym::char_to_digit) { Some((false, char_arg, radix_arg)) } else { None diff --git a/clippy_lints/src/useless_concat.rs b/clippy_lints/src/useless_concat.rs index 1ed1fbb3b9c69..96845adb04a2b 100644 --- a/clippy_lints/src/useless_concat.rs +++ b/clippy_lints/src/useless_concat.rs @@ -1,8 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::macro_backtrace; -use clippy_utils::paths::CONCAT; use clippy_utils::source::snippet_opt; -use clippy_utils::tokenize_with_text; +use clippy_utils::{sym, tokenize_with_text}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -43,7 +42,7 @@ impl LateLintPass<'_> for UselessConcat { // Get the direct parent of the expression. && let Some(macro_call) = macro_backtrace(expr.span).next() // Check if the `concat` macro from the `core` library. - && CONCAT.matches(cx, macro_call.def_id) + && cx.tcx.is_diagnostic_item(sym::macro_concat, macro_call.def_id) // We get the original code to parse it. && let Some(original_code) = snippet_opt(cx, macro_call.span) // This check allows us to ensure that the code snippet: diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index f37a609497eb7..8bbcb220210af 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -126,15 +126,6 @@ path_macros! { macro_path: PathNS::Macro, } -// Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items. -pub static ALIGN_OF: PathLookup = value_path!(core::mem::align_of); -pub static CHAR_TO_DIGIT: PathLookup = value_path!(char::to_digit); -pub static CONCAT: PathLookup = macro_path!(core::concat); -pub static IO_ERROR_NEW: PathLookup = value_path!(std::io::Error::new); -pub static IO_ERRORKIND_OTHER_CTOR: PathLookup = value_path!(std::io::ErrorKind::Other); -pub static ITER_STEP: PathLookup = type_path!(core::iter::Step); -pub static SLICE_FROM_REF: PathLookup = value_path!(core::slice::from_ref); - // Paths in external crates pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt); pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt); From ea72620f62a2777e505e3edd9a3f5ce027797d98 Mon Sep 17 00:00:00 2001 From: klensy Date: Tue, 17 Jun 2025 16:08:45 +0300 Subject: [PATCH 53/75] lint Option::get_or_insert and Result::map_or too --- clippy_lints/src/methods/or_fun_call.rs | 11 +-- clippy_utils/src/sym.rs | 1 + tests/ui/or_fun_call.fixed | 30 ++++++++ tests/ui/or_fun_call.rs | 30 ++++++++ tests/ui/or_fun_call.stderr | 96 +++++++++++++++---------- 5 files changed, 124 insertions(+), 44 deletions(-) diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 7bdd999bbbadd..2139466ce7466 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_utils/src/sym.rs b/clippy_utils/src/sym.rs index a544954931bad..60582ee6f8573 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -167,6 +167,7 @@ generate! { futures_util, get, get_mut, + get_or_insert, get_or_insert_with, get_unchecked, get_unchecked_mut, diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index a1119d75c231b..34f3e04684193 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 a7cd632bf166f..dc57bd6060ac0 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 35bda7e4d3314..0f159fe8bff4b 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 From 1e30fa253be8aa18fd3d36568be7435c26d3a80c Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 20 Jun 2025 19:17:35 -0400 Subject: [PATCH 54/75] Fix typo in tests/ui/missing_const_for_fn/const_trait.rs --- tests/ui/missing_const_for_fn/const_trait.fixed | 2 +- tests/ui/missing_const_for_fn/const_trait.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/missing_const_for_fn/const_trait.fixed b/tests/ui/missing_const_for_fn/const_trait.fixed index 7e0d4fccaae28..f1d5579a7230b 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 439da4622d7e0..d495759526d30 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, From e50ef68f0c617a3024dd1bcbf7941977f21028e1 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 21 Jun 2025 13:41:38 +0800 Subject: [PATCH 55/75] fix: `wildcard_enum_match_arm` suggests wrongly with raw identifiers --- clippy_lints/src/matches/match_wild_enum.rs | 12 +++++++----- tests/ui/wildcard_enum_match_arm.fixed | 14 ++++++++++++++ tests/ui/wildcard_enum_match_arm.rs | 14 ++++++++++++++ tests/ui/wildcard_enum_match_arm.stderr | 8 +++++++- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 24b4a6758004f..d9e5b07221d70 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 { diff --git a/tests/ui/wildcard_enum_match_arm.fixed b/tests/ui/wildcard_enum_match_arm.fixed index 141ff6eb2ac75..e40e526738327 100644 --- a/tests/ui/wildcard_enum_match_arm.fixed +++ b/tests/ui/wildcard_enum_match_arm.fixed @@ -105,3 +105,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 a13684e9100bd..8259f05984709 100644 --- a/tests/ui/wildcard_enum_match_arm.rs +++ b/tests/ui/wildcard_enum_match_arm.rs @@ -105,3 +105,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 088c6b7b2841a..1f1de166d001e 100644 --- a/tests/ui/wildcard_enum_match_arm.stderr +++ b/tests/ui/wildcard_enum_match_arm.stderr @@ -40,5 +40,11 @@ error: wildcard match will also match any future added variants 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:118:9 + | +LL | r#type => {}, + | ^^^^^^ help: try: `r#type @ Foo::B | r#type @ Foo::C` + +error: aborting due to 7 previous errors From e0cb4ced0a42d137b2f85f06dd45667299863fa2 Mon Sep 17 00:00:00 2001 From: donkomura Date: Sat, 21 Jun 2025 14:43:00 +0900 Subject: [PATCH 56/75] Check the MSRV for `unnecessary_debug_formatting` `unnecessary_debug_formatting` suggests display() respected for MSRV but lacking of tests. This adds tests to check MSRV for OsStr. changelog: none --- tests/ui/unnecessary_os_str_debug_formatting.rs | 13 +++++++++++++ tests/ui/unnecessary_os_str_debug_formatting.stderr | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/ui/unnecessary_os_str_debug_formatting.rs b/tests/ui/unnecessary_os_str_debug_formatting.rs index 6652efd9ae1d8..66590be3d0543 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 382e59b046193..f04d2d5bdc821 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 From c0dd2d5a38b80b6b30b224f89cca851350d73b53 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 12 May 2025 23:47:02 +0200 Subject: [PATCH 57/75] Emit lint about redundant closure on the closure node itself --- clippy_lints/src/eta_reduction.rs | 92 ++++++++++++++++++------------- tests/ui/eta.fixed | 18 ++++++ tests/ui/eta.rs | 18 ++++++ 3 files changed, 89 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 645f93068496b..92f31095d2f6f 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; @@ -108,14 +108,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!())` @@ -197,41 +203,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) => { @@ -244,9 +257,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/tests/ui/eta.fixed b/tests/ui/eta.fixed index 0ba631fda051b..c93b83f53ecb6 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 4d8b29d450c54..273c8b21f4ad8 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, + ); +} From db539d051d2204521c1fb914c91e5e55d4e0b4be Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 21 Jun 2025 18:30:55 +0200 Subject: [PATCH 58/75] Add missing space when expanding a struct-like variant In `wildcard_enum_match_arm`, expanding a variant with struct-like fields was missing a space between the variant name and the opening bracket. Also, add a test for tuple-like variants with only one field, as those are expanded as `VariantName(_)` instead of `VariantName(..)`. --- clippy_lints/src/matches/match_wild_enum.rs | 2 +- tests/ui/wildcard_enum_match_arm.fixed | 15 +++++++++++++++ tests/ui/wildcard_enum_match_arm.rs | 15 +++++++++++++++ tests/ui/wildcard_enum_match_arm.stderr | 10 ++++++++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index d9e5b07221d70..70a03ff937625 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -140,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/tests/ui/wildcard_enum_match_arm.fixed b/tests/ui/wildcard_enum_match_arm.fixed index e40e526738327..5f738a254dcdd 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 { diff --git a/tests/ui/wildcard_enum_match_arm.rs b/tests/ui/wildcard_enum_match_arm.rs index 8259f05984709..4bc4bfdcb7945 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 { diff --git a/tests/ui/wildcard_enum_match_arm.stderr b/tests/ui/wildcard_enum_match_arm.stderr index 1f1de166d001e..d0929989494a4 100644 --- a/tests/ui/wildcard_enum_match_arm.stderr +++ b/tests/ui/wildcard_enum_match_arm.stderr @@ -37,14 +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: wildcard match will also match any future added variants - --> tests/ui/wildcard_enum_match_arm.rs:118:9 + --> 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 7 previous errors +error: aborting due to 8 previous errors From 953bf2be8939f9d7938d2341c22c4a7580f36537 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 19 Jun 2025 23:56:23 -0700 Subject: [PATCH 59/75] All HIR attributes are outer --- clippy_lints/src/doc/doc_suspicious_footnotes.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/clippy_lints/src/doc/doc_suspicious_footnotes.rs index 289b6b915d46d..d3c396869766f 100644 --- a/clippy_lints/src/doc/doc_suspicious_footnotes.rs +++ b/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::attr::AttributeExt as _; use rustc_ast::token::CommentKind; use rustc_errors::Applicability; use rustc_hir::{AttrStyle, Attribute}; @@ -43,13 +44,19 @@ pub fn check(cx: &LateContext<'_>, doc: &str, range: Range, fragments: &F "looks like a footnote ref, but has no matching footnote", |diag| { if this_fragment.kind == DocFragmentKind::SugaredDoc { - let (doc_attr, (_, doc_attr_comment_kind)) = attrs + let (doc_attr, (_, doc_attr_comment_kind), attr_style) = attrs .iter() .filter(|attr| attr.span().overlaps(this_fragment.span)) .rev() - .find_map(|attr| Some((attr, attr.doc_str_and_comment_kind()?))) + .find_map(|attr| { + Some(( + attr, + attr.doc_str_and_comment_kind()?, + attr.doc_resolution_scope()?, + )) + }) .unwrap(); - let (to_add, terminator) = match (doc_attr_comment_kind, doc_attr.style()) { + let (to_add, terminator) = match (doc_attr_comment_kind, attr_style) { (CommentKind::Line, AttrStyle::Outer) => ("\n///\n/// ", ""), (CommentKind::Line, AttrStyle::Inner) => ("\n//!\n//! ", ""), (CommentKind::Block, AttrStyle::Outer) => ("\n/** ", " */"), From b92cccb9772c5546c41f228821de6eddf5eb0ecf Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Jun 2025 12:09:14 +0200 Subject: [PATCH 60/75] Port `#[must_use]` to new attribute parsing infrastructure Signed-off-by: Jonathan Brouwer --- clippy_lints/src/functions/must_use.rs | 30 +++++++++++--------- clippy_lints/src/return_self_not_must_use.rs | 9 ++++-- clippy_utils/src/lib.rs | 5 +++- clippy_utils/src/ty/mod.rs | 16 ++++++++--- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 70655838b6af0..ea9ed4ddade70 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -15,6 +15,9 @@ 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_attr_data_structures::AttributeKind; +use rustc_span::Symbol; +use rustc_attr_data_structures::find_attr; use core::ops::ControlFlow; @@ -22,7 +25,7 @@ use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let attrs = cx.tcx.hir_attrs(item.hir_id()); - let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); + let attr = find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason)); if let hir::ItemKind::Fn { ref sig, body: ref body_id, @@ -31,8 +34,8 @@ 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) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, sig); + 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); } else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { check_must_use_candidate( cx, @@ -52,9 +55,9 @@ 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 = cx.tcx.get_attr(item.owner_id, sym::must_use); - if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, sig); + 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); } else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id).is_none() { check_must_use_candidate( cx, @@ -75,9 +78,9 @@ 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 = cx.tcx.get_attr(item.owner_id, sym::must_use); - if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, sig); + 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); } 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) { @@ -103,7 +106,8 @@ fn check_needless_must_use( item_id: hir::OwnerId, item_span: Span, fn_header_span: Span, - attr: &Attribute, + attr_span: Span, + reason: Option, attrs: &[Attribute], sig: &FnSig<'_>, ) { @@ -119,7 +123,7 @@ fn check_needless_must_use( "this unit-returning function has a `#[must_use]` attribute", |diag| { diag.span_suggestion( - attr.span(), + attr_span, "remove the attribute", "", Applicability::MachineApplicable, @@ -137,11 +141,11 @@ fn check_needless_must_use( MUST_USE_UNIT, fn_header_span, "this unit-returning function has a `#[must_use]` attribute", - Some(attr.span()), + Some(attr_span), "remove `must_use`", ); } - } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) { + } else if reason.is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) { // Ignore async functions unless Future::Output type is a must_use type if sig.header.is_async() { let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 07ae92fa98439..1b304dc57680b 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -6,7 +6,9 @@ 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, sym}; +use rustc_span::{Span}; +use rustc_attr_data_structures::AttributeKind; +use rustc_attr_data_structures::find_attr; declare_clippy_lint! { /// ### What it does @@ -74,7 +76,10 @@ fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, spa // We only show this warning for public exported methods. && cx.effective_visibilities.is_exported(fn_def) // We don't want to emit this lint if the `#[must_use]` attribute is already there. - && !cx.tcx.hir_attrs(owner_id.into()).iter().any(|attr| attr.has_name(sym::must_use)) + && !find_attr!( + cx.tcx.hir_attrs(owner_id.into()), + AttributeKind::MustUse { .. } + ) && cx.tcx.visibility(fn_def.to_def_id()).is_public() && let ret_ty = return_ty(cx, owner_id) && let self_arg = nth_arg(cx, owner_id, 0) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index c7a2375c8df7c..913589319fcce 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1886,7 +1886,10 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => None, }; - did.is_some_and(|did| cx.tcx.has_attr(did, sym::must_use)) + 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: diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 32a992ccc2d7b..782b079ce0931 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -31,6 +31,8 @@ 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}; @@ -326,8 +328,14 @@ 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, _) => cx.tcx.has_attr(adt.did(), sym::must_use), - ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use), + 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 @@ -337,7 +345,7 @@ 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() - && cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) + && find_attr!(cx.tcx.get_all_attrs(trait_predicate.trait_ref.def_id), AttributeKind::MustUse { ..}) { return true; } @@ -347,7 +355,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() - && cx.tcx.has_attr(trait_ref.def_id, sym::must_use) + && find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { ..}) { return true; } From 1d764e022f8d1dd9ab2a496d559f3af6800e4946 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Jun 2025 12:14:38 +0200 Subject: [PATCH 61/75] Port `#[no_mangle]` to new attribute parsing infrastructure Signed-off-by: Jonathan Brouwer --- clippy_lints/src/functions/must_use.rs | 5 ++--- clippy_lints/src/no_mangle_with_rust_abi.rs | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index ea9ed4ddade70..c0c23e217fd4b 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -15,9 +15,8 @@ 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_attr_data_structures::AttributeKind; use rustc_span::Symbol; -use rustc_attr_data_structures::find_attr; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use core::ops::ControlFlow; @@ -36,7 +35,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> 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); - } else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { + } else if is_public && !is_proc_macro(attrs) && !find_attr!(attrs, AttributeKind::NoMangle(..)) { check_must_use_candidate( cx, sig.decl, diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index b71dde9069184..0159c5d2ac1b1 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -6,6 +6,8 @@ use rustc_hir::{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 @@ -44,8 +46,7 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { let mut app = Applicability::MaybeIncorrect; let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(ident.span.lo()), "..", &mut app); for attr in attrs { - if let Some(ident) = attr.ident() - && ident.name == rustc_span::sym::no_mangle + if let Attribute::Parsed(AttributeKind::NoMangle(attr_span)) = attr && fn_sig.header.abi == ExternAbi::Rust && let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn") && !fn_attrs.contains("extern") @@ -54,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { .span .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) .shrink_to_lo(); - let attr_snippet = snippet(cx, attr.span(), ".."); + let attr_snippet = snippet(cx, *attr_span, ".."); span_lint_and_then( cx, From 05b74d558ffd35f038f572b65952622a84f3e274 Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Fri, 7 Mar 2025 11:17:39 +0000 Subject: [PATCH 62/75] update to literal-escaper 0.0.4 for better API without `unreachable` and faster string parsing --- clippy_dev/src/update_lints.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 08592f2521f7d..3b827cc5603e5 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -2,6 +2,7 @@ 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::ops::Range; @@ -342,7 +343,7 @@ fn parse_str_lit(s: &str) -> String { .and_then(|s| s.strip_suffix('"')) .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); let mut res = String::with_capacity(s.len()); - rustc_literal_escaper::unescape_unicode(s, mode, &mut |_, ch| { + rustc_literal_escaper::unescape_str(s, |range, ch| { if let Ok(ch) = ch { res.push(ch); } From 2e63c7c68447eaa6245255d15a109a2f8d4953ab Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 21 Jun 2025 13:57:30 +0500 Subject: [PATCH 63/75] Changelog for Clippy 1.88 --- CHANGELOG.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad1a7177eb41..8520465107d28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,78 @@ 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`) + [#14219](https://github.com/rust-lang/rust-clippy/pull/14219) +* [`match_on_vec_items`] deprecated in favor of [`indexing_slicing`] + [#14217](https://github.com/rust-lang/rust-clippy/pull/14217) + +### 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) + +### 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) + +### 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) ## Rust 1.87 From ae6ab44d68cbd97dd47b065ae1b82f3eafb660e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sat, 21 Jun 2025 12:42:24 +0200 Subject: [PATCH 64/75] fix clippy --- clippy_lints/src/eta_reduction.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 6ed7c87915b27..b0077a9b05fe8 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -7,6 +7,7 @@ use clippy_utils::{ get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, }; use rustc_abi::ExternAbi; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_errors::Applicability; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; @@ -155,7 +156,7 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx let sig = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => { // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` - if cx.tcx.has_attr(*def, sym::track_caller) { + if find_attr!(cx.tcx.get_all_attrs(*def), AttributeKind::TrackCaller(..)) { return; } @@ -236,7 +237,7 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx }, ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) - && !cx.tcx.has_attr(method_def_id, sym::track_caller) + && !find_attr!(cx.tcx.get_all_attrs(method_def_id), AttributeKind::TrackCaller(..)) && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) { let mut app = Applicability::MachineApplicable; From c11bc75fb0037f293c94d88ecc772f37c4fe00cc Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 25 Jun 2025 01:00:34 +0200 Subject: [PATCH 65/75] changelog: link to the correct PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8520465107d28..6700d5e27a66a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Current stable, released 2025-06-26 ### Moves and Deprecations * Moved [`uninlined_format_args`] to `style` (from `pedantic`) - [#14219](https://github.com/rust-lang/rust-clippy/pull/14219) + [#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) From 95f0991ad4b6e3390f19a30d42c810605fd448c8 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 25 Jun 2025 15:28:10 +0500 Subject: [PATCH 66/75] Add beta-nominated to changelog for 1.88 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8520465107d28..d5598bbd08926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ Current stable, released 2025-06-26 [#14219](https://github.com/rust-lang/rust-clippy/pull/14219) * [`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 @@ -51,6 +54,10 @@ Current stable, released 2025-06-26 [#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 @@ -68,6 +75,13 @@ Current stable, released 2025-06-26 [#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 @@ -78,6 +92,8 @@ Current stable, released 2025-06-26 * 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 From b88d162d2afd3f65d90b84f8bad1793858ecfe8c Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 26 Jun 2025 18:40:51 +0200 Subject: [PATCH 67/75] Add link to the feature freeze tracking issue to the book --- .github/workflows/{feature-freeze.yml => feature_freeze.yml} | 2 +- book/src/development/feature_freeze.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) rename .github/workflows/{feature-freeze.yml => feature_freeze.yml} (96%) diff --git a/.github/workflows/feature-freeze.yml b/.github/workflows/feature_freeze.yml similarity index 96% rename from .github/workflows/feature-freeze.yml rename to .github/workflows/feature_freeze.yml index 1b3a2f4ca2815..a5f8d4bc145cc 100644 --- a/.github/workflows/feature-freeze.yml +++ b/.github/workflows/feature_freeze.yml @@ -22,4 +22,4 @@ jobs: 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\"}" \ No newline at end of file + curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md index f522be2fbd1b7..260cb136cc075 100644 --- a/book/src/development/feature_freeze.md +++ b/book/src/development/feature_freeze.md @@ -26,6 +26,9 @@ adding additional load into our reviewing schedules. 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 @@ -49,3 +52,4 @@ trench coat. [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 From 4116a72a08e52e227c719b7d9109183673e77b21 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 26 Jun 2025 18:44:41 +0200 Subject: [PATCH 68/75] Ping notriddle when a clippy lint in `clippy_lints/doc` is modified --- triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 16557a4bebb83..4f370758c0064 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] From 9b41d8fdb6065c0320f9e8d53956808c0382437a Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 26 Jun 2025 19:29:50 +0200 Subject: [PATCH 69/75] Bump nightly version -> 2025-06-26 --- 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 1aa16e3943c46..649748d1534bb 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/rust-toolchain.toml b/rust-toolchain.toml index 3fc5a1224a8dc..124756a360095 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" From 32fcff8aa8a5c6092917ac8c87da4341a7ed2d0b Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 26 Jun 2025 19:30:02 +0200 Subject: [PATCH 70/75] Bump Clippy version -> 0.1.90 --- Cargo.toml | 2 +- clippy_config/Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- clippy_utils/Cargo.toml | 2 +- declare_clippy_lint/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da6046f020fbf..8cbdcf456932f 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" diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 0606245f990c1..858366c8a5c47 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_lints/Cargo.toml b/clippy_lints/Cargo.toml index 37b554fd0d85a..c03cc99b581f0 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" diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 615c0995e8b17..73291aa8cdf73 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/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index c8a9b13e6ce93..bd6b4dfdee4dc 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.89" +version = "0.1.90" edition = "2024" repository = "https://github.com/rust-lang/rust-clippy" license = "MIT OR Apache-2.0" From 22c0226aff66cf302a14078092bdac97107e2099 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 26 Jun 2025 19:59:04 +0200 Subject: [PATCH 71/75] Lint docs: Sort versions, so that stable comes first I expect that most people use stable Clippy. So having `master` be the first documentation link in the list is weird. Now the versions are sorted stable->beta->master. --- util/versions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/versions.py b/util/versions.py index fee0d292df16f..6e06d77a77147 100755 --- a/util/versions.py +++ b/util/versions.py @@ -6,11 +6,11 @@ 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 1b5420a8bed8b3644eca8352df17f2c7538e657a Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 27 Jun 2025 12:21:14 +0200 Subject: [PATCH 72/75] Update Cargo.lock --- Cargo.lock | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e95cacf1f6d3d..e1cf17e2c01ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -537,7 +537,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clippy" -version = "0.1.89" +version = "0.1.90" dependencies = [ "anstream", "askama", @@ -547,6 +547,7 @@ dependencies = [ "clippy_lints_internal", "clippy_utils", "color-print", + "declare_clippy_lint", "filetime", "futures", "if_chain", @@ -569,7 +570,7 @@ dependencies = [ [[package]] name = "clippy_config" -version = "0.1.89" +version = "0.1.90" dependencies = [ "clippy_utils", "itertools", @@ -592,12 +593,13 @@ dependencies = [ [[package]] name = "clippy_lints" -version = "0.1.89" +version = "0.1.90" dependencies = [ "arrayvec", "cargo_metadata 0.18.1", "clippy_config", "clippy_utils", + "declare_clippy_lint", "itertools", "quine-mc_cluskey", "regex-syntax 0.8.5", @@ -622,7 +624,7 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.89" +version = "0.1.90" dependencies = [ "arrayvec", "itertools", @@ -931,6 +933,10 @@ dependencies = [ "winapi", ] +[[package]] +name = "declare_clippy_lint" +version = "0.1.90" + [[package]] name = "derive-where" version = "1.4.0" From f13d785a264cbe3cbba759ed8e9d581db362603e Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 27 Jun 2025 12:21:41 +0200 Subject: [PATCH 73/75] broken_links: Fix rustdoc API usage --- src/tools/clippy/clippy_lints/src/doc/broken_link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/clippy/clippy_lints/src/doc/broken_link.rs b/src/tools/clippy/clippy_lints/src/doc/broken_link.rs index a97b807e605f7..4af10510023d3 100644 --- a/src/tools/clippy/clippy_lints/src/doc/broken_link.rs +++ b/src/tools/clippy/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 4b3f31df4fd6bcca2c212a75494bb77e81d6f49f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 26 Jun 2025 20:56:20 +0200 Subject: [PATCH 74/75] Use `.is_multiple_of()` in bootstrap This makes the intent clear, and silences Clippy. --- src/bootstrap/src/utils/render_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/utils/render_tests.rs b/src/bootstrap/src/utils/render_tests.rs index 77e645a9e3cb8..051d7dd9fd4dd 100644 --- a/src/bootstrap/src/utils/render_tests.rs +++ b/src/bootstrap/src/utils/render_tests.rs @@ -202,7 +202,9 @@ impl<'a> Renderer<'a> { } fn render_test_outcome_terse(&mut self, outcome: Outcome<'_>, test: &TestOutcome) { - if self.terse_tests_in_line != 0 && self.terse_tests_in_line % TERSE_TESTS_PER_LINE == 0 { + if self.terse_tests_in_line != 0 + && self.terse_tests_in_line.is_multiple_of(TERSE_TESTS_PER_LINE) + { if let Some(total) = self.tests_count { let total = total.to_string(); let executed = format!("{:>width$}", self.executed_tests - 1, width = total.len()); From d9a4fd5d51d7c93d8c8b759c656395a0ccade198 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 27 Jun 2025 13:40:33 +0200 Subject: [PATCH 75/75] rustc_codegen_gcc: Fix clippy::manual_is_multiple_of --- compiler/rustc_codegen_gcc/src/intrinsic/simd.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 6f6bc93b8b261..ac8b7f4ea482f 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -61,7 +61,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let (len, _) = args[1].layout.ty.simd_size_and_type(bx.tcx()); let expected_int_bits = (len.max(8) - 1).next_power_of_two(); - let expected_bytes = len / 8 + ((len % 8 > 0) as u64); + let expected_bytes = len / 8 + ((!len.is_multiple_of(8)) as u64); let mask_ty = args[0].layout.ty; let mut mask = match *mask_ty.kind() { @@ -676,7 +676,8 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let elem_type = vector_type.get_element_type(); let expected_int_bits = in_len.max(8); - let expected_bytes = expected_int_bits / 8 + ((expected_int_bits % 8 > 0) as u64); + let expected_bytes = + expected_int_bits / 8 + ((!expected_int_bits.is_multiple_of(8)) as u64); // FIXME(antoyo): that's not going to work for masks bigger than 128 bits. let result_type = bx.type_ix(expected_int_bits);