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:
bors 2025-06-27 20:07:50 +00:00
commit bdaba05a95
157 changed files with 3179 additions and 855 deletions

View file

@ -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());

View file

@ -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

View file

@ -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)*

View 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\"}"

View file

@ -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

View file

@ -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 }

View file

@ -1,5 +1,9 @@
# Clippy
[### IMPORTANT NOTE FOR CONTRIBUTORS ================](development/feature_freeze.md)
----
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license)
A collection of lints to catch common mistakes and improve your

View file

@ -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)

View file

@ -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.

View 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

View file

@ -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)

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.89"
version = "0.1.90"
edition = "2024"
publish = false

View file

@ -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

View file

@ -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(),
);

View file

@ -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",
];

View file

@ -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()

View file

@ -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);
}

View file

@ -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>,

View file

@ -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"

View file

@ -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() {

View file

@ -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.

View file

@ -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),

View file

@ -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))
}

View file

@ -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.

View file

@ -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()
}

View file

@ -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
}

View file

@ -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)?
}
};
}

View file

@ -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,

View file

@ -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

View file

@ -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 },
/// ]
/// ```
///

View file

@ -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 }
/// ]
/// ```
///

View 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",
);
}

View file

@ -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) {

View file

@ -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,

View file

@ -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();

View file

@ -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,
});
}

View file

@ -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| {

View file

@ -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(),

View file

@ -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;

View file

@ -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 {

View file

@ -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`",
),

View file

@ -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,

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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

View file

@ -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 => " { .. }",
}
)
};

View file

@ -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"]

View file

@ -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;

View file

@ -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};

View file

@ -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);

View file

@ -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;

View file

@ -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,
}
})

View file

@ -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

View file

@ -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() {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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};

View file

@ -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;

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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, &macro_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, &macro_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
}
}

View file

@ -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"

View file

@ -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 -->

View file

@ -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)
}

View file

@ -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`

View file

@ -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

View file

@ -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 }

View file

@ -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,

View file

@ -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};

View file

@ -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,

View file

@ -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;
}

View 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

View 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)*
}
};
}

View file

@ -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 {

View file

@ -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"

View file

@ -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);
}));

View file

@ -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()

View file

@ -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,
}
}

View file

@ -40,6 +40,7 @@ fn dogfood() {
"clippy_lints",
"clippy_utils",
"clippy_config",
"declare_clippy_lint",
"lintcheck",
"rustc_tools_util",
] {

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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)
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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
};
}

View file

@ -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

View file

@ -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!();
}
}
}

View file

@ -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!();
}
}
}

View file

@ -154,3 +154,12 @@ fn issue14722() {
None
};
}
fn issue14799() {
if true {
#[cfg(target_os = "freebsd")]
todo!();
if true {}
};
}

View file

@ -164,3 +164,12 @@ fn issue14722() {
None
};
}
fn issue14799() {
if true {
#[cfg(target_os = "freebsd")]
todo!();
if true {}
};
}

View file

@ -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() {}

View 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