Merge commit '1e5237f4a5
' into clippy-subtree-update
This commit is contained in:
commit
ae31f7a024
216 changed files with 4613 additions and 1146 deletions
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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`"
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: `\\`");
|
||||
},
|
||||
);
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
|
|
@ -40,7 +40,7 @@ declare_clippy_lint! {
|
|||
/// }
|
||||
///
|
||||
/// fn f(to: TO) -> Option<usize> {
|
||||
/// to.magic
|
||||
/// to.magic
|
||||
/// }
|
||||
///
|
||||
/// struct TR {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
/// ```
|
||||
|
|
|
@ -26,7 +26,7 @@ declare_clippy_lint! {
|
|||
/// let a = a;
|
||||
///
|
||||
/// fn foo(b: i32) {
|
||||
/// let b = b;
|
||||
/// let b = b;
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(..),
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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)`
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ declare_tool_lint! {
|
|||
}
|
||||
|
||||
declare_tool_lint! {
|
||||
//~^ default_lint
|
||||
pub clippy::TEST_LINT_DEFAULT,
|
||||
Warn,
|
||||
"default lint description",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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| {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue