1
Fork 0

Merge commit '1e5237f4a5' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-03-20 22:34:29 +01:00
commit ae31f7a024
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
216 changed files with 4613 additions and 1146 deletions

View file

@ -27,6 +27,8 @@ jobs:
host: x86_64-pc-windows-msvc
- os: macos-13
host: x86_64-apple-darwin
- os: macos-latest
host: aarch64-apple-darwin
runs-on: ${{ matrix.os }}

View file

@ -37,6 +37,7 @@ jobs:
- name: Linkcheck book
run: |
rustup toolchain install nightly --component rust-docs
rustup override set nightly
curl https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh -o linkcheck.sh
sh linkcheck.sh clippy --path ./book

View file

@ -5570,6 +5570,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_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
[`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code
@ -6372,6 +6373,7 @@ Released 2018-09-13
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
[`module-items-ordered-within-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groupings
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior

View file

@ -1,6 +1,6 @@
// REUSE-IgnoreStart
Copyright 2014-2024 The Rust Project Developers
Copyright 2014-2025 The Rust Project Developers
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license

View file

@ -33,7 +33,7 @@ anstream = "0.6.18"
[dev-dependencies]
cargo_metadata = "0.18.1"
ui_test = "0.26.4"
ui_test = "0.29.2"
regex = "1.5.5"
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.122"

View file

@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014-2024 The Rust Project Developers
Copyright 2014-2025 The Rust Project Developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2014-2024 The Rust Project Developers
Copyright (c) 2014-2025 The Rust Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated

View file

@ -277,7 +277,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT
<!-- REUSE-IgnoreStart -->
Copyright 2014-2024 The Rust Project Developers
Copyright 2014-2025 The Rust Project Developers
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license

View file

@ -150,9 +150,85 @@ if foo_span.in_external_macro(cx.sess().source_map()) {
}
```
### The `is_from_proc_macro` function
A common point of confusion is the existence of [`is_from_proc_macro`]
and how it differs from the other [`in_external_macro`]/[`from_expansion`] functions.
While [`in_external_macro`] and [`from_expansion`] both work perfectly fine for detecting expanded code
from *declarative* macros (i.e. `macro_rules!` and macros 2.0),
detecting *proc macro*-generated code is a bit more tricky, as proc macros can (and often do)
freely manipulate the span of returned tokens.
In practice, this often happens through the use of [`quote::quote_spanned!`] with a span from the input tokens.
In those cases, there is no *reliable* way for the compiler (and tools like Clippy)
to distinguish code that comes from such a proc macro from code that the user wrote directly,
and [`in_external_macro`] will return `false`.
This is usually not an issue for the compiler and actually helps proc macro authors create better error messages,
as it allows associating parts of the expansion with parts of the macro input and lets the compiler
point the user to the relevant code in case of a compile error.
However, for Clippy this is inconvenient, because most of the time *we don't* want
to lint proc macro-generated code and this makes it impossible to tell what is and isn't proc macro code.
> NOTE: this is specifically only an issue when a proc macro explicitly sets the span to that of an **input span**.
>
> For example, other common ways of creating `TokenStream`s, such as `"fn foo() {...}".parse::<TokenStream>()`,
> sets each token's span to `Span::call_site()`, which already marks the span as coming from a proc macro
> and the usual span methods have no problem detecting that as a macro span.
As such, Clippy has its own `is_from_proc_macro` function which tries to *approximate*
whether a span comes from a proc macro, by checking whether the source text at the given span
lines up with the given AST node.
This function is typically used in combination with the other mentioned macro span functions,
but is usually called much later into the condition chain as it's a bit heavier than most other conditions,
so that the other cheaper conditions can fail faster. For example, the `borrow_deref_ref` lint:
```rs
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
if let ... = ...
&& ...
&& !e.span.from_expansion()
&& ...
&& ...
&& !is_from_proc_macro(cx, e)
&& ...
{
...
}
}
}
```
### Testing lints with macro expansions
To test that all of these cases are handled correctly in your lint,
we have a helper auxiliary crate that exposes various macros, used by tests like so:
```rust
//@aux-build:proc_macros.rs
extern crate proc_macros;
fn main() {
proc_macros::external!{ code_that_should_trigger_your_lint }
proc_macros::with_span!{ span code_that_should_trigger_your_lint }
}
```
This exercises two cases:
- `proc_macros::external!` is a simple proc macro that echos the input tokens back but with a macro span:
this represents the usual, common case where an external macro expands to code that your lint would trigger,
and is correctly handled by `in_external_macro` and `Span::from_expansion`.
- `proc_macros::with_span!` echos back the input tokens starting from the second token
with the span of the first token: this is where the other functions will fail and `is_from_proc_macro` is needed
[`ctxt`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.ctxt
[expansion]: https://rustc-dev-guide.rust-lang.org/macro-expansion.html#expansion-and-ast-integration
[`from_expansion`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
[`in_external_macro`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.in_external_macro
[Span]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
[SyntaxContext]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html
[`is_from_proc_macro`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.is_from_proc_macro.html
[`quote::quote_spanned!`]: https://docs.rs/quote/latest/quote/macro.quote_spanned.html

View file

@ -102,8 +102,7 @@ is responsible for maintaining Clippy.
5. **Update the changelog**
This needs to be done for every release, every six weeks. This is usually
done by @xFrednet.
This needs to be done for every release, every six weeks.
### Membership

View file

@ -744,6 +744,19 @@ The named groupings of different source item kinds within modules.
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
## `module-items-ordered-within-groupings`
Whether the items within module groups should be ordered alphabetically or not.
This option can be configured to "all", "none", or a list of specific grouping names that should be checked
(e.g. only "enums").
**Default Value:** `"none"`
---
**Affected lints:**
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
## `msrv`
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
@ -806,6 +819,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or)
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
* [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark)
* [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
* [`redundant_static_lifetimes`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes)
* [`repeat_vec_with_capacity`](https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity)

View file

@ -3,14 +3,17 @@ use crate::types::{
DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
SourceItemOrderingWithinModuleItemGroupings,
};
use clippy_utils::msrvs::Msrv;
use itertools::Itertools;
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::edit_distance::edit_distance;
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Range;
use std::path::PathBuf;
@ -79,6 +82,7 @@ const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
#[derive(Default)]
struct TryConf {
conf: Conf,
value_spans: HashMap<String, Range<usize>>,
errors: Vec<ConfError>,
warnings: Vec<ConfError>,
}
@ -87,6 +91,7 @@ impl TryConf {
fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
Self {
conf: Conf::default(),
value_spans: HashMap::default(),
errors: vec![ConfError::from_toml(file, error)],
warnings: vec![],
}
@ -210,6 +215,7 @@ macro_rules! define_Conf {
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
let mut value_spans = HashMap::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
$(let mut $name = None;)*
@ -232,6 +238,7 @@ macro_rules! define_Conf {
}
None => {
$name = Some(value);
value_spans.insert(name.get_ref().as_str().to_string(), value_span);
// $new_conf is the same as one of the defined `$name`s, so
// this variable is defined in line 2 of this function.
$(match $new_conf {
@ -250,7 +257,7 @@ macro_rules! define_Conf {
}
}
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
Ok(TryConf { conf, errors, warnings })
Ok(TryConf { conf, value_spans, errors, warnings })
}
}
@ -596,6 +603,13 @@ define_Conf! {
/// The named groupings of different source item kinds within modules.
#[lints(arbitrary_source_item_ordering)]
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
/// Whether the items within module groups should be ordered alphabetically or not.
///
/// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
/// (e.g. only "enums").
#[lints(arbitrary_source_item_ordering)]
module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
SourceItemOrderingWithinModuleItemGroupings::None,
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = "current version"]
#[lints(
@ -654,6 +668,7 @@ define_Conf! {
option_as_ref_deref,
option_map_unwrap_or,
ptr_as_ptr,
question_mark,
redundant_field_names,
redundant_static_lifetimes,
repeat_vec_with_capacity,
@ -815,6 +830,36 @@ fn deserialize(file: &SourceFile) -> TryConf {
&mut conf.conf.allow_renamed_params_for,
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
);
// Confirms that the user has not accidentally configured ordering requirements for groups that
// aren't configured.
if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
&conf.conf.module_items_ordered_within_groupings
{
for grouping in groupings {
if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
// Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
// info for the user instead.
let names = conf.conf.module_item_order_groupings.grouping_names();
let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
.map(|s| format!(" perhaps you meant `{s}`?"))
.unwrap_or_default();
let names = names.iter().map(|s| format!("`{s}`")).join(", ");
let message = format!(
"unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
);
let span = conf
.value_spans
.get("module_item_order_groupings")
.cloned()
.unwrap_or_default();
conf.errors.push(ConfError::spanned(file, message, None, span));
}
}
}
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
conf.conf
@ -860,6 +905,7 @@ impl Conf {
let TryConf {
mut conf,
value_spans: _,
errors,
warnings,
} = match path {
@ -950,17 +996,10 @@ impl serde::de::Error for FieldError {
}
}
let suggestion = expected
.iter()
.filter_map(|expected| {
let dist = edit_distance(field, expected, 4)?;
Some((dist, expected))
})
.min_by_key(|&(dist, _)| dist)
.map(|(_, suggestion)| Suggestion {
message: "perhaps you meant",
suggestion,
});
let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
message: "perhaps you meant",
suggestion,
});
Self { error: msg, suggestion }
}
@ -998,6 +1037,22 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
(rows, column_widths)
}
/// Given a user-provided value that couldn't be matched to a known option, finds the most likely
/// candidate among candidates that the user might have meant.
fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
where
I: IntoIterator<Item = &'a str>,
{
candidates
.into_iter()
.filter_map(|expected| {
let dist = edit_distance(value, expected, 4)?;
Some((dist, expected))
})
.min_by_key(|&(dist, _)| dist)
.map(|(_, suggestion)| suggestion)
}
#[cfg(test)]
mod tests {
use serde::de::IgnoredAny;

View file

@ -305,6 +305,7 @@ impl SourceItemOrderingModuleItemKind {
pub struct SourceItemOrderingModuleItemGroupings {
groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
back_lut: HashMap<SourceItemOrderingModuleItemKind, String>,
}
impl SourceItemOrderingModuleItemGroupings {
@ -320,6 +321,30 @@ impl SourceItemOrderingModuleItemGroupings {
lut
}
fn build_back_lut(
groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
) -> HashMap<SourceItemOrderingModuleItemKind, String> {
let mut lut = HashMap::new();
for (group_name, items) in groups {
for item in items {
lut.insert(item.clone(), group_name.clone());
}
}
lut
}
pub fn grouping_name_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<&String> {
self.back_lut.get(item)
}
pub fn grouping_names(&self) -> Vec<String> {
self.groups.iter().map(|(name, _)| name.clone()).collect()
}
pub fn is_grouping(&self, grouping: &str) -> bool {
self.groups.iter().any(|(g, _)| g == grouping)
}
pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<usize> {
self.lut.get(item).copied()
}
@ -330,7 +355,8 @@ impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrdering
let groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)> =
value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect();
let lut = Self::build_lut(&groups);
Self { groups, lut }
let back_lut = Self::build_back_lut(&groups);
Self { groups, lut, back_lut }
}
}
@ -348,6 +374,7 @@ impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
let groups = Vec::<(String, Vec<SourceItemOrderingModuleItemKind>)>::deserialize(deserializer)?;
let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
let lut = Self::build_lut(&groups);
let back_lut = Self::build_back_lut(&groups);
let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
for item in lut.keys() {
@ -370,7 +397,7 @@ impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
));
}
Ok(Self { groups, lut })
Ok(Self { groups, lut, back_lut })
} else if items_total != all_items.len() {
Err(de::Error::custom(format!(
"Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
@ -482,6 +509,83 @@ impl Serialize for SourceItemOrderingTraitAssocItemKinds {
}
}
/// Describes which specific groupings should have their items ordered
/// alphabetically.
///
/// This is separate from defining and enforcing groupings. For example,
/// defining enums are grouped before structs still allows for an enum B to be
/// placed before an enum A. Only when enforcing ordering within the grouping,
/// will it be checked if A is placed before B.
#[derive(Clone, Debug)]
pub enum SourceItemOrderingWithinModuleItemGroupings {
/// All groupings should have their items ordered.
All,
/// None of the groupings should have their order checked.
None,
/// Only the specified groupings should have their order checked.
Custom(Vec<String>),
}
impl SourceItemOrderingWithinModuleItemGroupings {
pub fn ordered_within(&self, grouping_name: &String) -> bool {
match self {
SourceItemOrderingWithinModuleItemGroupings::All => true,
SourceItemOrderingWithinModuleItemGroupings::None => false,
SourceItemOrderingWithinModuleItemGroupings::Custom(groups) => groups.contains(grouping_name),
}
}
}
/// Helper struct for deserializing the [`SourceItemOrderingWithinModuleItemGroupings`].
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVecOfString {
String(String),
Vec(Vec<String>),
}
impl<'de> Deserialize<'de> for SourceItemOrderingWithinModuleItemGroupings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let description = "The available options for configuring an ordering within module item groups are: \
\"all\", \"none\", or a list of module item group names \
(as configured with the `module-item-order-groupings` configuration option).";
match StringOrVecOfString::deserialize(deserializer) {
Ok(StringOrVecOfString::String(preset)) if preset == "all" => {
Ok(SourceItemOrderingWithinModuleItemGroupings::All)
},
Ok(StringOrVecOfString::String(preset)) if preset == "none" => {
Ok(SourceItemOrderingWithinModuleItemGroupings::None)
},
Ok(StringOrVecOfString::String(preset)) => Err(de::Error::custom(format!(
"Unknown configuration option: {preset}.\n{description}"
))),
Ok(StringOrVecOfString::Vec(groupings)) => {
Ok(SourceItemOrderingWithinModuleItemGroupings::Custom(groupings))
},
Err(e) => Err(de::Error::custom(format!("{e}\n{description}"))),
}
}
}
impl Serialize for SourceItemOrderingWithinModuleItemGroupings {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
match self {
SourceItemOrderingWithinModuleItemGroupings::All => serializer.serialize_str("all"),
SourceItemOrderingWithinModuleItemGroupings::None => serializer.serialize_str("none"),
SourceItemOrderingWithinModuleItemGroupings::Custom(vec) => vec.serialize(serializer),
}
}
}
// these impls are never actually called but are used by the various config options that default to
// empty lists
macro_rules! unimplemented_serialize {

View file

@ -2,11 +2,12 @@ use clippy_config::Conf;
use clippy_config::types::{
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
SourceItemOrderingWithinModuleItemGroupings,
};
use clippy_utils::diagnostics::span_lint_and_note;
use rustc_hir::{
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind,
Variant, VariantData,
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant,
VariantData,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
@ -36,7 +37,7 @@ declare_clippy_lint! {
/// 2. Individual ordering rules per item kind.
///
/// The item kinds that can be linted are:
/// - Module (with customized groupings, alphabetical within)
/// - Module (with customized groupings, alphabetical within - configurable)
/// - Trait (with customized order of associated items, alphabetical within)
/// - Enum, Impl, Struct (purely alphabetical)
///
@ -57,8 +58,31 @@ declare_clippy_lint! {
/// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" |
/// | `lower_snake_case` | "fn" |
///
/// The groups' names are arbitrary and can be changed to suit the
/// conventions that should be enforced for a specific project.
///
/// All item kinds must be accounted for to create an enforceable linting
/// rule set.
/// rule set. Following are some example configurations that may be useful.
///
/// Example: *module inclusions and use statements to be at the top*
///
/// ```toml
/// module-item-order-groupings = [
/// [ "modules", [ "extern_crate", "mod", "foreign_mod" ], ],
/// [ "use", [ "use", ], ],
/// [ "everything_else", [ "macro", "global_asm", "static", "const", "ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl", "fn", ], ],
/// ]
/// ```
///
/// Example: *only consts and statics should be alphabetically ordered*
///
/// It is also possible to configure a selection of module item groups that
/// should be ordered alphabetically. This may be useful if for example
/// statics and consts should be ordered, but the rest should be left open.
///
/// ```toml
/// module-items-ordered-within-groupings = ["UPPER_SNAKE_CASE"]
/// ```
///
/// ### Known Problems
///
@ -143,6 +167,7 @@ pub struct ArbitrarySourceItemOrdering {
enable_ordering_for_struct: bool,
enable_ordering_for_trait: bool,
module_item_order_groupings: SourceItemOrderingModuleItemGroupings,
module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings,
}
impl ArbitrarySourceItemOrdering {
@ -157,6 +182,7 @@ impl ArbitrarySourceItemOrdering {
enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct),
enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait),
module_item_order_groupings: conf.module_item_order_groupings.clone(),
module_items_ordered_within_groupings: conf.module_items_ordered_within_groupings.clone(),
}
}
@ -176,11 +202,7 @@ impl ArbitrarySourceItemOrdering {
}
/// Produces a linting warning for incorrectly ordered item members.
fn lint_member_name<T: LintContext>(
cx: &T,
ident: &rustc_span::Ident,
before_ident: &rustc_span::Ident,
) {
fn lint_member_name<T: LintContext>(cx: &T, ident: &rustc_span::Ident, before_ident: &rustc_span::Ident) {
span_lint_and_note(
cx,
ARBITRARY_SOURCE_ITEM_ORDERING,
@ -191,7 +213,7 @@ impl ArbitrarySourceItemOrdering {
);
}
fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>) {
fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>, msg: &'static str) {
let span = if let Some(ident) = item.kind.ident() {
ident.span
} else {
@ -199,10 +221,7 @@ impl ArbitrarySourceItemOrdering {
};
let (before_span, note) = if let Some(ident) = before_item.kind.ident() {
(
ident.span,
format!("should be placed before `{}`", ident.as_str(),),
)
(ident.span, format!("should be placed before `{}`", ident.as_str(),))
} else {
(
before_item.span,
@ -215,14 +234,7 @@ impl ArbitrarySourceItemOrdering {
return;
}
span_lint_and_note(
cx,
ARBITRARY_SOURCE_ITEM_ORDERING,
span,
"incorrect ordering of items (must be alphabetically ordered)",
Some(before_span),
note,
);
span_lint_and_note(cx, ARBITRARY_SOURCE_ITEM_ORDERING, span, msg, Some(before_span), note);
}
/// Produces a linting warning for incorrectly ordered trait items.
@ -376,6 +388,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
}
let item_kind = convert_module_item_kind(&item.kind);
let grouping_name = self.module_item_order_groupings.grouping_name_of(&item_kind);
let module_level_order = self
.module_item_order_groupings
.module_level_order_of(&item_kind)
@ -385,13 +398,27 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
use std::cmp::Ordering; // Better legibility.
match module_level_order.cmp(&cur_t.order) {
Ordering::Less => {
Self::lint_member_item(cx, item, cur_t.item);
Self::lint_member_item(
cx,
item,
cur_t.item,
"incorrect ordering of items (module item groupings specify another order)",
);
},
Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => {
// Skip ordering use statements, as these should be ordered by rustfmt.
},
Ordering::Equal if cur_t.name > get_item_name(item) => {
Self::lint_member_item(cx, item, cur_t.item);
Ordering::Equal
if (grouping_name.is_some_and(|grouping_name| {
self.module_items_ordered_within_groupings.ordered_within(grouping_name)
}) && cur_t.name > get_item_name(item)) =>
{
Self::lint_member_item(
cx,
item,
cur_t.item,
"incorrect ordering of items (must be alphabetically ordered)",
);
},
Ordering::Equal | Ordering::Greater => {
// Nothing to do in this case, they're already in the right order.
@ -501,6 +528,12 @@ fn get_item_name(item: &Item<'_>) -> String {
},
// FIXME: `Ident::empty` for anonymous items is a bit strange, is there
// a better way to do it?
_ => item.kind.ident().unwrap_or(rustc_span::Ident::empty()).name.as_str().to_owned(),
_ => item
.kind
.ident()
.unwrap_or(rustc_span::Ident::empty())
.name
.as_str()
.to_owned(),
}
}

View file

@ -466,7 +466,9 @@ impl Attributes {
impl<'tcx> LateLintPass<'tcx> for Attributes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let attrs = cx.tcx.hir_attrs(item.hir_id());
if let ItemKind::Fn { ident, .. } = item.kind && is_relevant_item(cx, item) {
if let ItemKind::Fn { ident, .. } = item.kind
&& is_relevant_item(cx, item)
{
inline_always::check(cx, item.span, ident.name, attrs);
}
repr_attributes::check(cx, item.span, attrs, self.msrv);

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_block_with_applicability;
use clippy_utils::{higher, is_from_proc_macro};
use clippy_utils::{contains_return, higher, is_from_proc_macro};
use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -86,6 +86,13 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
if expr.span.from_expansion() || ex.span.from_expansion() {
return;
}
// Linting should not be triggered to cases where `return` is included in the condition.
// #9911
if contains_return(block.expr) {
return;
}
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,

View file

@ -2,11 +2,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::{is_lint_allowed, msrvs, std_or_core};
use clippy_utils::{is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::BytePos;
use super::BORROW_AS_PTR;
@ -25,12 +24,7 @@ pub(super) fn check<'tcx>(
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
// Fix #9884
if !e.is_place_expr(|base| {
cx.typeck_results()
.adjustments()
.get(base.hir_id)
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
}) {
if is_expr_temporary_value(cx, e) {
return false;
}

View file

@ -53,7 +53,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.40.0"]
pub COMPARISON_CHAIN,
style,
pedantic,
"`if`s that can be rewritten with `match` and `cmp`"
}

View file

@ -137,6 +137,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_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO,
crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
crate::doc::DOC_LAZY_CONTINUATION_INFO,
crate::doc::DOC_LINK_CODE_INFO,
@ -614,7 +615,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::operators::MODULO_ONE_INFO,
crate::operators::NEEDLESS_BITWISE_BOOL_INFO,
crate::operators::OP_REF_INFO,
crate::operators::PTR_EQ_INFO,
crate::operators::REDUNDANT_COMPARISONS_INFO,
crate::operators::SELF_ASSIGNMENT_INFO,
crate::operators::VERBOSE_BIT_MASK_INFO,
@ -641,6 +641,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::ptr::INVALID_NULL_PTR_USAGE_INFO,
crate::ptr::MUT_FROM_REF_INFO,
crate::ptr::PTR_ARG_INFO,
crate::ptr::PTR_EQ_INFO,
crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO,
crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO,
crate::pub_use::PUB_USE_INFO,

View file

@ -0,0 +1,33 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_span::{BytePos, Span};
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS;
pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
if collected_breaks.is_empty() {
return;
}
let breaks: Vec<_> = collected_breaks
.iter()
.map(|span| span.with_hi(span.lo() + BytePos(2)))
.collect();
span_lint_and_then(
cx,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
breaks.clone(),
"doc comment uses two spaces for a hard line break",
|diag| {
let suggs: Vec<_> = breaks.iter().map(|span| (*span, "\\".to_string())).collect();
diag.tool_only_multipart_suggestion(
"replace this double space with a backslash:",
suggs,
Applicability::MachineApplicable,
);
diag.help("replace this double space with a backslash: `\\`");
},
);
}

View file

@ -1,12 +1,10 @@
#![allow(clippy::lint_without_lint_pass)]
mod lazy_continuation;
mod too_long_first_doc_paragraph;
use clippy_config::Conf;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable;
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@ -33,12 +31,15 @@ use rustc_span::{Span, sym};
use std::ops::Range;
use url::Url;
mod doc_comment_double_space_linebreaks;
mod include_in_doc_without_cfg;
mod lazy_continuation;
mod link_with_quotes;
mod markdown;
mod missing_headers;
mod needless_doctest_main;
mod suspicious_doc_comments;
mod too_long_first_doc_paragraph;
declare_clippy_lint! {
/// ### What it does
@ -567,6 +568,39 @@ declare_clippy_lint! {
"link reference defined in list item or quote"
}
declare_clippy_lint! {
/// ### What it does
/// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (`\`).
///
/// ### Why is this bad?
/// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
/// accidentally be removed during automatic formatting or manual refactoring. The use of a back-slash (`\`)
/// is clearer in this regard.
///
/// ### Example
/// The two replacement dots in this example represent a double space.
/// ```no_run
/// /// This command takes two numbers as inputs and··
/// /// adds them together, and then returns the result.
/// fn add(l: i32, r: i32) -> i32 {
/// l + r
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// /// This command takes two numbers as inputs and\
/// /// adds them together, and then returns the result.
/// fn add(l: i32, r: i32) -> i32 {
/// l + r
/// }
/// ```
#[clippy::version = "1.87.0"]
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
pedantic,
"double-space used for doc comment linebreak instead of `\\`"
}
pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
@ -598,6 +632,7 @@ impl_lint_pass!(Documentation => [
DOC_OVERINDENTED_LIST_ITEMS,
TOO_LONG_FIRST_DOC_PARAGRAPH,
DOC_INCLUDE_WITHOUT_CFG,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS
]);
impl EarlyLintPass for Documentation {
@ -894,6 +929,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut paragraph_range = 0..0;
let mut code_level = 0;
let mut blockquote_level = 0;
let mut collected_breaks: Vec<Span> = Vec::new();
let mut is_first_paragraph = true;
let mut containers = Vec::new();
@ -1010,11 +1046,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
start -= 1;
}
if start > range.start {
start - range.start
} else {
0
}
start.saturating_sub(range.start)
}
} else {
0
@ -1073,6 +1105,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
&containers[..],
);
}
if let Some(span) = fragments.span(cx, range.clone())
&& !span.from_expansion()
&& let Some(snippet) = snippet_opt(cx, span)
&& !snippet.trim().starts_with('\\')
&& event == HardBreak {
collected_breaks.push(span);
}
},
Text(text) => {
paragraph_range.end = range.end;
@ -1123,6 +1163,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
FootnoteReference(_) => {}
}
}
doc_comment_double_space_linebreaks::check(cx, &collected_breaks);
headers
}

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
SpanlessEq, can_move_expr_to_closure_no_visit, higher, is_expr_final_block_expr, is_expr_used_or_unified,
@ -84,14 +85,21 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
return;
};
let lint_msg = format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name());
let mut app = Applicability::MachineApplicable;
let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
let sugg = if let Some(else_expr) = else_expr {
let Some(else_search) = find_insert_calls(cx, &contains_expr, else_expr) else {
return;
};
if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
return;
}
if then_search.edits.is_empty() && else_search.edits.is_empty() {
// No insertions
return;
@ -184,15 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
}
};
span_lint_and_sugg(
cx,
MAP_ENTRY,
expr.span,
format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name()),
"try",
sugg,
app,
);
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
}
}
@ -354,6 +354,8 @@ struct InsertSearcher<'cx, 'tcx> {
key: &'tcx Expr<'tcx>,
/// The context of the top level block. All insert calls must be in the same context.
ctxt: SyntaxContext,
/// The spanless equality utility used to compare expressions.
spanless_eq: SpanlessEq<'cx, 'tcx>,
/// Whether this expression can be safely moved into a closure.
allow_insert_closure: bool,
/// Whether this expression can use the entry api.
@ -364,6 +366,8 @@ struct InsertSearcher<'cx, 'tcx> {
is_single_insert: bool,
/// If the visitor has seen the map being used.
is_map_used: bool,
/// If the visitor has seen the key being used.
is_key_used: bool,
/// The locations where changes need to be made for the suggestion.
edits: Vec<Edit<'tcx>>,
/// A stack of loops the visitor is currently in.
@ -479,11 +483,11 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
}
match try_parse_insert(self.cx, expr) {
Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
Some(insert_expr) if self.spanless_eq.eq_expr(self.map, insert_expr.map) => {
self.visit_insert_expr_arguments(&insert_expr);
// Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
if self.is_map_used
|| !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)
|| !self.spanless_eq.eq_expr(self.key, insert_expr.key)
|| expr.span.ctxt() != self.ctxt
{
self.can_use_entry = false;
@ -502,9 +506,12 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
self.visit_non_tail_expr(insert_expr.value);
self.is_single_insert = is_single_insert;
},
_ if is_any_expr_in_map_used(self.cx, self.map, expr) => {
_ if is_any_expr_in_map_used(self.cx, &mut self.spanless_eq, self.map, expr) => {
self.is_map_used = true;
},
_ if self.spanless_eq.eq_expr(self.key, expr) => {
self.is_key_used = true;
},
_ => match expr.kind {
ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
self.is_single_insert = false;
@ -568,9 +575,14 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
/// Check if the given expression is used for each sub-expression in the given map.
/// For example, in map `a.b.c.my_map`, The expression `a.b.c.my_map`, `a.b.c`, `a.b`, and `a` are
/// all checked.
fn is_any_expr_in_map_used<'tcx>(cx: &LateContext<'tcx>, map: &'tcx Expr<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
fn is_any_expr_in_map_used<'tcx>(
cx: &LateContext<'tcx>,
spanless_eq: &mut SpanlessEq<'_, 'tcx>,
map: &'tcx Expr<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> bool {
for_each_expr(cx, map, |e| {
if SpanlessEq::new(cx).eq_expr(e, expr) {
if spanless_eq.eq_expr(e, expr) {
return ControlFlow::Break(());
}
ControlFlow::Continue(())
@ -582,6 +594,7 @@ struct InsertSearchResults<'tcx> {
edits: Vec<Edit<'tcx>>,
allow_insert_closure: bool,
is_single_insert: bool,
is_key_used_and_no_copy: bool,
}
impl<'tcx> InsertSearchResults<'tcx> {
fn as_single_insertion(&self) -> Option<Insertion<'tcx>> {
@ -694,11 +707,13 @@ fn find_insert_calls<'tcx>(
map: contains_expr.map,
key: contains_expr.key,
ctxt: expr.span.ctxt(),
spanless_eq: SpanlessEq::new(cx),
allow_insert_closure: true,
can_use_entry: true,
in_tail_pos: true,
is_single_insert: true,
is_map_used: false,
is_key_used: false,
edits: Vec::new(),
loops: Vec::new(),
locals: HirIdSet::default(),
@ -706,10 +721,12 @@ fn find_insert_calls<'tcx>(
s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure;
let is_single_insert = s.is_single_insert;
let is_key_used_and_no_copy = s.is_key_used && !is_copy(cx, cx.typeck_results().expr_ty(contains_expr.key));
let edits = s.edits;
s.can_use_entry.then_some(InsertSearchResults {
edits,
allow_insert_closure,
is_single_insert,
is_key_used_and_no_copy,
})
}

View file

@ -162,25 +162,23 @@ impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
}
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
if cmt.place.projections.is_empty() {
if is_argument(self.cx.tcx, cmt.hir_id) {
// Skip closure arguments
let parent_id = self.cx.tcx.parent_hir_id(cmt.hir_id);
if let Node::Expr(..) = self.cx.tcx.parent_hir_node(parent_id) {
if cmt.place.projections.is_empty() && is_argument(self.cx.tcx, cmt.hir_id) {
// Skip closure arguments
let parent_id = self.cx.tcx.parent_hir_id(cmt.hir_id);
if let Node::Expr(..) = self.cx.tcx.parent_hir_node(parent_id) {
return;
}
// skip if there is a `self` parameter binding to a type
// that contains `Self` (i.e.: `self: Box<Self>`), see #4804
if let Some(trait_self_ty) = self.trait_self_ty {
if self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) {
return;
}
}
// skip if there is a `self` parameter binding to a type
// that contains `Self` (i.e.: `self: Box<Self>`), see #4804
if let Some(trait_self_ty) = self.trait_self_ty {
if self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) {
return;
}
}
if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
self.set.insert(cmt.hir_id);
}
if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
self.set.insert(cmt.hir_id);
}
}
}

View file

@ -15,12 +15,17 @@ declare_clippy_lint! {
/// use of bools in structs.
///
/// ### Why is this bad?
/// Excessive bools in a struct
/// is often a sign that it's used as a state machine,
/// which is much better implemented as an enum.
/// If it's not the case, excessive bools usually benefit
/// from refactoring into two-variant enums for better
/// readability and API.
/// Excessive bools in a struct is often a sign that
/// the type is being used to represent a state
/// machine, which is much better implemented as an
/// enum.
///
/// The reason an enum is better for state machines
/// over structs is that enums more easily forbid
/// invalid states.
///
/// Structs with too many booleans may benefit from refactoring
/// into multi variant enums for better readability and API.
///
/// ### Example
/// ```no_run

View file

@ -9,7 +9,7 @@ use clippy_utils::macros::{
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{SpanRangeExt, snippet};
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use clippy_utils::{is_diag_trait_item, is_from_proc_macro};
use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test};
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
@ -484,7 +484,8 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
fn check_unnecessary_debug_formatting(&self, name: Symbol, value: &Expr<'tcx>) {
let cx = self.cx;
if !value.span.from_expansion()
if !is_in_test(cx.tcx, value.hir_id)
&& !value.span.from_expansion()
&& !is_from_proc_macro(cx, value)
&& let ty = cx.typeck_results().expr_ty(value)
&& self.can_display_format(ty)

View file

@ -176,8 +176,8 @@ fn convert_to_from(
return None;
};
let body = cx.tcx.hir_body(body_id);
let [input] = body.params else { return None };
let PatKind::Binding(.., self_ident, None) = input.pat.kind else {
let [self_param] = body.params else { return None };
let PatKind::Binding(.., self_ident, None) = self_param.pat.kind else {
return None;
};
@ -194,12 +194,12 @@ fn convert_to_from(
// impl Into<T> for U -> impl Into<T> for T
// ~ ~
(self_ty.span, into.to_owned()),
// fn into(self) -> T -> fn from(self) -> T
// ~~~~ ~~~~
// fn into(self: U) -> T -> fn from(self) -> T
// ~~~~ ~~~~
(impl_item.ident.span, String::from("from")),
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
// fn into([mut] self: U) -> T -> fn into([mut] val: T) -> T
// ~~~~~~~ ~~~~~~
(self_ident.span.to(self_param.ty_span), format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {

View file

@ -17,15 +17,15 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// match std::fs::create_dir("tmp-work-dir") {
/// Ok(_) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// Ok(_) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// }
/// ```
/// Use instead:
/// ```no_run
/// match std::fs::create_dir("tmp-work-dir") {
/// Ok(()) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// Ok(()) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// }
/// ```
#[clippy::version = "1.73.0"]

View file

@ -1,14 +1,14 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::{
SpanlessEq, higher, is_in_const_context, is_integer_literal, path_to_local, peel_blocks, peel_blocks_with_stmt,
SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt,
};
use rustc_ast::ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, HirId, QPath};
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -170,22 +170,20 @@ fn check_gt(
cx: &LateContext<'_>,
condition_span: Span,
expr_span: Span,
big_var: &Expr<'_>,
little_var: &Expr<'_>,
big_expr: &Expr<'_>,
little_expr: &Expr<'_>,
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: Msrv,
is_composited: bool,
) {
if let Some(big_var) = Var::new(big_var)
&& let Some(little_var) = Var::new(little_var)
{
if is_side_effect_free(cx, big_expr) && is_side_effect_free(cx, little_expr) {
check_subtraction(
cx,
condition_span,
expr_span,
big_var,
little_var,
big_expr,
little_expr,
if_block,
else_block,
msrv,
@ -194,18 +192,8 @@ fn check_gt(
}
}
struct Var {
span: Span,
hir_id: HirId,
}
impl Var {
fn new(expr: &Expr<'_>) -> Option<Self> {
path_to_local(expr).map(|hir_id| Self {
span: expr.span,
hir_id,
})
}
fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
eq_expr_value(cx, expr, expr)
}
#[allow(clippy::too_many_arguments)]
@ -213,8 +201,8 @@ fn check_subtraction(
cx: &LateContext<'_>,
condition_span: Span,
expr_span: Span,
big_var: Var,
little_var: Var,
big_expr: &Expr<'_>,
little_expr: &Expr<'_>,
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: Msrv,
@ -234,8 +222,8 @@ fn check_subtraction(
cx,
condition_span,
expr_span,
little_var,
big_var,
little_expr,
big_expr,
else_block,
if_block,
msrv,
@ -247,17 +235,15 @@ fn check_subtraction(
&& let ExprKind::Binary(op, left, right) = if_block.kind
&& let BinOpKind::Sub = op.node
{
let local_left = path_to_local(left);
let local_right = path_to_local(right);
if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right {
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
&& (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
let sugg = format!(
"{}{big_var_snippet}.saturating_sub({little_var_snippet}){}",
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
if is_composited { "{ " } else { "" },
if is_composited { " }" } else { "" }
);
@ -271,11 +257,12 @@ fn check_subtraction(
Applicability::MachineApplicable,
);
}
} else if Some(little_var.hir_id) == local_left
&& Some(big_var.hir_id) == local_right
&& let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
} else if eq_expr_value(cx, left, little_expr)
&& eq_expr_value(cx, right, big_expr)
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
let sugg = make_binop(BinOpKind::Sub, &big_expr_sugg, &little_expr_sugg);
span_lint_and_then(
cx,
INVERTED_SATURATING_SUB,
@ -284,12 +271,12 @@ fn check_subtraction(
|diag| {
diag.span_note(
if_block.span,
format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"),
format!("this subtraction underflows when `{little_expr_sugg} < {big_expr_sugg}`"),
);
diag.span_suggestion(
if_block.span,
"try replacing it with",
format!("{big_var_snippet} - {little_var_snippet}"),
format!("{sugg}"),
Applicability::MaybeIncorrect,
);
},

View file

@ -4,12 +4,12 @@ use clippy_utils::is_in_test;
use clippy_utils::msrvs::Msrv;
use rustc_attr_parsing::{RustcVersion, StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind, HirId};
use rustc_hir::{Expr, ExprKind, HirId, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::{ExpnKind, Span};
use rustc_span::{ExpnKind, Span, sym};
declare_clippy_lint! {
/// ### What it does
@ -93,6 +93,21 @@ impl IncompatibleMsrv {
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
return;
}
// Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the
// macros may have existed prior to the checked MSRV, but their expansion with a recent compiler
// might use recent functions or methods. Compiling with an older compiler would not use those.
if span.from_expansion()
&& cx.tcx.crate_name(def_id.krate) == sym::core
&& span
.ctxt()
.outer_expn_data()
.macro_def_id
.is_some_and(|def_id| cx.tcx.crate_name(def_id.krate) == sym::core)
{
return;
}
if (self.check_in_tests || !is_in_test(cx.tcx, node))
&& let Some(current) = self.msrv.current(cx)
&& let version = self.get_def_id_version(cx.tcx, def_id)
@ -118,8 +133,11 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
}
},
ExprKind::Call(call, [_]) => {
if let ExprKind::Path(qpath) = call.kind
ExprKind::Call(call, _) => {
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should
// not be linted as they will not be generated in older compilers if the function is not available,
// and the compiler is allowed to call unstable functions.
if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = call.kind
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
{
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span);

View file

@ -32,11 +32,7 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind
&& let Some(attr) = cx
.tcx
.hir_attrs(item.hir_id())
.iter()
.find(|a| a.has_name(sym::inline))
&& let Some(attr) = cx.tcx.hir_attrs(item.hir_id()).iter().find(|a| a.has_name(sym::inline))
{
span_lint_and_then(
cx,

View file

@ -8,7 +8,6 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{Ident, Span};
use rustc_span::symbol::Symbol;
declare_clippy_lint! {
@ -196,80 +195,176 @@ fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
prefixes.iter().all(|p| p == &"" || p == &"_")
}
fn check_fields(cx: &LateContext<'_>, threshold: u64, ident: Ident, span: Span, fields: &[FieldDef<'_>]) {
if (fields.len() as u64) < threshold {
return;
}
check_struct_name_repetition(cx, ident, fields);
// if the SyntaxContext of the identifiers of the fields and struct differ dont lint them.
// this prevents linting in macros in which the location of the field identifier names differ
if !fields.iter().all(|field| ident.span.eq_ctxt(field.ident.span)) {
return;
}
let mut pre: Vec<&str> = match fields.first() {
Some(first_field) => first_field.ident.name.as_str().split('_').collect(),
None => return,
};
let mut post = pre.clone();
post.reverse();
for field in fields {
let field_split: Vec<&str> = field.ident.name.as_str().split('_').collect();
if field_split.len() == 1 {
impl ItemNameRepetitions {
/// Lint the names of enum variants against the name of the enum.
fn check_variants(&self, cx: &LateContext<'_>, item: &Item<'_>, def: &EnumDef<'_>) {
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id) {
return;
}
pre = pre
.into_iter()
.zip(field_split.iter())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
post = post
.into_iter()
.zip(field_split.iter().rev())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
}
let prefix = pre.join("_");
post.reverse();
let postfix = match post.last() {
Some(&"") => post.join("_") + "_",
Some(_) | None => post.join("_"),
};
if fields.len() > 1 {
let (what, value) = match (
prefix.is_empty() || prefix.chars().all(|c| c == '_'),
postfix.is_empty(),
) {
(true, true) => return,
(false, _) => ("pre", prefix),
(true, false) => ("post", postfix),
if (def.variants.len() as u64) < self.enum_threshold {
return;
}
let Some(ident) = item.kind.ident() else {
return;
};
if fields.iter().all(|field| is_bool(field.ty)) {
// If all fields are booleans, we don't want to emit this lint.
let item_name = ident.name.as_str();
for var in def.variants {
check_enum_start(cx, item_name, var);
check_enum_end(cx, item_name, var);
}
Self::check_enum_common_affix(cx, item, def);
}
/// Lint the names of struct fields against the name of the struct.
fn check_fields(&self, cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
if (fields.len() as u64) < self.struct_threshold {
return;
}
self.check_struct_name_repetition(cx, item, fields);
self.check_struct_common_affix(cx, item, fields);
}
fn check_enum_common_affix(cx: &LateContext<'_>, item: &Item<'_>, def: &EnumDef<'_>) {
let first = match def.variants.first() {
Some(variant) => variant.ident.name.as_str(),
None => return,
};
let mut pre = camel_case_split(first);
let mut post = pre.clone();
post.reverse();
for var in def.variants {
let name = var.ident.name.as_str();
let variant_split = camel_case_split(name);
if variant_split.len() == 1 {
return;
}
pre = pre
.iter()
.zip(variant_split.iter())
.take_while(|(a, b)| a == b)
.map(|e| *e.0)
.collect();
post = post
.iter()
.zip(variant_split.iter().rev())
.take_while(|(a, b)| a == b)
.map(|e| *e.0)
.collect();
}
let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) {
(true, true) => return,
(false, _) => ("pre", pre.join("")),
(true, false) => {
post.reverse();
("post", post.join(""))
},
};
span_lint_and_help(
cx,
STRUCT_FIELD_NAMES,
span,
format!("all fields have the same {what}fix: `{value}`"),
ENUM_VARIANT_NAMES,
item.span,
format!("all variants have the same {what}fix: `{value}`"),
None,
format!("remove the {what}fixes"),
format!(
"remove the {what}fixes and use full paths to \
the variants instead of glob imports"
),
);
}
}
fn check_struct_name_repetition(cx: &LateContext<'_>, ident: Ident, fields: &[FieldDef<'_>]) {
let snake_name = to_snake_case(ident.name.as_str());
let item_name_words: Vec<&str> = snake_name.split('_').collect();
for field in fields {
if field.ident.span.eq_ctxt(ident.span) {
//consider linting only if the field identifier has the same SyntaxContext as the item(struct)
fn check_struct_common_affix(&self, cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
// if the SyntaxContext of the identifiers of the fields and struct differ dont lint them.
// this prevents linting in macros in which the location of the field identifier names differ
if !fields
.iter()
.all(|field| item.kind.ident().is_some_and(|i| i.span.eq_ctxt(field.ident.span)))
{
return;
}
if self.avoid_breaking_exported_api
&& fields
.iter()
.any(|field| cx.effective_visibilities.is_exported(field.def_id))
{
return;
}
let mut pre: Vec<&str> = match fields.first() {
Some(first_field) => first_field.ident.name.as_str().split('_').collect(),
None => return,
};
let mut post = pre.clone();
post.reverse();
for field in fields {
let field_split: Vec<&str> = field.ident.name.as_str().split('_').collect();
if field_split.len() == 1 {
return;
}
pre = pre
.into_iter()
.zip(field_split.iter())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
post = post
.into_iter()
.zip(field_split.iter().rev())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
}
let prefix = pre.join("_");
post.reverse();
let postfix = match post.last() {
Some(&"") => post.join("_") + "_",
Some(_) | None => post.join("_"),
};
if fields.len() > 1 {
let (what, value) = match (
prefix.is_empty() || prefix.chars().all(|c| c == '_'),
postfix.is_empty(),
) {
(true, true) => return,
(false, _) => ("pre", prefix),
(true, false) => ("post", postfix),
};
if fields.iter().all(|field| is_bool(field.ty)) {
// If all fields are booleans, we don't want to emit this lint.
return;
}
span_lint_and_help(
cx,
STRUCT_FIELD_NAMES,
item.span,
format!("all fields have the same {what}fix: `{value}`"),
None,
format!("remove the {what}fixes"),
);
}
}
fn check_struct_name_repetition(&self, cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
let Some(ident) = item.kind.ident() else { return };
let snake_name = to_snake_case(ident.name.as_str());
let item_name_words: Vec<&str> = snake_name.split('_').collect();
for field in fields {
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(field.def_id) {
continue;
}
if !field.ident.span.eq_ctxt(ident.span) {
// consider linting only if the field identifier has the same SyntaxContext as the item(struct)
continue;
}
let field_words: Vec<&str> = field.ident.name.as_str().split('_').collect();
if field_words.len() >= item_name_words.len() {
// if the field name is shorter than the struct name it cannot contain it
@ -337,65 +432,6 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
}
}
fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_name: &str, span: Span) {
if (def.variants.len() as u64) < threshold {
return;
}
for var in def.variants {
check_enum_start(cx, item_name, var);
check_enum_end(cx, item_name, var);
}
let first = match def.variants.first() {
Some(variant) => variant.ident.name.as_str(),
None => return,
};
let mut pre = camel_case_split(first);
let mut post = pre.clone();
post.reverse();
for var in def.variants {
let name = var.ident.name.as_str();
let variant_split = camel_case_split(name);
if variant_split.len() == 1 {
return;
}
pre = pre
.iter()
.zip(variant_split.iter())
.take_while(|(a, b)| a == b)
.map(|e| *e.0)
.collect();
post = post
.iter()
.zip(variant_split.iter().rev())
.take_while(|(a, b)| a == b)
.map(|e| *e.0)
.collect();
}
let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) {
(true, true) => return,
(false, _) => ("pre", pre.join("")),
(true, false) => {
post.reverse();
("post", post.join(""))
},
};
span_lint_and_help(
cx,
ENUM_VARIANT_NAMES,
span,
format!("all variants have the same {what}fix: `{value}`"),
None,
format!(
"remove the {what}fixes and use full paths to \
the variants instead of glob imports"
),
);
}
impl LateLintPass<'_> for ItemNameRepetitions {
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
let Some(_ident) = item.kind.ident() else { return };
@ -462,13 +498,14 @@ impl LateLintPass<'_> for ItemNameRepetitions {
}
}
}
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id))
&& span_is_local(item.span)
{
if span_is_local(item.span) {
match item.kind {
ItemKind::Enum(_, def, _) => check_variant(cx, self.enum_threshold, &def, item_name, item.span),
ItemKind::Enum(_, def, _) => {
self.check_variants(cx, item, &def);
},
ItemKind::Struct(_, VariantData::Struct { fields, .. }, _) => {
check_fields(cx, self.struct_threshold, ident, item.span, fields);
self.check_fields(cx, item, fields);
},
_ => (),
}

View file

@ -28,9 +28,9 @@ declare_clippy_lint! {
/// use std::str::Chars;
/// struct Data {}
/// impl Data {
/// fn iter(&self) -> Chars<'static> {
/// todo!()
/// }
/// fn iter(&self) -> Chars<'static> {
/// todo!()
/// }
/// }
/// ```
#[clippy::version = "1.57.0"]

View file

@ -72,7 +72,9 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
"importing a legacy numeric constant"
},
|diag| {
if let UseKind::Single(ident) = kind && ident.name == kw::Underscore {
if let UseKind::Single(ident) = kind
&& ident.name == kw::Underscore
{
diag.help("remove this import");
return;
}

View file

@ -469,7 +469,7 @@ declare_clippy_lint! {
/// let item2 = 3;
/// let mut vec: Vec<u8> = Vec::new();
/// for _ in 0..20 {
/// vec.push(item1);
/// vec.push(item1);
/// }
/// for _ in 0..30 {
/// vec.push(item2);

View file

@ -4,11 +4,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use std::iter::once;
use std::ops::ControlFlow;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -24,17 +26,23 @@ pub(super) fn check<'tcx>(
arg: iterator,
pat,
span: for_span,
label,
..
}) = for_loop
{
// Suggests using an `if let` instead. This is `Unspecified` because the
// loop may (probably) contain `break` statements which would be invalid
// in an `if let`.
// If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not
// appropriate.
let app = if !contains_any_break_or_continue(block) && label.is_none() {
Applicability::MachineApplicable
} else {
Applicability::Unspecified
};
diag.span_suggestion_verbose(
for_span.with_hi(iterator.span.hi()),
"if you need the first element of the iterator, try writing",
for_to_if_let_sugg(cx, iterator, pat),
Applicability::Unspecified,
app,
);
}
});
@ -43,6 +51,15 @@ pub(super) fn check<'tcx>(
}
}
fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
for_each_expr_without_closures(block, |e| match e.kind {
ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
_ => ControlFlow::Continue(Descend::Yes),
})
.is_some()
}
/// The `never_loop` analysis keeps track of three things:
///
/// * Has any (reachable) code path hit a `continue` of the main loop?

View file

@ -14,7 +14,7 @@ use rustc_span::{Span, sym};
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `std::mem::size_of::<T>() * 8` when
/// Checks for usage of `size_of::<T>() * 8` when
/// `T::BITS` is available.
///
/// ### Why is this bad?
@ -22,7 +22,7 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// std::mem::size_of::<usize>() * 8;
/// size_of::<usize>() * 8;
/// ```
/// Use instead:
/// ```no_run
@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
cx,
MANUAL_BITS,
expr.span,
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
"usage of `size_of::<T>()` to obtain the size of `T` in bits",
"consider using",
sugg,
app,

View file

@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualDivCeil {
&& check_int_ty_and_feature(cx, div_lhs)
&& check_int_ty_and_feature(cx, div_rhs)
&& let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = div_lhs.kind
&& self.msrv.meets(cx, msrvs::MANUAL_DIV_CEIL)
&& self.msrv.meets(cx, msrvs::DIV_CEIL)
{
// (x + (y - 1)) / y
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind

View file

@ -29,7 +29,7 @@ declare_clippy_lint! {
/// Use instead:
/// ```no_run
/// fn compare(a: &str, b: &str) -> bool {
/// a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")
/// a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")
/// }
/// ```
#[clippy::version = "1.84.0"]

View file

@ -5,7 +5,8 @@ use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_ast::BindingMode;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
@ -113,7 +114,7 @@ fn emit_manual_let_else(
cx: &LateContext<'_>,
span: Span,
expr: &Expr<'_>,
ident_map: &FxHashMap<Symbol, &Pat<'_>>,
ident_map: &FxHashMap<Symbol, (&Pat<'_>, BindingMode)>,
pat: &Pat<'_>,
else_body: &Expr<'_>,
) {
@ -167,7 +168,7 @@ fn emit_manual_let_else(
fn replace_in_pattern(
cx: &LateContext<'_>,
span: Span,
ident_map: &FxHashMap<Symbol, &Pat<'_>>,
ident_map: &FxHashMap<Symbol, (&Pat<'_>, BindingMode)>,
pat: &Pat<'_>,
app: &mut Applicability,
top_level: bool,
@ -185,15 +186,16 @@ fn replace_in_pattern(
match pat.kind {
PatKind::Binding(_ann, _id, binding_name, opt_subpt) => {
let Some(pat_to_put) = ident_map.get(&binding_name.name) else {
let Some((pat_to_put, binding_mode)) = ident_map.get(&binding_name.name) else {
break 'a;
};
let sn_pfx = binding_mode.prefix_str();
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
if let Some(subpt) = opt_subpt {
let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false);
return format!("{sn_ptp} @ {subpt}");
return format!("{sn_pfx}{sn_ptp} @ {subpt}");
}
return sn_ptp.to_string();
return format!("{sn_pfx}{sn_ptp}");
},
PatKind::Or(pats) => {
let patterns = pats
@ -211,17 +213,18 @@ fn replace_in_pattern(
.iter()
.map(|fld| {
if let PatKind::Binding(_, _, name, None) = fld.pat.kind
&& let Some(pat_to_put) = ident_map.get(&name.name)
&& let Some((pat_to_put, binding_mode)) = ident_map.get(&name.name)
{
let sn_pfx = binding_mode.prefix_str();
let (sn_fld_name, _) = snippet_with_context(cx, fld.ident.span, span.ctxt(), "", app);
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
// TODO: this is a bit of a hack, but it does its job. Ideally, we'd check if pat_to_put is
// a PatKind::Binding but that is also hard to get right.
if sn_fld_name == sn_ptp {
// Field init shorthand
return format!("{sn_fld_name}");
return format!("{sn_pfx}{sn_fld_name}");
}
return format!("{sn_fld_name}: {sn_ptp}");
return format!("{sn_fld_name}: {sn_pfx}{sn_ptp}");
}
let (sn_fld, _) = snippet_with_context(cx, fld.span, span.ctxt(), "", app);
sn_fld.into_owned()
@ -334,7 +337,7 @@ fn expr_simple_identity_map<'a, 'hir>(
local_pat: &'a Pat<'hir>,
let_pat: &'_ Pat<'hir>,
expr: &'_ Expr<'hir>,
) -> Option<FxHashMap<Symbol, &'a Pat<'hir>>> {
) -> Option<FxHashMap<Symbol, (&'a Pat<'hir>, BindingMode)>> {
let peeled = peel_blocks(expr);
let (sub_pats, paths) = match (local_pat.kind, peeled.kind) {
(PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) | (PatKind::Slice(pats, ..), ExprKind::Array(exprs)) => {
@ -351,9 +354,9 @@ fn expr_simple_identity_map<'a, 'hir>(
return None;
}
let mut pat_bindings = FxHashSet::default();
let_pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| {
pat_bindings.insert(ident);
let mut pat_bindings = FxHashMap::default();
let_pat.each_binding_or_first(&mut |binding_mode, _hir_id, _sp, ident| {
pat_bindings.insert(ident, binding_mode);
});
if pat_bindings.len() < paths.len() {
// This rebinds some bindings from the outer scope, or it repeats some copy-able bindings multiple
@ -366,12 +369,10 @@ fn expr_simple_identity_map<'a, 'hir>(
for (sub_pat, path) in sub_pats.iter().zip(paths.iter()) {
if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind
&& let [path_seg] = path.segments
&& let ident = path_seg.ident
&& let Some(let_binding_mode) = pat_bindings.remove(&ident)
{
let ident = path_seg.ident;
if !pat_bindings.remove(&ident) {
return None;
}
ident_map.insert(ident.name, sub_pat);
ident_map.insert(ident.name, (sub_pat, let_binding_mode));
} else {
return None;
}

View file

@ -24,12 +24,12 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// # let data : &[i32] = &[1, 2, 3];
/// let newlen = data.len() * std::mem::size_of::<i32>();
/// let newlen = data.len() * size_of::<i32>();
/// ```
/// Use instead:
/// ```no_run
/// # let data : &[i32] = &[1, 2, 3];
/// let newlen = std::mem::size_of_val(data);
/// let newlen = size_of_val(data);
/// ```
#[clippy::version = "1.70.0"]
pub MANUAL_SLICE_SIZE_CALCULATION,

View file

@ -1110,11 +1110,9 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
}
}
// If there are still comments, it means they are outside of the arms, therefore
// we should not lint.
if match_comments.is_empty() {
single_match::check(cx, ex, arms, expr);
}
// If there are still comments, it means they are outside of the arms. Tell the lint
// code about it.
single_match::check(cx, ex, arms, expr, !match_comments.is_empty());
match_bool::check(cx, ex, arms, expr);
overlapping_arms::check(cx, ex, arms);
match_wild_enum::check(cx, ex, arms);

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, expr_block, snippet, snippet_block_with_context};
use clippy_utils::ty::implements_trait;
use clippy_utils::{
@ -6,7 +6,7 @@ use clippy_utils::{
};
use core::ops::ControlFlow;
use rustc_arena::DroplessArena;
use rustc_errors::Applicability;
use rustc_errors::{Applicability, Diag};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{Visitor, walk_pat};
use rustc_hir::{Arm, Expr, ExprKind, HirId, Node, Pat, PatExpr, PatExprKind, PatKind, QPath, StmtKind};
@ -32,7 +32,7 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
}
#[rustfmt::skip]
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>) {
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>, contains_comments: bool) {
if let [arm1, arm2] = arms
&& arm1.guard.is_none()
&& arm2.guard.is_none()
@ -77,15 +77,31 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tc
}
}
report_single_pattern(cx, ex, arm1, expr, els);
report_single_pattern(cx, ex, arm1, expr, els, contains_comments);
}
}
}
fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, expr: &Expr<'_>, els: Option<&Expr<'_>>) {
fn report_single_pattern(
cx: &LateContext<'_>,
ex: &Expr<'_>,
arm: &Arm<'_>,
expr: &Expr<'_>,
els: Option<&Expr<'_>>,
contains_comments: bool,
) {
let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
let ctxt = expr.span.ctxt();
let mut app = Applicability::MachineApplicable;
let note = |diag: &mut Diag<'_, ()>| {
if contains_comments {
diag.note("you might want to preserve the comments from inside the `match`");
}
};
let mut app = if contains_comments {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
let els_str = els.map_or(String::new(), |els| {
format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app))
});
@ -109,7 +125,10 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
}
(sugg, "try")
};
span_lint_and_sugg(cx, lint, expr.span, msg, help, sugg.to_string(), app);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
diag.span_suggestion(expr.span, help, sugg.to_string(), app);
note(diag);
});
return;
}
@ -162,7 +181,10 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
(msg, sugg)
};
span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
diag.span_suggestion(expr.span, "try", sugg.to_string(), app);
note(diag);
});
}
struct PatVisitor<'tcx> {

View file

@ -27,7 +27,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args
"use `std::io::Error::other`",
vec![
(new_segment.ident.span, "other".to_owned()),
(error_kind.span.until(error.span), String::new()),
(error_kind.span.until(error.span.source_callsite()), String::new()),
],
Applicability::MachineApplicable,
);

View file

@ -4447,13 +4447,13 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// fn foo(values: &[u8]) -> bool {
/// values.iter().any(|&v| v == 10)
/// values.iter().any(|&v| v == 10)
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn foo(values: &[u8]) -> bool {
/// values.contains(&10)
/// values.contains(&10)
/// }
/// ```
#[clippy::version = "1.86.0"]

View file

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use super::NEEDLESS_COLLECT;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet, snippet_with_applicability};
@ -9,9 +11,9 @@ use clippy_utils::{
};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt};
use rustc_hir::{
BindingMode, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Mutability, Node, PatKind, Stmt, StmtKind,
BindingMode, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Mutability, Node, Pat, PatKind, Stmt, StmtKind,
};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
@ -103,6 +105,12 @@ pub(super) fn check<'tcx>(
return;
}
if let IterFunctionKind::IntoIter(hir_id) = iter_call.func
&& !check_iter_expr_used_only_as_iterator(cx, hir_id, block)
{
return;
}
// Suggest replacing iter_call with iter_replacement, and removing stmt
let mut span = MultiSpan::from_span(name_span);
span.push_span_label(iter_call.span, "the iterator could be used here instead");
@ -253,7 +261,7 @@ struct IterFunction {
impl IterFunction {
fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
match &self.func {
IterFunctionKind::IntoIter => String::new(),
IterFunctionKind::IntoIter(_) => String::new(),
IterFunctionKind::Len => String::from(".count()"),
IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
IterFunctionKind::Contains(span) => {
@ -268,7 +276,7 @@ impl IterFunction {
}
fn get_suggestion_text(&self) -> &'static str {
match &self.func {
IterFunctionKind::IntoIter => {
IterFunctionKind::IntoIter(_) => {
"use the original Iterator instead of collecting it and then producing a new one"
},
IterFunctionKind::Len => {
@ -284,7 +292,7 @@ impl IterFunction {
}
}
enum IterFunctionKind {
IntoIter,
IntoIter(HirId),
Len,
IsEmpty,
Contains(Span),
@ -343,7 +351,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
}
match method_name.ident.name.as_str() {
"into_iter" => self.uses.push(Some(IterFunction {
func: IterFunctionKind::IntoIter,
func: IterFunctionKind::IntoIter(expr.hir_id),
span: expr.span,
})),
"len" => self.uses.push(Some(IterFunction {
@ -520,3 +528,61 @@ fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
set
}
struct IteratorMethodCheckVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
hir_id_of_expr: HirId,
hir_id_of_let_binding: Option<HirId>,
}
impl<'tcx> Visitor<'tcx> for IteratorMethodCheckVisitor<'_, 'tcx> {
type Result = ControlFlow<()>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> ControlFlow<()> {
if let ExprKind::MethodCall(_method_name, recv, _args, _) = &expr.kind
&& (recv.hir_id == self.hir_id_of_expr
|| self
.hir_id_of_let_binding
.is_some_and(|hid| path_to_local_id(recv, hid)))
&& !is_trait_method(self.cx, expr, sym::Iterator)
{
return ControlFlow::Break(());
} else if let ExprKind::Assign(place, value, _span) = &expr.kind
&& value.hir_id == self.hir_id_of_expr
&& let Some(id) = path_to_local(place)
{
// our iterator was directly assigned to a variable
self.hir_id_of_let_binding = Some(id);
}
walk_expr(self, expr)
}
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> ControlFlow<()> {
if let StmtKind::Let(LetStmt {
init: Some(expr),
pat:
Pat {
kind: PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None),
..
},
..
}) = &stmt.kind
&& expr.hir_id == self.hir_id_of_expr
{
// our iterator was directly assigned to a variable
self.hir_id_of_let_binding = Some(*id);
}
walk_stmt(self, stmt)
}
}
fn check_iter_expr_used_only_as_iterator<'tcx>(
cx: &LateContext<'tcx>,
hir_id_of_expr: HirId,
block: &'tcx Block<'tcx>,
) -> bool {
let mut visitor = IteratorMethodCheckVisitor {
cx,
hir_id_of_expr,
hir_id_of_let_binding: None,
};
visitor.visit_block(block).is_continue()
}

View file

@ -6,7 +6,8 @@ use clippy_utils::source::{SpanRangeExt, snippet};
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs, return_ty,
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs,
return_ty,
};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -219,6 +220,8 @@ fn check_into_iter_call_arg(
&& let Some(receiver_snippet) = receiver.span.get_source_text(cx)
// If the receiver is a `Cow`, we can't remove the `into_owned` generally, see https://github.com/rust-lang/rust-clippy/issues/13624.
&& !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::Cow)
// Calling `iter()` on a temporary object can lead to false positives. #14242
&& !is_expr_temporary_value(cx, receiver)
{
if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
return true;

View file

@ -1,32 +1,51 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_lint::EarlyContext;
use rustc_span::Span;
use super::MIXED_CASE_HEX_LITERALS;
pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, suffix: &str, lit_snip: &str) {
let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
return; // It's useless so shouldn't lint.
let num_end_idx = match lit_snip.strip_suffix(suffix) {
Some(p) if p.ends_with('_') => lit_snip.len() - (suffix.len() + 1),
Some(_) => lit_snip.len() - suffix.len(),
None => lit_snip.len(),
};
if maybe_last_sep_idx <= 2 {
if num_end_idx <= 2 {
// It's meaningless or causes range error.
return;
}
let mut seen = (false, false);
for ch in &lit_snip.as_bytes()[2..=maybe_last_sep_idx] {
for ch in &lit_snip.as_bytes()[2..num_end_idx] {
match ch {
b'a'..=b'f' => seen.0 = true,
b'A'..=b'F' => seen.1 = true,
_ => {},
}
if seen.0 && seen.1 {
span_lint(
let raw_digits = &lit_snip[2..num_end_idx];
let (sugg_lower, sugg_upper) = if suffix.is_empty() {
(
format!("0x{}", raw_digits.to_lowercase()),
format!("0x{}", raw_digits.to_uppercase()),
)
} else {
(
format!("0x{}_{}", raw_digits.to_lowercase(), suffix),
format!("0x{}_{}", raw_digits.to_uppercase(), suffix),
)
};
span_lint_and_help(
cx,
MIXED_CASE_HEX_LITERALS,
lit_span,
"inconsistent casing in hexadecimal literal",
None,
format!("consider using `{sugg_lower}` or `{sugg_upper}`"),
);
break;
return;
}
}
}

View file

@ -37,7 +37,7 @@ declare_clippy_lint! {
///
/// struct Baz;
/// impl Baz {
/// fn private() {} // ok
/// fn private() {} // ok
/// }
///
/// impl Bar for Baz {
@ -46,13 +46,13 @@ declare_clippy_lint! {
///
/// pub struct PubBaz;
/// impl PubBaz {
/// fn private() {} // ok
/// pub fn not_private() {} // missing #[inline]
/// fn private() {} // ok
/// pub fn not_private() {} // missing #[inline]
/// }
///
/// impl Bar for PubBaz {
/// fn bar() {} // missing #[inline]
/// fn def_bar() {} // missing #[inline]
/// fn bar() {} // missing #[inline]
/// fn def_bar() {} // missing #[inline]
/// }
/// ```
///

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::{SourceText, SpanRangeExt};
use clippy_utils::source::{SourceText, SpanRangeExt, snippet};
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures, is_local_used};
use core::ops::ControlFlow;
@ -100,7 +100,6 @@ fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
#[derive(Debug)]
struct LocalAssign {
lhs_id: HirId,
lhs_span: Span,
rhs_span: Span,
span: Span,
}
@ -118,7 +117,6 @@ impl LocalAssign {
Some(Self {
lhs_id: path_to_local(lhs)?,
lhs_span: lhs.span,
rhs_span: rhs.span.source_callsite(),
span,
})
@ -281,7 +279,10 @@ fn check<'tcx>(
format!("move the declaration `{binding_name}` here"),
vec![
(local_stmt.span, String::new()),
(assign.lhs_span, let_snippet.to_owned()),
(
assign.span,
let_snippet.to_owned() + " = " + &snippet(cx, assign.rhs_span, ".."),
),
],
Applicability::MachineApplicable,
);

View file

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_self;
use clippy_utils::ptr::get_spans;
use clippy_utils::source::{SpanRangeExt, snippet};
use clippy_utils::ty::{
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
};
use clippy_utils::{is_self, peel_hir_ty_options};
use rustc_abi::ExternAbi;
use rustc_errors::{Applicability, Diag};
use rustc_hir::intravisit::FnKind;
@ -279,10 +279,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
}
}
diag.span_suggestion(
input.span,
diag.span_suggestion_verbose(
peel_hir_ty_options(cx, input).span.shrink_to_lo(),
"consider taking a reference instead",
format!("&{}", snippet(cx, input.span, "_")),
'&',
Applicability::MaybeIncorrect,
);
};

View file

@ -40,7 +40,7 @@ declare_clippy_lint! {
/// }
///
/// fn f(to: TO) -> Option<usize> {
/// to.magic
/// to.magic
/// }
///
/// struct TR {

View file

@ -18,7 +18,6 @@ mod modulo_one;
mod needless_bitwise_bool;
mod numeric_arithmetic;
mod op_ref;
mod ptr_eq;
mod self_assignment;
mod verbose_bit_mask;
@ -768,35 +767,6 @@ declare_clippy_lint! {
"Boolean expressions that use bitwise rather than lazy operators"
}
declare_clippy_lint! {
/// ### What it does
/// Use `std::ptr::eq` when applicable
///
/// ### Why is this bad?
/// `ptr::eq` can be used to compare `&T` references
/// (which coerce to `*const T` implicitly) by their address rather than
/// comparing the values they point to.
///
/// ### Example
/// ```no_run
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(a as *const _ as usize == b as *const _ as usize);
/// ```
/// Use instead:
/// ```no_run
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(std::ptr::eq(a, b));
/// ```
#[clippy::version = "1.49.0"]
pub PTR_EQ,
style,
"use `std::ptr::eq` when comparing raw pointers"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for explicit self-assignments.
@ -902,7 +872,6 @@ impl_lint_pass!(Operators => [
MODULO_ONE,
MODULO_ARITHMETIC,
NEEDLESS_BITWISE_BOOL,
PTR_EQ,
SELF_ASSIGNMENT,
MANUAL_MIDPOINT,
]);
@ -921,7 +890,6 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
erasing_op::check(cx, e, op.node, lhs, rhs);
identity_op::check(cx, e, op.node, lhs, rhs);
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
ptr_eq::check(cx, e, op.node, lhs, rhs);
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
}
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);

View file

@ -1,62 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::std_or_core;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use super::PTR_EQ;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if BinOpKind::Eq == op {
let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
(Some(lhs), Some(rhs)) => (lhs, rhs),
_ => (left, right),
};
if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left)
&& let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right)
&& let Some(left_snip) = left_var.span.get_source_text(cx)
&& let Some(right_snip) = right_var.span.get_source_text(cx)
{
let Some(top_crate) = std_or_core(cx) else { return };
span_lint_and_sugg(
cx,
PTR_EQ,
expr.span,
format!("use `{top_crate}::ptr::eq` when comparing raw pointers"),
"try",
format!("{top_crate}::ptr::eq({left_snip}, {right_snip})"),
Applicability::MachineApplicable,
);
}
}
}
// If the given expression is a cast to a usize, return the lhs of the cast
// E.g., `foo as *const _ as usize` returns `foo as *const _`.
fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}
// If the given expression is a cast to a `*const` pointer, return the lhs of the cast
// E.g., `foo as *const _` returns `foo`.
fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr).is_raw_ptr() {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}

View file

@ -1,16 +1,23 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_copy;
use clippy_utils::{
CaptureKind, can_move_expr_to_closure, eager_or_lazy, higher, is_else_clause, is_in_const_context,
is_res_lang_ctor, peel_blocks, peel_hir_expr_while,
};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::def::Res;
use rustc_hir::intravisit::{Visitor, walk_expr, walk_path};
use rustc_hir::{
Arm, BindingMode, Expr, ExprKind, MatchSource, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, UnOp,
Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path,
QPath, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
@ -110,11 +117,12 @@ fn format_option_in_sugg(cond_sugg: Sugg<'_>, as_ref: bool, as_mut: bool) -> Str
)
}
#[expect(clippy::too_many_lines)]
fn try_get_option_occurrence<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
pat: &Pat<'tcx>,
expr: &Expr<'_>,
expr: &'tcx Expr<'_>,
if_then: &'tcx Expr<'_>,
if_else: &'tcx Expr<'_>,
) -> Option<OptionOccurrence> {
@ -182,6 +190,26 @@ fn try_get_option_occurrence<'tcx>(
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
} else if !is_copy(cx, cx.typeck_results().expr_ty(expr))
// TODO: Cover more match cases
&& matches!(
expr.kind,
ExprKind::Field(_, _) | ExprKind::Path(_) | ExprKind::Index(_, _, _)
)
{
let mut condition_visitor = ConditionVisitor {
cx,
identifiers: FxHashSet::default(),
};
condition_visitor.visit_expr(cond_expr);
let mut reference_visitor = ReferenceVisitor {
cx,
identifiers: condition_visitor.identifiers,
};
if reference_visitor.visit_expr(none_body).is_break() {
return None;
}
}
let mut app = Applicability::Unspecified;
@ -219,6 +247,60 @@ fn try_get_option_occurrence<'tcx>(
None
}
/// This visitor looks for bindings in the <then> block that mention a local variable. Then gets the
/// identifiers. The list of identifiers will then be used to check if the <none> block mentions the
/// same local. See [`ReferenceVisitor`] for more.
struct ConditionVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for ConditionVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
if let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
{
self.identifiers.insert(local_id);
}
walk_path(self, path);
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
/// This visitor checks if the <none> block contains references to the local variables that are
/// used in the <then> block. See [`ConditionVisitor`] for more.
struct ReferenceVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
type Result = ControlFlow<()>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> ControlFlow<()> {
if let ExprKind::Path(ref path) = expr.kind
&& let QPath::Resolved(_, path) = path
&& let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
&& self.identifiers.contains(&local_id)
{
return ControlFlow::Break(());
}
walk_expr(self, expr)
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> {
if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind {
let res = cx.qpath_res(qpath, pat.hir_id);

View file

@ -19,8 +19,8 @@ declare_clippy_lint! {
/// struct Foo;
///
/// impl PartialEq for Foo {
/// fn eq(&self, other: &Foo) -> bool { true }
/// fn ne(&self, other: &Foo) -> bool { !(self == other) }
/// fn eq(&self, other: &Foo) -> bool { true }
/// fn ne(&self, other: &Foo) -> bool { !(self == other) }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]

View file

@ -148,7 +148,36 @@ declare_clippy_lint! {
"invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
}
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
declare_clippy_lint! {
/// ### What it does
/// Use `std::ptr::eq` when applicable
///
/// ### Why is this bad?
/// `ptr::eq` can be used to compare `&T` references
/// (which coerce to `*const T` implicitly) by their address rather than
/// comparing the values they point to.
///
/// ### Example
/// ```no_run
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(a as *const _ as usize == b as *const _ as usize);
/// ```
/// Use instead:
/// ```no_run
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(std::ptr::eq(a, b));
/// ```
#[clippy::version = "1.49.0"]
pub PTR_EQ,
style,
"use `std::ptr::eq` when comparing raw pointers"
}
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE, PTR_EQ]);
impl<'tcx> LateLintPass<'tcx> for Ptr {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
@ -253,10 +282,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
if let ExprKind::Binary(op, l, r) = expr.kind
&& (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne)
{
let non_null_path_snippet = match (is_null_path(cx, l), is_null_path(cx, r)) {
(true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_par(),
(false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_par(),
_ => return,
let non_null_path_snippet = match (
is_lint_allowed(cx, CMP_NULL, expr.hir_id),
is_null_path(cx, l),
is_null_path(cx, r),
) {
(false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_par(),
(false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_par(),
_ => return check_ptr_eq(cx, expr, op.node, l, r),
};
span_lint_and_sugg(
@ -740,3 +773,71 @@ fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
false
}
}
fn check_ptr_eq<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if expr.span.from_expansion() {
return;
}
// Remove one level of usize conversion if any
let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
(Some(lhs), Some(rhs)) => (lhs, rhs),
_ => (left, right),
};
// This lint concerns raw pointers
let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right));
if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() {
return;
}
let (left_var, right_var) = (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty));
if let Some(left_snip) = left_var.span.get_source_text(cx)
&& let Some(right_snip) = right_var.span.get_source_text(cx)
{
let Some(top_crate) = std_or_core(cx) else { return };
let invert = if op == BinOpKind::Eq { "" } else { "!" };
span_lint_and_sugg(
cx,
PTR_EQ,
expr.span,
format!("use `{top_crate}::ptr::eq` when comparing raw pointers"),
"try",
format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"),
Applicability::MachineApplicable,
);
}
}
// If the given expression is a cast to a usize, return the lhs of the cast
// E.g., `foo as *const _ as usize` returns `foo as *const _`.
fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize
&& let ExprKind::Cast(expr, _) = cast_expr.kind
{
Some(expr)
} else {
None
}
}
// Peel raw casts if the remaining expression can be coerced to it
fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> &'tcx Expr<'tcx> {
if let ExprKind::Cast(inner, _) = expr.kind
&& let ty::RawPtr(target_ty, _) = expr_ty.kind()
&& let inner_ty = cx.typeck_results().expr_ty(inner)
&& let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind()
&& target_ty == inner_target_ty
{
peel_raw_casts(cx, inner, inner_ty)
} else {
expr
}
}

View file

@ -3,7 +3,7 @@ use crate::question_mark_used::QUESTION_MARK_USED;
use clippy_config::Conf;
use clippy_config::types::MatchLintBehaviour;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{
@ -145,8 +145,47 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
{
let mut applicability = Applicability::MaybeIncorrect;
let init_expr_str = snippet_with_applicability(cx, init_expr.span, "..", &mut applicability);
let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
let sugg = format!("let {receiver_str} = {init_expr_str}?;",);
// Take care when binding is `ref`
let sugg = if let PatKind::Binding(
BindingMode(ByRef::Yes(ref_mutability), binding_mutability),
_hir_id,
ident,
subpattern,
) = inner_pat.kind
{
let (from_method, replace_to) = match ref_mutability {
Mutability::Mut => (".as_mut()", "&mut "),
Mutability::Not => (".as_ref()", "&"),
};
let mutability_str = match binding_mutability {
Mutability::Mut => "mut ",
Mutability::Not => "",
};
// Handle subpattern (@ subpattern)
let maybe_subpattern = match subpattern {
Some(Pat {
kind: PatKind::Binding(BindingMode(ByRef::Yes(_), _), _, subident, None),
..
}) => {
// avoid `&ref`
// note that, because you can't have aliased, mutable references, we don't have to worry about
// the outer and inner mutability being different
format!(" @ {subident}")
},
Some(subpattern) => {
let substr = snippet_with_applicability(cx, subpattern.span, "..", &mut applicability);
format!(" @ {replace_to}{substr}")
},
None => String::new(),
};
format!("let {mutability_str}{ident}{maybe_subpattern} = {init_expr_str}{from_method}?;")
} else {
let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
format!("let {receiver_str} = {init_expr_str}?;")
};
span_lint_and_sugg(
cx,
QUESTION_MARK,
@ -230,7 +269,7 @@ fn expr_return_none_or_err(
///
/// ```ignore
/// if option.is_none() {
/// return None;
/// return None;
/// }
/// ```
///
@ -485,7 +524,8 @@ fn is_inferred_ret_closure(expr: &Expr<'_>) -> bool {
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) {
if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) || !self.msrv.meets(cx, msrvs::QUESTION_MARK_OPERATOR)
{
return;
}
@ -501,7 +541,10 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
return;
}
if !self.inside_try_block() && !is_in_const_context(cx) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id)
if !self.inside_try_block()
&& !is_in_const_context(cx)
&& is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id)
&& self.msrv.meets(cx, msrvs::QUESTION_MARK_OPERATOR)
{
check_is_none_or_err_and_early_return(cx, expr);
check_if_let_some_or_err_and_early_return(cx, expr);

View file

@ -23,7 +23,7 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// let f = async {
/// 1 + 2
/// 1 + 2
/// };
/// let fut = async {
/// f.await
@ -32,7 +32,7 @@ declare_clippy_lint! {
/// Use instead:
/// ```no_run
/// let f = async {
/// 1 + 2
/// 1 + 2
/// };
/// let fut = f;
/// ```

View file

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// let a = a;
///
/// fn foo(b: i32) {
/// let b = b;
/// let b = b;
/// }
/// ```
/// Use instead:

View file

@ -55,7 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
// FIXME: `DUMMY_SP` isn't right here, because it causes the
// resulting span to begin at the start of the file.
let span = item.span.with_hi(
item.kind.ident().map(|ident| ident.span.hi()).unwrap_or(rustc_span::DUMMY_SP.hi())
item.kind
.ident()
.map_or(rustc_span::DUMMY_SP.hi(), |ident| ident.span.hi()),
);
let descr = cx.tcx.def_kind(item.owner_id).descr(item.owner_id.to_def_id());
span_lint_and_then(

View file

@ -18,7 +18,6 @@ declare_clippy_lint! {
/// ### Example
/// ```rust,no_run
/// # use std::ptr::copy_nonoverlapping;
/// # use std::mem::size_of;
/// const SIZE: usize = 128;
/// let x = [2u8; SIZE];
/// let mut y = [2u8; SIZE];

View file

@ -8,7 +8,7 @@ use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for calls to `std::mem::size_of_val()` where the argument is
/// Checks for calls to `size_of_val()` where the argument is
/// a reference to a reference.
///
/// ### Why is this bad?
@ -29,7 +29,7 @@ declare_clippy_lint! {
/// // is already a reference, `&self` is a double-reference.
/// // The return value of `size_of_val()` therefore is the
/// // size of the reference-type, not the size of `self`.
/// std::mem::size_of_val(&self)
/// size_of_val(&self)
/// }
/// }
/// ```
@ -42,14 +42,14 @@ declare_clippy_lint! {
/// impl Foo {
/// fn size(&self) -> usize {
/// // Correct
/// std::mem::size_of_val(self)
/// size_of_val(self)
/// }
/// }
/// ```
#[clippy::version = "1.68.0"]
pub SIZE_OF_REF,
suspicious,
"Argument to `std::mem::size_of_val()` is a double-reference, which is almost certainly unintended"
"Argument to `size_of_val()` is a double-reference, which is almost certainly unintended"
}
declare_lint_pass!(SizeOfRef => [SIZE_OF_REF]);
@ -65,9 +65,9 @@ impl LateLintPass<'_> for SizeOfRef {
cx,
SIZE_OF_REF,
expr.span,
"argument to `std::mem::size_of_val()` is a reference to a reference",
"argument to `size_of_val()` is a reference to a reference",
None,
"dereference the argument to `std::mem::size_of_val()` to get the size of the value instead of the size of the reference-type",
"dereference the argument to `size_of_val()` to get the size of the value instead of the size of the reference-type",
);
}
}

View file

@ -14,6 +14,8 @@ use rustc_session::declare_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::sym;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
/// Checks for string appends of the form `x = x + y` (without
@ -438,27 +440,94 @@ declare_clippy_lint! {
declare_lint_pass!(StringToString => [STRING_TO_STRING]);
fn is_parent_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(name, _, _, parent_span) = parent_expr.kind
&& name.ident.name == sym::map
&& let Some(caller_def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& (clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Result)
|| clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Option)
|| clippy_utils::is_diag_trait_item(cx, caller_def_id, sym::Iterator))
{
Some(parent_span)
} else {
None
}
}
fn is_called_from_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
// Look for a closure as parent of `expr`, discarding simple blocks
let parent_closure = cx
.tcx
.hir_parent_iter(expr.hir_id)
.try_fold(expr.hir_id, |child_hir_id, (_, node)| match node {
// Check that the child expression is the only expression in the block
Node::Block(block) if block.stmts.is_empty() && block.expr.map(|e| e.hir_id) == Some(child_hir_id) => {
ControlFlow::Continue(block.hir_id)
},
Node::Expr(expr) if matches!(expr.kind, ExprKind::Block(..)) => ControlFlow::Continue(expr.hir_id),
Node::Expr(expr) if matches!(expr.kind, ExprKind::Closure(_)) => ControlFlow::Break(Some(expr)),
_ => ControlFlow::Break(None),
})
.break_value()?;
is_parent_map_like(cx, parent_closure?)
}
fn suggest_cloned_string_to_string(cx: &LateContext<'_>, span: rustc_span::Span) {
span_lint_and_sugg(
cx,
STRING_TO_STRING,
span,
"`to_string()` called on a `String`",
"try",
"cloned()".to_string(),
Applicability::MachineApplicable,
);
}
impl<'tcx> LateLintPass<'tcx> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind
&& path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
&& is_type_lang_item(cx, ty, LangItem::String)
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
STRING_TO_STRING,
expr.span,
"`to_string()` called on a `String`",
|diag| {
diag.help("consider using `.clone()`");
},
);
match &expr.kind {
ExprKind::MethodCall(path, self_arg, [], _) => {
if path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
&& is_type_lang_item(cx, ty.peel_refs(), LangItem::String)
{
if let Some(parent_span) = is_called_from_map_like(cx, expr) {
suggest_cloned_string_to_string(cx, parent_span);
} else {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
STRING_TO_STRING,
expr.span,
"`to_string()` called on a `String`",
|diag| {
diag.help("consider using `.clone()`");
},
);
}
}
},
ExprKind::Path(QPath::TypeRelative(ty, segment)) => {
if segment.ident.name == sym::to_string
&& let rustc_hir::TyKind::Path(QPath::Resolved(_, path)) = ty.peel_refs().kind
&& let rustc_hir::def::Res::Def(_, def_id) = path.res
&& cx
.tcx
.lang_items()
.get(LangItem::String)
.is_some_and(|lang_id| lang_id == def_id)
&& let Some(parent_span) = is_parent_map_like(cx, expr)
{
suggest_cloned_string_to_string(cx, parent_span);
}
},
_ => {},
}
}
}

View file

@ -61,10 +61,6 @@ declare_clippy_lint! {
/// `Vec` already keeps its contents in a separate area on
/// the heap. So if you `Box` its contents, you just add another level of indirection.
///
/// ### Known problems
/// Vec<Box<T: Sized>> makes sense if T is a large type (see [#3530](https://github.com/rust-lang/rust-clippy/issues/3530),
/// 1st comment).
///
/// ### Example
/// ```no_run
/// struct X {

View file

@ -312,6 +312,25 @@ fn expr_has_unnecessary_safety_comment<'tcx>(
},
_,
) => ControlFlow::Break(()),
// `_ = foo()` is desugared to `{ let _ = foo(); }`
hir::ExprKind::Block(
Block {
rules: BlockCheckMode::DefaultBlock,
stmts:
[
hir::Stmt {
kind:
hir::StmtKind::Let(hir::LetStmt {
source: hir::LocalSource::AssignDesugar(_),
..
}),
..
},
],
..
},
_,
) => ControlFlow::Continue(Descend::Yes),
// statements will be handled by check_stmt itself again
hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
_ => ControlFlow::Continue(Descend::Yes),
@ -339,6 +358,33 @@ fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
.is_none_or(|src| !src.starts_with("unsafe"))
}
fn find_unsafe_block_parent_in_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Option<(Span, HirId)> {
match cx.tcx.parent_hir_node(expr.hir_id) {
Node::LetStmt(hir::LetStmt { span, hir_id, .. })
| Node::Expr(hir::Expr {
hir_id,
kind: hir::ExprKind::Assign(_, _, span),
..
}) => Some((*span, *hir_id)),
Node::Expr(expr) => find_unsafe_block_parent_in_expr(cx, expr),
node if let Some((span, hir_id)) = span_and_hid_of_item_alike_node(&node)
&& is_const_or_static(&node) =>
{
Some((span, hir_id))
},
_ => {
if is_branchy(expr) {
return None;
}
Some((expr.span, expr.hir_id))
},
}
}
// Checks if any parent {expression, statement, block, local, const, static}
// has a safety comment
fn block_parents_have_safety_comment(
@ -348,21 +394,7 @@ fn block_parents_have_safety_comment(
id: HirId,
) -> bool {
let (span, hir_id) = match cx.tcx.parent_hir_node(id) {
Node::Expr(expr) => match cx.tcx.parent_hir_node(expr.hir_id) {
Node::LetStmt(hir::LetStmt { span, hir_id, .. }) => (*span, *hir_id),
Node::Item(hir::Item {
kind: ItemKind::Const(..) | ItemKind::Static(..),
span,
owner_id,
..
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
_ => {
if is_branchy(expr) {
return false;
}
(expr.span, expr.hir_id)
},
},
Node::Expr(expr) if let Some(inner) = find_unsafe_block_parent_in_expr(cx, expr) => inner,
Node::Stmt(hir::Stmt {
kind:
hir::StmtKind::Let(hir::LetStmt { span, hir_id, .. })
@ -371,12 +403,13 @@ fn block_parents_have_safety_comment(
..
})
| Node::LetStmt(hir::LetStmt { span, hir_id, .. }) => (*span, *hir_id),
Node::Item(hir::Item {
kind: ItemKind::Const(..) | ItemKind::Static(..),
span,
owner_id,
..
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
node if let Some((span, hir_id)) = span_and_hid_of_item_alike_node(&node)
&& is_const_or_static(&node) =>
{
(span, hir_id)
},
_ => return false,
};
// if unsafe block is part of a let/const/static statement,
@ -427,11 +460,12 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
}
fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span {
span.to(cx
.tcx
.hir_attrs(hir_id)
.iter()
.fold(span, |acc, attr| acc.to(attr.span())))
span.to(cx.tcx.hir_attrs(hir_id).iter().fold(span, |acc, attr| {
if attr.is_doc_comment() {
return acc;
}
acc.to(attr.span())
}))
}
enum HasSafetyComment {
@ -603,31 +637,35 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
let body = cx.enclosing_body?;
let mut span = cx.tcx.hir_body(body).value.span;
let mut maybe_global_var = false;
for (_, node) in cx.tcx.hir_parent_iter(body.hir_id) {
match node {
Node::Expr(e) => span = e.span,
Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::LetStmt(_) => (),
let mut maybe_mod_item = None;
for (_, parent_node) in cx.tcx.hir_parent_iter(body.hir_id) {
match parent_node {
Node::Crate(mod_) => return Some(mod_.spans.inner_span),
Node::Item(hir::Item {
kind: ItemKind::Const(..) | ItemKind::Static(..),
..
}) => maybe_global_var = true,
Node::Item(hir::Item {
kind: ItemKind::Mod(..),
span: item_span,
kind: ItemKind::Mod(_, mod_),
span,
..
}) => {
span = *item_span;
break;
return maybe_mod_item
.and_then(|item| comment_start_before_item_in_mod(cx, mod_, *span, &item))
.map(|comment_start| mod_.spans.inner_span.with_lo(comment_start))
.or(Some(*span));
},
Node::Crate(mod_) if maybe_global_var => {
span = mod_.spans.inner_span;
node if let Some((span, _)) = span_and_hid_of_item_alike_node(&node)
&& !is_const_or_static(&node) =>
{
return Some(span);
},
Node::Item(item) => {
maybe_mod_item = Some(*item);
},
_ => {
maybe_mod_item = None;
},
_ => break,
}
}
Some(span)
None
}
fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
@ -716,3 +754,28 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos
}
}
}
fn span_and_hid_of_item_alike_node(node: &Node<'_>) -> Option<(Span, HirId)> {
match node {
Node::Item(item) => Some((item.span, item.owner_id.into())),
Node::TraitItem(ti) => Some((ti.span, ti.owner_id.into())),
Node::ImplItem(ii) => Some((ii.span, ii.owner_id.into())),
_ => None,
}
}
fn is_const_or_static(node: &Node<'_>) -> bool {
matches!(
node,
Node::Item(hir::Item {
kind: ItemKind::Const(..) | ItemKind::Static(..),
..
}) | Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Const(..),
..
}) | Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Const(..),
..
})
)
}

View file

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// ```no_run
/// # let a: u32 = 42;
/// if a > 10 {
/// println!("a is greater than 10");
/// println!("a is greater than 10");
/// }
/// ```
#[clippy::version = "1.86.0"]

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::sugg::{DiagExt as _, Sugg};
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{
get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local,
};
@ -13,7 +13,7 @@ use rustc_infer::traits::Obligation;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, sym};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
@ -412,24 +412,14 @@ pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &
}
fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool {
let recv_ty = cx.typeck_results().expr_ty(recv);
if is_inherent_method_call(cx, expr)
&& let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
{
if let Some(diag_name) = cx.tcx.get_diagnostic_name(recv_ty_defid)
&& matches!(diag_name, sym::Option | sym::Result)
{
return true;
}
if cx.tcx.is_diagnostic_item(sym::ControlFlow, recv_ty_defid) {
return true;
}
if is_inherent_method_call(cx, expr) {
matches!(
get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv)),
Some(sym::Option | sym::Result | sym::ControlFlow)
)
} else {
is_trait_method(cx, expr, sym::Iterator)
}
if is_trait_method(cx, expr, sym::Iterator) {
return true;
}
false
}
fn adjustments(cx: &LateContext<'_>, expr: &Expr<'_>) -> String {

View file

@ -44,11 +44,10 @@ impl AlmostStandardFormulation {
impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let mut check_next = false;
if let ItemKind::Static(ty, Mutability::Not, _) = item.kind {
if let ItemKind::Static(_, ty, Mutability::Not, _) = item.kind {
let lines = cx
.tcx
.hir()
.attrs(item.hir_id())
.hir_attrs(item.hir_id())
.iter()
.filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr)));
if is_lint_ref_type(cx, ty) {

View file

@ -104,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
return;
}
if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
if let hir::ItemKind::Static(ident, ty, Mutability::Not, body_id) = item.kind {
if is_lint_ref_type(cx, ty) {
check_invalid_clippy_version_attribute(cx, item);
@ -133,10 +133,10 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
cx,
DEFAULT_LINT,
item.span,
format!("the lint `{}` has the default lint description", item.ident.name),
format!("the lint `{}` has the default lint description", ident.name),
);
}
self.declared_lints.insert(item.ident.name, item.span);
self.declared_lints.insert(ident.name, item.span);
}
}
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) {

View file

@ -1,6 +1,6 @@
use rustc_ast::ast::NodeId;
use rustc_ast::visit::FnKind;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -24,8 +24,12 @@ declare_clippy_lint! {
declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
impl EarlyLintPass for ProduceIce {
fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
assert!(!is_trigger_fn(fn_kind), "Would you like some help with that?");
fn check_fn(&mut self, ctx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
if is_trigger_fn(fn_kind) {
ctx.sess()
.dcx()
.span_delayed_bug(span, "Would you like some help with that?");
}
}
}

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-02-27
nightly-2025-03-20
```
<!-- end autogenerated nightly -->
@ -30,7 +30,7 @@ Function signatures can change or be removed without replacement without any pri
<!-- REUSE-IgnoreStart -->
Copyright 2014-2024 The Rust Project Developers
Copyright 2014-2025 The Rust Project Developers
Licensed under the Apache License, Version 2.0
<[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license

View file

@ -688,7 +688,7 @@ pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
use WherePredicateKind::*;
over(&l.attrs, &r.attrs, eq_attr)
over(&l.attrs, &r.attrs, eq_attr)
&& match (&l.kind, &r.kind) {
(BoundPredicate(l), BoundPredicate(r)) => {
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {

View file

@ -85,7 +85,7 @@ fn validate_diag(diag: &Diag<'_, impl EmissionGuarantee>) {
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// If you're unsure which function you should use, you can test if the `#[expect]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir`] instead.
///
@ -128,7 +128,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// If you're unsure which function you should use, you can test if the `#[expect]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///
@ -183,7 +183,7 @@ pub fn span_lint_and_help<T: LintContext>(
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// If you're unsure which function you should use, you can test if the `#[expect]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///
@ -241,7 +241,7 @@ pub fn span_lint_and_note<T: LintContext>(
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// If you're unsure which function you should use, you can test if the `#[expect]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
pub fn span_lint_and_then<C, S, M, F>(cx: &C, lint: &'static Lint, sp: S, msg: M, f: F)
@ -358,7 +358,7 @@ pub fn span_lint_hir_and_then(
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// If you're unsure which function you should use, you can test if the `#[expect]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///

View file

@ -106,10 +106,10 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
use rustc_hir::{
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind,
TraitItemRef, TraitRef, TyKind, UnOp, def,
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem,
ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem,
TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
};
use rustc_lexer::{TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
@ -434,7 +434,7 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tc
.map_or(&[][..], |a| a.args)
.iter()
.filter_map(|a| match a {
hir::GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
_ => None,
})
}
@ -1420,8 +1420,7 @@ pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
let parent_id = cx.tcx.hir_get_parent_item(expr.hir_id).def_id;
match cx.tcx.hir_node_by_def_id(parent_id) {
Node::Item(item) => item.kind.ident().map(|ident| ident.name),
Node::TraitItem(TraitItem { ident, .. })
| Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
Node::TraitItem(TraitItem { ident, .. }) | Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
_ => None,
}
}
@ -2334,6 +2333,18 @@ pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
}
/// Checks if the expression is a temporary value.
// This logic is the same as the one used in rustc's `check_named_place_expr function`.
// https://github.com/rust-lang/rust/blob/3ed2a10d173d6c2e0232776af338ca7d080b1cd4/compiler/rustc_hir_typeck/src/expr.rs#L482-L499
pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
!expr.is_place_expr(|base| {
cx.typeck_results()
.adjustments()
.get(base.hir_id)
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
})
}
pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
if !is_no_std_crate(cx) {
Some("std")
@ -3548,7 +3559,7 @@ pub fn is_block_like(expr: &Expr<'_>) -> bool {
pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
match expr.kind {
ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => contains_block(lhs, true),
_ if is_block_like(expr) => is_operand,
_ => false,
}
@ -3695,3 +3706,21 @@ pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
true
}
}
/// Peel `Option<…>` from `hir_ty` as long as the HIR name is `Option` and it corresponds to the
/// `core::Option<_>` type.
pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
return hir_ty;
};
while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
&& let Some(segment) = path.segments.last()
&& segment.ident.name == sym::Option
&& let Res::Def(DefKind::Enum, def_id) = segment.res
&& def_id == option_def_id
&& let [GenericArg::Type(arg_ty)] = segment.args().args
{
hir_ty = arg_ty.as_unambig_ty();
}
hir_ty
}

View file

@ -6,8 +6,7 @@ use rustc_index::bit_set::DenseBitSet;
use rustc_lint::LateContext;
use rustc_middle::mir::visit::Visitor as _;
use rustc_middle::mir::{self, Mutability};
use rustc_middle::ty::TypeVisitor;
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::ty::{self, TyCtxt, TypeVisitor};
use rustc_mir_dataflow::impls::MaybeStorageLive;
use rustc_mir_dataflow::{Analysis, ResultsCursor};
use std::borrow::Cow;

View file

@ -33,7 +33,7 @@ msrv_aliases! {
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
1,75,0 { OPTION_AS_SLICE }
1,74,0 { REPR_RUST, IO_ERROR_OTHER }
1,73,0 { MANUAL_DIV_CEIL }
1,73,0 { DIV_CEIL }
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
1,68,0 { PATH_MAIN_SEPARATOR_STR }
@ -74,6 +74,7 @@ msrv_aliases! {
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
1,16,0 { STR_REPEAT }
1,15,0 { MAYBE_BOUND_IN_WHERE }
1,13,0 { QUESTION_MARK_OPERATOR }
}
/// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic

View file

@ -395,24 +395,32 @@ fn check_terminator<'tcx>(
fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bool {
cx.tcx.is_const_fn(def_id)
&& cx.tcx.lookup_const_stability(def_id).is_none_or(|const_stab| {
if let rustc_attr_parsing::StabilityLevel::Stable { since, .. } = const_stab.level {
// Checking MSRV is manually necessary because `rustc` has no such concept. This entire
// function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`.
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
&& cx
.tcx
.lookup_const_stability(def_id)
.or_else(|| {
cx.tcx
.trait_of_item(def_id)
.and_then(|trait_def_id| cx.tcx.lookup_const_stability(trait_def_id))
})
.is_none_or(|const_stab| {
if let rustc_attr_parsing::StabilityLevel::Stable { since, .. } = const_stab.level {
// Checking MSRV is manually necessary because `rustc` has no such concept. This entire
// function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`.
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
let const_stab_rust_version = match since {
StableSince::Version(version) => version,
StableSince::Current => RustcVersion::CURRENT,
StableSince::Err => return false,
};
let const_stab_rust_version = match since {
StableSince::Version(version) => version,
StableSince::Current => RustcVersion::CURRENT,
StableSince::Err => return false,
};
msrv.meets(cx, const_stab_rust_version)
} else {
// Unstable const fn, check if the feature is enabled.
cx.tcx.features().enabled(const_stab.feature) && msrv.current(cx).is_none()
}
})
msrv.meets(cx, const_stab_rust_version)
} else {
// Unstable const fn, check if the feature is enabled.
cx.tcx.features().enabled(const_stab.feature) && msrv.current(cx).is_none()
}
})
}
fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {

View file

@ -4,8 +4,8 @@
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
use crate::ty::expr_sig;
use crate::{get_parent_expr_for_hir, higher};
use rustc_ast::util::parser::AssocOp;
use rustc_ast::ast;
use rustc_ast::util::parser::AssocOp;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
@ -444,7 +444,7 @@ impl<'a> Not for Sugg<'a> {
type Output = Sugg<'a>;
fn not(self) -> Sugg<'a> {
use AssocOp::Binary;
use ast::BinOpKind::{Eq, Gt, Ge, Lt, Le, Ne};
use ast::BinOpKind::{Eq, Ge, Gt, Le, Lt, Ne};
if let Sugg::BinOp(op, lhs, rhs) = self {
let to_op = match op {
@ -515,10 +515,10 @@ pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static>
op,
AssocOp::Binary(
ast::BinOpKind::Add
| ast::BinOpKind::Sub
| ast::BinOpKind::Mul
| ast::BinOpKind::Div
| ast::BinOpKind::Rem
| ast::BinOpKind::Sub
| ast::BinOpKind::Mul
| ast::BinOpKind::Div
| ast::BinOpKind::Rem
)
)
}
@ -578,10 +578,8 @@ enum Associativity {
/// associative.
#[must_use]
fn associativity(op: AssocOp) -> Associativity {
use ast::BinOpKind::{Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub};
use rustc_ast::util::parser::AssocOp::{Assign, AssignOp, Binary, Cast, Range};
use ast::BinOpKind::{
Add, BitAnd, BitOr, BitXor, Div, Eq, Gt, Ge, And, Or, Lt, Le, Rem, Mul, Ne, Shl, Shr, Sub,
};
match op {
Assign | AssignOp(_) => Associativity::Right,
@ -994,6 +992,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
mod test {
use super::Sugg;
use rustc_ast as ast;
use rustc_ast::util::parser::AssocOp;
use std::borrow::Cow;
@ -1011,15 +1010,15 @@ mod test {
#[test]
fn binop_maybe_par() {
let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into());
let sugg = Sugg::BinOp(AssocOp::Binary(ast::BinOpKind::Add), "1".into(), "1".into());
assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into());
let sugg = Sugg::BinOp(AssocOp::Binary(ast::BinOpKind::Add), "(1 + 1)".into(), "(1 + 1)".into());
assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
}
#[test]
fn not_op() {
use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual};
use ast::BinOpKind::{Add, And, Eq, Ge, Gt, Le, Lt, Ne, Or};
fn test_not(op: AssocOp, correct: &str) {
let sugg = Sugg::BinOp(op, "x".into(), "y".into());
@ -1027,16 +1026,16 @@ mod test {
}
// Invert the comparison operator.
test_not(Equal, "x != y");
test_not(NotEqual, "x == y");
test_not(Less, "x >= y");
test_not(LessEqual, "x > y");
test_not(Greater, "x <= y");
test_not(GreaterEqual, "x < y");
test_not(AssocOp::Binary(Eq), "x != y");
test_not(AssocOp::Binary(Ne), "x == y");
test_not(AssocOp::Binary(Lt), "x >= y");
test_not(AssocOp::Binary(Le), "x > y");
test_not(AssocOp::Binary(Gt), "x <= y");
test_not(AssocOp::Binary(Ge), "x < y");
// Other operators are inverted like !(..).
test_not(Add, "!(x + y)");
test_not(LAnd, "!(x && y)");
test_not(LOr, "!(x || y)");
test_not(AssocOp::Binary(Add), "!(x + y)");
test_not(AssocOp::Binary(And), "!(x && y)");
test_not(AssocOp::Binary(Or), "!(x || y)");
}
}

View file

@ -1,3 +1,9 @@
//! JSON output and comparison functionality for Clippy warnings.
//!
//! This module handles serialization of Clippy warnings to JSON format,
//! loading warnings from JSON files, and generating human-readable diffs
//! between different linting runs.
use std::fs;
use std::path::Path;
@ -8,8 +14,10 @@ use crate::ClippyWarning;
/// This is the total number. 300 warnings results in 100 messages per section.
const DEFAULT_LIMIT_PER_LINT: usize = 300;
/// Target for total warnings to display across all lints when truncating output.
const TRUNCATION_TOTAL_TARGET: usize = 1000;
/// Representation of a single Clippy warning for JSON serialization.
#[derive(Debug, Deserialize, Serialize)]
struct LintJson {
/// The lint name e.g. `clippy::bytes_nth`
@ -21,10 +29,12 @@ struct LintJson {
}
impl LintJson {
/// Returns a tuple of name and `file_line` for sorting and comparison.
fn key(&self) -> impl Ord + '_ {
(self.name.as_str(), self.file_line.as_str())
}
/// Formats the warning information with an action verb for display.
fn info_text(&self, action: &str) -> String {
format!("{action} `{}` at [`{}`]({})", self.name, self.file_line, self.file_url)
}
@ -53,12 +63,17 @@ pub(crate) fn output(clippy_warnings: Vec<ClippyWarning>) -> String {
serde_json::to_string(&lints).unwrap()
}
/// Loads lint warnings from a JSON file at the given path.
fn load_warnings(path: &Path) -> Vec<LintJson> {
let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display()))
}
/// Generates and prints a diff between two sets of lint warnings.
///
/// Compares warnings from `old_path` and `new_path`, then displays a summary table
/// and detailed information about added, removed, and changed warnings.
pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
let old_warnings = load_warnings(old_path);
let new_warnings = load_warnings(new_path);
@ -116,6 +131,7 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
}
}
/// Container for grouped lint warnings organized by status (added/removed/changed).
#[derive(Debug)]
struct LintWarnings {
name: String,
@ -124,6 +140,7 @@ struct LintWarnings {
changed: Vec<(LintJson, LintJson)>,
}
/// Prints a formatted report for a single lint type with its warnings.
fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) {
let name = &lint.name;
let html_id = to_html_id(name);
@ -145,6 +162,7 @@ fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) {
print_changed_diff(&lint.changed, truncate_after / 3);
}
/// Prints a summary table of all lints with counts of added, removed, and changed warnings.
fn print_summary_table(lints: &[LintWarnings]) {
println!("| Lint | Added | Removed | Changed |");
println!("| ------------------------------------------ | ------: | ------: | ------: |");
@ -160,6 +178,7 @@ fn print_summary_table(lints: &[LintWarnings]) {
}
}
/// Prints a section of warnings with a header and formatted code blocks.
fn print_warnings(title: &str, warnings: &[LintJson], truncate_after: usize) {
if warnings.is_empty() {
return;
@ -180,6 +199,7 @@ fn print_warnings(title: &str, warnings: &[LintJson], truncate_after: usize) {
}
}
/// Prints a section of changed warnings with unified diff format.
fn print_changed_diff(changed: &[(LintJson, LintJson)], truncate_after: usize) {
if changed.is_empty() {
return;
@ -213,6 +233,7 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)], truncate_after: usize) {
}
}
/// Truncates a list to a maximum number of items and prints a message about truncation.
fn truncate<T>(list: &[T], truncate_after: usize) -> &[T] {
if list.len() > truncate_after {
println!(
@ -227,6 +248,7 @@ fn truncate<T>(list: &[T], truncate_after: usize) -> &[T] {
}
}
/// Prints a level 3 heading with an appropriate HTML ID for linking.
fn print_h3(lint: &str, title: &str) {
let html_id = to_html_id(lint);
// We have to use HTML here to be able to manually add an id.

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-02-27"
channel = "nightly-2025-03-20"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -51,7 +51,7 @@ The changelog for `rustc_tools_util` is available under:
<!-- REUSE-IgnoreStart -->
Copyright 2014-2024 The Rust Project Developers
Copyright 2014-2025 The Rust Project Developers
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license

View file

@ -16,7 +16,7 @@ use test_utils::IS_RUSTC_TEST_SUITE;
use ui_test::custom_flags::Flag;
use ui_test::custom_flags::rustfix::RustfixMode;
use ui_test::spanned::Spanned;
use ui_test::{Args, CommandBuilder, Config, Match, OutputConflictHandling, status_emitter};
use ui_test::{Args, CommandBuilder, Config, Match, error_on_output_conflict, status_emitter};
use std::collections::{BTreeMap, HashMap};
use std::env::{self, set_var, var_os};
@ -142,7 +142,7 @@ impl TestContext {
fn base_config(&self, test_dir: &str, mandatory_annotations: bool) -> Config {
let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into()));
let mut config = Config {
output_conflict_handling: OutputConflictHandling::Error,
output_conflict_handling: error_on_output_conflict,
filter_files: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(),
@ -220,7 +220,7 @@ fn run_internal_tests(cx: &TestContext) {
if !RUN_INTERNAL_TESTS {
return;
}
let mut config = cx.base_config("ui-internal", false);
let mut config = cx.base_config("ui-internal", true);
config.bless_command = Some("cargo uitest --features internal -- -- --bless".into());
ui_test::run_tests_generic(

View file

@ -44,6 +44,7 @@ impl Message {
".*AT&T x86 assembly syntax used",
"note: Clippy version: .*",
"the compiler unexpectedly panicked. this is a bug.",
"internal compiler error:",
])
.unwrap()
});

View file

@ -38,6 +38,7 @@ declare_tool_lint! {
// Invalid attributes
///////////////////////
declare_tool_lint! {
//~^ invalid_clippy_version_attribute
#[clippy::version = "1.2.3.4.5.6"]
pub clippy::INVALID_ONE,
Warn,
@ -46,6 +47,7 @@ declare_tool_lint! {
}
declare_tool_lint! {
//~^ invalid_clippy_version_attribute
#[clippy::version = "I'm a string"]
pub clippy::INVALID_TWO,
Warn,
@ -57,6 +59,7 @@ declare_tool_lint! {
// Missing attribute test
///////////////////////
declare_tool_lint! {
//~^ missing_clippy_version_attribute
#[clippy::version]
pub clippy::MISSING_ATTRIBUTE_ONE,
Warn,
@ -65,6 +68,7 @@ declare_tool_lint! {
}
declare_tool_lint! {
//~^ missing_clippy_version_attribute
pub clippy::MISSING_ATTRIBUTE_TWO,
Warn,
"Two",

View file

@ -2,10 +2,10 @@ error: this item has an invalid `clippy::version` attribute
--> tests/ui-internal/check_clippy_version_attribute.rs:40:1
|
LL | / declare_tool_lint! {
LL | |
LL | | #[clippy::version = "1.2.3.4.5.6"]
LL | | pub clippy::INVALID_ONE,
LL | | Warn,
LL | | "One",
... |
LL | | report_in_external_macro: true
LL | | }
| |_^
@ -20,13 +20,13 @@ LL | #![deny(clippy::internal)]
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this item has an invalid `clippy::version` attribute
--> tests/ui-internal/check_clippy_version_attribute.rs:48:1
--> tests/ui-internal/check_clippy_version_attribute.rs:49:1
|
LL | / declare_tool_lint! {
LL | |
LL | | #[clippy::version = "I'm a string"]
LL | | pub clippy::INVALID_TWO,
LL | | Warn,
LL | | "Two",
... |
LL | | report_in_external_macro: true
LL | | }
| |_^
@ -35,13 +35,13 @@ LL | | }
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this lint is missing the `clippy::version` attribute or version value
--> tests/ui-internal/check_clippy_version_attribute.rs:59:1
--> tests/ui-internal/check_clippy_version_attribute.rs:61:1
|
LL | / declare_tool_lint! {
LL | |
LL | | #[clippy::version]
LL | | pub clippy::MISSING_ATTRIBUTE_ONE,
LL | | Warn,
LL | | "Two",
... |
LL | | report_in_external_macro: true
LL | | }
| |_^
@ -51,9 +51,10 @@ LL | | }
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this lint is missing the `clippy::version` attribute or version value
--> tests/ui-internal/check_clippy_version_attribute.rs:67:1
--> tests/ui-internal/check_clippy_version_attribute.rs:70:1
|
LL | / declare_tool_lint! {
LL | |
LL | | pub clippy::MISSING_ATTRIBUTE_TWO,
LL | | Warn,
LL | | "Two",

View file

@ -21,6 +21,7 @@ declare_tool_lint! {
declare_tool_lint! {
/// # What it does
/// Check for lint formulations that are correct
//~^ almost_standard_lint_formulation
#[clippy::version = "pre 1.29.0"]
pub clippy::INVALID1,
Warn,
@ -31,6 +32,7 @@ declare_tool_lint! {
declare_tool_lint! {
/// # What it does
/// Detects uses of incorrect formulations
//~^ almost_standard_lint_formulation
#[clippy::version = "pre 1.29.0"]
pub clippy::INVALID2,
Warn,

View file

@ -9,7 +9,7 @@ LL | /// Check for lint formulations that are correct
= help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]`
error: non-standard lint formulation
--> tests/ui-internal/check_formulation.rs:33:5
--> tests/ui-internal/check_formulation.rs:34:5
|
LL | /// Detects uses of incorrect formulations
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -33,18 +33,23 @@ impl EarlyLintPass for Pass {
let predicate = true;
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_help(expr.span, help_msg);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.help(help_msg);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_note(expr.span, note_msg);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.note(note_msg);
});

View file

@ -2,6 +2,7 @@ error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:35:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
LL | | });
| |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)`
@ -14,33 +15,37 @@ LL | #![deny(clippy::internal)]
= note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:38:9
--> tests/ui-internal/collapsible_span_lint_calls.rs:39:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_help(expr.span, help_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:41:9
--> tests/ui-internal/collapsible_span_lint_calls.rs:43:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.help(help_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:44:9
--> tests/ui-internal/collapsible_span_lint_calls.rs:47:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_note(expr.span, note_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:47:9
--> tests/ui-internal/collapsible_span_lint_calls.rs:51:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.note(note_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`

View file

@ -10,5 +10,6 @@
#![allow(clippy::missing_clippy_version_attribute)]
fn it_looks_like_you_are_trying_to_kill_clippy() {}
//~^ ice: Would you like some help with that?
fn main() {}

View file

@ -1,9 +1,18 @@
note: no errors encountered even though delayed bugs were created
thread '<unnamed>' panicked at clippy_lints/src/utils/internal_lints/produce_ice.rs:
Would you like some help with that?
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: those delayed bugs will now be shown as internal compiler errors
error: the compiler unexpectedly panicked. this is a bug.
error: internal compiler error: Would you like some help with that?
--> tests/ui-internal/custom_ice_message.rs:12:1
|
LL | fn it_looks_like_you_are_trying_to_kill_clippy() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: delayed at clippy_lints/src/utils/internal_lints/produce_ice.rs - disabled backtrace
--> tests/ui-internal/custom_ice_message.rs:12:1
|
LL | fn it_looks_like_you_are_trying_to_kill_clippy() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml
@ -13,9 +22,5 @@ note: rustc <version> running on <target>
note: compiler flags: -Z ui-testing -Z deduplicate-diagnostics=no
query stack during panic:
#0 [early_lint_checks] perform lints prior to AST lowering
#1 [hir_crate] getting the crate HIR
... and 3 other queries... use `env RUST_BACKTRACE=1` to see the full query stack
note: Clippy version: foo

View file

@ -16,6 +16,7 @@ declare_tool_lint! {
}
declare_tool_lint! {
//~^ default_lint
pub clippy::TEST_LINT_DEFAULT,
Warn,
"default lint description",

View file

@ -2,6 +2,7 @@ error: the lint `TEST_LINT_DEFAULT` has the default lint description
--> tests/ui-internal/default_lint.rs:18:1
|
LL | / declare_tool_lint! {
LL | |
LL | | pub clippy::TEST_LINT_DEFAULT,
LL | | Warn,
LL | | "default lint description",

View file

@ -12,12 +12,14 @@ use rustc_middle::ty::TyCtxt;
pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) {
cx.span_lint(lint, span, |lint| {
//~^ disallowed_methods
lint.primary_message(msg);
});
}
pub fn b(tcx: TyCtxt<'_>, lint: &'static Lint, hir_id: HirId, span: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) {
tcx.node_span_lint(lint, hir_id, span, |lint| {
//~^ disallowed_methods
lint.primary_message(msg);
});
}

View file

@ -9,7 +9,7 @@ LL | cx.span_lint(lint, span, |lint| {
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
--> tests/ui-internal/disallow_span_lint.rs:20:9
--> tests/ui-internal/disallow_span_lint.rs:21:9
|
LL | tcx.node_span_lint(lint, hir_id, span, |lint| {
| ^^^^^^^^^^^^^^

View file

@ -15,15 +15,19 @@ macro_rules! sym {
fn main() {
// Direct use of Symbol::intern
let _ = rustc_span::sym::f32;
//~^ interning_defined_symbol
// Using a sym macro
let _ = rustc_span::sym::f32;
//~^ interning_defined_symbol
// Correct suggestion when symbol isn't stringified constant name
let _ = rustc_span::sym::proc_dash_macro;
//~^ interning_defined_symbol
// interning a keyword
let _ = rustc_span::kw::SelfLower;
//~^ interning_defined_symbol
// Interning a symbol that is not defined
let _ = Symbol::intern("xyz123");

View file

@ -15,15 +15,19 @@ macro_rules! sym {
fn main() {
// Direct use of Symbol::intern
let _ = Symbol::intern("f32");
//~^ interning_defined_symbol
// Using a sym macro
let _ = sym!(f32);
//~^ interning_defined_symbol
// Correct suggestion when symbol isn't stringified constant name
let _ = Symbol::intern("proc-macro");
//~^ interning_defined_symbol
// interning a keyword
let _ = Symbol::intern("self");
//~^ interning_defined_symbol
// Interning a symbol that is not defined
let _ = Symbol::intern("xyz123");

View file

@ -12,19 +12,19 @@ LL | #![deny(clippy::internal)]
= note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]`
error: interning a defined symbol
--> tests/ui-internal/interning_defined_symbol.rs:20:13
--> tests/ui-internal/interning_defined_symbol.rs:21:13
|
LL | let _ = sym!(f32);
| ^^^^^^^^^ help: try: `rustc_span::sym::f32`
error: interning a defined symbol
--> tests/ui-internal/interning_defined_symbol.rs:23:13
--> tests/ui-internal/interning_defined_symbol.rs:25:13
|
LL | let _ = Symbol::intern("proc-macro");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro`
error: interning a defined symbol
--> tests/ui-internal/interning_defined_symbol.rs:26:13
--> tests/ui-internal/interning_defined_symbol.rs:29:13
|
LL | let _ = Symbol::intern("self");
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::kw::SelfLower`

View file

@ -27,6 +27,7 @@ impl_lint_pass!(Pass => [TEST_LINT]);
impl EarlyLintPass for Pass {
extract_msrv_attr!();
//~^ missing_msrv_attr_impl
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
}

View file

@ -26,6 +26,7 @@ struct Pass {
impl_lint_pass!(Pass => [TEST_LINT]);
impl EarlyLintPass for Pass {
//~^ missing_msrv_attr_impl
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
}

Some files were not shown because too many files have changed in this diff Show more