Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
ab7e525929
377 changed files with 6426 additions and 3724 deletions
2
.github/workflows/clippy_changelog.yml
vendored
2
.github/workflows/clippy_changelog.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
|
|
@ -8,6 +8,10 @@ on:
|
|||
tags:
|
||||
- rust-1.**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TARGET_BRANCH: 'gh-pages'
|
||||
SHA: '${{ github.sha }}'
|
||||
|
|
|
|||
4
.github/workflows/lintcheck.yml
vendored
4
.github/workflows/lintcheck.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
55
book/src/development/infrastructure/benchmarking.md
Normal file
55
book/src/development/infrastructure/benchmarking.md
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
rustc::untranslatable_diagnostic
|
||||
)]
|
||||
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_hir;
|
||||
extern crate rustc_middle;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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}"))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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, ".."))
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal file
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal 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
|
||||
})
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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![]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
141
clippy_lints/src/loops/char_indices_as_byte_indices.rs
Normal file
141
clippy_lints/src/loops/char_indices_as_byte_indices.rs
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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});")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<'_>) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue