Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2025-04-03 21:31:02 +02:00
commit ab7e525929
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
377 changed files with 6426 additions and 3724 deletions

View file

@ -24,7 +24,7 @@ jobs:
- name: Check Changelog
if: ${{ github.event_name == 'pull_request' }}
run: |
body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR_NUMBER" | \
body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | \
python -c "import sys, json; print(json.load(sys.stdin)['body'])")
output=$(awk '/^changelog:\s*\S/ && !/changelog: \[.*\]: your change/' <<< "$body" | sed "s/changelog:\s*//g")
if [ -z "$output" ]; then

View file

@ -8,6 +8,10 @@ on:
tags:
- rust-1.**
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
env:
TARGET_BRANCH: 'gh-pages'
SHA: '${{ github.sha }}'

View file

@ -66,7 +66,7 @@ jobs:
- name: Run lintcheck
if: steps.cache-json.outputs.cache-hit != 'true'
run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
- name: Upload base JSON
uses: actions/upload-artifact@v4
@ -97,7 +97,7 @@ jobs:
run: cargo build --manifest-path=lintcheck/Cargo.toml
- name: Run lintcheck
run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
- name: Upload head JSON
uses: actions/upload-artifact@v4

View file

@ -5516,6 +5516,7 @@ Released 2018-09-13
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts
[`cfg_not_test`]: https://rust-lang.github.io/rust-clippy/master/index.html#cfg_not_test
[`char_indices_as_byte_indices`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_indices_as_byte_indices
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
@ -5681,6 +5682,7 @@ Released 2018-09-13
[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
[`ignore_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignore_without_reason
[`ignored_unit_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignored_unit_patterns
[`impl_hash_borrow_with_str_and_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_hash_borrow_with_str_and_bytes
[`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params
@ -5789,6 +5791,7 @@ Released 2018-09-13
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
@ -6346,6 +6349,7 @@ Released 2018-09-13
[`await-holding-invalid-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#await-holding-invalid-types
[`cargo-ignore-publish`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cargo-ignore-publish
[`check-incompatible-msrv-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-incompatible-msrv-in-tests
[`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
@ -6362,7 +6366,7 @@ Released 2018-09-13
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
[`lint-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-inconsistent-struct-field-initializers
[`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools

View file

@ -42,7 +42,7 @@ walkdir = "2.3"
filetime = "0.2.9"
itertools = "0.12"
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
rinja = { version = "0.3", default-features = false, features = ["config"] }
askama = { version = "0.13", default-features = false, features = ["alloc", "config", "derive"] }
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
@ -71,3 +71,10 @@ harness = false
[[test]]
name = "dogfood"
harness = false
# quine-mc_cluskey makes up a significant part of the runtime in dogfood
# due to the number of conditions in the clippy_lints crate
# and enabling optimizations for that specific dependency helps a bit
# without increasing total build times.
[profile.dev.package.quine-mc_cluskey]
opt-level = 3

View file

@ -30,6 +30,7 @@
- [Updating the Changelog](development/infrastructure/changelog_update.md)
- [Release a New Version](development/infrastructure/release.md)
- [The Clippy Book](development/infrastructure/book.md)
- [Benchmarking Clippy](development/infrastructure/benchmarking.md)
- [Proposals](development/proposals/README.md)
- [Roadmap 2021](development/proposals/roadmap-2021.md)
- [Syntax Tree Patterns](development/proposals/syntax-tree-patterns.md)

View file

@ -99,6 +99,7 @@ struct A;
impl A {
pub fn fo(&self) {}
pub fn foo(&self) {}
//~^ foo_functions
pub fn food(&self) {}
}
@ -106,12 +107,14 @@ impl A {
trait B {
fn fo(&self) {}
fn foo(&self) {}
//~^ foo_functions
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {}
//~^ foo_functions
fn food() {}
fn main() {
@ -122,17 +125,20 @@ fn main() {
}
```
Now we can run the test with `TESTNAME=foo_functions cargo uibless`, currently
this test is meaningless though.
Note that we are adding comment annotations with the name of our lint to mark
lines where we expect an error. Once we have implemented our lint we can run
`TESTNAME=foo_functions cargo uibless` to generate the `.stderr` file. If our
lint makes use of structured suggestions then this command will also generate
the corresponding `.fixed` file.
While we are working on implementing our lint, we can keep running the UI test.
That allows us to check if the output is turning into what we want by checking the
`.stderr` file that gets updated on every test run.
Running `TESTNAME=foo_functions cargo uitest` should pass on its own. When we
commit our lint, we need to commit the generated `.stderr` files, too. In
general, you should only commit files changed by `cargo bless` for the
specific lint you are creating/editing.
Once we have implemented our lint running `TESTNAME=foo_functions cargo uitest`
should pass on its own. When we commit our lint, we need to commit the generated
`.stderr` and if applicable `.fixed` files, too. In general, you should only
commit files changed by `cargo bless` for the specific lint you are creating/editing.
> _Note:_ you can run multiple test files by specifying a comma separated list:
> `TESTNAME=foo_functions,test2,test3`.

View file

@ -147,7 +147,7 @@ following:
First, take note of the toolchain
[override](https://rust-lang.github.io/rustup/overrides.html) in
`/rust-toolchain`. We will use this override to install Clippy into the right
`/rust-toolchain.toml`. We will use this override to install Clippy into the right
toolchain.
> Tip: You can view the active toolchain for the current directory with `rustup

View file

@ -0,0 +1,55 @@
# Benchmarking Clippy
Benchmarking Clippy is similar to using our Lintcheck tool, in fact, it even
uses the same tool! Just by adding a `--perf` flag it will transform Lintcheck
into a very simple but powerful benchmarking tool!
It requires having the [`perf` tool][perf] installed, as `perf` is what's actually
profiling Clippy under the hood.
The lintcheck `--perf` tool generates a series of `perf.data` in the
`target/lintcheck/sources/<package>-<version>` directories. Each `perf.data`
corresponds to the package which is contained.
Lintcheck uses the `-g` flag, meaning that you can get stack traces for richer
analysis, including with tools such as [flamegraph][flamegraph-perf]
(or [`flamegraph-rs`][flamegraph-rs]).
Currently, we only measure instruction count, as it's the most reproducible metric
and [rustc-perf][rustc-perf] also considers it the main number to focus on.
## Benchmarking a PR
Having a benchmarking tool directly implemented into lintcheck gives us the
ability to benchmark any given PR just by making a before and after
Here's the way you can get into any PR, benchmark it, and then benchmark
`master`.
The first `perf.data` will not have any numbers appended, but any subsequent
benchmark will be written to `perf.data.number` with a number growing for 0.
All benchmarks are compressed so that you can
```bash
git fetch upstream pull/<PR_NUMBER>/head:<BRANCH_NAME>
git switch BRANCHNAME
# Bench
cargo lintcheck --perf
# Get last common commit, checkout that
LAST_COMMIT=$(git log BRANCHNAME..master --oneline | tail -1 | cut -c 1-11)
git switch -c temporary $LAST_COMMIT
# We're now on master
# Bench
cargo lintcheck --perf
perf diff ./target/lintcheck/sources/CRATE/perf.data ./target/lintcheck/sources/CRATE/perf.data.0
```
[perf]: https://perfwiki.github.io/main/
[flamegraph-perf]: https://github.com/brendangregg/FlameGraph
[flamegraph-rs]: https://github.com/flamegraph-rs/flamegraph
[rustc-perf]: https://github.com/rust-lang/rustc-perf

View file

@ -88,9 +88,6 @@ git push upstream stable
After updating the `stable` branch, tag the HEAD commit and push it to the
Clippy repo.
> Note: Only push the tag once the Deploy GitHub action of the `beta` branch is
> finished. Otherwise the deploy for the tag might fail.
```bash
git tag rust-1.XX.0 # XX should be exchanged with the corresponding version
git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote

View file

@ -86,7 +86,7 @@ to be run inside the `rust` directory):
4. Bump the nightly version in the Clippy repository by running these commands:
```bash
cargo dev sync update_nightly
git commit -m "Bump nightly version -> YYYY-MM-DD" rust-toolchain clippy_utils/README.md
git commit -m "Bump nightly version -> YYYY-MM-DD" rust-toolchain.toml clippy_utils/README.md
```
5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to
accelerate the process ping the `@rust-lang/clippy` team in your PR and/or

View file

@ -41,20 +41,23 @@ Update the file with some examples to get started:
struct A;
impl A {
pub fn fo(&self) {}
pub fn foo(&self) {} //~ ERROR: function called "foo"
pub fn foo(&self) {}
//~^ foo_functions
pub fn food(&self) {}
}
// Default trait methods
trait B {
fn fo(&self) {}
fn foo(&self) {} //~ ERROR: function called "foo"
fn foo(&self) {}
//~^ foo_functions
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {} //~ ERROR: function called "foo"
fn foo() {}
//~^ foo_functions
fn food() {}
fn main() {
@ -66,19 +69,38 @@ fn main() {
```
Without actual lint logic to emit the lint when we see a `foo` function name,
this test will just pass, because no lint will be emitted. However, we can now
run the test with the following command:
this test will fail, because we expect errors at lines marked with
`//~^ foo_functions`. However, we can now run the test with the following command:
```sh
$ TESTNAME=foo_functions cargo uitest
```
Clippy will compile and it will conclude with an `ok` for the tests:
Clippy will compile and it will fail complaining it didn't receive any errors:
```
...Clippy warnings and test outputs...
test compile_test ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
error: diagnostic code `clippy::foo_functions` not found on line 8
--> tests/ui/foo_functions.rs:9:10
|
9 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
error: diagnostic code `clippy::foo_functions` not found on line 16
--> tests/ui/foo_functions.rs:17:10
|
17 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
error: diagnostic code `clippy::foo_functions` not found on line 23
--> tests/ui/foo_functions.rs:24:6
|
24 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
```
This is normal. After all, we wrote a bunch of Rust code but we haven't really

View file

@ -425,6 +425,33 @@ Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
* [`incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv)
## `check-inconsistent-struct-field-initializers`
Whether to suggest reordering constructor fields when initializers are present.
Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
suggested code would compile, it can change semantics if the initializer expressions have side effects. The
following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
```rust
struct MyStruct {
vector: Vec<u32>,
length: usize
}
fn main() {
let vector = vec![1,2,3];
MyStruct { length: vector.len(), vector};
}
```
[from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
**Default Value:** `false`
---
**Affected lints:**
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
## `check-private-items`
Whether to also run the listed lints on private items.
@ -613,31 +640,15 @@ The maximum size of the `Err`-variant in a `Result` returned from a function
* [`result_large_err`](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err)
## `lint-inconsistent-struct-field-initializers`
Whether to suggest reordering constructor fields when initializers are present.
Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
suggested code would compile, it can change semantics if the initializer expressions have side effects. The
following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
```rust
struct MyStruct {
vector: Vec<u32>,
length: usize
}
fn main() {
let vector = vec![1,2,3];
MyStruct { length: vector.len(), vector};
}
```
[from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
## `lint-commented-code`
Whether collapsible `if` chains are linted if they contain comments inside the parts
that would be collapsed.
**Default Value:** `false`
---
**Affected lints:**
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
* [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if)
## `literal-representation-threshold`
@ -1059,7 +1070,8 @@ The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
## `warn-on-all-wildcard-imports`
Whether to allow certain wildcard imports (prelude, super in tests).
Whether to emit warnings on all wildcard imports, including those from `prelude`, from `super` in tests,
or for `pub use` reexports.
**Default Value:** `false`

View file

@ -1,15 +1,20 @@
avoid-breaking-exported-api = false
lint-inconsistent-struct-field-initializers = true
check-inconsistent-struct-field-initializers = true
lint-commented-code = true
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
allow-invalid = true
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
allow-invalid = true
[[disallowed-methods]]
path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"
allow-invalid = true

View file

@ -120,12 +120,7 @@ impl ConfError {
Self {
message: message.into(),
suggestion,
span: Span::new(
file.start_pos + BytePos::from_usize(span.start),
file.start_pos + BytePos::from_usize(span.end),
SyntaxContext::root(),
None,
),
span: span_from_toml_range(file, span),
}
}
}
@ -176,11 +171,61 @@ macro_rules! default_text {
};
}
macro_rules! deserialize {
($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
let value_span = raw_value.span();
let value = match <$ty>::deserialize(raw_value.into_inner()) {
Err(e) => {
$errors.push(ConfError::spanned(
$file,
e.to_string().replace('\n', " ").trim(),
None,
value_span,
));
continue;
},
Ok(value) => value,
};
(value, value_span)
}};
($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
let mut disallowed_paths_span = Range {
start: usize::MAX,
end: usize::MIN,
};
let mut disallowed_paths = Vec::new();
for raw_value in array {
let value_span = raw_value.span();
let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
{
Err(e) => {
$errors.push(ConfError::spanned(
$file,
e.to_string().replace('\n', " ").trim(),
None,
value_span,
));
continue;
},
Ok(disallowed_path) => disallowed_path,
};
disallowed_paths_span = union(&disallowed_paths_span, &value_span);
disallowed_path.set_span(span_from_toml_range($file, value_span));
disallowed_paths.push(disallowed_path);
}
(disallowed_paths, disallowed_paths_span)
}};
}
macro_rules! define_Conf {
($(
$(#[doc = $doc:literal])+
$(#[conf_deprecated($dep:literal, $new_conf:ident)])?
$(#[default_text = $default_text:expr])?
$(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
$(#[lints($($for_lints:ident),* $(,)?)])?
$name:ident: $ty:ty = $default:expr,
)*) => {
@ -218,42 +263,46 @@ macro_rules! define_Conf {
let mut value_spans = HashMap::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
// Declare a local variable for each field available to a configuration file.
$(let mut $name = None;)*
// could get `Field` here directly, but get `String` first for diagnostics
while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
Err(e) => {
let e: FieldError = e;
errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
continue;
}
$(Ok(Field::$name) => {
Ok(field) => field
};
match field {
$(Field::$name => {
// Is this a deprecated field, i.e., is `$dep` set? If so, push a warning.
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
let value_span = raw_value.span();
match <$ty>::deserialize(raw_value.into_inner()) {
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
Ok(value) => match $name {
Some(_) => {
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
}
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 {
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
"duplicate field `", stringify!($new_conf),
"` (provided as `", stringify!($name), "`)"
), None, name.span())),
None => $new_conf = $name.clone(),
})?
},
}
let (value, value_span) =
deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
// Was this field set previously?
if $name.is_some() {
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
continue;
}
$name = Some(value);
value_spans.insert(name.get_ref().as_str().to_string(), value_span);
// If this is a deprecated field, was the new field (`$new_conf`) set previously?
// Note that `$new_conf` is one of the defined `$name`s.
$(match $new_conf {
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
"duplicate field `", stringify!($new_conf),
"` (provided as `", stringify!($name), "`)"
), None, name.span())),
None => $new_conf = $name.clone(),
})?
})*
// ignore contents of the third_party key
Ok(Field::third_party) => drop(map.next_value::<IgnoredAny>())
Field::third_party => drop(map.next_value::<IgnoredAny>())
}
}
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
@ -275,6 +324,22 @@ macro_rules! define_Conf {
};
}
fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
Range {
start: cmp::min(x.start, y.start),
end: cmp::max(x.end, y.end),
}
}
fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
Span::new(
file.start_pos + BytePos::from_usize(span.start),
file.start_pos + BytePos::from_usize(span.end),
SyntaxContext::root(),
None,
)
}
define_Conf! {
/// Which crates to allow absolute paths from
#[lints(absolute_paths)]
@ -461,6 +526,7 @@ define_Conf! {
)]
avoid_breaking_exported_api: bool = true,
/// The list of types which may not be held across an await point.
#[disallowed_paths_allow_replacements = false]
#[lints(await_holding_invalid_type)]
await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
/// DEPRECATED LINT: BLACKLISTED_NAME.
@ -474,6 +540,26 @@ define_Conf! {
/// Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
#[lints(incompatible_msrv)]
check_incompatible_msrv_in_tests: bool = false,
/// Whether to suggest reordering constructor fields when initializers are present.
///
/// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
/// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
/// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
///
/// ```rust
/// struct MyStruct {
/// vector: Vec<u32>,
/// length: usize
/// }
/// fn main() {
/// let vector = vec![1,2,3];
/// MyStruct { length: vector.len(), vector};
/// }
/// ```
///
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
#[lints(inconsistent_struct_constructor)]
check_inconsistent_struct_field_initializers: bool = false,
/// Whether to also run the listed lints on private items.
#[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
check_private_items: bool = false,
@ -486,9 +572,11 @@ define_Conf! {
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
cyclomatic_complexity_threshold: u64 = 25,
/// The list of disallowed macros, written as fully qualified paths.
#[disallowed_paths_allow_replacements = true]
#[lints(disallowed_macros)]
disallowed_macros: Vec<DisallowedPath> = Vec::new(),
/// The list of disallowed methods, written as fully qualified paths.
#[disallowed_paths_allow_replacements = true]
#[lints(disallowed_methods)]
disallowed_methods: Vec<DisallowedPath> = Vec::new(),
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
@ -497,6 +585,7 @@ define_Conf! {
#[lints(disallowed_names)]
disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
/// The list of disallowed types, written as fully qualified paths.
#[disallowed_paths_allow_replacements = true]
#[lints(disallowed_types)]
disallowed_types: Vec<DisallowedPath> = Vec::new(),
/// The list of words this lint should not consider as identifiers needing ticks. The value
@ -549,25 +638,15 @@ define_Conf! {
/// The maximum size of the `Err`-variant in a `Result` returned from a function
#[lints(result_large_err)]
large_error_threshold: u64 = 128,
/// Whether collapsible `if` chains are linted if they contain comments inside the parts
/// that would be collapsed.
#[lints(collapsible_if)]
lint_commented_code: bool = false,
/// Whether to suggest reordering constructor fields when initializers are present.
/// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers
///
/// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
/// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
/// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
///
/// ```rust
/// struct MyStruct {
/// vector: Vec<u32>,
/// length: usize
/// }
/// fn main() {
/// let vector = vec![1,2,3];
/// MyStruct { length: vector.len(), vector};
/// }
/// ```
///
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
#[lints(inconsistent_struct_constructor)]
/// Use the `check-inconsistent-struct-field-initializers` configuration instead.
#[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
lint_inconsistent_struct_field_initializers: bool = false,
/// The lower bound for linting decimal literals
#[lints(decimal_literal_representation)]
@ -760,7 +839,8 @@ define_Conf! {
/// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
#[lints(verbose_bit_mask)]
verbose_bit_mask_threshold: u64 = 1,
/// Whether to allow certain wildcard imports (prelude, super in tests).
/// Whether to emit warnings on all wildcard imports, including those from `prelude`, from `super` in tests,
/// or for `pub use` reexports.
#[lints(wildcard_imports)]
warn_on_all_wildcard_imports: bool = false,
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
@ -981,7 +1061,23 @@ impl serde::de::Error for FieldError {
// set and allows it.
use fmt::Write;
let mut expected = expected.to_vec();
let metadata = get_configuration_metadata();
let deprecated = metadata
.iter()
.filter_map(|conf| {
if conf.deprecation_reason.is_some() {
Some(conf.name.as_str())
} else {
None
}
})
.collect::<Vec<_>>();
let mut expected = expected
.iter()
.copied()
.filter(|name| !deprecated.contains(name))
.collect::<Vec<_>>();
expected.sort_unstable();
let (rows, column_widths) = calculate_dimensions(&expected);
@ -1064,7 +1160,13 @@ mod tests {
fn configs_are_tested() {
let mut names: HashSet<String> = crate::get_configuration_metadata()
.into_iter()
.map(|meta| meta.name.replace('_', "-"))
.filter_map(|meta| {
if meta.deprecation_reason.is_none() {
Some(meta.name.replace('_', "-"))
} else {
None
}
})
.collect();
let toml_files = WalkDir::new("../tests")

View file

@ -13,6 +13,7 @@
rustc::untranslatable_diagnostic
)]
extern crate rustc_data_structures;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_middle;

View file

@ -1,5 +1,7 @@
use clippy_utils::def_path_def_ids;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, Diag};
use rustc_hir::PrimTy;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
@ -21,6 +23,17 @@ pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
path: String,
reason: Option<String>,
replacement: Option<String>,
/// Setting `allow_invalid` to true suppresses a warning if `path` does not refer to an existing
/// definition.
///
/// This could be useful when conditional compilation is used, or when a clippy.toml file is
/// shared among multiple projects.
allow_invalid: bool,
/// The span of the `DisallowedPath`.
///
/// Used for diagnostics.
#[serde(skip_serializing)]
span: Span,
}
impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
@ -36,6 +49,8 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<R
path: enum_.path().to_owned(),
reason: enum_.reason().map(ToOwned::to_owned),
replacement: enum_.replacement().map(ToOwned::to_owned),
allow_invalid: enum_.allow_invalid(),
span: Span::default(),
})
}
}
@ -50,6 +65,8 @@ enum DisallowedPathEnum {
path: String,
reason: Option<String>,
replacement: Option<String>,
#[serde(rename = "allow-invalid")]
allow_invalid: Option<bool>,
},
}
@ -58,7 +75,7 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
&self.path
}
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) + use<'_, REPLACEMENT_ALLOWED> {
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) {
move |diag| {
if let Some(replacement) = &self.replacement {
diag.span_suggestion(
@ -72,6 +89,14 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
}
}
}
pub fn span(&self) -> Span {
self.span
}
pub fn set_span(&mut self, span: Span) {
self.span = span;
}
}
impl DisallowedPathEnum {
@ -94,20 +119,87 @@ impl DisallowedPathEnum {
Self::Simple(_) => None,
}
}
fn allow_invalid(&self) -> bool {
match &self {
Self::WithReason { allow_invalid, .. } => allow_invalid.unwrap_or_default(),
Self::Simple(_) => false,
}
}
}
/// Creates a map of disallowed items to the reason they were disallowed.
#[allow(clippy::type_complexity)]
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
tcx: TyCtxt<'_>,
disallowed: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
) -> DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> {
disallowed
.iter()
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x))
.flat_map(|(name, path, disallowed_path)| {
def_path_def_ids(tcx, &path).map(move |id| (id, (name, disallowed_path)))
})
.collect()
disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
def_kind_predicate: impl Fn(DefKind) -> bool,
predicate_description: &str,
allow_prim_tys: bool,
) -> (
DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
) {
let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
FxHashMap::default();
for disallowed_path in disallowed_paths {
let path = disallowed_path.path();
let mut resolutions = clippy_utils::def_path_res(tcx, &path.split("::").collect::<Vec<_>>());
let mut found_def_id = None;
let mut found_prim_ty = false;
resolutions.retain(|res| match res {
Res::Def(def_kind, def_id) => {
found_def_id = Some(*def_id);
def_kind_predicate(*def_kind)
},
Res::PrimTy(_) => {
found_prim_ty = true;
allow_prim_tys
},
_ => false,
});
if resolutions.is_empty() {
let span = disallowed_path.span();
if let Some(def_id) = found_def_id {
tcx.sess.dcx().span_warn(
span,
format!(
"expected a {predicate_description}, found {} {}",
tcx.def_descr_article(def_id),
tcx.def_descr(def_id)
),
);
} else if found_prim_ty {
tcx.sess.dcx().span_warn(
span,
format!("expected a {predicate_description}, found a primitive type",),
);
} else if !disallowed_path.allow_invalid {
tcx.sess.dcx().span_warn(
span,
format!("`{path}` does not refer to an existing {predicate_description}"),
);
}
}
for res in resolutions {
match res {
Res::Def(_, def_id) => {
def_ids.insert(def_id, (path, disallowed_path));
},
Res::PrimTy(ty) => {
prim_tys.insert(ty, (path, disallowed_path));
},
_ => unreachable!(),
}
}
}
(def_ids, prim_tys)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]

View file

@ -334,7 +334,7 @@ struct SyncCommand {
#[derive(Subcommand)]
enum SyncSubcommand {
#[command(name = "update_nightly")]
/// Update nightly version in rust-toolchain and `clippy_utils`
/// Update nightly version in `rust-toolchain.toml` and `clippy_utils`
UpdateNightly,
}

View file

@ -62,7 +62,7 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
println!("Created toolchain {name}, use it in other projects with e.g. `cargo +{name} clippy`");
if !standalone {
println!("Note: This will need to be re-run whenever the Clippy `rust-toolchain` changes");
println!("Note: This will need to be re-run whenever the Clippy `rust-toolchain.toml` changes");
}
}

View file

@ -10,7 +10,7 @@ pub fn update_nightly() {
let date = Utc::now().format("%Y-%m-%d").to_string();
replace_region_in_file(
UpdateMode::Change,
Path::new("rust-toolchain"),
Path::new("rust-toolchain.toml"),
"# begin autogenerated nightly\n",
"# end autogenerated nightly",
|res| {

View file

@ -402,53 +402,53 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
}
}
if path.exists() {
if let Some(lint) = lints.iter().find(|l| l.name == name) {
if lint.module == name {
// The lint name is the same as the file, we can just delete the entire file
fs::remove_file(path)?;
} else {
// We can't delete the entire file, just remove the declaration
if path.exists()
&& let Some(lint) = lints.iter().find(|l| l.name == name)
{
if lint.module == name {
// The lint name is the same as the file, we can just delete the entire file
fs::remove_file(path)?;
} else {
// We can't delete the entire file, just remove the declaration
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
// Remove clippy_lints/src/some_mod/some_lint.rs
let mut lint_mod_path = path.to_path_buf();
lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs");
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
// Remove clippy_lints/src/some_mod/some_lint.rs
let mut lint_mod_path = path.to_path_buf();
lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs");
let _ = fs::remove_file(lint_mod_path);
}
let mut content =
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
eprintln!(
"warn: you will have to manually remove any code related to `{name}` from `{}`",
path.display()
);
assert!(
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
"error: `{}` does not contain lint `{}`'s declaration",
path.display(),
lint.name
);
// Remove lint declaration (declare_clippy_lint!)
content.replace_range(lint.declaration_range.clone(), "");
// Remove the module declaration (mod xyz;)
let mod_decl = format!("\nmod {name};");
content = content.replacen(&mod_decl, "", 1);
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
let _ = fs::remove_file(lint_mod_path);
}
remove_test_assets(name);
remove_lint(name, lints);
return Ok(true);
let mut content =
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
eprintln!(
"warn: you will have to manually remove any code related to `{name}` from `{}`",
path.display()
);
assert!(
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
"error: `{}` does not contain lint `{}`'s declaration",
path.display(),
lint.name
);
// Remove lint declaration (declare_clippy_lint!)
content.replace_range(lint.declaration_range.clone(), "");
// Remove the module declaration (mod xyz;)
let mod_decl = format!("\nmod {name};");
content = content.replacen(&mod_decl, "", 1);
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
}
remove_test_assets(name);
remove_lint(name, lints);
return Ok(true);
}
Ok(false)

View file

@ -30,10 +30,10 @@ pub fn clippy_project_root() -> PathBuf {
let current_dir = std::env::current_dir().unwrap();
for path in current_dir.ancestors() {
let result = fs::read_to_string(path.join("Cargo.toml"));
if let Err(err) = &result {
if err.kind() == io::ErrorKind::NotFound {
continue;
}
if let Err(err) = &result
&& err.kind() == io::ErrorKind::NotFound
{
continue;
}
let content = result.unwrap();

View file

@ -263,10 +263,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
continue;
}
if let Some(cur_v) = cur_v {
if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span {
Self::lint_member_name(cx, &variant.ident, &cur_v.ident);
}
if let Some(cur_v) = cur_v
&& cur_v.ident.name.as_str() > variant.ident.name.as_str()
&& cur_v.span != variant.span
{
Self::lint_member_name(cx, &variant.ident, &cur_v.ident);
}
cur_v = Some(variant);
}
@ -278,10 +279,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
continue;
}
if let Some(cur_f) = cur_f {
if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span {
Self::lint_member_name(cx, &field.ident, &cur_f.ident);
}
if let Some(cur_f) = cur_f
&& cur_f.ident.name.as_str() > field.ident.name.as_str()
&& cur_f.span != field.span
{
Self::lint_member_name(cx, &field.ident, &cur_f.ident);
}
cur_f = Some(field);
}

View file

@ -12,17 +12,17 @@ declare_clippy_lint! {
/// regardless of whether good alternatives exist or not. If you want more
/// precise lints for `as`, please consider using these separate lints:
///
/// - clippy::cast_lossless
/// - clippy::cast_possible_truncation
/// - clippy::cast_possible_wrap
/// - clippy::cast_precision_loss
/// - clippy::cast_sign_loss
/// - clippy::char_lit_as_u8
/// - clippy::fn_to_numeric_cast
/// - clippy::fn_to_numeric_cast_with_truncation
/// - clippy::ptr_as_ptr
/// - clippy::unnecessary_cast
/// - invalid_reference_casting
/// - `clippy::cast_lossless`
/// - `clippy::cast_possible_truncation`
/// - `clippy::cast_possible_wrap`
/// - `clippy::cast_precision_loss`
/// - `clippy::cast_sign_loss`
/// - `clippy::char_lit_as_u8`
/// - `clippy::fn_to_numeric_cast`
/// - `clippy::fn_to_numeric_cast_with_truncation`
/// - `clippy::ptr_as_ptr`
/// - `clippy::unnecessary_cast`
/// - `invalid_reference_casting`
///
/// There is a good explanation the reason why this lint should work in this
/// way and how it is useful [in this

View file

@ -243,7 +243,7 @@ fn build_sugg<'tcx>(
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app)
}
.maybe_par();
.maybe_paren();
// Determine whether we need to reference the argument to clone_from().
let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);
@ -284,7 +284,7 @@ fn build_sugg<'tcx>(
let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
// `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par();
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_paren();
let inner_type = cx.typeck_results().expr_ty(ref_expr);
// If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
// deref to a mutable reference.
@ -296,7 +296,7 @@ fn build_sugg<'tcx>(
} else {
// `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
// `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr()
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_paren().mut_addr()
};
match call_kind {

View file

@ -8,17 +8,18 @@ use rustc_span::{DUMMY_SP, sym};
pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner]) {
for lint in items {
if let Some(lint_name) = extract_clippy_lint(lint) {
if lint_name.as_str() == "restriction" && name != sym::allow {
span_lint_and_help(
cx,
BLANKET_CLIPPY_RESTRICTION_LINTS,
lint.span(),
"`clippy::restriction` is not meant to be enabled as a group",
None,
"enable the restriction lints you need individually",
);
}
if let Some(lint_name) = extract_clippy_lint(lint)
&& lint_name.as_str() == "restriction"
&& name != sym::allow
{
span_lint_and_help(
cx,
BLANKET_CLIPPY_RESTRICTION_LINTS,
lint.span(),
"`clippy::restriction` is not meant to be enabled as a group",
None,
"enable the restriction lints you need individually",
);
}
}
}

View file

@ -6,10 +6,10 @@ use rustc_span::Span;
use semver::Version;
pub(super) fn check(cx: &EarlyContext<'_>, span: Span, lit: &MetaItemLit) {
if let LitKind::Str(is, _) = lit.kind {
if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() {
return;
}
if let LitKind::Str(is, _) = lit.kind
&& (is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok())
{
return;
}
span_lint(
cx,

View file

@ -36,10 +36,7 @@ fn check_duplicated_attr(
}
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc
|| name == sym::cfg_attr_trace
|| name == sym::rustc_on_unimplemented
|| name == sym::reason {
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.

View file

@ -14,8 +14,9 @@ mod useless_attribute;
mod utils;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv, MsrvStack};
use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -448,6 +449,31 @@ declare_clippy_lint! {
"duplicated attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for ignored tests without messages.
///
/// ### Why is this bad?
/// The reason for ignoring the test may not be obvious.
///
/// ### Example
/// ```no_run
/// #[test]
/// #[ignore]
/// fn test() {}
/// ```
/// Use instead:
/// ```no_run
/// #[test]
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
#[clippy::version = "1.85.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,
"ignored tests without messages"
}
pub struct Attributes {
msrv: Msrv,
}
@ -532,6 +558,7 @@ impl_lint_pass!(PostExpansionEarlyAttributes => [
ALLOW_ATTRIBUTES,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
DEPRECATED_SEMVER,
IGNORE_WITHOUT_REASON,
USELESS_ATTRIBUTE,
BLANKET_CLIPPY_RESTRICTION_LINTS,
SHOULD_PANIC_WITHOUT_EXPECT,
@ -546,28 +573,27 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
if let Some(items) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() {
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes::check(cx, attr);
}
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
if let Some(items) = &attr.meta_item_list()
&& let Some(ident) = attr.ident()
{
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes::check(cx, attr);
}
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes_without_reason::check(cx, ident.name, items, attr);
}
if is_lint_level(ident.name, attr.id) {
blanket_clippy_restriction_lints::check(cx, ident.name, items);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
for item in items {
if let MetaItemInner::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
allow_attributes_without_reason::check(cx, ident.name, items, attr);
}
if is_lint_level(ident.name, attr.id) {
blanket_clippy_restriction_lints::check(cx, ident.name, items);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
for item in items {
if let MetaItemInner::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
deprecated_semver::check(cx, item.span(), lit);
}
deprecated_semver::check(cx, item.span(), lit);
}
}
}
@ -575,6 +601,22 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
if attr.has_name(sym::should_panic) {
should_panic_without_expect::check(cx, attr);
}
if attr.has_name(sym::ignore)
&& match &attr.kind {
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }),
AttrKind::DocComment(..) => true,
}
{
span_lint_and_help(
cx,
IGNORE_WITHOUT_REASON,
attr.span,
"`#[ignore]` without reason",
None,
"add a reason with `= \"..\"`",
);
}
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) {

View file

@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute],
diag.warn(
"unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI",
)
.help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
.help("qualify the desired ABI explicitly via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
.span_label(packed_span, "`packed` representation set here");
},
);

View file

@ -14,75 +14,75 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
if attr.span.in_external_macro(cx.sess().source_map()) {
return;
}
if let Some(lint_list) = &attr.meta_item_list() {
if attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id)) {
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
return;
};
if let Some(lint_list) = &attr.meta_item_list()
&& attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id))
{
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
return;
};
if namespace.is_none()
&& matches!(
name.as_str(),
"ambiguous_glob_reexports"
| "dead_code"
| "deprecated"
| "hidden_glob_reexports"
| "unreachable_pub"
| "unused"
| "unused_braces"
| "unused_import_braces"
| "unused_imports"
)
{
return;
}
if namespace.is_none()
&& matches!(
name.as_str(),
"ambiguous_glob_reexports"
| "dead_code"
| "deprecated"
| "hidden_glob_reexports"
| "unreachable_pub"
| "unused"
| "unused_braces"
| "unused_import_braces"
| "unused_imports"
)
{
return;
}
if namespace == Some(sym::clippy)
&& matches!(
name.as_str(),
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports"
| "unsafe_removed_from_name"
| "module_name_repetitions"
| "single_component_path_imports"
| "disallowed_types"
| "unused_trait_names"
)
{
return;
}
},
ItemKind::ExternCrate(..) => {
if is_word(lint, sym::unused_imports) && skip_unused_imports {
return;
}
if is_word(lint, sym!(unused_extern_crates)) {
return;
}
},
_ => {},
}
if namespace == Some(sym::clippy)
&& matches!(
name.as_str(),
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports"
| "unsafe_removed_from_name"
| "module_name_repetitions"
| "single_component_path_imports"
| "disallowed_types"
| "unused_trait_names"
)
{
return;
}
},
ItemKind::ExternCrate(..) => {
if is_word(lint, sym::unused_imports) && skip_unused_imports {
return;
}
if is_word(lint, sym!(unused_extern_crates)) {
return;
}
},
_ => {},
}
let line_span = first_line_of_span(cx, attr.span);
}
let line_span = first_line_of_span(cx, attr.span);
if let Some(src) = line_span.get_source_text(cx) {
if src.contains("#[") {
#[expect(clippy::collapsible_span_lint_calls)]
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
diag.span_suggestion(
line_span,
"if you just forgot a `!`, use",
src.replacen("#[", "#![", 1),
Applicability::MaybeIncorrect,
);
});
}
}
if let Some(src) = line_span.get_source_text(cx)
&& src.contains("#[")
{
#[expect(clippy::collapsible_span_lint_calls)]
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
diag.span_suggestion(
line_span,
"if you just forgot a `!`, use",
src.replacen("#[", "#![", 1),
Applicability::MaybeIncorrect,
);
});
}
}
}

View file

@ -179,9 +179,14 @@ pub struct AwaitHolding {
impl AwaitHolding {
pub(crate) fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
def_ids: create_disallowed_map(tcx, &conf.await_holding_invalid_types),
}
let (def_ids, _) = create_disallowed_map(
tcx,
&conf.await_holding_invalid_types,
crate::disallowed_types::def_kind_predicate,
"type",
false,
);
Self { def_ids }
}
}
@ -192,10 +197,9 @@ impl<'tcx> LateLintPass<'tcx> for AwaitHolding {
def_id,
..
}) = expr.kind
&& let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id)
{
if let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) {
self.check_interior_types(cx, coroutine_layout);
}
self.check_interior_types(cx, coroutine_layout);
}
}
}

View file

@ -72,7 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
s
};
let into_snippet = snippet.clone().maybe_par();
let into_snippet = snippet.clone().maybe_paren();
let as_snippet = snippet.as_ty(ty);
span_lint_and_then(

View file

@ -13,7 +13,7 @@ use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
use rustc_lint::{LateContext, LateLintPass, Level};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_span::{Span, SyntaxContext, sym};
declare_clippy_lint! {
/// ### What it does
@ -242,11 +242,11 @@ struct Hir2Qmm<'a, 'tcx, 'v> {
impl<'v> Hir2Qmm<'_, '_, 'v> {
fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
for a in a {
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
if binop.node == op {
v = self.extract(op, &[lhs, rhs], v)?;
continue;
}
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind
&& binop.node == op
{
v = self.extract(op, &[lhs, rhs], v)?;
continue;
}
v.push(self.run(a)?);
}
@ -349,9 +349,13 @@ impl SuggestContext<'_, '_, '_> {
if let Some(str) = simplify_not(self.cx, self.msrv, terminal) {
self.output.push_str(&str);
} else {
self.output.push('!');
self.output
.push_str(&Sugg::hir_opt(self.cx, terminal)?.maybe_par().to_string());
let mut app = Applicability::MachineApplicable;
let snip = Sugg::hir_with_context(self.cx, terminal, SyntaxContext::root(), "", &mut app);
// Ignore the case If the expression is inside a macro expansion, or the default snippet is used
if app != Applicability::MachineApplicable {
return None;
}
self.output.push_str(&(!snip).to_string());
}
},
True | False | Not(_) => {
@ -414,12 +418,12 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio
let lhs_snippet = lhs.span.get_source_text(cx)?;
let rhs_snippet = rhs.span.get_source_text(cx)?;
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) {
if let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) {
// e.g. `(a as u64) < b`. Without the parens the `<` is
// interpreted as a start of generic arguments for `u64`
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
}
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')'))
&& let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node)
{
// e.g. `(a as u64) < b`. Without the parens the `<` is
// interpreted as a start of generic arguments for `u64`
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
}
Some(format!("{lhs_snippet}{op}{rhs_snippet}"))

View file

@ -2,7 +2,7 @@ use crate::reference::DEREF_ADDROF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed, is_mutable};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -73,6 +73,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
}
})
&& !is_from_proc_macro(cx, e)
&& let e_ty = cx.typeck_results().expr_ty_adjusted(e)
// check if the reference is coercing to a mutable reference
&& (!matches!(e_ty.kind(), ty::Ref(_, _, Mutability::Mut)) || is_mutable(cx, deref_target))
&& let Some(deref_text) = deref_target.span.get_source_text(cx)
{
span_lint_and_then(
@ -90,10 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
// has deref trait -> give 2 help
// doesn't have deref trait -> give 1 help
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() {
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
return;
}
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait()
&& !implements_trait(cx, *inner_ty, deref_trait_id, &[])
{
return;
}
diag.span_suggestion(

View file

@ -36,7 +36,7 @@ pub(super) fn check(
span,
format!("casting the result of `{cast_from}::abs()` to {cast_to}"),
"replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}

View file

@ -42,7 +42,7 @@ pub(super) fn check(
diag.span_suggestion_verbose(
expr.span,
"use `Into::into` instead",
format!("{}.into()", from_sugg.maybe_par()),
format!("{}.into()", from_sugg.maybe_paren()),
applicability,
);
},

View file

@ -64,11 +64,11 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX))
},
ExprKind::MethodCall(method, _, [lo, hi], _) => {
if method.ident.as_str() == "clamp" {
if method.ident.as_str() == "clamp"
//FIXME: make this a diagnostic item
if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
return lo_bits.max(hi_bits);
}
&& let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi))
{
return lo_bits.max(hi_bits);
}
nbits
},
@ -185,7 +185,7 @@ fn offer_suggestion(
) {
let cast_to_snip = snippet(cx, cast_to_span, "..");
let suggestion = if cast_to_snip == "_" {
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_paren())
} else {
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
};

View file

@ -19,16 +19,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
cx.typeck_results().expr_ty(expr),
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind {
if method_path.ident.name.as_str() == "cast"
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
{
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind
&& method_path.ident.name.as_str() == "cast"
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
{
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
}

View file

@ -21,42 +21,41 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
start_ty,
end_ty,
}) = expr_cast_chain_tys(cx, expr)
&& let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty))
{
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
format!(
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
start_ty.ty, end_ty.ty,
),
|diag| {
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
format!(
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
start_ty.ty, end_ty.ty,
),
|diag| {
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
// T`, extract just the `T`
end_ty.ty
);
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
// T`, extract just the `T`
end_ty.ty
);
diag.span_suggestion(
expr.span,
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
diag.span_suggestion(
expr.span,
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
}
}

View file

@ -0,0 +1,82 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::is_normalizable;
use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::MANUAL_DANGLING_PTR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
if let TyKind::Ptr(ref ptr_ty) = to.kind {
let init_expr = expr_or_init(cx, from);
if is_expr_const_aligned(cx, init_expr, ptr_ty.ty)
&& let Some(std_or_core) = std_or_core(cx)
{
let sugg_fn = match ptr_ty.mutbl {
Mutability::Not => "ptr::dangling",
Mutability::Mut => "ptr::dangling_mut",
};
let sugg = if let TyKind::Infer(()) = ptr_ty.ty.kind {
format!("{std_or_core}::{sugg_fn}()")
} else if let Some(mut_ty_snip) = ptr_ty.ty.span.get_source_text(cx) {
format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
} else {
return;
};
span_lint_and_sugg(
cx,
MANUAL_DANGLING_PTR,
expr.span,
"manual creation of a dangling pointer",
"use",
sugg,
Applicability::MachineApplicable,
);
}
}
}
// Checks if the given expression is a call to `align_of` whose generic argument matches the target
// type, or a positive constant literal that matches the target type's alignment.
fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool {
match expr.kind {
ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to),
ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to),
_ => false,
}
}
fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind
&& let Some(fun_id) = path_def_id(cx, fun)
&& match_def_path(cx, fun_id, &paths::ALIGN_OF)
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
&& let [GenericArg::Type(generic_ty)] = args.args
{
let typeck = cx.typeck_results();
return typeck.node_type(generic_ty.hir_id) == typeck.node_type(to.hir_id);
}
false
}
fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>) -> bool {
let LitKind::Int(val, _) = lit.node else { return false };
if val == 0 {
return false;
}
let to_mid_ty = cx.typeck_results().node_type(to.hir_id);
is_normalizable(cx, cx.param_env, to_mid_ty)
&& cx
.tcx
.layout_of(cx.typing_env().as_query_input(to_mid_ty))
.is_ok_and(|layout| {
let align = u128::from(layout.align.abi.bytes());
u128::from(val) <= align
})
}

View file

@ -17,6 +17,7 @@ mod char_lit_as_u8;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
@ -71,7 +72,7 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// let y: i8 = -1;
/// y as u128; // will return 18446744073709551615
/// y as u64; // will return 18446744073709551615
/// ```
#[clippy::version = "pre 1.29.0"]
pub CAST_SIGN_LOSS,
@ -759,6 +760,32 @@ declare_clippy_lint! {
"detects `as *mut _` and `as *const _` conversion"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of small constant literals or `mem::align_of` results to raw pointers.
///
/// ### Why is this bad?
/// This creates a dangling pointer and is better expressed as
/// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}.
///
/// ### Example
/// ```no_run
/// let ptr = 4 as *const u32;
/// let aligned = std::mem::align_of::<u32>() as *const u32;
/// let mut_ptr: *mut i64 = 8 as *mut _;
/// ```
/// Use instead:
/// ```no_run
/// let ptr = std::ptr::dangling::<u32>();
/// let aligned = std::ptr::dangling::<u32>();
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
/// ```
#[clippy::version = "1.87.0"]
pub MANUAL_DANGLING_PTR,
style,
"casting small constant literals to pointers to create dangling pointers"
}
pub struct Casts {
msrv: Msrv,
}
@ -795,6 +822,7 @@ impl_lint_pass!(Casts => [
ZERO_PTR,
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -823,6 +851,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
}
if cast_to.is_numeric() {
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
if cast_from.is_numeric() {

View file

@ -81,7 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
(
"try `pointer::cast`, a safer alternative",
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_paren()),
)
};

View file

@ -65,7 +65,7 @@ pub(super) fn check<'tcx>(
expr.span,
"`as` casting between raw pointers while changing only its constness",
format!("try `pointer::cast_{constness}`, a safer alternative"),
format!("{}.cast_{constness}()", sugg.maybe_par()),
format!("{}.cast_{constness}()", sugg.maybe_paren()),
Applicability::MachineApplicable,
);
}

View file

@ -130,11 +130,11 @@ pub(super) fn check<'tcx>(
| LitKind::Float(_, LitFloatType::Suffixed(_))
if cast_from.kind() == cast_to.kind() =>
{
if let Some(src) = cast_expr.span.get_source_text(cx) {
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) {
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
if let Some(src) = cast_expr.span.get_source_text(cx)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
{
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
},
_ => {},

View file

@ -253,11 +253,11 @@ fn get_types_from_cast<'a>(
match limit.kind {
// `from_type::from(_)`
ExprKind::Call(path, _) => {
if let ExprKind::Path(ref path) = path.kind {
if let ExprKind::Path(ref path) = path.kind
// `to_type`
if let Some(to_type) = get_implementing_type(path, types, func) {
return Some((from_type, to_type));
}
&& let Some(to_type) = get_implementing_type(path, types, func)
{
return Some((from_type, to_type));
}
},
// `to_type::MAX`

View file

@ -1,10 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
use clippy_utils::sugg::Sugg;
use rustc_ast::ast;
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
@ -75,105 +77,152 @@ declare_clippy_lint! {
"nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
}
declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
pub struct CollapsibleIf {
let_chains_enabled: bool,
lint_commented_code: bool,
}
impl EarlyLintPass for CollapsibleIf {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ast::ExprKind::If(cond, then, else_) = &expr.kind
impl CollapsibleIf {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
let_chains_enabled: tcx.features().let_chains(),
lint_commented_code: conf.lint_commented_code,
}
}
fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
if !block_starts_with_comment(cx, else_block)
&& let Some(else_) = expr_block(else_block)
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
&& !else_.span.from_expansion()
&& let ExprKind::If(..) = else_.kind
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(else_block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
else_block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability)
),
applicability,
);
}
}
fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) {
if let Some(inner) = expr_block(then)
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
&& let ExprKind::If(check_inner, _, None) = &inner.kind
&& self.eligible_condition(check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
{
span_lint_and_then(
cx,
COLLAPSIBLE_IF,
expr.span,
"this `if` statement can be collapsed",
|diag| {
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
let then_closing_bracket = {
let end = then.span.shrink_to_hi();
end.with_lo(end.lo() - rustc_span::BytePos(1))
.with_leading_whitespace(cx)
.into_span()
};
let inner_if = inner.span.split_at(2).0;
let mut sugg = vec![
// Remove the outer then block `{`
(then_open_bracket, String::new()),
// Remove the outer then block '}'
(then_closing_bracket, String::new()),
// Replace inner `if` by `&&`
(inner_if, String::from("&&")),
];
sugg.extend(parens_around(check));
sugg.extend(parens_around(check_inner));
diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable);
},
);
}
}
pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
}
}
impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
impl LateLintPass<'_> for CollapsibleIf {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::If(cond, then, else_) = &expr.kind
&& !expr.span.from_expansion()
{
if let Some(else_) = else_ {
check_collapsible_maybe_if_let(cx, then.span, else_);
} else if !matches!(cond.kind, ast::ExprKind::Let(..)) {
check_collapsible_no_if_let(cx, expr, cond, then);
if let Some(else_) = else_
&& let ExprKind::Block(else_, None) = else_.kind
{
Self::check_collapsible_else_if(cx, then.span, else_);
} else if else_.is_none()
&& self.eligible_condition(cond)
&& let ExprKind::Block(then, None) = then.kind
{
self.check_collapsible_if_if(cx, expr, cond, then);
}
}
}
}
fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
// We trim all opening braces and whitespaces and then check if the next string is a comment.
let trimmed_block_text = snippet_block(cx, expr.span, "..", None)
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
.to_owned();
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
}
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
if let ast::ExprKind::Block(ref block, _) = else_.kind
&& !block_starts_with_comment(cx, block)
&& let Some(else_) = expr_block(block)
&& else_.attrs.is_empty()
&& !else_.span.from_expansion()
&& let ast::ExprKind::If(..) = else_.kind
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
),
applicability,
);
/// If `block` is a block with either one expression or a statement containing an expression,
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.stmts {
[] => block.expr,
[stmt] => {
if let StmtKind::Semi(expr) = stmt.kind {
Some(expr)
} else {
None
}
},
_ => None,
}
}
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
if !block_starts_with_comment(cx, then)
&& let Some(inner) = expr_block(then)
&& inner.attrs.is_empty()
&& let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind
// Prevent triggering on `if c { if let a = b { .. } }`.
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
/// If the expression is a `||`, suggest parentheses around it.
fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
if let ExprKind::Binary(op, _, _) = expr.peel_drop_temps().kind
&& op.node == BinOpKind::Or
{
span_lint_and_then(
cx,
COLLAPSIBLE_IF,
expr.span,
"this `if` statement can be collapsed",
|diag| {
let mut app = Applicability::MachineApplicable;
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
diag.span_suggestion(
expr.span,
"collapse nested if block",
format!(
"if {} {}",
lhs.and(&rhs),
snippet_block(cx, content.span, "..", Some(expr.span)),
),
app, // snippet
);
},
);
}
}
/// If the block contains only one expression, return it.
fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
if let [stmt] = &*block.stmts
&& let ast::StmtKind::Expr(expr) | ast::StmtKind::Semi(expr) = &stmt.kind
{
Some(expr)
vec![
(expr.span.shrink_to_lo(), String::from("(")),
(expr.span.shrink_to_hi(), String::from(")")),
]
} else {
None
vec![]
}
}

View file

@ -125,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
unreachable!();
};
let lhs = Sugg::hir(cx, lhs, "..").maybe_par();
let lhs = Sugg::hir(cx, lhs, "..").maybe_paren();
let rhs = Sugg::hir(cx, rhs, "..").addr();
span_lint_and_sugg(
cx,

View file

@ -256,7 +256,7 @@ fn lint_branches_sharing_code<'tcx>(
let suggestion = reindent_multiline(&suggestion, true, indent);
let span = span.with_hi(last_block.span.hi());
// Improve formatting if the inner block has indention (i.e. normal Rust formatting)
// Improve formatting if the inner block has indentation (i.e. normal Rust formatting)
let span = span
.map_range(cx, |src, range| {
(range.start > 4 && src.get(range.start - 4..range.start)? == " ")
@ -539,10 +539,10 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
.filter(|stmt| !ignore_span.overlaps(stmt.span))
.try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
if let Some(expr) = block.expr {
if res.is_continue() {
res = intravisit::walk_expr(&mut walker, expr);
}
if let Some(expr) = block.expr
&& res.is_continue()
{
res = intravisit::walk_expr(&mut walker, expr);
}
res.is_break()

View file

@ -52,6 +52,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
crate::attrs::IGNORE_WITHOUT_REASON_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO,
@ -96,6 +97,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
@ -286,6 +288,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::literal_representation::UNREADABLE_LITERAL_INFO,
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO,
crate::loops::EMPTY_LOOP_INFO,
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
@ -334,7 +337,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
crate::match_result_ok::MATCH_RESULT_OK_INFO,
@ -344,6 +346,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::matches::MANUAL_MAP_INFO,
crate::matches::MANUAL_OK_ERR_INFO,
crate::matches::MANUAL_UNWRAP_OR_INFO,
crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::matches::MATCH_AS_REF_INFO,
crate::matches::MATCH_BOOL_INFO,
crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO,

View file

@ -1133,61 +1133,60 @@ fn report<'tcx>(
impl<'tcx> Dereferencing<'tcx> {
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
if let Some(pat) = outer_pat {
// Check for auto-deref
if !matches!(
cx.typeck_results().expr_adjustments(e),
[
Adjustment {
kind: Adjust::Deref(_),
..
},
Adjustment {
kind: Adjust::Deref(_),
..
},
if let Some(outer_pat) = self.ref_locals.get_mut(&local)
&& let Some(pat) = outer_pat
// Check for auto-deref
&& !matches!(
cx.typeck_results().expr_adjustments(e),
[
Adjustment {
kind: Adjust::Deref(_),
..
]
) {
match get_parent_expr(cx, e) {
// Field accesses are the same no matter the number of references.
Some(Expr {
kind: ExprKind::Field(..),
..
}) => (),
Some(&Expr {
span,
kind: ExprKind::Unary(UnOp::Deref, _),
..
}) if !span.from_expansion() => {
// Remove explicit deref.
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((span, snip.into()));
},
Some(parent) if !parent.span.from_expansion() => {
// Double reference might be needed at this point.
if parent.precedence() == ExprPrecedence::Unambiguous {
// Parentheses would be needed here, don't lint.
*outer_pat = None;
} else {
pat.always_deref = false;
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((e.span, format!("&{snip}")));
}
},
_ if !e.span.from_expansion() => {
// Double reference might be needed at this point.
pat.always_deref = false;
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
pat.replacements.push((e.span, format!("&{snip}")));
},
// Edge case for macros. The span of the identifier will usually match the context of the
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
// macros
_ => *outer_pat = None,
},
Adjustment {
kind: Adjust::Deref(_),
..
},
..
]
)
{
match get_parent_expr(cx, e) {
// Field accesses are the same no matter the number of references.
Some(Expr {
kind: ExprKind::Field(..),
..
}) => (),
Some(&Expr {
span,
kind: ExprKind::Unary(UnOp::Deref, _),
..
}) if !span.from_expansion() => {
// Remove explicit deref.
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((span, snip.into()));
},
Some(parent) if !parent.span.from_expansion() => {
// Double reference might be needed at this point.
if parent.precedence() == ExprPrecedence::Unambiguous {
// Parentheses would be needed here, don't lint.
*outer_pat = None;
} else {
pat.always_deref = false;
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((e.span, format!("&{snip}")));
}
}
},
_ if !e.span.from_expansion() => {
// Double reference might be needed at this point.
pat.always_deref = false;
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
pat.replacements.push((e.span, format!("&{snip}")));
},
// Edge case for macros. The span of the identifier will usually match the context of the
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
// macros
_ => *outer_pat = None,
}
}
}

View file

@ -94,18 +94,18 @@ fn check_struct<'tcx>(
ty_args: GenericArgsRef<'_>,
typeck_results: &'tcx TypeckResults<'tcx>,
) {
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
if let Some(PathSegment { args, .. }) = p.segments.last() {
let args = args.map(|a| a.args).unwrap_or(&[]);
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
&& let Some(PathSegment { args, .. }) = p.segments.last()
{
let args = args.map(|a| a.args).unwrap_or(&[]);
// ty_args contains the generic parameters of the type declaration, while args contains the
// arguments used at instantiation time. If both len are not equal, it means that some
// parameters were not provided (which means that the default values were used); in this
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
// is a type or a const.
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
return;
}
// ty_args contains the generic parameters of the type declaration, while args contains the
// arguments used at instantiation time. If both len are not equal, it means that some
// parameters were not provided (which means that the default values were used); in this
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
// is a type or a const.
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
return;
}
}
@ -188,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
self_ty,
..
}) = item.kind
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
&& !item.span.from_expansion()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)

View file

@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
}) = item.kind
{
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
@ -235,7 +235,7 @@ fn check_hash_peq<'tcx>(
{
// Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
@ -278,7 +278,7 @@ fn check_ord_partial_ord<'tcx>(
{
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
@ -426,10 +426,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
}
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
if let ExprKind::Block(block, _) = expr.kind {
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
return ControlFlow::Break(());
}
if let ExprKind::Block(block, _) = expr.kind
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
{
return ControlFlow::Break(());
}
walk_expr(self, expr)
@ -479,7 +479,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
}
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
// Initial map from generic index to param def.
// Vec<(param_def, needs_eq)>

View file

@ -1,9 +1,9 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
@ -72,8 +72,15 @@ pub struct DisallowedMacros {
impl DisallowedMacros {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_macros,
|def_kind| matches!(def_kind, DefKind::Macro(_)),
"macro",
false,
);
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
disallowed,
seen: FxHashSet::default(),
derive_src: None,
earlies,

View file

@ -63,9 +63,19 @@ pub struct DisallowedMethods {
impl DisallowedMethods {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_methods),
}
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_methods,
|def_kind| {
matches!(
def_kind,
DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn
)
},
"function",
false,
);
Self { disallowed }
}
}
@ -74,12 +84,7 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (id, span) = match &expr.kind {
ExprKind::Path(path)
if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) =
cx.qpath_res(path, expr.hir_id) =>
{
(id, expr.span)
},
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {
(id, name.ident.span)
},

View file

@ -1,8 +1,8 @@
use clippy_config::Conf;
use clippy_config::types::DisallowedPath;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::Res;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
use rustc_lint::{LateContext, LateLintPass};
@ -60,22 +60,7 @@ pub struct DisallowedTypes {
impl DisallowedTypes {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
let mut def_ids = DefIdMap::default();
let mut prim_tys = FxHashMap::default();
for disallowed_path in &conf.disallowed_types {
let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>();
for res in clippy_utils::def_path_res(tcx, &path) {
match res {
Res::Def(_, id) => {
def_ids.insert(id, (disallowed_path.path(), disallowed_path));
},
Res::PrimTy(ty) => {
prim_tys.insert(ty, (disallowed_path.path(), disallowed_path));
},
_ => {},
}
}
}
let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
Self { def_ids, prim_tys }
}
@ -95,6 +80,19 @@ impl DisallowedTypes {
}
}
pub fn def_kind_predicate(def_kind: DefKind) -> bool {
matches!(
def_kind,
DefKind::Struct
| DefKind::Union
| DefKind::Enum
| DefKind::Trait
| DefKind::TyAlias
| DefKind::ForeignTy
| DefKind::AssocTy
)
}
impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {

View file

@ -113,20 +113,20 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b
s != "-" && s.contains('-')
}
if let Ok(url) = Url::parse(word) {
if let Ok(url) = Url::parse(word)
// try to get around the fact that `foo::bar` parses as a valid URL
if !url.cannot_be_a_base() {
span_lint_and_sugg(
cx,
DOC_MARKDOWN,
span,
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
"try",
format!("<{word}>"),
Applicability::MachineApplicable,
);
return;
}
&& !url.cannot_be_a_base()
{
span_lint_and_sugg(
cx,
DOC_MARKDOWN,
span,
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
"try",
format!("<{word}>"),
Applicability::MachineApplicable,
);
return;
}
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)

View file

@ -1,11 +1,14 @@
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item};
use clippy_utils::{is_doc_hidden, return_ty};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty};
use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, sym};
use std::ops::ControlFlow;
pub fn check(
cx: &LateContext<'_>,
@ -13,7 +16,6 @@ pub fn check(
sig: FnSig<'_>,
headers: DocHeaders,
body_id: Option<BodyId>,
panic_info: Option<(Span, bool)>,
check_private_items: bool,
) {
if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) {
@ -46,13 +48,16 @@ pub fn check(
),
_ => (),
}
if !headers.panics && panic_info.is_some_and(|el| !el.1) {
if !headers.panics
&& let Some(body_id) = body_id
&& let Some(panic_span) = find_panic(cx, body_id)
{
span_lint_and_note(
cx,
MISSING_PANICS_DOC,
span,
"docs for function which may panic missing `# Panics` section",
panic_info.map(|el| el.0),
Some(panic_span),
"first possible panic found here",
);
}
@ -89,3 +94,39 @@ pub fn check(
}
}
}
fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
let mut panic_span = None;
let typeck = cx.tcx.typeck_body(body_id);
for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& (is_panic(cx, macro_call.def_id)
|| matches!(
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
))
&& !cx.tcx.hir_is_inside_const_context(expr.hir_id)
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
&& panic_span.is_none()
{
panic_span = Some(macro_call.span);
}
// check for `unwrap` and `expect` for both `Option` and `Result`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or_else(|| method_chain_args(expr, &["expect"]))
&& let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs()
&& matches!(
get_type_diagnostic_name(cx, receiver_ty),
Some(sym::Option | sym::Result)
)
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
&& panic_span.is_none()
{
panic_span = Some(expr.span);
}
// Visit all nodes to fulfill any `#[expect]`s after the first linted panic
ControlFlow::<!>::Continue(())
});
panic_span
}

View file

@ -3,11 +3,8 @@
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};
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
use pulldown_cmark::Event::{
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
TaskListMarker, Text,
@ -16,18 +13,15 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty;
use rustc_resolve::rustdoc::{
DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range,
span_of_fragments,
};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::edition::Edition;
use rustc_span::{Span, sym};
use std::ops::Range;
use url::Url;
@ -194,6 +188,19 @@ declare_clippy_lint! {
/// }
/// }
/// ```
///
/// Individual panics within a function can be ignored with `#[expect]` or
/// `#[allow]`:
///
/// ```no_run
/// # use std::num::NonZeroUsize;
/// pub fn will_not_panic(x: usize) {
/// #[expect(clippy::missing_panics_doc, reason = "infallible")]
/// let y = NonZeroUsize::new(1).unwrap();
///
/// // If any panics are added in the future the lint will still catch them
/// }
/// ```
#[clippy::version = "1.51.0"]
pub MISSING_PANICS_DOC,
pedantic,
@ -657,20 +664,16 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
self.check_private_items,
);
match item.kind {
ItemKind::Fn { sig, body: body_id, .. } => {
ItemKind::Fn { sig, body, .. } => {
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id())
|| item.span.in_external_macro(cx.tcx.sess.source_map()))
{
let body = cx.tcx.hir_body(body_id);
let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
missing_headers::check(
cx,
item.owner_id,
sig,
headers,
Some(body_id),
panic_info,
Some(body),
self.check_private_items,
);
}
@ -697,15 +700,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
if let TraitItemKind::Fn(sig, ..) = trait_item.kind
&& !trait_item.span.in_external_macro(cx.tcx.sess.source_map())
{
missing_headers::check(
cx,
trait_item.owner_id,
sig,
headers,
None,
None,
self.check_private_items,
);
missing_headers::check(cx, trait_item.owner_id, sig, headers, None, self.check_private_items);
}
},
Node::ImplItem(impl_item) => {
@ -713,16 +708,12 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
&& !impl_item.span.in_external_macro(cx.tcx.sess.source_map())
&& !is_trait_impl_item(cx, impl_item.hir_id())
{
let body = cx.tcx.hir_body(body_id);
let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(impl_item.owner_id), body.value);
missing_headers::check(
cx,
impl_item.owner_id,
sig,
headers,
Some(body_id),
panic_span,
self.check_private_items,
);
}
@ -880,19 +871,18 @@ fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a
if let Some(start) = code_starts_at
&& let Some(end) = code_ends_at
&& code_includes_link
&& let Some(span) = fragments.span(cx, start..end)
{
if let Some(span) = fragments.span(cx, start..end) {
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
diag.span_suggestion_verbose(
span,
"wrap the entire group in `<code>` tags",
sugg,
Applicability::MaybeIncorrect,
);
diag.help("separate code snippets will be shown with a gap");
});
}
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
diag.span_suggestion_verbose(
span,
"wrap the entire group in `<code>` tags",
sugg,
Applicability::MaybeIncorrect,
);
diag.help("separate code snippets will be shown with a gap");
});
}
code_includes_link = false;
code_starts_at = None;
@ -1169,72 +1159,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
headers
}
struct FindPanicUnwrap<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_const: bool,
panic_span: Option<Span>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
}
impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
pub fn find_span(
cx: &'a LateContext<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
body: impl Visitable<'tcx>,
) -> Option<(Span, bool)> {
let mut vis = Self {
cx,
is_const: false,
panic_span: None,
typeck_results,
};
body.visit(&mut vis);
vis.panic_span.map(|el| (el, vis.is_const))
}
}
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.panic_span.is_some() {
return;
}
if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
if is_panic(self.cx, macro_call.def_id)
|| matches!(
self.cx.tcx.item_name(macro_call.def_id).as_str(),
"assert" | "assert_eq" | "assert_ne"
)
{
self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id);
self.panic_span = Some(macro_call.span);
}
}
// check for `unwrap` and `expect` for both `Option` and `Result`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
{
self.panic_span = Some(expr.span);
}
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
// Panics in const blocks will cause compilation to fail.
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
if range.end < range.start {

View file

@ -64,7 +64,10 @@ pub fn check(
match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => match &item.kind {
ItemKind::Fn(box Fn {
ident, sig, body: Some(block), ..
ident,
sig,
body: Some(block),
..
}) if ident.name == sym::main => {
if !ignore {
get_test_spans(&item, *ident, &mut test_attr_spans);

View file

@ -144,10 +144,10 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
// ..
// }
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
return body.hir_id == drop_expr.hir_id;
}
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
&& let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id)
{
return body.hir_id == drop_expr.hir_id;
}
false
}

View file

@ -49,10 +49,10 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
.ok()
.map(|val| rustc_middle::mir::Const::from_value(val, ty));
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() {
ty = adt.repr().discr_type().to_ty(cx.tcx);
}
if let ty::Adt(adt, _) = ty.kind()
&& adt.is_enum()
{
ty = adt.repr().discr_type().to_ty(cx.tcx);
}
match ty.kind() {
ty::Int(IntTy::Isize) => {

View file

@ -72,10 +72,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
_: Span,
fn_def_id: LocalDefId,
) {
if let Some(header) = fn_kind.header() {
if header.abi != ExternAbi::Rust {
return;
}
if let Some(header) = fn_kind.header()
&& header.abi != ExternAbi::Rust
{
return;
}
let parent_id = cx
@ -93,12 +93,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
// find `self` ty for this trait if relevant
if let ItemKind::Trait(_, _, _, _, _, items) = item.kind {
for trait_item in items {
if trait_item.id.owner_id.def_id == fn_def_id {
if trait_item.id.owner_id.def_id == fn_def_id
// be sure we have `self` parameter in this function
if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
trait_self_ty =
Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
}
&& trait_item.kind == (AssocItemKind::Fn { has_self: true })
{
trait_self_ty = Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
}
}
}
@ -142,22 +141,22 @@ fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool {
impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
if cmt.place.projections.is_empty() {
if let PlaceBase::Local(lid) = cmt.place.base {
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
if cmt.place.projections.is_empty()
&& let PlaceBase::Local(lid) = cmt.place.base
{
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
}
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
if cmt.place.projections.is_empty() {
if let PlaceBase::Local(lid) = cmt.place.base {
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
if cmt.place.projections.is_empty()
&& let PlaceBase::Local(lid) = cmt.place.base
{
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
}
@ -171,10 +170,11 @@ impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
// 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 let Some(trait_self_ty) = self.trait_self_ty
&& 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()) {

View file

@ -75,10 +75,10 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
if is_panic(self.lcx, macro_call.def_id) {
self.result.push(expr.span);
}
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr)
&& is_panic(self.lcx, macro_call.def_id)
{
self.result.push(expr.span);
}
// check for `unwrap`

View file

@ -154,7 +154,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
};
}
suggestion.maybe_par()
suggestion.maybe_paren()
}
fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
@ -165,7 +165,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, ar
expr.span,
"logarithm for bases 2, 10 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
@ -228,24 +228,24 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) {
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver)
&& let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp")
} else if F32(2.0) == value || F64(2.0) == value {
Some("exp2")
} else {
None
} {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
Applicability::MachineApplicable,
);
}
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
Applicability::MachineApplicable,
);
}
// Check argument
@ -254,13 +254,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
(
SUBOPTIMAL_FLOPS,
"square-root of a number can be computed more efficiently and accurately",
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
(
IMPRECISE_FLOPS,
"cube-root of a number can be computed more accurately",
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if let Some(exponent) = get_integer_from_float_constant(&value) {
(
@ -268,7 +268,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
"exponentiation with integer powers can be computed more efficiently",
format!(
"{}.powi({})",
Sugg::hir(cx, receiver, "..").maybe_par(),
Sugg::hir(cx, receiver, "..").maybe_paren(),
numeric_literal::format(&exponent.to_string(), None, false)
),
)
@ -289,55 +289,53 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
}
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
if value == Int(2) {
if let Some(parent) = get_parent_expr(cx, expr) {
if let Some(grandparent) = get_parent_expr(cx, parent) {
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
{
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
return;
}
}
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0])
&& value == Int(2)
&& let Some(parent) = get_parent_expr(cx, expr)
{
if let Some(grandparent) = get_parent_expr(cx, parent)
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
&& method_name.as_str() == "sqrt"
&& detect_hypot(cx, receiver).is_some()
{
return;
}
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id| {
let sugg = Sugg::hir(cx, expr, "..");
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id| {
let sugg = Sugg::hir(cx, expr, "..");
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir(cx, receiver, "..").maybe_par(),
maybe_neg_sugg(receiver, expr.hir_id),
maybe_neg_sugg(other_addend, other_addend.hir_id),
),
Applicability::MachineApplicable,
);
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir(cx, receiver, "..").maybe_paren(),
maybe_neg_sugg(receiver, expr.hir_id),
maybe_neg_sugg(other_addend, other_addend.hir_id),
),
Applicability::MachineApplicable,
);
}
}
}
@ -371,7 +369,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
Sugg::hir(cx, lmul_lhs, "..").maybe_paren(),
Sugg::hir(cx, rmul_lhs, "..")
));
}
@ -403,7 +401,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, largs_0, "..").maybe_par(),
Sugg::hir(cx, largs_0, "..").maybe_paren(),
Sugg::hir(cx, rargs_0, "..")
));
}
@ -449,7 +447,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
@ -483,12 +481,12 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs,
) = &expr.kind
{
if let Some(parent) = get_parent_expr(cx, expr) {
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind {
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
return;
}
}
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind
&& method_name.as_str() == "sqrt"
&& detect_hypot(cx, receiver).is_some()
{
return;
}
let maybe_neg_sugg = |expr| {
@ -566,15 +564,15 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
if eq_expr_value(cx, expr1_negated, expr2) {
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
if eq_expr_value(cx, expr1, expr2_negated) {
return Some((true, expr1));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
{
return Some((true, expr1));
}
None
}
@ -591,11 +589,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
{
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
@ -672,7 +670,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
"consider using",
format!(
"{}.log({})",
Sugg::hir(cx, largs_self, "..").maybe_par(),
Sugg::hir(cx, largs_self, "..").maybe_paren(),
Sugg::hir(cx, rargs_self, ".."),
),
Applicability::MachineApplicable,
@ -703,7 +701,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
@ -726,7 +724,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed

View file

@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
.into_owned()
} else {
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
format!("{}.to_string()", sugg.maybe_par())
format!("{}.to_string()", sugg.maybe_paren())
};
span_useless_format(cx, call_site, sugg, applicability);
}

View file

@ -141,7 +141,7 @@ declare_clippy_lint! {
/// format!("{var:.prec$}");
/// ```
///
/// If allow-mixed-uninlined-format-args is set to false in clippy.toml,
/// If `allow-mixed-uninlined-format-args` is set to `false` in clippy.toml,
/// the following code will also trigger the lint:
/// ```no_run
/// # let var = 42;
@ -159,7 +159,7 @@ declare_clippy_lint! {
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
#[clippy::version = "1.66.0"]
pub UNINLINED_FORMAT_ARGS,
pedantic,
style,
"using non-inlined variables in `format!` calls"
}

View file

@ -138,27 +138,28 @@ impl EarlyLintPass for Formatting {
/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
if !lhs.span.from_expansion() && !rhs.span.from_expansion() {
let eq_span = lhs.span.between(rhs.span);
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
let op = op.as_str();
let eqop_span = lhs.span.between(sub_rhs.span);
if eq_snippet.ends_with('=') {
span_lint_and_note(
cx,
SUSPICIOUS_ASSIGNMENT_FORMATTING,
eqop_span,
format!(
"this looks like you are trying to use `.. {op}= ..`, but you \
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind
&& !lhs.span.from_expansion()
&& !rhs.span.from_expansion()
{
let eq_span = lhs.span.between(rhs.span);
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind
&& let Some(eq_snippet) = snippet_opt(cx, eq_span)
{
let op = op.as_str();
let eqop_span = lhs.span.between(sub_rhs.span);
if eq_snippet.ends_with('=') {
span_lint_and_note(
cx,
SUSPICIOUS_ASSIGNMENT_FORMATTING,
eqop_span,
format!(
"this looks like you are trying to use `.. {op}= ..`, but you \
really are doing `.. = ({op} ..)`"
),
None,
format!("to remove this lint, use either `{op}=` or `= {op}`"),
);
}
}
),
None,
format!("to remove this lint, use either `{op}=` or `= {op}`"),
);
}
}
}

View file

@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
};
let sugg =
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_paren();
span_lint_and_sugg(
cx,

View file

@ -59,9 +59,7 @@ impl RenamedFnArgs {
let mut renamed: Vec<(Span, String)> = vec![];
debug_assert!(default_idents.size_hint() == current_idents.size_hint());
while let (Some(default_ident), Some(current_ident)) =
(default_idents.next(), current_idents.next())
{
while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) {
let has_name_to_check = |ident: Option<Ident>| {
if let Some(ident) = ident
&& ident.name != kw::Underscore

View file

@ -47,16 +47,16 @@ pub(super) fn check_fn(
}
pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind
// don't lint extern functions decls, it's not their fault
if sig.header.abi == ExternAbi::Rust {
check_arg_number(
cx,
sig.decl,
item.span.with_hi(sig.decl.output.span().hi()),
too_many_arguments_threshold,
);
}
&& sig.header.abi == ExternAbi::Rust
{
check_arg_number(
cx,
sig.decl,
item.span.with_hi(sig.decl.output.span().hi()),
too_many_arguments_threshold,
);
}
}

View file

@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
|diag| {
let mut app = Applicability::MachineApplicable;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
.maybe_par()
.maybe_paren()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let method_body = if let Some(first_stmt) = then_block.stmts.first() {

View file

@ -153,18 +153,18 @@ fn lint_implicit_returns(
ExprKind::Loop(block, ..) => {
let mut add_return = false;
let _: Option<!> = for_each_expr_without_closures(block, |e| {
if let ExprKind::Break(dest, sub_expr) = e.kind {
if dest.target_id.ok() == Some(expr.hir_id) {
if call_site_span.is_none() && e.span.ctxt() == ctxt {
// At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type.
if let Some(sub_expr) = sub_expr {
lint_break(cx, e.hir_id, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
if let ExprKind::Break(dest, sub_expr) = e.kind
&& dest.target_id.ok() == Some(expr.hir_id)
{
if call_site_span.is_none() && e.span.ctxt() == ctxt {
// At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type.
if let Some(sub_expr) = sub_expr {
lint_break(cx, e.hir_id, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
}
}
ControlFlow::Continue(())

View file

@ -239,7 +239,7 @@ fn check_subtraction(
// 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 (!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(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
let sugg = format!(

View file

@ -65,13 +65,13 @@ declare_clippy_lint! {
}
pub struct InconsistentStructConstructor {
lint_inconsistent_struct_field_initializers: bool,
check_inconsistent_struct_field_initializers: bool,
}
impl InconsistentStructConstructor {
pub fn new(conf: &'static Conf) -> Self {
Self {
lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers,
check_inconsistent_struct_field_initializers: conf.check_inconsistent_struct_field_initializers,
}
}
}
@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand);
let applicability = if all_fields_are_shorthand {
Applicability::MachineApplicable
} else if self.lint_inconsistent_struct_field_initializers {
} else if self.check_inconsistent_struct_field_initializers {
Applicability::MaybeIncorrect
} else {
return;

View file

@ -136,28 +136,28 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
let const_range = to_const_range(cx, range, size);
if let (Some(start), _) = const_range {
if start > size {
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.start.map_or(expr.span, |start| start.span),
"range is out of bounds",
);
return;
}
if let (Some(start), _) = const_range
&& start > size
{
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.start.map_or(expr.span, |start| start.span),
"range is out of bounds",
);
return;
}
if let (_, Some(end)) = const_range {
if end > size {
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.end.map_or(expr.span, |end| end.span),
"range is out of bounds",
);
return;
}
if let (_, Some(end)) = const_range
&& end > size
{
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.end.map_or(expr.span, |end| end.span),
"range is out of bounds",
);
return;
}
if let (Some(_), Some(_)) = const_range {

View file

@ -156,11 +156,12 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
.and(cap);
}
}
if method.ident.name.as_str() == "flat_map" && args.len() == 1 {
if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
let body = cx.tcx.hir_body(body);
return is_infinite(cx, body.value);
}
if method.ident.name.as_str() == "flat_map"
&& args.len() == 1
&& let ExprKind::Closure(&Closure { body, .. }) = args[0].kind
{
let body = cx.tcx.hir_body(body);
return is_infinite(cx, body.value);
}
Finite
},

View file

@ -123,7 +123,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg
expr.span,
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_par()),
format!("{}.elapsed()", sugg.maybe_paren()),
Applicability::MachineApplicable,
);
}

View file

@ -130,14 +130,14 @@ impl IntPlusOne {
BinOpKind::Le => "<",
_ => return None,
};
if let Some(snippet) = node.span.get_source_text(cx) {
if let Some(other_side_snippet) = other_side.span.get_source_text(cx) {
let rec = match side {
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
};
return rec;
}
if let Some(snippet) = node.span.get_source_text(cx)
&& let Some(other_side_snippet) = other_side.span.get_source_text(cx)
{
let rec = match side {
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
};
return rec;
}
None
}
@ -157,10 +157,10 @@ impl IntPlusOne {
impl EarlyLintPass for IntPlusOne {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
Self::emit_warning(cx, item, rec);
}
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind
&& let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs)
{
Self::emit_warning(cx, item, rec);
}
}
}

View file

@ -91,49 +91,49 @@ fn upcast_comparison_bounds_err<'tcx>(
rhs: &'tcx Expr<'_>,
invert: bool,
) {
if let Some((lb, ub)) = lhs_bounds {
if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) {
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
}
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val < lb
} else {
ub < norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val <= lb
} else {
ub <= norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, true);
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val >= ub
} else {
lb >= norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val > ub
} else {
lb > norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, false);
if let Some((lb, ub)) = lhs_bounds
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs)
{
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
}
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val < lb
} else {
ub < norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val <= lb
} else {
ub <= norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, true);
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val >= ub
} else {
lb >= norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val > ub
} else {
lb > norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, false);
}
}
}

View file

@ -377,22 +377,21 @@ impl ItemNameRepetitions {
"field name starts with the struct's name",
);
}
if field_words.len() > item_name_words.len() {
if field_words.len() > item_name_words.len()
// lint only if the end is not covered by the start
if field_words
&& field_words
.iter()
.rev()
.zip(item_name_words.iter().rev())
.all(|(a, b)| a == b)
{
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name ends with the struct's name",
);
}
{
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name ends with the struct's name",
);
}
}
}
@ -445,57 +444,56 @@ impl LateLintPass<'_> for ItemNameRepetitions {
let item_name = ident.name.as_str();
let item_camel = to_camel_case(item_name);
if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules {
// constants don't have surrounding modules
if !mod_camel.is_empty() {
if mod_name == &ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
{
span_lint(
if !item.span.from_expansion() && is_present_in_source(cx, item.span)
&& let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules
// constants don't have surrounding modules
&& !mod_camel.is_empty()
{
if mod_name == &ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
{
span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is accepted.
if cx.tcx.visibility(item.owner_id).is_public()
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
&& item_camel.len() > mod_camel.len()
{
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
if matching.char_count == nchars {
match item_camel.chars().nth(nchars) {
Some(c) if is_word_beginning(c) => span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is accepted.
if cx.tcx.visibility(item.owner_id).is_public()
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
&& item_camel.len() > mod_camel.len()
{
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
if matching.char_count == nchars {
match item_camel.chars().nth(nchars) {
Some(c) if is_word_beginning(c) => span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name starts with its containing module's name",
),
_ => (),
}
}
if rmatching.char_count == nchars
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
{
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name ends with its containing module's name",
);
}
MODULE_NAME_REPETITIONS,
ident.span,
"item name starts with its containing module's name",
),
_ => (),
}
}
if rmatching.char_count == nchars
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
{
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name ends with its containing module's name",
);
}
}
}

View file

@ -180,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
let mut applicability = Applicability::MachineApplicable;
let lit1 = peel_ref_operators(cx, lt.init);
let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_par();
let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_paren();
span_lint_and_sugg(
cx,
@ -202,7 +202,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
expr.span,
lhs_expr,
peel_ref_operators(cx, rhs_expr),
(method.ident.name == sym::ne).then_some("!").unwrap_or_default(),
if method.ident.name == sym::ne {
"!"
} else {
Default::default()
},
);
}
@ -523,10 +527,10 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>
if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
// check if we are in an is_empty() method
if let Some(name) = get_item_name(cx, method) {
if name.as_str() == "is_empty" {
return;
}
if let Some(name) = get_item_name(cx, method)
&& name.as_str() == "is_empty"
{
return;
}
check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to);
@ -573,7 +577,7 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex
let mut applicability = Applicability::MachineApplicable;
let lit1 = peel_ref_operators(cx, lit1);
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_par();
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren();
span_lint_and_sugg(
cx,
@ -588,11 +592,11 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex
}
fn is_empty_string(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = expr.kind {
if let LitKind::Str(lit, _) = lit.node {
let lit = lit.as_str();
return lit.is_empty();
}
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(lit, _) = lit.node
{
let lit = lit.as_str();
return lit.is_empty();
}
false
}

View file

@ -226,7 +226,6 @@ mod manual_rotate;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod manual_unwrap_or_default;
mod map_unit_fn;
mod match_result_ok;
mod matches;
@ -408,9 +407,9 @@ mod zombie_processes;
use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
use clippy_utils::macros::FormatArgsStorage;
use utils::attr_collector::{AttrCollector, AttrStorage};
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use utils::attr_collector::{AttrCollector, AttrStorage};
/// Register all pre expansion lints
///
@ -772,7 +771,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
store.register_early_pass(|| Box::new(precedence::Precedence));
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
@ -960,7 +959,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));

View file

@ -150,10 +150,10 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
} = item.kind
{
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv);
} else if let ItemKind::Impl(impl_) = item.kind {
if !item.span.from_expansion() {
report_extra_impl_lifetimes(cx, impl_);
}
} else if let ItemKind::Impl(impl_) = item.kind
&& !item.span.from_expansion()
{
report_extra_impl_lifetimes(cx, impl_);
}
}
@ -300,8 +300,8 @@ fn could_use_elision<'tcx>(
let input_lts = input_visitor.lts;
let output_lts = output_visitor.lts;
if let Some(trait_sig) = trait_sig
&& non_elidable_self_type(cx, func, trait_sig.first().copied(), msrv)
if let Some(&[trait_sig]) = trait_sig
&& non_elidable_self_type(cx, func, trait_sig, msrv)
{
return None;
}
@ -310,7 +310,7 @@ fn could_use_elision<'tcx>(
let body = cx.tcx.hir_body(body_id);
let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
if non_elidable_self_type(cx, func, Some(first_ident), msrv) {
if non_elidable_self_type(cx, func, first_ident, msrv) {
return None;
}
@ -384,8 +384,8 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefI
}
// elision doesn't work for explicit self types before Rust 1.81, see rust-lang/rust#69064
fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Option<Ident>>, msrv: Msrv) -> bool {
if let Some(Some(ident)) = ident
fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>, msrv: Msrv) -> bool {
if let Some(ident) = ident
&& ident.name == kw::SelfLower
&& !func.implicit_self.has_implicit_self()
&& let Some(self_ty) = func.inputs.first()

View file

@ -387,12 +387,11 @@ impl LiteralDigitGrouping {
let first = groups.next().expect("At least one group");
if radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal {
if let Some(second_size) = groups.next() {
if !groups.all(|i| i == second_size) || first > second_size {
return Err(WarningType::UnusualByteGroupings);
}
}
if (radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal)
&& let Some(second_size) = groups.next()
&& (!groups.all(|i| i == second_size) || first > second_size)
{
return Err(WarningType::UnusualByteGroupings);
}
if let Some(second) = groups.next() {

View file

@ -45,15 +45,14 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<Strin
let spans = spans
.iter()
.filter_map(|(span, name)| {
if let Some(name) = name {
if let Some(name) = name
// We need to check that the name is a local.
if !mir
&& !mir
.var_debug_info
.iter()
.any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name)
{
return None;
}
{
return None;
}
Some(*span)
})

View file

@ -0,0 +1,141 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, higher, path_to_local_id};
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::{Expr, ExprKind, LangItem, Node, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::{Span, sym};
use super::CHAR_INDICES_AS_BYTE_INDICES;
// The list of `str` methods we want to lint that have a `usize` argument representing a byte index.
// Note: `String` also has methods that work with byte indices,
// but they all take `&mut self` and aren't worth considering since the user couldn't have called
// them while the chars iterator is live anyway.
const BYTE_INDEX_METHODS: &[&str] = &[
"is_char_boundary",
"floor_char_boundary",
"ceil_char_boundary",
"get",
"index",
"index_mut",
"get_mut",
"get_unchecked",
"get_unchecked_mut",
"slice_unchecked",
"slice_mut_unchecked",
"split_at",
"split_at_mut",
"split_at_checked",
"split_at_mut_checked",
];
const CONTINUE: ControlFlow<!, ()> = ControlFlow::Continue(());
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, iterable: &Expr<'_>, body: &'tcx Expr<'tcx>) {
if let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = iterable.kind
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(iterable.hir_id)
&& cx.tcx.is_diagnostic_item(sym::enumerate_method, method_id)
&& let ExprKind::MethodCall(_, chars_recv, _, chars_span) = enumerate_recv.kind
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(enumerate_recv.hir_id)
&& cx.tcx.is_diagnostic_item(sym::str_chars, method_id)
{
if let PatKind::Tuple([pat, _], _) = pat.kind
&& let PatKind::Binding(_, binding_id, ..) = pat.kind
{
// Destructured iterator element `(idx, _)`, look for uses of the binding
for_each_expr(cx, body, |expr| {
if path_to_local_id(expr, binding_id) {
check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv);
}
CONTINUE
});
} else if let PatKind::Binding(_, binding_id, ..) = pat.kind {
// Bound as a tuple, look for `tup.0`
for_each_expr(cx, body, |expr| {
if let ExprKind::Field(e, field) = expr.kind
&& path_to_local_id(e, binding_id)
&& field.name == sym::integer(0)
{
check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv);
}
CONTINUE
});
}
}
}
fn check_index_usage<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
pat: &Pat<'_>,
enumerate_span: Span,
chars_span: Span,
chars_recv: &Expr<'_>,
) {
let Some(parent_expr) = index_consumed_at(cx, expr) else {
return;
};
let is_string_like = |ty: Ty<'_>| ty.is_str() || is_type_lang_item(cx, ty, LangItem::String);
let message = match parent_expr.kind {
ExprKind::MethodCall(segment, recv, ..)
// We currently only lint `str` methods (which `String` can deref to), so a `.is_str()` check is sufficient here
// (contrary to the `ExprKind::Index` case which needs to handle both with `is_string_like` because `String` implements
// `Index` directly and no deref to `str` would happen in that case).
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str()
&& BYTE_INDEX_METHODS.contains(&segment.ident.name.as_str())
&& eq_expr_value(cx, chars_recv, recv) =>
{
"passing a character position to a method that expects a byte index"
},
ExprKind::Index(target, ..)
if is_string_like(cx.typeck_results().expr_ty_adjusted(target).peel_refs())
&& eq_expr_value(cx, chars_recv, target) =>
{
"indexing into a string with a character position where a byte index is expected"
},
_ => return,
};
span_lint_hir_and_then(
cx,
CHAR_INDICES_AS_BYTE_INDICES,
expr.hir_id,
expr.span,
message,
|diag| {
diag.note("a character can take up more than one byte, so they are not interchangeable")
.span_note(
MultiSpan::from_spans(vec![pat.span, enumerate_span]),
"position comes from the enumerate iterator",
)
.span_suggestion_verbose(
chars_span.to(enumerate_span),
"consider using `.char_indices()` instead",
"char_indices()",
Applicability::MaybeIncorrect,
);
},
);
}
/// Returns the expression which ultimately consumes the index.
/// This is usually the parent expression, i.e. `.split_at(idx)` for `idx`,
/// but for `.get(..idx)` we want to consider the method call the consuming expression,
/// which requires skipping past the range expression.
fn index_consumed_at<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
match node {
Node::Expr(expr) if higher::Range::hir(expr).is_some() => {},
Node::ExprField(_) => {},
Node::Expr(expr) => return Some(expr),
_ => break,
}
}
None
}

View file

@ -13,45 +13,45 @@ use rustc_span::sym;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
let pat_span = pat.span;
if let PatKind::Tuple(pat, _) = pat.kind {
if pat.len() == 2 {
let arg_span = arg.span;
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
(key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
(_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
_ => return,
},
if let PatKind::Tuple(pat, _) = pat.kind
&& pat.len() == 2
{
let arg_span = arg.span;
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
(key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
(_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
_ => return,
};
let mutbl = match mutbl {
Mutability::Not => "",
Mutability::Mut => "_mut",
};
let arg = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
_ => arg,
};
},
_ => return,
};
let mutbl = match mutbl {
Mutability::Not => "",
Mutability::Mut => "_mut",
};
let arg = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
_ => arg,
};
if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
span_lint_and_then(
cx,
FOR_KV_MAP,
arg_span,
format!("you seem to want to iterate on a map's {kind}s"),
|diag| {
let map = sugg::Sugg::hir(cx, arg, "map");
diag.multipart_suggestion(
"use the corresponding method",
vec![
(pat_span, snippet(cx, new_pat_span, kind).into_owned()),
(arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())),
],
Applicability::MachineApplicable,
);
},
);
}
if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
span_lint_and_then(
cx,
FOR_KV_MAP,
arg_span,
format!("you seem to want to iterate on a map's {kind}s"),
|diag| {
let map = sugg::Sugg::hir(cx, arg, "map");
diag.multipart_suggestion(
"use the corresponding method",
vec![
(pat_span, snippet(cx, new_pat_span, kind).into_owned()),
(arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_paren())),
],
Applicability::MachineApplicable,
);
},
);
}
}
}

View file

@ -3,6 +3,7 @@ use super::utils::make_iterator_snippet;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -35,6 +36,7 @@ pub(super) fn check<'tcx>(
&& let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind
&& is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome)
&& path_res(cx, inner_ret) == Res::Local(binding_id)
&& !contains_return_break_continue_macro(cond)
&& let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
{
let mut applicability = Applicability::MachineApplicable;

View file

@ -28,37 +28,37 @@ pub(super) fn check<'tcx>(
end: Some(end),
limits,
}) = higher::Range::hir(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
let mut starts = vec![Start {
id: canonical_id,
kind: StartKind::Range,
}];
&& let PatKind::Binding(_, canonical_id, _, _) = pat.kind
{
let mut starts = vec![Start {
id: canonical_id,
kind: StartKind::Range,
}];
// This is one of few ways to return different iterators
// derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
let mut iter_a = None;
let mut iter_b = None;
// This is one of few ways to return different iterators
// derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
let mut iter_a = None;
let mut iter_b = None;
if let ExprKind::Block(block, _) = body.kind {
if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
starts.extend(loop_counters);
}
iter_a = Some(get_assignments(block, &starts));
} else {
iter_b = Some(get_assignment(body));
if let ExprKind::Block(block, _) = body.kind {
if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
starts.extend(loop_counters);
}
iter_a = Some(get_assignments(block, &starts));
} else {
iter_b = Some(get_assignment(body));
}
let assignments = iter_a.into_iter().flatten().chain(iter_b);
let assignments = iter_a.into_iter().flatten().chain(iter_b);
let big_sugg = assignments
// The only statements in the for loops can be indexed assignments from
// indexed retrievals (except increments of loop counters).
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
let big_sugg = assignments
// The only statements in the for loops can be indexed assignments from
// indexed retrievals (except increments of loop counters).
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
&& let ExprKind::Index(base_right, idx_right, _) = rhs.kind
&& let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left))
&& get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some()
@ -68,42 +68,41 @@ pub(super) fn check<'tcx>(
&& !local_used_in(cx, canonical_id, base_right)
// Source and destination must be different
&& path_to_local(base_left) != path_to_local(base_right)
{
Some((
ty,
IndexExpr {
base: base_left,
idx: start_left,
idx_offset: offset_left,
},
IndexExpr {
base: base_right,
idx: start_right,
idx_offset: offset_right,
},
))
} else {
None
}
})
{
Some((
ty,
IndexExpr {
base: base_left,
idx: start_left,
idx_offset: offset_left,
},
IndexExpr {
base: base_right,
idx: start_right,
idx_offset: offset_right,
},
))
} else {
None
}
})
.map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src)))
.collect::<Option<Vec<_>>>()
.filter(|v| !v.is_empty())
.map(|v| v.join("\n "));
})
.map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src)))
.collect::<Option<Vec<_>>>()
.filter(|v| !v.is_empty())
.map(|v| v.join("\n "));
if let Some(big_sugg) = big_sugg {
span_lint_and_sugg(
cx,
MANUAL_MEMCPY,
expr.span,
"it looks like you're manually copying between slices",
"try replacing the loop by",
big_sugg,
Applicability::Unspecified,
);
return true;
}
if let Some(big_sugg) = big_sugg {
span_lint_and_sugg(
cx,
MANUAL_MEMCPY,
expr.span,
"it looks like you're manually copying between slices",
"try replacing the loop by",
big_sugg,
Applicability::Unspecified,
);
return true;
}
}
false
@ -184,7 +183,12 @@ fn build_manual_memcpy_suggestion<'tcx>(
{
dst_base_str
} else {
format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into()
format!(
"{dst_base_str}[{}..{}]",
dst_offset.maybe_paren(),
dst_limit.maybe_paren()
)
.into()
};
let method_str = if is_copy(cx, elem_ty) {
@ -196,7 +200,12 @@ fn build_manual_memcpy_suggestion<'tcx>(
let src = if is_array_length_equal_to_range(cx, start, end, src.base) {
src_base_str
} else {
format!("{src_base_str}[{}..{}]", src_offset.maybe_par(), src_limit.maybe_par()).into()
format!(
"{src_base_str}[{}..{}]",
src_offset.maybe_paren(),
src_limit.maybe_paren()
)
.into()
};
format!("{dst}.{method_str}(&{src});")

View file

@ -81,15 +81,15 @@ fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>,
}
fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, loop_span: Span) {
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind {
if let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind {
let offending_arg = args
.iter()
.find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span));
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind
&& let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind
{
let offending_arg = args
.iter()
.find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span));
if let Some(offending_arg) = offending_arg {
report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span);
}
if let Some(offending_arg) = offending_arg {
report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span);
}
}
}

View file

@ -1,3 +1,4 @@
mod char_indices_as_byte_indices;
mod empty_loop;
mod explicit_counter_loop;
mod explicit_into_iter_loop;
@ -740,6 +741,49 @@ declare_clippy_lint! {
"manually filling a slice with a value"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of a character position yielded by `.chars().enumerate()` in a context where a **byte index** is expected,
/// such as an argument to a specific `str` method or indexing into a `str` or `String`.
///
/// ### Why is this bad?
/// A character (more specifically, a Unicode scalar value) that is yielded by `str::chars` can take up multiple bytes,
/// so a character position does not necessarily have the same byte index at which the character is stored.
/// Thus, using the character position where a byte index is expected can unexpectedly return wrong values
/// or panic when the string consists of multibyte characters.
///
/// For example, the character `a` in `äa` is stored at byte index 2 but has the character position 1.
/// Using the character position 1 to index into the string will lead to a panic as it is in the middle of the first character.
///
/// Instead of `.chars().enumerate()`, the correct iterator to use is `.char_indices()`, which yields byte indices.
///
/// This pattern is technically fine if the strings are known to only use the ASCII subset,
/// though in those cases it would be better to use `bytes()` directly to make the intent clearer,
/// but there is also no downside to just using `.char_indices()` directly and supporting non-ASCII strings.
///
/// You may also want to read the [chapter on strings in the Rust Book](https://doc.rust-lang.org/book/ch08-02-strings.html)
/// which goes into this in more detail.
///
/// ### Example
/// ```no_run
/// # let s = "...";
/// for (idx, c) in s.chars().enumerate() {
/// let _ = s[idx..]; // ⚠️ Panics for strings consisting of multibyte characters
/// }
/// ```
/// Use instead:
/// ```no_run
/// # let s = "...";
/// for (idx, c) in s.char_indices() {
/// let _ = s[idx..];
/// }
/// ```
#[clippy::version = "1.83.0"]
pub CHAR_INDICES_AS_BYTE_INDICES,
correctness,
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"
}
pub struct Loops {
msrv: Msrv,
enforce_iter_loop_reborrow: bool,
@ -777,6 +821,7 @@ impl_lint_pass!(Loops => [
UNUSED_ENUMERATE_INDEX,
INFINITE_LOOP,
MANUAL_SLICE_FILL,
CHAR_INDICES_AS_BYTE_INDICES,
]);
impl<'tcx> LateLintPass<'tcx> for Loops {
@ -860,6 +905,7 @@ impl Loops {
manual_flatten::check(cx, pat, arg, body, span, self.msrv);
manual_find::check(cx, pat, arg, body, span, expr);
unused_enumerate_index::check(cx, pat, arg, body);
char_indices_as_byte_indices::check(cx, pat, arg, body);
}
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {

View file

@ -82,14 +82,14 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if bk == ty::BorrowKind::Mutable {
if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if bk == ty::BorrowKind::Mutable
&& let PlaceBase::Local(id) = cmt.place.base
{
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id));
}
}
}

View file

@ -31,155 +31,154 @@ pub(super) fn check<'tcx>(
ref end,
limits,
}) = higher::Range::hir(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
let mut visitor = VarVisitor {
cx,
var: canonical_id,
indexed_mut: FxHashSet::default(),
indexed_indirectly: FxHashMap::default(),
indexed_directly: FxIndexMap::default(),
referenced: FxHashSet::default(),
nonindex: false,
prefer_mutable: false,
&& let PatKind::Binding(_, canonical_id, ident, _) = pat.kind
{
let mut visitor = VarVisitor {
cx,
var: canonical_id,
indexed_mut: FxHashSet::default(),
indexed_indirectly: FxHashMap::default(),
indexed_directly: FxIndexMap::default(),
referenced: FxHashSet::default(),
nonindex: false,
prefer_mutable: false,
};
walk_expr(&mut visitor, body);
// linting condition: we only indexed one variable, and indexed it directly
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
let (indexed, (indexed_extent, indexed_ty)) = visitor
.indexed_directly
.into_iter()
.next()
.expect("already checked that we have exactly 1 element");
// ensure that the indexed variable was declared before the loop, see #601
if let Some(indexed_extent) = indexed_extent {
let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id);
let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
return;
}
}
// don't lint if the container that is indexed does not have .iter() method
let has_iter = has_iter_method(cx, indexed_ty);
if has_iter.is_none() {
return;
}
// don't lint if the container that is indexed into is also used without
// indexing
if visitor.referenced.contains(&indexed) {
return;
}
let starts_at_zero = is_integer_const(cx, start, 0);
let skip = if starts_at_zero {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) {
return;
} else {
format!(".skip({})", snippet(cx, start.span, ".."))
};
walk_expr(&mut visitor, body);
// linting condition: we only indexed one variable, and indexed it directly
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
let (indexed, (indexed_extent, indexed_ty)) = visitor
.indexed_directly
.into_iter()
.next()
.expect("already checked that we have exactly 1 element");
let mut end_is_start_plus_val = false;
// ensure that the indexed variable was declared before the loop, see #601
if let Some(indexed_extent) = indexed_extent {
let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id);
let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
return;
let take = if let Some(end) = *end {
let mut take_expr = end;
if let ExprKind::Binary(ref op, left, right) = end.kind
&& op.node == BinOpKind::Add
{
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
if start_equal_left {
take_expr = right;
} else if start_equal_right {
take_expr = left;
}
end_is_start_plus_val = start_equal_left | start_equal_right;
}
// don't lint if the container that is indexed does not have .iter() method
let has_iter = has_iter_method(cx, indexed_ty);
if has_iter.is_none() {
return;
}
// don't lint if the container that is indexed into is also used without
// indexing
if visitor.referenced.contains(&indexed) {
return;
}
let starts_at_zero = is_integer_const(cx, start, 0);
let skip = if starts_at_zero {
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) {
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) {
return;
} else {
format!(".skip({})", snippet(cx, start.span, ".."))
};
let mut end_is_start_plus_val = false;
let take = if let Some(end) = *end {
let mut take_expr = end;
if let ExprKind::Binary(ref op, left, right) = end.kind {
if op.node == BinOpKind::Add {
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
if start_equal_left {
take_expr = right;
} else if start_equal_right {
take_expr = left;
}
end_is_start_plus_val = start_equal_left | start_equal_right;
}
}
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) {
return;
} else {
match limits {
ast::RangeLimits::Closed => {
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
format!(".take({})", take_expr + sugg::ONE)
},
ast::RangeLimits::HalfOpen => {
format!(".take({})", snippet(cx, take_expr.span, ".."))
},
}
}
} else {
String::new()
};
let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
("mut ", "iter_mut")
} else {
("", "iter")
};
let take_is_empty = take.is_empty();
let mut method_1 = take;
let mut method_2 = skip;
if end_is_start_plus_val {
mem::swap(&mut method_1, &mut method_2);
}
if visitor.nonindex {
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator and enumerate()",
vec![
(pat.span, format!("({}, <item>)", ident.name)),
(
arg.span,
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
),
],
Applicability::HasPlaceholders,
);
match limits {
ast::RangeLimits::Closed => {
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
format!(".take({})", take_expr + sugg::ONE)
},
);
} else {
let repl = if starts_at_zero && take_is_empty {
format!("&{ref_mut}{indexed}")
} else {
format!("{indexed}.{method}(){method_1}{method_2}")
};
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator",
vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
Applicability::HasPlaceholders,
);
ast::RangeLimits::HalfOpen => {
format!(".take({})", snippet(cx, take_expr.span, ".."))
},
);
}
}
} else {
String::new()
};
let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
("mut ", "iter_mut")
} else {
("", "iter")
};
let take_is_empty = take.is_empty();
let mut method_1 = take;
let mut method_2 = skip;
if end_is_start_plus_val {
mem::swap(&mut method_1, &mut method_2);
}
if visitor.nonindex {
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator and enumerate()",
vec![
(pat.span, format!("({}, <item>)", ident.name)),
(
arg.span,
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
),
],
Applicability::HasPlaceholders,
);
},
);
} else {
let repl = if starts_at_zero && take_is_empty {
format!("&{ref_mut}{indexed}")
} else {
format!("{indexed}.{method}(){method_1}{method_2}")
};
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator",
vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
Applicability::HasPlaceholders,
);
},
);
}
}
}
@ -346,10 +345,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
for expr in args {
let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
self.prefer_mutable = false;
if let ty::Ref(_, _, mutbl) = *ty.kind() {
if mutbl == Mutability::Mut {
self.prefer_mutable = true;
}
if let ty::Ref(_, _, mutbl) = *ty.kind()
&& mutbl == Mutability::Mut
{
self.prefer_mutable = true;
}
self.visit_expr(expr);
}
@ -361,10 +360,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
iter::once(receiver).chain(args.iter()),
) {
self.prefer_mutable = false;
if let ty::Ref(_, _, mutbl) = *ty.kind() {
if mutbl == Mutability::Mut {
self.prefer_mutable = true;
}
if let ty::Ref(_, _, mutbl) = *ty.kind()
&& mutbl == Mutability::Mut
{
self.prefer_mutable = true;
}
self.visit_expr(expr);
}

View file

@ -244,10 +244,10 @@ fn never_loop_expr<'tcx>(
});
combine_seq(first, || {
// checks if break targets a block instead of a loop
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
*reachable = true;
}
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind
&& let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t)
{
*reachable = true;
}
NeverLoopResult::Diverging
})

View file

@ -263,7 +263,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
if impls_iterator {
format!(
"{}",
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren()
)
} else {
// (&x).into_iter() ==> x.iter()
@ -281,12 +281,12 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
};
format!(
"{}.{method_name}()",
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_paren(),
)
},
_ => format!(
"{}.into_iter()",
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren()
),
}
}

View file

@ -5,7 +5,7 @@ use itertools::Itertools;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt};
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, SyntaxContext, sym};
use std::collections::BTreeMap;
@ -249,6 +249,15 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
})
.flatten()
.copied()
.inspect(|&unsafe_block| {
if let Level::Expect(id) = cx.tcx.lint_level_at_node(MACRO_METAVARS_IN_UNSAFE, unsafe_block).0 {
// Since we're going to deduplicate expanded unsafe blocks by its enclosing macro definition soon,
// which would lead to unfulfilled `#[expect()]`s in all other unsafe blocks that are filtered out
// except for the one we emit the warning at, we must manually fulfill the lint
// for all unsafe blocks here.
cx.fulfill_expectation(id);
}
})
.map(|id| {
// Remove the syntax context to hide "in this macro invocation" in the diagnostic.
// The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything

View file

@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert {
ExprKind::Unary(UnOp::Not, e) => (e, ""),
_ => (cond, "!"),
};
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par();
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren();
let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" };
let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}");
// we show to the user the suggestion without the comments, but when applying the fix, include the

View file

@ -181,7 +181,7 @@ fn maybe_emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggest
make_assignment,
hir_with_ignore_attr,
} = suggestion;
let input = Sugg::hir(cx, input, "..").maybe_par();
let input = Sugg::hir(cx, input, "..").maybe_paren();
let min = Sugg::hir(cx, min, "..");
let max = Sugg::hir(cx, max, "..");
let semicolon = if make_assignment.is_some() { ";" } else { "" };

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