Auto merge of #143064 - flip1995:clippy-subtree-update, r=GuillaumeGomez
Clippy subtree update r? `@Manishearth` Cargo.lock update due to version bump
This commit is contained in:
commit
bdaba05a95
157 changed files with 3179 additions and 855 deletions
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ order to get feedback.
|
|||
|
||||
Delete this line and everything above before opening your PR.
|
||||
|
||||
Note that we are currently not taking in new PRs that add new lints. We are in a
|
||||
feature freeze. Check out the book for more information. If you open a
|
||||
feature-adding pull request, its review will be delayed.
|
||||
|
||||
---
|
||||
|
||||
*Please write a short comment explaining your change (or "none" for internal only changes)*
|
||||
|
|
|
|||
25
src/tools/clippy/.github/workflows/feature_freeze.yml
vendored
Normal file
25
src/tools/clippy/.github/workflows/feature_freeze.yml
vendored
Normal file
|
|
@ -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\"}"
|
||||
|
|
@ -6,7 +6,94 @@ document.
|
|||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[1e5237f4...master](https://github.com/rust-lang/rust-clippy/compare/1e5237f4...master)
|
||||
[03a5b6b9...master](https://github.com/rust-lang/rust-clippy/compare/03a5b6b9...master)
|
||||
|
||||
## Rust 1.88
|
||||
|
||||
Current stable, released 2025-06-26
|
||||
|
||||
[View all 126 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-03-21T10%3A30%3A57Z..2025-05-01T08%3A03%3A26Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
* Added [`swap_with_temporary`] to `complexity` [#14046](https://github.com/rust-lang/rust-clippy/pull/14046)
|
||||
* Added [`redundant_test_prefix`] to `restriction` [#13710](https://github.com/rust-lang/rust-clippy/pull/13710)
|
||||
* Added [`manual_dangling_ptr`] to `style` [#14107](https://github.com/rust-lang/rust-clippy/pull/14107)
|
||||
* Added [`char_indices_as_byte_indices`] to `correctness` [#13435](https://github.com/rust-lang/rust-clippy/pull/13435)
|
||||
* Added [`manual_abs_diff`] to `complexity` [#14482](https://github.com/rust-lang/rust-clippy/pull/14482)
|
||||
* Added [`ignore_without_reason`] to `pedantic` [#13931](https://github.com/rust-lang/rust-clippy/pull/13931)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`uninlined_format_args`] to `style` (from `pedantic`)
|
||||
[#14160](https://github.com/rust-lang/rust-clippy/pull/14160)
|
||||
* [`match_on_vec_items`] deprecated in favor of [`indexing_slicing`]
|
||||
[#14217](https://github.com/rust-lang/rust-clippy/pull/14217)
|
||||
* Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`,
|
||||
`transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc)
|
||||
[#14703](https://github.com/rust-lang/rust-clippy/pull/14703)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Configuration renamed from `lint-inconsistent-struct-field-initializers`
|
||||
to `check-inconsistent-struct-field-initializers`
|
||||
[#14280](https://github.com/rust-lang/rust-clippy/pull/14280)
|
||||
* Paths in `disallowed_*` configurations are now validated
|
||||
[#14397](https://github.com/rust-lang/rust-clippy/pull/14397)
|
||||
* [`borrow_as_ptr`] now lints implicit casts as well
|
||||
[#14408](https://github.com/rust-lang/rust-clippy/pull/14408)
|
||||
* [`iter_kv_map`] now recognizes references on maps
|
||||
[#14596](https://github.com/rust-lang/rust-clippy/pull/14596)
|
||||
* [`empty_enum_variants_with_brackets`] no longer lints reachable enums or enums used
|
||||
as functions within same crate [#12971](https://github.com/rust-lang/rust-clippy/pull/12971)
|
||||
* [`needless_lifetimes`] now checks for lifetime uses in closures
|
||||
[#14608](https://github.com/rust-lang/rust-clippy/pull/14608)
|
||||
* [`wildcard_imports`] now lints on `pub use` when `warn_on_all_wildcard_imports` is enabled
|
||||
[#14182](https://github.com/rust-lang/rust-clippy/pull/14182)
|
||||
* [`collapsible_if`] now recognizes the `let_chains` feature
|
||||
[#14481](https://github.com/rust-lang/rust-clippy/pull/14481)
|
||||
* [`match_single_binding`] now allows macros in scrutinee and patterns
|
||||
[#14635](https://github.com/rust-lang/rust-clippy/pull/14635)
|
||||
* [`needless_borrow`] does not contradict the compiler's
|
||||
`dangerous_implicit_autorefs` lint even though the references
|
||||
are not mandatory
|
||||
[#14810](https://github.com/rust-lang/rust-clippy/pull/14810)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`double_ended_iterator_last`] and [`needless_collect`] fixed FP when iter has side effects
|
||||
[#14490](https://github.com/rust-lang/rust-clippy/pull/14490)
|
||||
* [`mut_from_ref`] fixed FP where lifetimes nested in types were not considered
|
||||
[#14471](https://github.com/rust-lang/rust-clippy/pull/14471)
|
||||
* [`redundant_clone`] fixed FP in overlapping lifetime
|
||||
[#14237](https://github.com/rust-lang/rust-clippy/pull/14237)
|
||||
* [`map_entry`] fixed FP where lint would trigger without insert calls present
|
||||
[#14568](https://github.com/rust-lang/rust-clippy/pull/14568)
|
||||
* [`iter_cloned_collect`] fixed FP with custom `From`/`IntoIterator` impl
|
||||
[#14473](https://github.com/rust-lang/rust-clippy/pull/14473)
|
||||
* [`shadow_unrelated`] fixed FP in destructuring assignments
|
||||
[#14381](https://github.com/rust-lang/rust-clippy/pull/14381)
|
||||
* [`redundant_clone`] fixed FP on enum cast
|
||||
[#14395](https://github.com/rust-lang/rust-clippy/pull/14395)
|
||||
* [`collapsible_if`] fixed FP on block stmt before expr
|
||||
[#14730](https://github.com/rust-lang/rust-clippy/pull/14730)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`missing_const_for_fn`] fix ICE with `-Z validate-mir` compilation option
|
||||
[#14776](https://github.com/rust-lang/rust-clippy/pull/14776)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`missing_asserts_for_indexing`] improved documentation and examples
|
||||
[#14108](https://github.com/rust-lang/rust-clippy/pull/14108)
|
||||
|
||||
### Others
|
||||
|
||||
* We're testing with edition 2024 now
|
||||
[#14602](https://github.com/rust-lang/rust-clippy/pull/14602)
|
||||
* Don't warn about unloaded crates in `clippy.toml` disallowed paths
|
||||
[#14733](https://github.com/rust-lang/rust-clippy/pull/14733)
|
||||
|
||||
## Rust 1.87
|
||||
|
||||
|
|
@ -5729,6 +5816,7 @@ Released 2018-09-13
|
|||
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
|
||||
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
|
||||
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
|
||||
[`doc_broken_link`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_broken_link
|
||||
[`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks
|
||||
[`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
|
||||
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
|
||||
|
|
@ -5967,6 +6055,7 @@ Released 2018-09-13
|
|||
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
|
||||
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
|
||||
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
|
||||
[`manual_is_multiple_of`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of
|
||||
[`manual_is_power_of_two`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two
|
||||
[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
|
||||
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.89"
|
||||
version = "0.1.90"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
@ -24,6 +24,7 @@ path = "src/driver.rs"
|
|||
clippy_config = { path = "clippy_config" }
|
||||
clippy_lints = { path = "clippy_lints" }
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
declare_clippy_lint = { path = "declare_clippy_lint" }
|
||||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
clippy_lints_internal = { path = "clippy_lints_internal", optional = true }
|
||||
tempfile = { version = "3.20", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
# Clippy
|
||||
|
||||
[### IMPORTANT NOTE FOR CONTRIBUTORS ================](development/feature_freeze.md)
|
||||
|
||||
----
|
||||
|
||||
[](https://github.com/rust-lang/rust-clippy#license)
|
||||
|
||||
A collection of lints to catch common mistakes and improve your
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
55
src/tools/clippy/book/src/development/feature_freeze.md
Normal file
55
src/tools/clippy/book/src/development/feature_freeze.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# IMPORTANT: FEATURE FREEZE
|
||||
|
||||
This is a temporary notice.
|
||||
|
||||
From the 26th of June until the 18th of September we will perform a feature freeze. Only bugfix PRs will be reviewed
|
||||
except already open ones. Every feature-adding PR opened in between those dates will be moved into a
|
||||
milestone to be reviewed separately at another time.
|
||||
|
||||
We do this because of the long backlog of bugs that need to be addressed
|
||||
in order to continue being the state-of-the-art linter that Clippy has become known for being.
|
||||
|
||||
## For contributors
|
||||
|
||||
If you are a contributor or are planning to become one, **please do not open a lint-adding PR**, we have lots of open
|
||||
bugs of all levels of difficulty that you can address instead!
|
||||
|
||||
We currently have about 800 lints, each one posing a maintainability challenge that needs to account to every possible
|
||||
use case of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a
|
||||
refinement period.
|
||||
|
||||
If you open a PR at this time, we will not review it but push it into a milestone until the refinement period ends,
|
||||
adding additional load into our reviewing schedules.
|
||||
|
||||
## I want to help, what can I do
|
||||
|
||||
Thanks a lot to everyone who wants to help Clippy become better software in this feature freeze period!
|
||||
If you'd like to help, making a bugfix, making sure that it works, and opening a PR is a great step!
|
||||
|
||||
To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like, go there and claim that
|
||||
issue with `@rustbot claim`.
|
||||
|
||||
As a general metric and always taking into account your skill and knowledge level, you can use this guide:
|
||||
|
||||
- 🟥 [ICEs][search_ice], these are compiler errors that causes Clippy to panic and crash. Usually involves high-level
|
||||
debugging, sometimes interacting directly with the upstream compiler. Difficult to fix but a great challenge that
|
||||
improves a lot developer workflows!
|
||||
|
||||
- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way.
|
||||
Unacceptable, as this may have disastrous consequences. Easier to fix than ICEs
|
||||
|
||||
- 🟨 [Suggestion causes error][sugg_causes_error], Clippy suggested code snippet that caused a compiler error
|
||||
when applied. We need to make sure that Clippy doesn't suggest using a variable twice at the same time or similar
|
||||
easy-to-happen occurrences.
|
||||
|
||||
- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just"
|
||||
identifying the root of a false positive and making an exception for those cases.
|
||||
|
||||
Note that false negatives do not have priority unless the case is very clear, as they are a feature-request in a
|
||||
trench coat.
|
||||
|
||||
[search_ice]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22I-ICE%22
|
||||
[sugg_causes_bug]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-bug
|
||||
[sugg_causes_error]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-error%20
|
||||
[false_positive]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-false-positive
|
||||
[tracking_issue]: https://github.com/rust-lang/rust-clippy/issues/15086
|
||||
|
|
@ -488,6 +488,13 @@ The maximum cognitive complexity a function can have
|
|||
## `disallowed-macros`
|
||||
The list of disallowed macros, written as fully qualified paths.
|
||||
|
||||
**Fields:**
|
||||
- `path` (required): the fully qualified path to the macro that should be disallowed
|
||||
- `reason` (optional): explanation why this macro is disallowed
|
||||
- `replacement` (optional): suggested alternative macro
|
||||
- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
|
||||
if the path doesn't exist, instead of emitting an error
|
||||
|
||||
**Default Value:** `[]`
|
||||
|
||||
---
|
||||
|
|
@ -498,6 +505,13 @@ The list of disallowed macros, written as fully qualified paths.
|
|||
## `disallowed-methods`
|
||||
The list of disallowed methods, written as fully qualified paths.
|
||||
|
||||
**Fields:**
|
||||
- `path` (required): the fully qualified path to the method that should be disallowed
|
||||
- `reason` (optional): explanation why this method is disallowed
|
||||
- `replacement` (optional): suggested alternative method
|
||||
- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
|
||||
if the path doesn't exist, instead of emitting an error
|
||||
|
||||
**Default Value:** `[]`
|
||||
|
||||
---
|
||||
|
|
@ -520,6 +534,13 @@ default configuration of Clippy. By default, any configuration will replace the
|
|||
## `disallowed-types`
|
||||
The list of disallowed types, written as fully qualified paths.
|
||||
|
||||
**Fields:**
|
||||
- `path` (required): the fully qualified path to the type that should be disallowed
|
||||
- `reason` (optional): explanation why this type is disallowed
|
||||
- `replacement` (optional): suggested alternative type
|
||||
- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
|
||||
if the path doesn't exist, instead of emitting an error
|
||||
|
||||
**Default Value:** `[]`
|
||||
|
||||
---
|
||||
|
|
@ -651,13 +672,14 @@ The maximum size of the `Err`-variant in a `Result` returned from a function
|
|||
|
||||
|
||||
## `lint-commented-code`
|
||||
Whether collapsible `if` chains are linted if they contain comments inside the parts
|
||||
Whether collapsible `if` and `else if` chains are linted if they contain comments inside the parts
|
||||
that would be collapsed.
|
||||
|
||||
**Default Value:** `false`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`collapsible_else_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if)
|
||||
* [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.89"
|
||||
version = "0.1.90"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -575,10 +575,24 @@ define_Conf! {
|
|||
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
|
||||
cyclomatic_complexity_threshold: u64 = 25,
|
||||
/// The list of disallowed macros, written as fully qualified paths.
|
||||
///
|
||||
/// **Fields:**
|
||||
/// - `path` (required): the fully qualified path to the macro that should be disallowed
|
||||
/// - `reason` (optional): explanation why this macro is disallowed
|
||||
/// - `replacement` (optional): suggested alternative macro
|
||||
/// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
|
||||
/// if the path doesn't exist, instead of emitting an error
|
||||
#[disallowed_paths_allow_replacements = true]
|
||||
#[lints(disallowed_macros)]
|
||||
disallowed_macros: Vec<DisallowedPath> = 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<DisallowedPath> = Vec::new(),
|
||||
|
|
@ -588,6 +602,13 @@ define_Conf! {
|
|||
#[lints(disallowed_names)]
|
||||
disallowed_names: Vec<String> = 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<DisallowedPath> = Vec::new(),
|
||||
|
|
@ -641,9 +662,9 @@ define_Conf! {
|
|||
/// The maximum size of the `Err`-variant in a `Result` returned from a function
|
||||
#[lints(result_large_err)]
|
||||
large_error_threshold: u64 = 128,
|
||||
/// Whether collapsible `if` chains are linted if they contain comments inside the parts
|
||||
/// Whether collapsible `if` and `else if` chains are linted if they contain comments inside the parts
|
||||
/// that would be collapsed.
|
||||
#[lints(collapsible_if)]
|
||||
#[lints(collapsible_else_if, collapsible_if)]
|
||||
lint_commented_code: bool = false,
|
||||
/// Whether to suggest reordering constructor fields when initializers are present.
|
||||
/// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>
|
|||
|
||||
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<Item = &'a String>
|
|||
);
|
||||
} 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(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
|
|||
.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()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use crate::utils::{
|
|||
ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_lexer::{LiteralKind, TokenKind, tokenize};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
|
||||
|
|
@ -37,123 +37,164 @@ pub fn generate_lint_files(
|
|||
deprecated: &[DeprecatedLint],
|
||||
renamed: &[RenamedLint],
|
||||
) {
|
||||
FileUpdater::default().update_files_checked(
|
||||
let mut updater = FileUpdater::default();
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
&mut [
|
||||
(
|
||||
"README.md",
|
||||
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
||||
write!(dst, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
}),
|
||||
),
|
||||
(
|
||||
"book/src/README.md",
|
||||
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
||||
write!(dst, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
}),
|
||||
),
|
||||
(
|
||||
"CHANGELOG.md",
|
||||
&mut update_text_region_fn(
|
||||
"<!-- begin autogenerated links to lint list -->\n",
|
||||
"<!-- end autogenerated links to lint list -->",
|
||||
|dst| {
|
||||
for lint in lints
|
||||
.iter()
|
||||
.map(|l| &*l.name)
|
||||
.chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
|
||||
.chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::")))
|
||||
.sorted()
|
||||
{
|
||||
writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"clippy_lints/src/lib.rs",
|
||||
&mut update_text_region_fn(
|
||||
"// begin lints modules, do not remove this comment, it's used in `update_lints`\n",
|
||||
"// end lints modules, do not remove this comment, it's used in `update_lints`",
|
||||
|dst| {
|
||||
for lint_mod in lints.iter().map(|l| &l.module).sorted().dedup() {
|
||||
writeln!(dst, "mod {lint_mod};").unwrap();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
("clippy_lints/src/declared_lints.rs", &mut |_, src, dst| {
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("pub static LINTS: &[&crate::LintInfo] = &[\n");
|
||||
for (module_name, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() {
|
||||
writeln!(dst, " crate::{module_name}::{lint_name}_INFO,").unwrap();
|
||||
"README.md",
|
||||
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
||||
write!(dst, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
}),
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
"book/src/README.md",
|
||||
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
||||
write!(dst, "{}", round_to_fifty(lints.len())).unwrap();
|
||||
}),
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
"CHANGELOG.md",
|
||||
&mut update_text_region_fn(
|
||||
"<!-- begin autogenerated links to lint list -->\n",
|
||||
"<!-- end autogenerated links to lint list -->",
|
||||
|dst| {
|
||||
for lint in lints
|
||||
.iter()
|
||||
.map(|l| &*l.name)
|
||||
.chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
|
||||
.chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::")))
|
||||
.sorted()
|
||||
{
|
||||
writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap();
|
||||
}
|
||||
dst.push_str("];\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| {
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
assert!(
|
||||
searcher.find_token(Token::Ident("declare_with_version"))
|
||||
&& searcher.find_token(Token::Ident("declare_with_version")),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
dst.push_str(&src[..searcher.pos() as usize]);
|
||||
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
|
||||
for lint in deprecated {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.name, lint.reason,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str(
|
||||
"]}\n\n\
|
||||
},
|
||||
),
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
"clippy_lints/src/deprecated_lints.rs",
|
||||
&mut |_, src, dst| {
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
assert!(
|
||||
searcher.find_token(Token::Ident("declare_with_version"))
|
||||
&& searcher.find_token(Token::Ident("declare_with_version")),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
dst.push_str(&src[..searcher.pos() as usize]);
|
||||
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
|
||||
for lint in deprecated {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.name, lint.reason,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str(
|
||||
"]}\n\n\
|
||||
#[rustfmt::skip]\n\
|
||||
declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\
|
||||
",
|
||||
);
|
||||
for lint in renamed {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.old_name, lint.new_name,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str("]}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("tests/ui/deprecated.rs", &mut |_, src, dst| {
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
for lint in deprecated {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap();
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("tests/ui/rename.rs", &mut move |_, src, dst| {
|
||||
let mut seen_lints = HashSet::new();
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("#![allow(clippy::duplicated_attributes)]\n");
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.new_name) {
|
||||
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
||||
}
|
||||
}
|
||||
seen_lints.clear();
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.old_name) {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
|
||||
}
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
],
|
||||
);
|
||||
for lint in renamed {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.old_name, lint.new_name,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str("]}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
},
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
"tests/ui/deprecated.rs",
|
||||
&mut |_, src, dst| {
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
for lint in deprecated {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap();
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
},
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
"tests/ui/rename.rs",
|
||||
&mut move |_, src, dst| {
|
||||
let mut seen_lints = HashSet::new();
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("#![allow(clippy::duplicated_attributes)]\n");
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.new_name) {
|
||||
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
||||
}
|
||||
}
|
||||
seen_lints.clear();
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.old_name) {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
|
||||
}
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
},
|
||||
);
|
||||
for (crate_name, lints) in lints.iter().into_group_map_by(|&l| {
|
||||
let Some(path::Component::Normal(name)) = l.path.components().next() else {
|
||||
// All paths should start with `{crate_name}/src` when parsed from `find_lint_decls`
|
||||
panic!("internal error: can't read crate name from path `{}`", l.path.display());
|
||||
};
|
||||
name
|
||||
}) {
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
Path::new(crate_name).join("src/lib.rs"),
|
||||
&mut update_text_region_fn(
|
||||
"// begin lints modules, do not remove this comment, it's used in `update_lints`\n",
|
||||
"// end lints modules, do not remove this comment, it's used in `update_lints`",
|
||||
|dst| {
|
||||
for lint_mod in lints
|
||||
.iter()
|
||||
.filter(|l| !l.module.is_empty())
|
||||
.map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0))
|
||||
.sorted()
|
||||
.dedup()
|
||||
{
|
||||
writeln!(dst, "mod {lint_mod};").unwrap();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
updater.update_file_checked(
|
||||
"cargo dev update_lints",
|
||||
update_mode,
|
||||
Path::new(crate_name).join("src/declared_lints.rs"),
|
||||
&mut |_, src, dst| {
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n");
|
||||
for (module_path, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() {
|
||||
if module_path.is_empty() {
|
||||
writeln!(dst, " crate::{lint_name}_INFO,").unwrap();
|
||||
} else {
|
||||
writeln!(dst, " crate::{module_path}::{lint_name}_INFO,").unwrap();
|
||||
}
|
||||
}
|
||||
dst.push_str("];\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn round_to_fifty(count: usize) -> usize {
|
||||
|
|
@ -187,13 +228,25 @@ pub struct RenamedLint {
|
|||
pub fn find_lint_decls() -> Vec<Lint> {
|
||||
let mut lints = Vec::with_capacity(1000);
|
||||
let mut contents = String::new();
|
||||
for (file, module) in read_src_with_module("clippy_lints/src".as_ref()) {
|
||||
parse_clippy_lint_decls(
|
||||
file.path(),
|
||||
File::open_read_to_cleared_string(file.path(), &mut contents),
|
||||
&module,
|
||||
&mut lints,
|
||||
);
|
||||
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() {
|
||||
continue;
|
||||
}
|
||||
let Ok(mut name) = e.file_name().into_string() else {
|
||||
continue;
|
||||
};
|
||||
if name.starts_with("clippy_lints") && name != "clippy_lints_internal" {
|
||||
name.push_str("/src");
|
||||
for (file, module) in read_src_with_module(name.as_ref()) {
|
||||
parse_clippy_lint_decls(
|
||||
file.path(),
|
||||
File::open_read_to_cleared_string(file.path(), &mut contents),
|
||||
&module,
|
||||
&mut lints,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
lints
|
||||
|
|
@ -205,7 +258,7 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirE
|
|||
let e = expect_action(e, ErrAction::Read, src_root);
|
||||
let path = e.path().as_os_str().as_encoded_bytes();
|
||||
if let Some(path) = path.strip_suffix(b".rs")
|
||||
&& let Some(path) = path.get("clippy_lints/src/".len()..)
|
||||
&& let Some(path) = path.get(src_root.as_os_str().len() + 1..)
|
||||
{
|
||||
if path == b"lib" {
|
||||
Some((e, String::new()))
|
||||
|
|
@ -333,17 +386,13 @@ pub fn read_deprecated_lints() -> (Vec<DeprecatedLint>, Vec<RenamedLint>) {
|
|||
|
||||
/// Removes the line splices and surrounding quotes from a string literal
|
||||
fn parse_str_lit(s: &str) -> String {
|
||||
let (s, mode) = if let Some(s) = s.strip_prefix("r") {
|
||||
(s.trim_matches('#'), rustc_literal_escaper::Mode::RawStr)
|
||||
} else {
|
||||
(s, rustc_literal_escaper::Mode::Str)
|
||||
};
|
||||
let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#');
|
||||
let s = s
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
||||
let mut res = String::with_capacity(s.len());
|
||||
rustc_literal_escaper::unescape_str(s, |range, ch| {
|
||||
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
|
||||
if let Ok(ch) = ch {
|
||||
res.push(ch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -383,21 +383,6 @@ impl FileUpdater {
|
|||
self.update_file_checked_inner(tool, mode, path.as_ref(), update);
|
||||
}
|
||||
|
||||
#[expect(clippy::type_complexity)]
|
||||
pub fn update_files_checked(
|
||||
&mut self,
|
||||
tool: &str,
|
||||
mode: UpdateMode,
|
||||
files: &mut [(
|
||||
impl AsRef<Path>,
|
||||
&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<Path>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.89"
|
||||
version = "0.1.90"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
@ -13,6 +13,7 @@ arrayvec = { version = "0.7", default-features = false }
|
|||
cargo_metadata = "0.18"
|
||||
clippy_config = { path = "../clippy_config" }
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
||||
itertools = "0.12"
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.8"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use super::INLINE_ALWAYS;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr};
|
||||
use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr};
|
||||
use rustc_hir::Attribute;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
|
||||
if span.from_expansion() {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
|
||||
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability};
|
||||
use clippy_utils::{span_contains_non_whitespace, tokenize_with_text};
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -90,35 +93,74 @@ impl CollapsibleIf {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
|
||||
if !block_starts_with_comment(cx, else_block)
|
||||
&& let Some(else_) = expr_block(else_block)
|
||||
fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
|
||||
if let Some(else_) = expr_block(else_block)
|
||||
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
|
||||
&& !else_.span.from_expansion()
|
||||
&& let ExprKind::If(..) = else_.kind
|
||||
&& let ExprKind::If(else_if_cond, ..) = else_.kind
|
||||
&& !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_code)
|
||||
{
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let up_to_else = then_span.between(else_block.span);
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
|
||||
!c.is_whitespace()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
else_block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
|diag| {
|
||||
let up_to_else = then_span.between(else_block.span);
|
||||
let else_before_if = else_.span.shrink_to_lo().with_hi(else_if_cond.span.lo() - BytePos(1));
|
||||
if self.lint_commented_code
|
||||
&& let Some(else_keyword_span) =
|
||||
span_extract_keyword(cx.tcx.sess.source_map(), up_to_else, "else")
|
||||
&& let Some(else_if_keyword_span) =
|
||||
span_extract_keyword(cx.tcx.sess.source_map(), else_before_if, "if")
|
||||
{
|
||||
let else_keyword_span = else_keyword_span.with_leading_whitespace(cx).into_span();
|
||||
let else_open_bracket = else_block.span.split_at(1).0.with_leading_whitespace(cx).into_span();
|
||||
let else_closing_bracket = {
|
||||
let end = else_block.span.shrink_to_hi();
|
||||
end.with_lo(end.lo() - BytePos(1))
|
||||
.with_leading_whitespace(cx)
|
||||
.into_span()
|
||||
};
|
||||
let sugg = vec![
|
||||
// Remove the outer else block `else`
|
||||
(else_keyword_span, String::new()),
|
||||
// Replace the inner `if` by `else if`
|
||||
(else_if_keyword_span, String::from("else if")),
|
||||
// Remove the outer else block `{`
|
||||
(else_open_bracket, String::new()),
|
||||
// Remove the outer else block '}'
|
||||
(else_closing_bracket, String::new()),
|
||||
];
|
||||
diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
|
||||
!c.is_whitespace()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
diag.span_suggestion(
|
||||
else_block.span,
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(
|
||||
cx,
|
||||
else_.span,
|
||||
"..",
|
||||
Some(else_block.span),
|
||||
&mut applicability
|
||||
)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +172,7 @@ impl CollapsibleIf {
|
|||
&& self.eligible_condition(cx, check_inner)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& inner.span.ctxt() == ctxt
|
||||
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
|
||||
&& !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
@ -141,7 +183,7 @@ impl CollapsibleIf {
|
|||
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
|
||||
let then_closing_bracket = {
|
||||
let end = then.span.shrink_to_hi();
|
||||
end.with_lo(end.lo() - rustc_span::BytePos(1))
|
||||
end.with_lo(end.lo() - BytePos(1))
|
||||
.with_leading_whitespace(cx)
|
||||
.into_span()
|
||||
};
|
||||
|
|
@ -179,7 +221,7 @@ impl LateLintPass<'_> for CollapsibleIf {
|
|||
if let Some(else_) = else_
|
||||
&& let ExprKind::Block(else_, None) = else_.kind
|
||||
{
|
||||
Self::check_collapsible_else_if(cx, then.span, else_);
|
||||
self.check_collapsible_else_if(cx, then.span, else_);
|
||||
} else if else_.is_none()
|
||||
&& self.eligible_condition(cx, cond)
|
||||
&& let ExprKind::Block(then, None) = then.kind
|
||||
|
|
@ -190,12 +232,16 @@ impl LateLintPass<'_> for CollapsibleIf {
|
|||
}
|
||||
}
|
||||
|
||||
fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
|
||||
// We trim all opening braces and whitespaces and then check if the next string is a comment.
|
||||
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
|
||||
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
|
||||
.to_owned();
|
||||
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
|
||||
// Check that nothing significant can be found but whitespaces between the initial `{` of `block`
|
||||
// and the beginning of `stop_at`.
|
||||
fn block_starts_with_significant_tokens(
|
||||
cx: &LateContext<'_>,
|
||||
block: &Block<'_>,
|
||||
stop_at: &Expr<'_>,
|
||||
lint_commented_code: bool,
|
||||
) -> bool {
|
||||
let span = block.span.split_at(1).1.until(stop_at.span);
|
||||
span_contains_non_whitespace(cx, span, lint_commented_code)
|
||||
}
|
||||
|
||||
/// If `block` is a block with either one expression or a statement containing an expression,
|
||||
|
|
@ -226,3 +272,16 @@ fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
|
|||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option<Span> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Use that command to update this file and do not edit by hand.
|
||||
// Manual edits will be overwritten.
|
||||
|
||||
pub static LINTS: &[&crate::LintInfo] = &[
|
||||
pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
||||
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
|
||||
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
|
||||
crate::approx_const::APPROX_CONSTANT_INFO,
|
||||
|
|
@ -112,6 +112,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::disallowed_names::DISALLOWED_NAMES_INFO,
|
||||
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
|
||||
crate::disallowed_types::DISALLOWED_TYPES_INFO,
|
||||
crate::doc::DOC_BROKEN_LINK_INFO,
|
||||
crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO,
|
||||
crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
|
||||
crate::doc::DOC_LAZY_CONTINUATION_INFO,
|
||||
|
|
@ -590,6 +591,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
|
||||
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
|
||||
crate::operators::INTEGER_DIVISION_INFO,
|
||||
crate::operators::MANUAL_IS_MULTIPLE_OF_INFO,
|
||||
crate::operators::MANUAL_MIDPOINT_INFO,
|
||||
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
|
||||
crate::operators::MODULO_ARITHMETIC_INFO,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ declare_clippy_lint! {
|
|||
/// # When using an inline table, can add a `reason` for why the macro
|
||||
/// # is disallowed.
|
||||
/// { path = "serde::Serialize", reason = "no serializing" },
|
||||
/// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`,
|
||||
/// # it will be silently ignored
|
||||
/// { path = "std::invalid_macro", reason = "use alternative instead", allow-invalid = true }
|
||||
/// ]
|
||||
/// ```
|
||||
/// ```no_run
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ declare_clippy_lint! {
|
|||
/// { path = "std::vec::Vec::leak", reason = "no leaking memory" },
|
||||
/// # Can also add a `replacement` that will be offered as a suggestion.
|
||||
/// { path = "std::sync::Mutex::new", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex::new" },
|
||||
/// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`,
|
||||
/// # it will be silently ignored
|
||||
/// { path = "std::fs::InvalidPath", reason = "use alternative instead", allow-invalid = true },
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ declare_clippy_lint! {
|
|||
/// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
|
||||
/// # Can also add a `replacement` that will be offered as a suggestion.
|
||||
/// { path = "std::sync::Mutex", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex" },
|
||||
/// # This would normally error if the path is incorrect, but with `allow-invalid` = `true`,
|
||||
/// # it will be silently ignored
|
||||
/// { path = "std::invalid::Type", reason = "use alternative instead", allow-invalid = true }
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
|
|
|
|||
83
src/tools/clippy/clippy_lints/src/doc/broken_link.rs
Normal file
83
src/tools/clippy/clippy_lints/src/doc/broken_link.rs
Normal file
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
|
@ -49,11 +49,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, fragments: &F
|
|||
.filter(|attr| attr.span().overlaps(this_fragment.span))
|
||||
.rev()
|
||||
.find_map(|attr| {
|
||||
Some((
|
||||
attr,
|
||||
attr.doc_str_and_comment_kind()?,
|
||||
attr.doc_resolution_scope()?,
|
||||
))
|
||||
Some((attr, attr.doc_str_and_comment_kind()?, attr.doc_resolution_scope()?))
|
||||
})
|
||||
.unwrap();
|
||||
let (to_add, terminator) = match (doc_attr_comment_kind, attr_style) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use rustc_span::edition::Edition;
|
|||
use std::ops::Range;
|
||||
use url::Url;
|
||||
|
||||
mod broken_link;
|
||||
mod doc_comment_double_space_linebreaks;
|
||||
mod doc_suspicious_footnotes;
|
||||
mod include_in_doc_without_cfg;
|
||||
|
|
@ -292,6 +293,34 @@ declare_clippy_lint! {
|
|||
"possible typo for an intra-doc link"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks the doc comments have unbroken links, mostly caused
|
||||
/// by bad formatted links such as broken across multiple lines.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Because documentation generated by rustdoc will be broken
|
||||
/// since expected links won't be links and just text.
|
||||
///
|
||||
/// ### Examples
|
||||
/// This link is broken:
|
||||
/// ```no_run
|
||||
/// /// [example of a bad link](https://
|
||||
/// /// github.com/rust-lang/rust-clippy/)
|
||||
/// pub fn do_something() {}
|
||||
/// ```
|
||||
///
|
||||
/// It shouldn't be broken across multiple lines to work:
|
||||
/// ```no_run
|
||||
/// /// [example of a good link](https://github.com/rust-lang/rust-clippy/)
|
||||
/// pub fn do_something() {}
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
pub DOC_BROKEN_LINK,
|
||||
pedantic,
|
||||
"broken document link"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the doc comments of publicly visible
|
||||
|
|
@ -656,6 +685,7 @@ impl Documentation {
|
|||
impl_lint_pass!(Documentation => [
|
||||
DOC_LINK_CODE,
|
||||
DOC_LINK_WITH_QUOTES,
|
||||
DOC_BROKEN_LINK,
|
||||
DOC_MARKDOWN,
|
||||
DOC_NESTED_REFDEFS,
|
||||
MISSING_SAFETY_DOC,
|
||||
|
|
@ -786,9 +816,9 @@ struct DocHeaders {
|
|||
/// back in the various late lint pass methods if they need the final doc headers, like "Safety" or
|
||||
/// "Panics" sections.
|
||||
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
|
||||
/// We don't want the parser to choke on intra doc links. Since we don't
|
||||
/// actually care about rendering them, just pretend that all broken links
|
||||
/// point to a fake address.
|
||||
// We don't want the parser to choke on intra doc links. Since we don't
|
||||
// actually care about rendering them, just pretend that all broken links
|
||||
// point to a fake address.
|
||||
#[expect(clippy::unnecessary_wraps)] // we're following a type signature
|
||||
fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
|
||||
Some(("fake".into(), "fake".into()))
|
||||
|
|
@ -828,14 +858,12 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
|||
return Some(DocHeaders::default());
|
||||
}
|
||||
|
||||
let mut cb = fake_broken_link_callback;
|
||||
|
||||
check_for_code_clusters(
|
||||
cx,
|
||||
pulldown_cmark::Parser::new_with_broken_link_callback(
|
||||
&doc,
|
||||
main_body_opts() - Options::ENABLE_SMART_PUNCTUATION,
|
||||
Some(&mut cb),
|
||||
Some(&mut fake_broken_link_callback),
|
||||
)
|
||||
.into_offset_iter(),
|
||||
&doc,
|
||||
|
|
@ -845,9 +873,17 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, 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,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ pub fn check(
|
|||
if !ignore {
|
||||
get_test_spans(&item, *ident, &mut test_attr_spans);
|
||||
}
|
||||
|
||||
let is_async = matches!(sig.header.coroutine_kind, Some(CoroutineKind::Async { .. }));
|
||||
let returns_nothing = match &sig.decl.output {
|
||||
FnRetTy::Default(..) => true,
|
||||
|
|
@ -89,9 +90,14 @@ pub fn check(
|
|||
// Another function was found; this case is ignored for needless_doctest_main
|
||||
ItemKind::Fn(fn_) => {
|
||||
eligible = false;
|
||||
if !ignore {
|
||||
get_test_spans(&item, fn_.ident, &mut test_attr_spans);
|
||||
if ignore {
|
||||
// If ignore is active invalidating one lint,
|
||||
// and we already found another function thus
|
||||
// invalidating the other one, we have no
|
||||
// business continuing.
|
||||
return (false, test_attr_spans);
|
||||
}
|
||||
get_test_spans(&item, fn_.ident, &mut test_attr_spans);
|
||||
},
|
||||
// Tests with one of these items are ignored
|
||||
ItemKind::Static(..)
|
||||
|
|
@ -104,7 +110,10 @@ pub fn check(
|
|||
},
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
e.cancel();
|
||||
// See issue #15041. When calling `.cancel()` on the `Diag`, Clippy will unexpectedly panic
|
||||
// when the `Diag` is unwinded. Meanwhile, we can just call `.emit()`, since the `DiagCtxt`
|
||||
// is just a sink, nothing will be printed.
|
||||
e.emit();
|
||||
return (false, test_attr_spans);
|
||||
},
|
||||
}
|
||||
|
|
@ -119,6 +128,18 @@ pub fn check(
|
|||
|
||||
let trailing_whitespace = text.len() - text.trim_end().len();
|
||||
|
||||
// We currently only test for "fn main". Checking for the real
|
||||
// entrypoint (with tcx.entry_fn(())) in each block would be unnecessarily
|
||||
// expensive, as those are probably intended and relevant. Same goes for
|
||||
// macros and other weird ways of declaring a main function.
|
||||
//
|
||||
// Also, as we only check for attribute names and don't do macro expansion,
|
||||
// we can check only for #[test]
|
||||
|
||||
if !((text.contains("main") && text.contains("fn")) || text.contains("#[test]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because of the global session, we need to create a new session in a different thread with
|
||||
// the edition we need.
|
||||
let text = text.to_owned();
|
||||
|
|
|
|||
|
|
@ -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<Symbol>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>()
|
||||
&& !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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
|
||||
use clippy_utils::ty::get_type_diagnostic_name;
|
||||
|
|
@ -109,14 +109,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
|
|||
{
|
||||
let vec_crate = if is_no_std_crate(cx) { "alloc" } else { "std" };
|
||||
// replace `|| vec![]` with `Vec::new`
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
REDUNDANT_CLOSURE,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
"redundant closure",
|
||||
"replace the closure with `Vec::new`",
|
||||
format!("{vec_crate}::vec::Vec::new"),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with `Vec::new`",
|
||||
format!("{vec_crate}::vec::Vec::new"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
// skip `foo(|| macro!())`
|
||||
|
|
@ -198,41 +204,48 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
|
|||
// For now ignore all callee types which reference a type parameter.
|
||||
&& !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_)))
|
||||
{
|
||||
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
|
||||
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
|
||||
if path_to_local(callee).is_some_and(|l| {
|
||||
// FIXME: Do we really need this `local_used_in` check?
|
||||
// Isn't it checking something like... `callee(callee)`?
|
||||
// If somehow this check is needed, add some test for it,
|
||||
// 'cuz currently nothing changes after deleting this check.
|
||||
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
|
||||
}) {
|
||||
match cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
) {
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
|
||||
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
|
||||
snippet = format!("&{snippet}");
|
||||
},
|
||||
_ => (),
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
REDUNDANT_CLOSURE,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
"redundant closure",
|
||||
|diag| {
|
||||
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
|
||||
if path_to_local(callee).is_some_and(|l| {
|
||||
// FIXME: Do we really need this `local_used_in` check?
|
||||
// Isn't it checking something like... `callee(callee)`?
|
||||
// If somehow this check is needed, add some test for it,
|
||||
// 'cuz currently nothing changes after deleting this check.
|
||||
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
|
||||
}) {
|
||||
match cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
) {
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
|
||||
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
|
||||
snippet = format!("&{snippet}");
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the function itself",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the function itself",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
|
||||
|
|
@ -245,9 +258,10 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
|
|||
Some(span) => format!("::{}", snippet_with_applicability(cx, span, "<..>", &mut app)),
|
||||
None => String::new(),
|
||||
};
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
"redundant closure",
|
||||
|diag| {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ use clippy_utils::source::SpanRangeExt;
|
|||
use clippy_utils::ty::is_must_use_ty;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{return_ty, trait_ref_of_method};
|
||||
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_span::Symbol;
|
||||
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
|
||||
|
||||
use core::ops::ControlFlow;
|
||||
|
||||
|
|
@ -34,7 +34,17 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
|
|||
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if let Some((attr_span, reason)) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig);
|
||||
check_needless_must_use(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.owner_id,
|
||||
item.span,
|
||||
fn_header_span,
|
||||
*attr_span,
|
||||
*reason,
|
||||
attrs,
|
||||
sig,
|
||||
);
|
||||
} else if is_public && !is_proc_macro(attrs) && !find_attr!(attrs, AttributeKind::NoMangle(..)) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
|
|
@ -54,9 +64,20 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
|
|||
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
let attrs = cx.tcx.hir_attrs(item.hir_id());
|
||||
let attr = find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason));
|
||||
let attr =
|
||||
find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason));
|
||||
if let Some((attr_span, reason)) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig);
|
||||
check_needless_must_use(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.owner_id,
|
||||
item.span,
|
||||
fn_header_span,
|
||||
*attr_span,
|
||||
*reason,
|
||||
attrs,
|
||||
sig,
|
||||
);
|
||||
} else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id).is_none() {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
|
|
@ -77,9 +98,20 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
|
|||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
|
||||
let attrs = cx.tcx.hir_attrs(item.hir_id());
|
||||
let attr = find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason));
|
||||
let attr =
|
||||
find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::MustUse { span, reason } => (span, reason));
|
||||
if let Some((attr_span, reason)) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, *attr_span, *reason, attrs, sig);
|
||||
check_needless_must_use(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.owner_id,
|
||||
item.span,
|
||||
fn_header_span,
|
||||
*attr_span,
|
||||
*reason,
|
||||
attrs,
|
||||
sig,
|
||||
);
|
||||
} else if let hir::TraitFn::Provided(eid) = *eid {
|
||||
let body = cx.tcx.hir_body(eid);
|
||||
if attr.is_none() && is_public && !is_proc_macro(attrs) {
|
||||
|
|
@ -121,12 +153,7 @@ fn check_needless_must_use(
|
|||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
attr_span,
|
||||
"remove the attribute",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.span_suggestion(attr_span, "remove the attribute", "", Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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`",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sugg::DiagExt;
|
||||
use rustc_attr_data_structures::{find_attr, AttributeKind};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -33,10 +33,10 @@ impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
|
|||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
|
||||
if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind
|
||||
&& let Some(attr_span) = find_attr!(cx
|
||||
.tcx
|
||||
.hir_attrs(item.hir_id()),
|
||||
AttributeKind::Inline(_, span) => *span
|
||||
)
|
||||
.tcx
|
||||
.hir_attrs(item.hir_id()),
|
||||
AttributeKind::Inline(_, span) => *span
|
||||
)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -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<LintId>,
|
||||
cargo: Vec<LintId>,
|
||||
complexity: Vec<LintId>,
|
||||
correctness: Vec<LintId>,
|
||||
nursery: Vec<LintId>,
|
||||
pedantic: Vec<LintId>,
|
||||
perf: Vec<LintId>,
|
||||
restriction: Vec<LintId>,
|
||||
style: Vec<LintId>,
|
||||
suspicious: Vec<LintId>,
|
||||
}
|
||||
|
||||
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<LintId> {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -116,11 +117,12 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
|
|||
let format_suggestion = |variant: &VariantDef| {
|
||||
format!(
|
||||
"{}{}{}{}",
|
||||
if let Some(ident) = wildcard_ident {
|
||||
format!("{} @ ", ident.name)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
wildcard_ident.map_or(String::new(), |ident| {
|
||||
ident
|
||||
.span
|
||||
.get_source_text(cx)
|
||||
.map_or_else(|| format!("{} @ ", ident.name), |s| format!("{s} @ "))
|
||||
}),
|
||||
if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
|
||||
let mut s = String::new();
|
||||
for seg in path_prefix {
|
||||
|
|
@ -138,7 +140,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
|
|||
Some(CtorKind::Fn) if variant.fields.len() == 1 => "(_)",
|
||||
Some(CtorKind::Fn) => "(..)",
|
||||
Some(CtorKind::Const) => "",
|
||||
None => "{ .. }",
|
||||
None => " { .. }",
|
||||
}
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ pub(super) fn check<'tcx>(
|
|||
fun_span: Option<Span>,
|
||||
) -> 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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr_data_structures::{find_attr, AttributeKind};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::Attribute;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ fn path_has_args(p: &QPath<'_>) -> bool {
|
|||
/// - `Copy` itself, or
|
||||
/// - the only use of a mutable reference, or
|
||||
/// - not a variable (created by a function call)
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||
fn needless_borrow_count<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
|
|
@ -232,11 +232,11 @@ fn needless_borrow_count<'tcx>(
|
|||
let mut args_with_referent_ty = callee_args.to_vec();
|
||||
|
||||
let mut check_reference_and_referent = |reference: &Expr<'tcx>, referent: &Expr<'tcx>| {
|
||||
if let ExprKind::Field(base, _) = &referent.kind {
|
||||
let base_ty = cx.typeck_results().expr_ty(base);
|
||||
if drop_trait_def_id.is_some_and(|id| implements_trait(cx, base_ty, id, &[])) {
|
||||
return false;
|
||||
}
|
||||
if let ExprKind::Field(base, _) = &referent.kind
|
||||
&& let base_ty = cx.typeck_results().expr_ty(base)
|
||||
&& drop_trait_def_id.is_some_and(|id| implements_trait(cx, base_ty, id, &[]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let referent_ty = cx.typeck_results().expr_ty(referent);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -124,8 +124,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
|
|||
// Note that we do not want to deal with qualified predicates here.
|
||||
match pred.kind().no_bound_vars() {
|
||||
Some(ty::ClauseKind::Trait(pred))
|
||||
if pred.def_id() != sized_trait && pred.def_id() != meta_sized_trait
|
||||
=> Some(pred),
|
||||
if pred.def_id() != sized_trait && pred.def_id() != meta_sized_trait =>
|
||||
{
|
||||
Some(pred)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_hir::{Attribute, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{BytePos, Pos};
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_hir::Attribute;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt};
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt, integer_const, is_zero_integer_const};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{clip, peel_hir_expr_refs, unsext};
|
||||
use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, kw};
|
||||
|
||||
use super::IDENTITY_OP;
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ pub(crate) fn check<'tcx>(
|
|||
left: &'tcx Expr<'_>,
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if !is_allowed(cx, op, left, right) {
|
||||
if !is_allowed(cx, expr, op, left, right) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -165,14 +166,27 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>)
|
|||
Parens::Needed
|
||||
}
|
||||
|
||||
fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
fn is_allowed<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
cmp: BinOpKind,
|
||||
left: &Expr<'tcx>,
|
||||
right: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
// Exclude case where the left or right side is associated function call returns a type which is
|
||||
// `Self` that is not given explicitly, and the expression is not a let binding's init
|
||||
// expression and the let binding has a type annotation, or a function's return value.
|
||||
if (is_assoc_fn_without_type_instance(cx, left) || is_assoc_fn_without_type_instance(cx, right))
|
||||
&& !is_expr_used_with_type_annotation(cx, expr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This lint applies to integers and their references
|
||||
cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|
||||
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
// `1 << 0` is a common pattern in bit manipulation code
|
||||
&& !(cmp == BinOpKind::Shl
|
||||
&& ConstEvalCtxt::new(cx).eval_simple(right) == Some(Constant::Int(0))
|
||||
&& ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1)))
|
||||
&& !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1))
|
||||
}
|
||||
|
||||
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
|
||||
|
|
@ -234,3 +248,47 @@ fn span_ineffective_operation(
|
|||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
match expr_use_ctxt(cx, expr).use_node(cx) {
|
||||
ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(),
|
||||
ExprUseNode::Return(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the expression is an associated function without a type instance.
|
||||
/// Example:
|
||||
/// ```
|
||||
/// trait Def {
|
||||
/// fn def() -> Self;
|
||||
/// }
|
||||
/// impl Def for usize {
|
||||
/// fn def() -> Self {
|
||||
/// 0
|
||||
/// }
|
||||
/// }
|
||||
/// fn test() {
|
||||
/// let _ = 0usize + &Default::default();
|
||||
/// let _ = 0usize + &Def::def();
|
||||
/// }
|
||||
/// ```
|
||||
fn is_assoc_fn_without_type_instance<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
|
||||
if let ExprKind::Call(func, _) = peel_hir_expr_refs(expr).0.kind
|
||||
&& let ExprKind::Path(QPath::Resolved(
|
||||
// If it's not None, don't need to go further.
|
||||
None,
|
||||
Path {
|
||||
res: Res::Def(DefKind::AssocFn, def_id),
|
||||
..
|
||||
},
|
||||
)) = func.kind
|
||||
&& let output_ty = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder().output()
|
||||
&& let ty::Param(ty::ParamTy {
|
||||
name: kw::SelfUpper, ..
|
||||
}) = output_ty.kind()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
|
||||
use clippy_utils::{is_self, is_self_ty};
|
||||
use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::is_must_use_ty;
|
||||
use clippy_utils::{nth_arg, return_ty};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{Span};
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_attr_data_structures::find_attr;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-06-12
|
||||
nightly-2025-06-26
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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<u128> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
|
|||
});
|
||||
}
|
||||
|
||||
/// Same as `span_lint` but with an extra `help` message.
|
||||
/// Same as [`span_lint`] but with an extra `help` message.
|
||||
///
|
||||
/// Use this if you want to provide some general help but
|
||||
/// can't provide a specific machine applicable suggestion.
|
||||
|
|
@ -166,7 +166,7 @@ pub fn span_lint_and_help<T: LintContext>(
|
|||
});
|
||||
}
|
||||
|
||||
/// 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<T: LintContext>(
|
|||
});
|
||||
}
|
||||
|
||||
/// 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`
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ use rustc_span::hygiene::{ExpnKind, MacroKind};
|
|||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::symbol::{Ident, Symbol, kw};
|
||||
use rustc_span::{InnerSpan, Span};
|
||||
use source::walk_span_to_context;
|
||||
use source::{SpanRangeExt, walk_span_to_context};
|
||||
use visitors::{Visitable, for_each_unconsumed_temporary};
|
||||
|
||||
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
|
||||
|
|
@ -1886,10 +1886,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
_ => None,
|
||||
};
|
||||
|
||||
did.is_some_and(|did| find_attr!(
|
||||
cx.tcx.get_all_attrs(did),
|
||||
AttributeKind::MustUse { ..}
|
||||
))
|
||||
did.is_some_and(|did| find_attr!(cx.tcx.get_all_attrs(did), AttributeKind::MustUse { .. }))
|
||||
}
|
||||
|
||||
/// Checks if a function's body represents the identity function. Looks for bodies of the form:
|
||||
|
|
@ -2713,7 +2710,7 @@ impl<'tcx> ExprUseNode<'tcx> {
|
|||
}
|
||||
|
||||
/// Gets the context an expression's value is used in.
|
||||
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> {
|
||||
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtxt<'tcx> {
|
||||
let mut adjustments = [].as_slice();
|
||||
let mut is_ty_unified = false;
|
||||
let mut moved_before_use = false;
|
||||
|
|
@ -2790,6 +2787,19 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
|
|||
});
|
||||
}
|
||||
|
||||
/// Checks whether a given span has any significant token. A significant token is a non-whitespace
|
||||
/// token, including comments unless `skip_comments` is set.
|
||||
/// This is useful to determine if there are any actual code tokens in the span that are omitted in
|
||||
/// the late pass, such as platform-specific code.
|
||||
pub fn span_contains_non_whitespace(cx: &impl source::HasSession, span: Span, skip_comments: bool) -> bool {
|
||||
matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)|
|
||||
match token {
|
||||
TokenKind::Whitespace => false,
|
||||
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
|
||||
_ => true,
|
||||
}
|
||||
))
|
||||
}
|
||||
/// Returns all the comments a given span contains
|
||||
///
|
||||
/// Comments are returned wrapped with their relevant delimiters
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ macro_rules! msrv_aliases {
|
|||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,88,0 { LET_CHAINS }
|
||||
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT }
|
||||
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF }
|
||||
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
|
||||
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
|
||||
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
|
||||
|
|
@ -42,6 +42,7 @@ msrv_aliases! {
|
|||
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
|
||||
1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
|
||||
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
|
||||
1,61,0 { CONST_FN_TRAIT_BOUND }
|
||||
1,60,0 { ABS_DIFF }
|
||||
1,59,0 { THREAD_LOCAL_CONST_INIT }
|
||||
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
|
||||
|
|
|
|||
|
|
@ -32,6 +32,21 @@ pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Ms
|
|||
for local in &body.local_decls {
|
||||
check_ty(cx, local.ty, local.source_info.span, msrv)?;
|
||||
}
|
||||
if !msrv.meets(cx, msrvs::CONST_FN_TRAIT_BOUND)
|
||||
&& let Some(sized_did) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(meta_sized_did) = cx.tcx.lang_items().meta_sized_trait()
|
||||
&& cx.tcx.param_env(def_id).caller_bounds().iter().any(|bound| {
|
||||
bound.as_trait_clause().is_some_and(|clause| {
|
||||
let did = clause.def_id();
|
||||
did != sized_did && did != meta_sized_did
|
||||
})
|
||||
})
|
||||
{
|
||||
return Err((
|
||||
body.span,
|
||||
"non-`Sized` trait clause before `const_fn_trait_bound` is stabilized".into(),
|
||||
));
|
||||
}
|
||||
// impl trait is gone in MIR, so check the return type manually
|
||||
check_ty(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -494,7 +494,17 @@ impl<T: Display> Display for ParenHelper<T> {
|
|||
/// 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 `<lhs> <op> <rhs>` 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};
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ generate! {
|
|||
DOUBLE_QUOTE: "\"",
|
||||
Deserialize,
|
||||
EarlyLintPass,
|
||||
ErrorKind,
|
||||
IntoIter,
|
||||
Itertools,
|
||||
LF: "\n",
|
||||
|
|
@ -65,7 +64,6 @@ generate! {
|
|||
RegexBuilder,
|
||||
RegexSet,
|
||||
Start,
|
||||
Step,
|
||||
Symbol,
|
||||
SyntaxContext,
|
||||
TBD,
|
||||
|
|
@ -158,7 +156,6 @@ generate! {
|
|||
from_ne_bytes,
|
||||
from_ptr,
|
||||
from_raw,
|
||||
from_ref,
|
||||
from_str,
|
||||
from_str_radix,
|
||||
fs,
|
||||
|
|
@ -166,6 +163,7 @@ generate! {
|
|||
futures_util,
|
||||
get,
|
||||
get_mut,
|
||||
get_or_insert,
|
||||
get_or_insert_with,
|
||||
get_unchecked,
|
||||
get_unchecked_mut,
|
||||
|
|
@ -216,7 +214,6 @@ generate! {
|
|||
max_by_key,
|
||||
max_value,
|
||||
maximum,
|
||||
mem,
|
||||
min,
|
||||
min_by,
|
||||
min_by_key,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use core::ops::ControlFlow;
|
|||
use itertools::Itertools;
|
||||
use rustc_abi::VariantIdx;
|
||||
use rustc_ast::ast::Mutability;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
|
|
@ -20,8 +21,8 @@ use rustc_middle::traits::EvaluationResult;
|
|||
use rustc_middle::ty::layout::ValidityRequirement;
|
||||
use rustc_middle::ty::{
|
||||
self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
|
||||
GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
|
||||
TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
|
||||
GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
|
||||
TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
|
||||
};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
|
||||
|
|
@ -31,8 +32,6 @@ use rustc_trait_selection::traits::{Obligation, ObligationCause};
|
|||
use std::assert_matches::debug_assert_matches;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::iter;
|
||||
use rustc_attr_data_structures::find_attr;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
|
||||
use crate::path_res;
|
||||
use crate::paths::{PathNS, lookup_path_str};
|
||||
|
|
@ -328,14 +327,8 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
|||
// Returns whether the type has #[must_use] attribute
|
||||
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => find_attr!(
|
||||
cx.tcx.get_all_attrs(adt.did()),
|
||||
AttributeKind::MustUse { ..}
|
||||
),
|
||||
ty::Foreign(did) => find_attr!(
|
||||
cx.tcx.get_all_attrs(*did),
|
||||
AttributeKind::MustUse { ..}
|
||||
),
|
||||
ty::Adt(adt, _) => find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::MustUse { .. }),
|
||||
ty::Foreign(did) => find_attr!(cx.tcx.get_all_attrs(*did), AttributeKind::MustUse { .. }),
|
||||
ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => {
|
||||
// for the Array case we don't need to care for the len == 0 case
|
||||
// because we don't want to lint functions returning empty arrays
|
||||
|
|
@ -345,7 +338,10 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
|||
ty::Alias(ty::Opaque, AliasTy { def_id, .. }) => {
|
||||
for (predicate, _) in cx.tcx.explicit_item_self_bounds(def_id).skip_binder() {
|
||||
if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
|
||||
&& find_attr!(cx.tcx.get_all_attrs(trait_predicate.trait_ref.def_id), AttributeKind::MustUse { ..})
|
||||
&& find_attr!(
|
||||
cx.tcx.get_all_attrs(trait_predicate.trait_ref.def_id),
|
||||
AttributeKind::MustUse { .. }
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -355,7 +351,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
|||
ty::Dynamic(binder, _, _) => {
|
||||
for predicate in *binder {
|
||||
if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder()
|
||||
&& find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { ..})
|
||||
&& find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
10
src/tools/clippy/declare_clippy_lint/Cargo.toml
Normal file
10
src/tools/clippy/declare_clippy_lint/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "declare_clippy_lint"
|
||||
version = "0.1.90"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
# This crate uses #[feature(rustc_private)]
|
||||
rustc_private = true
|
||||
280
src/tools/clippy/declare_clippy_lint/src/lib.rs
Normal file
280
src/tools/clippy/declare_clippy_lint/src/lib.rs
Normal file
|
|
@ -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<LintId>,
|
||||
cargo: Vec<LintId>,
|
||||
complexity: Vec<LintId>,
|
||||
correctness: Vec<LintId>,
|
||||
nursery: Vec<LintId>,
|
||||
pedantic: Vec<LintId>,
|
||||
perf: Vec<LintId>,
|
||||
restriction: Vec<LintId>,
|
||||
style: Vec<LintId>,
|
||||
suspicious: Vec<LintId>,
|
||||
}
|
||||
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)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ extern crate rustc_span;
|
|||
extern crate tikv_jemalloc_sys as jemalloc_sys;
|
||||
|
||||
use clippy_utils::sym;
|
||||
use declare_clippy_lint::LintListBuilder;
|
||||
use rustc_interface::interface;
|
||||
use rustc_session::EarlyDiagCtxt;
|
||||
use rustc_session::config::ErrorOutputType;
|
||||
|
|
@ -156,8 +157,13 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
|
|||
(previous)(sess, lint_store);
|
||||
}
|
||||
|
||||
let mut list_builder = LintListBuilder::default();
|
||||
list_builder.insert(clippy_lints::declared_lints::LINTS);
|
||||
list_builder.register(lint_store);
|
||||
|
||||
let conf = clippy_config::Conf::read(sess, &conf_path);
|
||||
clippy_lints::register_lints(lint_store, conf);
|
||||
clippy_lints::register_lint_passes(lint_store, conf);
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
clippy_lints_internal::register_lints(lint_store);
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ fn dogfood() {
|
|||
"clippy_lints",
|
||||
"clippy_utils",
|
||||
"clippy_config",
|
||||
"declare_clippy_lint",
|
||||
"lintcheck",
|
||||
"rustc_tools_util",
|
||||
] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -218,4 +218,20 @@ fn main() {
|
|||
let _ = &S::VALUE.1; //~ borrow_interior_mutable_const
|
||||
let _ = &S::VALUE.2;
|
||||
}
|
||||
{
|
||||
pub struct Foo<T, const N: usize>(pub Entry<N>, pub T);
|
||||
|
||||
pub struct Entry<const N: usize>(pub Cell<[u32; N]>);
|
||||
|
||||
impl<const N: usize> Entry<N> {
|
||||
const INIT: Self = Self(Cell::new([42; N]));
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Foo<T, N> {
|
||||
pub fn make_foo(v: T) -> Self {
|
||||
// Used to ICE due to incorrect instantiation.
|
||||
Foo(Entry::INIT, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ fn issue_10381() {
|
|||
impl Bar for Foo {}
|
||||
|
||||
fn maybe_get_bar(i: u32) -> Option<Box<dyn Bar>> {
|
||||
if i % 2 == 0 {
|
||||
if i.is_multiple_of(2) {
|
||||
Some(Box::new(Foo::default()))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ fn issue_10381() {
|
|||
impl Bar for Foo {}
|
||||
|
||||
fn maybe_get_bar(i: u32) -> Option<Box<dyn Bar>> {
|
||||
if i % 2 == 0 {
|
||||
if i.is_multiple_of(2) {
|
||||
Some(Box::new(Foo::default()))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,3 +154,12 @@ fn issue14722() {
|
|||
None
|
||||
};
|
||||
}
|
||||
|
||||
fn issue14799() {
|
||||
if true {
|
||||
#[cfg(target_os = "freebsd")]
|
||||
todo!();
|
||||
|
||||
if true {}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,3 +164,12 @@ fn issue14722() {
|
|||
None
|
||||
};
|
||||
}
|
||||
|
||||
fn issue14799() {
|
||||
if true {
|
||||
#[cfg(target_os = "freebsd")]
|
||||
todo!();
|
||||
|
||||
if true {}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@ check-pass
|
||||
|
||||
#![warn(clippy::needless_doctest_main)]
|
||||
//! issue 10491:
|
||||
//! ```rust,no_test
|
||||
|
|
@ -19,4 +17,114 @@
|
|||
/// ```
|
||||
fn foo() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Description
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
//~^ error: needless `fn main` in doctest
|
||||
/// let a = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn mulpipulpi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// With a `#[no_main]`
|
||||
/// ```rust
|
||||
/// #[no_main]
|
||||
/// fn a() {
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn pulpimulpi() {}
|
||||
|
||||
// Without a `#[no_main]` attribute
|
||||
/// ```rust
|
||||
/// fn a() {
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn plumilupi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Additional function, shouldn't trigger
|
||||
/// ```rust
|
||||
/// fn additional_function() {
|
||||
/// let _ = 0;
|
||||
/// // Thus `fn main` is actually relevant!
|
||||
/// }
|
||||
/// fn main() {
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn mlupipupi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Additional function AFTER main, shouldn't trigger
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// fn additional_function() {
|
||||
/// let _ = 0;
|
||||
/// // Thus `fn main` is actually relevant!
|
||||
/// }
|
||||
/// ```
|
||||
fn lumpimupli() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Ignore code block, should not lint at all
|
||||
/// ```rust, ignore
|
||||
/// fn main() {
|
||||
//~^ error: needless `fn main` in doctest
|
||||
/// // Hi!
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn mpulpilumi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Spaces in weird positions (including an \u{A0} after `main`)
|
||||
/// ```rust
|
||||
/// fn main (){
|
||||
//~^ error: needless `fn main` in doctest
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn plumpiplupi() {}
|
||||
|
||||
/// 4 Functions, this should not lint because there are several function
|
||||
///
|
||||
/// ```rust
|
||||
/// fn a() {let _ = 0; }
|
||||
/// fn b() {let _ = 0; }
|
||||
/// fn main() { let _ = 0; }
|
||||
/// fn d() { let _ = 0; }
|
||||
/// ```
|
||||
fn pulmipulmip() {}
|
||||
|
||||
/// 3 Functions but main is first, should also not lint
|
||||
///
|
||||
///```rust
|
||||
/// fn main() { let _ = 0; }
|
||||
/// fn b() { let _ = 0; }
|
||||
/// fn c() { let _ = 0; }
|
||||
/// ```
|
||||
fn pmuplimulip() {}
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn issue8244() -> Result<(), ()> {
|
||||
//! ```compile_fail
|
||||
//! fn test() -> Result< {}
|
||||
//! ```
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::error::Error;
|
||||
/// fn main() -> Result<(), Box<dyn Error>/* > */ {
|
||||
/// }
|
||||
/// ```
|
||||
fn issue15041() {}
|
||||
|
|
|
|||
36
src/tools/clippy/tests/ui/doc/needless_doctest_main.stderr
Normal file
36
src/tools/clippy/tests/ui/doc/needless_doctest_main.stderr
Normal file
|
|
@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue