Merge commit '0621446356' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-04-22 16:10:59 +02:00
parent ed892e72dd
commit ff428d91c2
746 changed files with 13925 additions and 7188 deletions

View file

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

View file

@ -66,7 +66,7 @@ jobs:
run: cargo test --features internal -- --skip dogfood
- name: Test clippy_lints
run: cargo test --features internal
run: cargo test
working-directory: clippy_lints
- name: Test clippy_utils

View file

@ -42,7 +42,7 @@ jobs:
run: cargo test --features internal
- name: Test clippy_lints
run: cargo test --features internal
run: cargo test
working-directory: clippy_lints
- name: Test clippy_utils

View file

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

View file

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

View file

@ -6,11 +6,68 @@ document.
## Unreleased / Beta / In Rust Nightly
[609cd310...master](https://github.com/rust-lang/rust-clippy/compare/609cd310...master)
[3e3715c3...master](https://github.com/rust-lang/rust-clippy/compare/3e3715c3...master)
## Rust 1.86
Current stable, released 2025-04-03
[View all 108 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-12-27T15%3A11%3A38Z..2025-02-06T13%3A57%3A58Z+base%3Amaster)
### New Lints
* Added [`unneeded_struct_pattern`] to `style` [#13465](https://github.com/rust-lang/rust-clippy/pull/13465)
* Added [`doc_overindented_list_items`] to `style` [#13711](https://github.com/rust-lang/rust-clippy/pull/13711)
* Added [`manual_ok_err`] to `complexity` [#13740](https://github.com/rust-lang/rust-clippy/pull/13740)
* Added [`non_std_lazy_statics`] to `pedantic` [#13770](https://github.com/rust-lang/rust-clippy/pull/13770)
* Added [`manual_repeat_n`] to `style` [#13858](https://github.com/rust-lang/rust-clippy/pull/13858)
* Added [`manual_option_as_slice`] to `complexity` [#13901](https://github.com/rust-lang/rust-clippy/pull/13901)
* Added [`double_ended_iterator_last`] to `perf` [#13922](https://github.com/rust-lang/rust-clippy/pull/13922)
* Added [`useless_nonzero_new_unchecked`] to `complexity` [#13993](https://github.com/rust-lang/rust-clippy/pull/13993)
* Added [`sliced_string_as_bytes`] to `perf` [#14002](https://github.com/rust-lang/rust-clippy/pull/14002)
* Added [`unnecessary_semicolon`] to `pedantic` [#14032](https://github.com/rust-lang/rust-clippy/pull/14032)
* Added [`return_and_then`] to `restriction` [#14051](https://github.com/rust-lang/rust-clippy/pull/14051)
* Added [`manual_slice_fill`] to `style` [#14082](https://github.com/rust-lang/rust-clippy/pull/14082)
* Added [`precedence_bits`] to `restriction` [#14115](https://github.com/rust-lang/rust-clippy/pull/14115)
### Moves and Deprecations
* Moved [`redundant_locals`] to `suspicious` (from `correctness`, now warn-by-default)
[#13747](https://github.com/rust-lang/rust-clippy/pull/13747)
* Moved [`format_push_string`] to `pedantic` (from `restriction`)
[#13894](https://github.com/rust-lang/rust-clippy/pull/13894)
* Moved [`format_collect`] to `pedantic` (from `perf`, now allow-by-default)
[#13894](https://github.com/rust-lang/rust-clippy/pull/13894)
* Moved [`mutex_integer`] to `restriction` (from `nursery`) [#14110](https://github.com/rust-lang/rust-clippy/pull/14110)
### Enhancements
* Add `lint-inconsistent-struct-field-initializers` configuration option to [`inconsistent_struct_constructor`]
[#13737](https://github.com/rust-lang/rust-clippy/pull/13737)
* [`len_zero`] now also triggers if deref target implements `is_empty()`
[#13871](https://github.com/rust-lang/rust-clippy/pull/13871)
* [`obfuscated_if_else`] now also triggers for the `.then(..).unwrap_or(..)` pattern
[#14021](https://github.com/rust-lang/rust-clippy/pull/14021)
### False Positive Fixes
* [`trailing_empty_array`] no longer triggers in tests [#13844](https://github.com/rust-lang/rust-clippy/pull/13844)
* [`missing_const_for_fn`] no longer triggers in tests [#13945](https://github.com/rust-lang/rust-clippy/pull/13945)
* [`significant_drop_in_scrutinee`]: do not falsely warn for temporaries created by `.await` expansion
[#13985](https://github.com/rust-lang/rust-clippy/pull/13985)
### ICE Fixes
* [`borrow_interior_mutable_const`] Fix an ICE that can occur when taking a reference to a tuple/`struct` field of an
interior mutable `const` [#13877](https://github.com/rust-lang/rust-clippy/pull/13877)
### Others
* Clippy now uses Rust edition 2024 [#13751](https://github.com/rust-lang/rust-clippy/pull/13751)
## Rust 1.85
Current stable, released 2025-02-20
Released 2025-02-20
[View all 72 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-11-15T19%3A31%3A08Z..2024-12-26T13%3A59%3A48Z+base%3Amaster)
@ -5516,6 +5573,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 +5739,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
@ -5783,12 +5842,14 @@ Released 2018-09-13
[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
[`manual_abs_diff`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`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
@ -6055,6 +6116,7 @@ Released 2018-09-13
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
[`redundant_test_prefix`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_test_prefix
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
@ -6156,6 +6218,7 @@ Released 2018-09-13
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
[`swap_with_temporary`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_with_temporary
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
@ -6346,6 +6409,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 +6426,7 @@ Released 2018-09-13
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
[`lint-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-inconsistent-struct-field-initializers
[`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools

View file

@ -1,7 +1,7 @@
[package]
name = "clippy"
# begin autogenerated version
version = "0.1.87"
version = "0.1.88"
# end autogenerated version
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
@ -27,6 +27,7 @@ clippy_config = { path = "clippy_config" }
clippy_lints = { path = "clippy_lints" }
clippy_utils = { path = "clippy_utils" }
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
clippy_lints_internal = { path = "clippy_lints_internal", optional = true }
tempfile = { version = "3.3", optional = true }
termize = "0.1"
color-print = "0.3.4"
@ -43,7 +44,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" }
@ -58,8 +59,8 @@ tokio = { version = "1", features = ["io-util"] }
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
[features]
integration = ["tempfile"]
internal = ["clippy_lints/internal", "tempfile"]
integration = ["dep:tempfile"]
internal = ["dep:clippy_lints_internal", "dep:tempfile"]
[package.metadata.rust-analyzer]
# This package uses #[feature(rustc_private)]

View file

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

View file

@ -99,6 +99,7 @@ struct A;
impl A {
pub fn fo(&self) {}
pub fn foo(&self) {}
//~^ foo_functions
pub fn food(&self) {}
}
@ -106,12 +107,14 @@ impl A {
trait B {
fn fo(&self) {}
fn foo(&self) {}
//~^ foo_functions
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {}
//~^ foo_functions
fn food() {}
fn main() {
@ -122,17 +125,24 @@ 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. Except for very specific situations
(`//@check-pass`), at least one error marker must be present in a test file for
it to be accepted.
Once we have implemented our lint we can run `TESTNAME=foo_functions cargo
uibless` to generate the `.stderr` file. If our lint makes use of structured
suggestions then this command will also generate the corresponding `.fixed`
file.
While we are working on implementing our lint, we can keep running the UI test.
That allows us to check if the output is turning into what we want by checking the
`.stderr` file that gets updated on every test run.
Running `TESTNAME=foo_functions cargo uitest` should pass on its own. When we
commit our lint, we need to commit the generated `.stderr` files, too. In
general, you should only commit files changed by `cargo bless` for the
specific lint you are creating/editing.
Once we have implemented our lint running `TESTNAME=foo_functions cargo uitest`
should pass on its own. When we commit our lint, we need to commit the generated
`.stderr` and if applicable `.fixed` files, too. In general, you should only
commit files changed by `cargo bless` for the specific lint you are creating/editing.
> _Note:_ you can run multiple test files by specifying a comma separated list:
> `TESTNAME=foo_functions,test2,test3`.

View file

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

View file

@ -159,19 +159,21 @@ paths for Clippy can be found in [paths.rs][paths]
To check if our type defines a method called `some_method`:
```rust
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::return_ty;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{sym, return_ty};
impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
// Check if item is a method/function
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// Check the method is named `some_method`
&& impl_item.ident.name.as_str() == "some_method"
//
// Add `some_method` to `clippy_utils::sym` if it's not already there
&& impl_item.ident.name == sym::some_method
// We can also check it has a parameter `self`
&& signature.decl.implicit_self.has_implicit_self()
// We can go further and even check if its return type is `String`
&& is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
&& is_type_lang_item(cx, return_ty(cx, impl_item.hir_id), LangItem::String)
{
// ...
}

View file

@ -9,7 +9,7 @@ lint involves some boilerplate code.
A lint type is the category of items and expressions in which your lint focuses on.
As of the writing of this documentation update, there are 12 _types_ of lints
As of the writing of this documentation update, there are 11 _types_ of lints
besides the numerous standalone lints living under `clippy_lints/src/`:
- `cargo`
@ -23,7 +23,6 @@ besides the numerous standalone lints living under `clippy_lints/src/`:
- `transmute`
- `types`
- `unit_types`
- `utils / internal` (Clippy internal lints)
These types group together lints that share some common behaviors. For instance,
`functions` groups together lints that deal with some aspects of functions in

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
[package]
name = "clippy_config"
# begin autogenerated version
version = "0.1.87"
version = "0.1.88"
# end autogenerated version
edition = "2024"
publish = false

View file

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

View file

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

View file

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

View file

@ -13,6 +13,7 @@
#[allow(unused_extern_crates)]
extern crate rustc_driver;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;
pub mod dogfood;
pub mod fmt;

View file

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

View file

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

View file

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

View file

@ -1,7 +1,8 @@
use crate::utils::{UpdateMode, clippy_project_root, exit_with_failure, replace_region_in_file};
use aho_corasick::AhoCorasickBuilder;
use itertools::Itertools;
use rustc_lexer::{LiteralKind, TokenKind, tokenize, unescape};
use rustc_lexer::{LiteralKind, TokenKind, tokenize};
use rustc_literal_escaper::{Mode, unescape_unicode};
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::fmt::{self, Write};
@ -37,9 +38,8 @@ fn generate_lint_files(
deprecated_lints: &[DeprecatedLint],
renamed_lints: &[RenamedLint],
) {
let internal_lints = Lint::internal_lints(lints);
let mut usable_lints = Lint::usable_lints(lints);
usable_lints.sort_by_key(|lint| lint.name.clone());
let mut lints = lints.to_owned();
lints.sort_by_key(|lint| lint.name.clone());
replace_region_in_file(
update_mode,
@ -47,7 +47,7 @@ fn generate_lint_files(
"[There are over ",
" lints included in this crate!]",
|res| {
write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
write!(res, "{}", round_to_fifty(lints.len())).unwrap();
},
);
@ -57,7 +57,7 @@ fn generate_lint_files(
"[There are over ",
" lints included in this crate!]",
|res| {
write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
write!(res, "{}", round_to_fifty(lints.len())).unwrap();
},
);
@ -67,7 +67,7 @@ fn generate_lint_files(
"<!-- begin autogenerated links to lint list -->\n",
"<!-- end autogenerated links to lint list -->",
|res| {
for lint in usable_lints
for lint in lints
.iter()
.map(|l| &*l.name)
.chain(deprecated_lints.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
@ -86,7 +86,7 @@ fn generate_lint_files(
"// begin lints modules, do not remove this comment, its used in `update_lints`\n",
"// end lints modules, do not remove this comment, its used in `update_lints`",
|res| {
for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
for lint_mod in lints.iter().map(|l| &l.module).unique().sorted() {
writeln!(res, "mod {lint_mod};").unwrap();
}
},
@ -95,7 +95,7 @@ fn generate_lint_files(
process_file(
"clippy_lints/src/declared_lints.rs",
update_mode,
&gen_declared_lints(internal_lints.iter(), usable_lints.iter()),
&gen_declared_lints(lints.iter()),
);
let content = gen_deprecated_lints_test(deprecated_lints);
@ -106,10 +106,9 @@ fn generate_lint_files(
}
pub fn print_lints() {
let (lint_list, _, _) = gather_all();
let usable_lints = Lint::usable_lints(&lint_list);
let usable_lint_count = usable_lints.len();
let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
let (lints, _, _) = gather_all();
let lint_count = lints.len();
let grouped_by_lint_group = Lint::by_lint_group(lints.into_iter());
for (lint_group, mut lints) in grouped_by_lint_group {
println!("\n## {lint_group}");
@ -121,7 +120,7 @@ pub fn print_lints() {
}
}
println!("there are {usable_lint_count} lints");
println!("there are {lint_count} lints");
}
/// Runs the `rename_lint` command.
@ -402,53 +401,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)
@ -527,22 +526,6 @@ impl Lint {
}
}
/// Returns all non-deprecated lints and non-internal lints
#[must_use]
fn usable_lints(lints: &[Self]) -> Vec<Self> {
lints
.iter()
.filter(|l| !l.group.starts_with("internal"))
.cloned()
.collect()
}
/// Returns all internal lints
#[must_use]
fn internal_lints(lints: &[Self]) -> Vec<Self> {
lints.iter().filter(|l| l.group == "internal").cloned().collect()
}
/// Returns the lints in a `HashMap`, grouped by the different lint groups
#[must_use]
fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
@ -579,23 +562,14 @@ impl RenamedLint {
/// Generates the code for registering lints
#[must_use]
fn gen_declared_lints<'a>(
internal_lints: impl Iterator<Item = &'a Lint>,
usable_lints: impl Iterator<Item = &'a Lint>,
) -> String {
let mut details: Vec<_> = internal_lints
.map(|l| (false, &l.module, l.name.to_uppercase()))
.chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
.collect();
fn gen_declared_lints<'a>(lints: impl Iterator<Item = &'a Lint>) -> String {
let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
details.sort_unstable();
let mut output = GENERATED_FILE_COMMENT.to_string();
output.push_str("pub static LINTS: &[&crate::LintInfo] = &[\n");
for (is_public, module_name, lint_name) in details {
if !is_public {
output.push_str(" #[cfg(feature = \"internal\")]\n");
}
for (module_name, lint_name) in details {
let _: fmt::Result = writeln!(output, " crate::{module_name}::{lint_name}_INFO,");
}
output.push_str("];\n");
@ -830,7 +804,7 @@ fn remove_line_splices(s: &str) -> String {
.and_then(|s| s.strip_suffix('"'))
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
let mut res = String::with_capacity(s.len());
unescape::unescape_unicode(s, unescape::Mode::Str, &mut |range, ch| {
unescape_unicode(s, Mode::Str, &mut |range, ch| {
if ch.is_ok() {
res.push_str(&s[range]);
}
@ -936,41 +910,6 @@ mod tests {
assert_eq!(expected, result);
}
#[test]
fn test_usable_lints() {
let lints = vec![
Lint::new(
"should_assert_eq2",
"Not Deprecated",
"\"abc\"",
"module_name",
Range::default(),
),
Lint::new(
"should_assert_eq2",
"internal",
"\"abc\"",
"module_name",
Range::default(),
),
Lint::new(
"should_assert_eq2",
"internal_style",
"\"abc\"",
"module_name",
Range::default(),
),
];
let expected = vec![Lint::new(
"should_assert_eq2",
"Not Deprecated",
"\"abc\"",
"module_name",
Range::default(),
)];
assert_eq!(expected, Lint::usable_lints(&lints));
}
#[test]
fn test_by_lint_group() {
let lints = vec![

View file

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

View file

@ -1,7 +1,7 @@
[package]
name = "clippy_lints"
# begin autogenerated version
version = "0.1.87"
version = "0.1.88"
# end autogenerated version
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
@ -19,10 +19,7 @@ itertools = "0.12"
quine-mc_cluskey = "0.2"
regex-syntax = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
tempfile = { version = "3.3.0", optional = true }
toml = "0.7.3"
regex = { version = "1.5", optional = true }
unicode-normalization = "0.1"
unicode-script = { version = "0.5", default-features = false }
semver = "1.0"
@ -31,10 +28,6 @@ url = "2.2"
[dev-dependencies]
walkdir = "2.3"
[features]
# build clippy with internal lints enabled, off by default
internal = ["serde_json", "tempfile", "regex"]
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]
rustc_private = true

View file

@ -5,6 +5,7 @@ use clippy_config::types::{
SourceItemOrderingWithinModuleItemGroupings,
};
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_cfg_test;
use rustc_hir::{
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant,
VariantData,
@ -263,10 +264,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 +280,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);
}
@ -342,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
struct CurItem<'a> {
item: &'a Item<'a>,
order: usize,
name: String,
name: Option<String>,
}
let mut cur_t: Option<CurItem<'_>> = None;
@ -359,32 +362,36 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
// as no sorting by source map/line of code has to be applied.
//
for item in items {
if is_cfg_test(cx.tcx, item.hir_id()) {
continue;
}
if item.span.in_external_macro(cx.sess().source_map()) {
continue;
}
let ident = if let Some(ident) = item.kind.ident() {
ident
} else if let ItemKind::Impl(_) = item.kind
&& !get_item_name(item).is_empty()
{
rustc_span::Ident::empty() // FIXME: a bit strange, is there a better way to do it?
} else {
continue;
};
if ident.name.as_str().starts_with('_') {
// Filters out unnamed macro-like impls for various derives,
// e.g. serde::Serialize or num_derive::FromPrimitive.
continue;
}
if ident.name == rustc_span::sym::std && item.span.is_dummy() {
if let ItemKind::ExternCrate(None, _) = item.kind {
// Filters the auto-included Rust standard library.
if let Some(ident) = item.kind.ident() {
if ident.name.as_str().starts_with('_') {
// Filters out unnamed macro-like impls for various derives,
// e.g. serde::Serialize or num_derive::FromPrimitive.
continue;
}
println!("Unknown item: {item:?}");
if ident.name == rustc_span::sym::std && item.span.is_dummy() {
if let ItemKind::ExternCrate(None, _) = item.kind {
// Filters the auto-included Rust standard library.
continue;
}
if cfg!(debug_assertions) {
rustc_middle::bug!("unknown item: {item:?}");
}
}
} else if let ItemKind::Impl(_) = item.kind
&& get_item_name(item).is_some()
{
// keep going below
} else {
continue;
}
let item_kind = convert_module_item_kind(&item.kind);
@ -493,7 +500,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte
/// further in the [Rust Reference, Paths Chapter][rust_ref].
///
/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1
fn get_item_name(item: &Item<'_>) -> String {
fn get_item_name(item: &Item<'_>) -> Option<String> {
match item.kind {
ItemKind::Impl(im) => {
if let TyKind::Path(path) = im.self_ty.kind {
@ -513,27 +520,19 @@ fn get_item_name(item: &Item<'_>) -> String {
}
segs.push(String::new());
segs.join("!!")
Some(segs.join("!!"))
},
QPath::TypeRelative(_, _path_seg) => {
// This case doesn't exist in the clippy tests codebase.
String::new()
None
},
QPath::LangItem(_, _) => String::new(),
QPath::LangItem(_, _) => None,
}
} else {
// Impls for anything that isn't a named type can be skipped.
String::new()
None
}
},
// FIXME: `Ident::empty` for anonymous items is a bit strange, is there
// a better way to do it?
_ => item
.kind
.ident()
.unwrap_or(rustc_span::Ident::empty())
.name
.as_str()
.to_owned(),
_ => item.kind.ident().map(|name| name.as_str().to_owned()),
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::HasSession;
use clippy_utils::sugg::Sugg;
use clippy_utils::{is_else_clause, is_in_const_context};
use clippy_utils::{higher, is_else_clause, is_in_const_context, span_contains_comment};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
@ -46,18 +47,25 @@ declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::If(cond, then, Some(else_)) = expr.kind
&& matches!(cond.kind, ExprKind::DropTemps(_))
if !expr.span.from_expansion()
&& let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let Some(then_lit) = as_int_bool_lit(then)
&& let Some(else_lit) = as_int_bool_lit(else_)
&& let Some(else_lit) = as_int_bool_lit(r#else)
&& then_lit != else_lit
&& !expr.span.from_expansion()
&& !is_in_const_context(cx)
{
let ty = cx.typeck_results().expr_ty(then);
let mut applicability = Applicability::MachineApplicable;
let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
let snippet = {
let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
let mut sugg = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability);
if !then_lit {
sugg = !sugg;
}
@ -72,7 +80,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(
@ -91,10 +99,11 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
}
}
fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> {
if let ExprKind::Block(b, _) = e.kind
fn as_int_bool_lit(expr: &Expr<'_>) -> Option<bool> {
if let ExprKind::Block(b, _) = expr.kind
&& b.stmts.is_empty()
&& let Some(e) = b.expr
&& !e.span.from_expansion()
&& let ExprKind::Lit(lit) = e.kind
&& let LitKind::Int(x, _) = lit.node
{

View file

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

View file

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

View file

@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::{is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow};
use rustc_span::BytePos;
use super::BORROW_AS_PTR;
@ -29,10 +30,6 @@ pub(super) fn check<'tcx>(
}
let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
let operator_kind = match mutability {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
// Make sure that the span to be replaced doesn't include parentheses, that could break the
// suggestion.
let span = if has_enclosing_paren(snippet_with_applicability(cx, expr.span, "", &mut app)) {
@ -42,7 +39,7 @@ pub(super) fn check<'tcx>(
} else {
expr.span
};
(format!("&raw {operator_kind} {snip}"), span)
(format!("&raw {} {snip}", mutability.ptr_str()), span)
} else {
let Some(std_or_core) = std_or_core(cx) else {
return false;
@ -59,3 +56,25 @@ pub(super) fn check<'tcx>(
}
false
}
/// Check for an implicit cast from reference to raw pointer outside an explicit `as`.
pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) {
if !expr.span.from_expansion()
&& let ExprKind::AddrOf(BorrowKind::Ref, _, pointee) = expr.kind
&& !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..)))
&& let [deref, borrow] = cx.typeck_results().expr_adjustments(expr)
&& matches!(deref.kind, Adjust::Deref(..))
&& let Adjust::Borrow(AutoBorrow::RawPtr(mutability)) = borrow.kind
// Do not suggest taking a raw pointer to a temporary value
&& !is_expr_temporary_value(cx, pointee)
{
span_lint_and_then(cx, BORROW_AS_PTR, expr.span, "implicit borrow as raw pointer", |diag| {
diag.span_suggestion_verbose(
expr.span.until(pointee.span),
"use a raw pointer instead",
format!("&raw {} ", mutability.ptr_str()),
Applicability::MachineApplicable,
);
});
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -53,7 +53,8 @@ pub(super) fn check<'tcx>(
}
if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) {
let sugg = Sugg::hir(cx, cast_expr, "_");
let mut app = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app);
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
@ -65,8 +66,8 @@ 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()),
Applicability::MachineApplicable,
format!("{}.cast_{constness}()", sugg.maybe_paren()),
app,
);
}
}

View file

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

View file

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

View file

@ -62,6 +62,7 @@ impl CognitiveComplexity {
let mut cc = 1u64;
let mut returns = 0u64;
let mut prev_expr: Option<&ExprKind<'tcx>> = None;
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
match e.kind {
ExprKind::If(_, _, _) => {
@ -73,9 +74,14 @@ impl CognitiveComplexity {
}
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) => returns += 1,
ExprKind::Ret(_) => {
if !matches!(prev_expr, Some(ExprKind::Ret(_))) {
returns += 1;
}
},
_ => {},
}
prev_expr = Some(&e.kind);
ControlFlow::Continue(())
});

View file

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

View file

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

View file

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

View file

@ -165,17 +165,4 @@ macro_rules! declare_clippy_lint {
$(, $eval_always)?
}
};
(
$(#[doc = $lit:literal])*
pub $lint_name:ident,
internal,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Internal, $desc,
None, "0.0.0"
}
};
}

View file

@ -3,36 +3,6 @@
// Manual edits will be overwritten.
pub static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")]
crate::utils::internal_lints::almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_INFO,
@ -52,6 +22,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 +67,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 +258,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,
@ -312,6 +285,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
crate::macro_use::MACRO_USE_IMPORTS_INFO,
crate::main_recursion::MAIN_RECURSION_INFO,
crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO,
crate::manual_assert::MANUAL_ASSERT_INFO,
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
crate::manual_bits::MANUAL_BITS_INFO,
@ -334,7 +308,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,10 +317,10 @@ 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,
crate::matches::MATCH_ON_VEC_ITEMS_INFO,
crate::matches::MATCH_OVERLAPPING_ARM_INFO,
crate::matches::MATCH_REF_PATS_INFO,
crate::matches::MATCH_SAME_ARMS_INFO,
@ -488,6 +461,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
crate::methods::SUSPICIOUS_SPLITN_INFO,
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
crate::methods::SWAP_WITH_TEMPORARY_INFO,
crate::methods::TYPE_ID_ON_BOX_INFO,
crate::methods::UNBUFFERED_BYTES_INFO,
crate::methods::UNINIT_ASSUMED_INIT_INFO,
@ -664,6 +638,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
crate::redundant_test_prefix::REDUNDANT_TEST_PREFIX_INFO,
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
crate::ref_option_ref::REF_OPTION_REF_INFO,
crate::ref_patterns::REF_PATTERNS_INFO,

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_ty_alias;
use clippy_utils::source::SpanRangeExt as _;
use hir::ExprKind;
use hir::def::Res;
use rustc_errors::Applicability;
@ -70,15 +71,26 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs {
&& let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant()
&& !var.is_field_list_non_exhaustive()
&& !expr.span.from_expansion() && !qpath.span().from_expansion()
// do not suggest replacing an expression by a type name with placeholders
&& !base.is_suggestable_infer_ty()
{
span_lint_and_sugg(
let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())];
if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) {
// Remove `<`, '>` has already been removed by the existing removal expression.
removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new()));
}
span_lint_and_then(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
expr.span,
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
|diag| {
diag.multipart_suggestion(
"remove this call to `default`",
removals,
Applicability::MachineApplicable,
);
},
);
}
}

View file

@ -42,6 +42,8 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
#[clippy::version = "1.86.0"]
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
#[clippy::version = "1.86.0"]
("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
// end deprecated lints. used by `cargo dev deprecate_lint`
]}

View file

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

View file

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

View file

@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
}) = item.kind
{
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
@ -235,7 +235,7 @@ fn check_hash_peq<'tcx>(
{
// Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
@ -278,7 +278,7 @@ fn check_ord_partial_ord<'tcx>(
{
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
@ -349,6 +349,10 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
{
return;
}
// The presence of `unsafe` fields prevents deriving `Clone` automatically
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
return;
}
span_lint_and_note(
cx,
@ -426,10 +430,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 +483,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
}
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
// Initial map from generic index to param def.
// Vec<(param_def, needs_eq)>

View file

@ -4,6 +4,7 @@ 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 +73,15 @@ pub struct DisallowedMacros {
impl DisallowedMacros {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_macros,
|def_kind| matches!(def_kind, DefKind::Macro(_)),
"macro",
false,
);
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
disallowed,
seen: FxHashSet::default(),
derive_src: None,
earlies,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, snippet_indent};
use clippy_utils::tokenize_with_text;
@ -89,7 +91,7 @@ declare_clippy_lint! {
#[derive(Debug)]
struct ItemInfo {
kind: &'static str,
name: Symbol,
name: Option<Symbol>,
span: Span,
mod_items: Option<NodeId>,
}
@ -315,8 +317,12 @@ impl EmptyLineAfter {
for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) {
stop.comment_out(cx, &mut suggestions);
}
let name = match info.name {
Some(name) => format!("{} `{name}`", info.kind).into(),
None => Cow::from("the following item"),
};
diag.multipart_suggestion_verbose(
format!("if the doc comment should not document `{}` comment it out", info.name),
format!("if the doc comment should not document {name} then comment it out"),
suggestions,
Applicability::MaybeIncorrect,
);
@ -381,13 +387,10 @@ impl EmptyLineAfter {
) {
self.items.push(ItemInfo {
kind: kind.descr(),
// FIXME: this `sym::empty` can be leaked, see
// https://github.com/rust-lang/rust/pull/138740#discussion_r2021979899
name: if let Some(ident) = ident { ident.name } else { kw::Empty },
span: if let Some(ident) = ident {
span.with_hi(ident.span.hi())
} else {
span.with_hi(span.lo())
name: ident.map(|ident| ident.name),
span: match ident {
Some(ident) => span.with_hi(ident.span.hi()),
None => span.shrink_to_lo(),
},
mod_items: match kind {
ItemKind::Mod(_, _, ModKind::Loaded(items, _, _, _)) => items
@ -447,7 +450,7 @@ impl EarlyLintPass for EmptyLineAfter {
fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) {
self.items.push(ItemInfo {
kind: "crate",
name: kw::Crate,
name: Some(kw::Crate),
span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()),
mod_items: krate
.items

View file

@ -1,10 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
use clippy_utils::attrs::span_contains_cfg;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_lexer::TokenKind;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_hir::def::CtorOf;
use rustc_hir::def::DefKind::Ctor;
use rustc_hir::def::Res::Def;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
@ -70,10 +75,23 @@ declare_clippy_lint! {
"finds enum variants with empty brackets"
}
declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
#[derive(Debug)]
enum Usage {
Unused { redundant_use_sites: Vec<Span> },
Used,
NoDefinition { redundant_use_sites: Vec<Span> },
}
impl EarlyLintPass for EmptyWithBrackets {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
#[derive(Default)]
pub struct EmptyWithBrackets {
// Value holds `Usage::Used` if the empty tuple variant was used as a function
empty_tuple_enum_variants: FxIndexMap<LocalDefId, Usage>,
}
impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
impl LateLintPass<'_> for EmptyWithBrackets {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let ItemKind::Struct(ident, var_data, _) = &item.kind
&& has_brackets(var_data)
&& let span_after_ident = item.span.with_lo(ident.span.hi())
@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets {
}
}
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
// the span of the parentheses/braces
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
span_lint_and_then(
if has_no_fields(cx, &variant.data, span_after_ident) {
match variant.data {
VariantData::Struct { .. } => {
// Empty struct variants can be linted immediately
span_lint_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
span_after_ident,
"enum variant has empty brackets",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
},
);
},
VariantData::Tuple(.., local_def_id) => {
// Don't lint reachable tuple enums
if cx.effective_visibilities.is_reachable(variant.def_id) {
return;
}
if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) {
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
// definition was encountered. Now that there's a definition, convert it
// to Usage::Unused.
if let Usage::NoDefinition { redundant_use_sites } = entry {
*entry = Usage::Unused {
redundant_use_sites: redundant_use_sites.clone(),
};
}
} else {
self.empty_tuple_enum_variants.insert(
local_def_id,
Usage::Unused {
redundant_use_sites: vec![],
},
);
}
},
VariantData::Unit(..) => {},
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) {
// Do not count expressions from macro expansion as a redundant use site.
if expr.span.from_expansion() {
return;
}
match self.empty_tuple_enum_variants.get_mut(&def_id) {
Some(
&mut (Usage::Unused {
ref mut redundant_use_sites,
}
| Usage::NoDefinition {
ref mut redundant_use_sites,
}),
) => {
redundant_use_sites.push(parentheses_span);
},
None => {
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
self.empty_tuple_enum_variants.insert(
def_id,
Usage::NoDefinition {
redundant_use_sites: vec![parentheses_span],
},
);
},
_ => {},
}
} else {
// The parentheses are not redundant.
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
}
}
}
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
// Ignore all variants with Usage::Used or Usage::NoDefinition
let Usage::Unused { redundant_use_sites } = usage else {
continue;
};
// Attempt to fetch the Variant from LocalDefId.
let Node::Variant(variant) = cx.tcx.hir_node(
cx.tcx
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
) else {
continue;
};
// Span of the parentheses in variant definition
let span = variant.span.with_lo(variant.ident.span.hi());
span_lint_hir_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
span_after_ident,
variant.hir_id,
span,
"enum variant has empty brackets",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
if redundant_use_sites.is_empty() {
// If there's no redundant use sites, the definition is the only place to modify.
diagnostic.span_suggestion_hidden(
span,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
} else {
let mut parentheses_spans: Vec<_> =
redundant_use_sites.iter().map(|span| (*span, String::new())).collect();
parentheses_spans.push((span, String::new()));
diagnostic.multipart_suggestion(
"remove the brackets",
parentheses_spans,
Applicability::MaybeIncorrect,
);
}
},
);
}
}
}
fn has_no_ident_token(braces_span_str: &str) -> bool {
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
fn has_brackets(var_data: &VariantData<'_>) -> bool {
!matches!(var_data, VariantData::Unit(..))
}
fn has_brackets(var_data: &VariantData) -> bool {
!matches!(var_data, VariantData::Unit(_))
}
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
if !var_data.fields().is_empty() {
return false;
}
fn has_no_fields(cx: &LateContext<'_>, var_data: &VariantData<'_>, braces_span: Span) -> bool {
var_data.fields().is_empty() &&
// there might still be field declarations hidden from the AST
// (conditionally compiled code using #[cfg(..)])
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
return false;
};
has_no_ident_token(braces_span_str.as_ref())
!span_contains_cfg(cx, braces_span)
}
#[cfg(test)]
mod unit_test {
use super::*;
#[test]
fn test_has_no_ident_token() {
let input = "{ field: u8 }";
assert!(!has_no_ident_token(input));
let input = "(u8, String);";
assert!(!has_no_ident_token(input));
let input = " {
// test = 5
}
";
assert!(has_no_ident_token(input));
let input = " ();";
assert!(has_no_ident_token(input));
// If expression HIR ID and callee HIR ID are same, returns the span of the parentheses, else,
// returns None.
fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
if let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::Call(callee, ..) = parent.kind
&& callee.hir_id == expr.hir_id
{
Some(parent.span.with_lo(expr.span.hi()))
} else {
None
}
}
// Returns the LocalDefId of the variant being called as a function if it exists.
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
if let ExprKind::Path(QPath::Resolved(
_,
Path {
res: Def(Ctor(CtorOf::Variant, _), def_id),
..
},
)) = expr.kind
{
def_id.as_local()
} else {
None
}
}

View file

@ -95,14 +95,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
return;
};
if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
return;
}
if then_search.edits.is_empty() && else_search.edits.is_empty() {
// No insertions
return;
} else if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
// If there are other uses of the key, and the key is not copy,
// we cannot perform a fix automatically, but continue to emit a lint.
None
} else if then_search.edits.is_empty() || else_search.edits.is_empty() {
// if .. { insert } else { .. } or if .. { .. } else { insert }
let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
@ -123,10 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
),
};
format!(
Some(format!(
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {then_str} else {else_str}",
map_ty.entry_path(),
)
))
} else {
// if .. { insert } else { insert }
let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
@ -142,13 +141,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
};
let indent_str = snippet_indent(cx, expr.span);
let indent_str = indent_str.as_deref().unwrap_or("");
format!(
Some(format!(
"match {map_str}.entry({key_str}) {{\n{indent_str} {entry}::{then_entry} => {}\n\
{indent_str} {entry}::{else_entry} => {}\n{indent_str}}}",
reindent_multiline(&then_str, true, Some(4 + indent_str.len())),
reindent_multiline(&else_str, true, Some(4 + indent_str.len())),
entry = map_ty.entry_path(),
)
))
}
} else {
if then_search.edits.is_empty() {
@ -163,17 +162,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
} else {
then_search.snippet_occupied(cx, then_expr.span, &mut app)
};
format!(
Some(format!(
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}",
map_ty.entry_path(),
)
))
} else if let Some(insertion) = then_search.as_single_insertion() {
let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
if contains_expr.negated {
if insertion.value.can_have_side_effects() {
format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});")
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});"))
} else {
format!("{map_str}.entry({key_str}).or_insert({value_str});")
Some(format!("{map_str}.entry({key_str}).or_insert({value_str});"))
}
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
@ -183,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
} else {
let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
if contains_expr.negated {
format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});")
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});"))
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
// This would need to be a different lint.
@ -192,7 +191,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
}
};
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
if let Some(sugg) = sugg {
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
} else {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators, sym};
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Symbol;
use rustc_span::symbol::kw;
use rustc_span::{Symbol, sym};
declare_clippy_lint! {
/// ### What it does
@ -185,13 +185,13 @@ impl FormatImplExpr<'_, '_> {
&& let trait_name = match placeholder.format_trait {
FormatTrait::Display => sym::Display,
FormatTrait::Debug => sym::Debug,
FormatTrait::LowerExp => sym!(LowerExp),
FormatTrait::UpperExp => sym!(UpperExp),
FormatTrait::Octal => sym!(Octal),
FormatTrait::LowerExp => sym::LowerExp,
FormatTrait::UpperExp => sym::UpperExp,
FormatTrait::Octal => sym::Octal,
FormatTrait::Pointer => sym::Pointer,
FormatTrait::Binary => sym!(Binary),
FormatTrait::LowerHex => sym!(LowerHex),
FormatTrait::UpperHex => sym!(UpperHex),
FormatTrait::Binary => sym::Binary,
FormatTrait::LowerHex => sym::LowerHex,
FormatTrait::UpperHex => sym::UpperHex,
}
&& trait_name == self.format_trait_impl.name
&& let Ok(index) = placeholder.argument.index

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind};
use rustc_hir::{BlockCheckMode, Body, ExprKind, FnDecl, ImplicitSelfKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
@ -40,14 +40,25 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
name
};
// Body must be &(mut) <self_data>.name
// Body must be `&(mut) <self_data>.name`, potentially in an `unsafe` block
// self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(block_expr) = block.expr
{
block_expr
if let ExprKind::Block(unsafe_block, _) = block_expr.kind
&& unsafe_block.stmts.is_empty()
&& matches!(
unsafe_block.rules,
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
)
&& let Some(unsafe_block_expr) = unsafe_block.expr
{
unsafe_block_expr
} else {
block_expr
}
} else {
return;
};

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
@ -134,6 +134,10 @@ fn lint_implicit_returns(
},
ExprKind::Match(_, arms, _) => {
if let Some(await_expr) = desugar_await(expr) {
lint_return(cx, await_expr.hir_id, await_expr.span);
return LintLocation::Inner;
}
for arm in arms {
let res = lint_implicit_returns(
cx,
@ -153,18 +157,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(())
@ -241,6 +245,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
Some(e) => e,
None => return,
}
} else if let Some(expr) = get_async_closure_expr(cx.tcx, body.value) {
expr
} else {
body.value
};

View file

@ -239,7 +239,7 @@ fn check_subtraction(
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
let sugg = format!(

View file

@ -8,7 +8,7 @@ use rustc_hir::{
};
use rustc_hir_analysis::lower_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
use rustc_middle::ty::{self, AssocItem, ClauseKind, Generics, Ty, TyCtxt};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
assocs
.filter_by_name_unhygienic(constraint.ident.name)
.next()
.is_some_and(|assoc| assoc.is_type())
.is_some_and(AssocItem::is_type)
})
{
emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound);

View file

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

View file

@ -2,13 +2,12 @@ use clippy_config::Conf;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
use clippy_utils::{higher, is_from_proc_macro, is_in_test};
use clippy_utils::{higher, is_from_proc_macro, is_in_test, sym};
use rustc_ast::ast::RangeLimits;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -136,28 +135,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 {
@ -268,7 +267,7 @@ fn ty_has_applicable_get_function<'tcx>(
index_expr: &Expr<'_>,
) -> bool {
if let ty::Adt(_, _) = array_ty.kind()
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| {
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym::get).map(|m| {
cx.tcx
.fn_sig(m.def_id)
.skip_binder()

View file

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

View file

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

View file

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

View file

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

View file

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

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