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

This commit is contained in:
Philipp Krones 2025-05-15 19:19:08 +02:00
commit 0bb1b5bd3b
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
263 changed files with 5325 additions and 4470 deletions

View file

@ -15,27 +15,18 @@ jobs:
changelog:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
# Run
- name: Check Changelog
if: ${{ github.event_name == 'pull_request' }}
run: |
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
echo "ERROR: pull request message must contain 'changelog: ...' with your changelog. Please add it."
if [[ -z $(grep -oP 'changelog: *\K\S+' <<< "$PR_BODY") ]]; then
echo "::error::Pull request message must contain 'changelog: ...' with your changelog. Please add it."
exit 1
else
echo "changelog: $output"
fi
env:
PYTHONIOENCODING: 'utf-8'
PR_NUMBER: '${{ github.event.number }}'
PR_BODY: ${{ github.event.pull_request.body }})
# We need to have the "conclusion" job also on PR CI, to make it possible
# to add PRs to a merge queue.

View file

@ -6,7 +6,105 @@ document.
## Unreleased / Beta / In Rust Nightly
[3e3715c3...master](https://github.com/rust-lang/rust-clippy/compare/3e3715c3...master)
[1e5237f4...master](https://github.com/rust-lang/rust-clippy/compare/1e5237f4...master)
## Rust 1.87
Current stable, released 2025-05-15
[View all 127 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-02-06T14%3A54%3A28Z..2025-03-20T20%3A07%3A53Z+base%3Amaster)
### New Lints
* Added [`doc_comment_double_space_linebreaks`] to `pedantic` [#12876](https://github.com/rust-lang/rust-clippy/pull/12876)
* Added [`manual_midpoint`] to `pedantic` [#13851](https://github.com/rust-lang/rust-clippy/pull/13851)
* Added [`io_other_error`] to `style` [#14022](https://github.com/rust-lang/rust-clippy/pull/14022)
* Added [`owned_cow`] to `pedantic` [#13948](https://github.com/rust-lang/rust-clippy/pull/13948)
* Added [`manual_contains`] to `perf` [#13817](https://github.com/rust-lang/rust-clippy/pull/13817)
* Added [`unnecessary_debug_formatting`] to `pedantic` [#13893](https://github.com/rust-lang/rust-clippy/pull/13893)
* Added [`elidable_lifetime_names`] to `pedantic` [#13960](https://github.com/rust-lang/rust-clippy/pull/13960)
* Added [`mem_replace_option_with_some`] to `style` [#14197](https://github.com/rust-lang/rust-clippy/pull/14197)
* Added [`unbuffered_bytes`] to `perf` [#14089](https://github.com/rust-lang/rust-clippy/pull/14089)
* Added [`single_option_map`] to `nursery` [#14033](https://github.com/rust-lang/rust-clippy/pull/14033)
### Moves and Deprecations
* Moved [`comparison_chain`] to `pedantic` (from `style`)
[#14219](https://github.com/rust-lang/rust-clippy/pull/14219)
* Moved [`manual_ok_or`] to `style` (from `pedantic`)
[#14027](https://github.com/rust-lang/rust-clippy/pull/14027)
* Deprecated [`option_map_or_err_ok`] in favor of [`manual_ok_or`]
[#14027](https://github.com/rust-lang/rust-clippy/pull/14027)
### Enhancements
* Add `allow_expect_in_consts` and `allow_unwrap_in_consts` configuration options to [`unwrap_used`], [`expect_used`]
[#14200](https://github.com/rust-lang/rust-clippy/pull/14200)
* Add `check-incompatible-msrv-in-tests` configuration option to [`incompatible_msrv`]
[#14279](https://github.com/rust-lang/rust-clippy/pull/14279)
* [`len_zero`] now also triggers if deref target implements `is_empty()`
[#13871](https://github.com/rust-lang/rust-clippy/pull/13871)
* [`ptr_eq`] now handles more cases, including `!=` in addition to `==`
[#14339](https://github.com/rust-lang/rust-clippy/pull/14339)
* [`struct_field_names`] now also checks private fields of public structs
[#14076](https://github.com/rust-lang/rust-clippy/pull/14076)
* [`needless_pass_by_value`] suggests using a reference on the innermost `Option` content
[#14392](https://github.com/rust-lang/rust-clippy/pull/14392)
* [`obfuscated_if_else`] now supports `then().unwrap_or_else()` and `then_some().unwrap_or_else()`
[#14165](https://github.com/rust-lang/rust-clippy/pull/14165)
* Format macros: all format-handling lints now validate `todo!` and `unimplemented!` macros
[#14266](https://github.com/rust-lang/rust-clippy/pull/14266)
* [`disallowed_methods`] now supports replacements
[#13669](https://github.com/rust-lang/rust-clippy/pull/13669)
* Added MSRV checks for several lints:
* [`question_mark`] [#14436](https://github.com/rust-lang/rust-clippy/pull/14436)
* [`repeat_vec_with_capacity`] [#14126](https://github.com/rust-lang/rust-clippy/pull/14126)
* [`manual_flatten`] [#14086](https://github.com/rust-lang/rust-clippy/pull/14086)
* [`lines_filter_map_ok`] [#14130](https://github.com/rust-lang/rust-clippy/pull/14130)
### False Positive Fixes
* [`missing_const_for_fn`] no longer triggers on unstable const traits [#14294](https://github.com/rust-lang/rust-clippy/pull/14294)
* [`unnecessary_to_owned`] now avoids suggesting to call `iter()` on a temporary object [#14243](https://github.com/rust-lang/rust-clippy/pull/14243)
* [`unnecessary_debug_formatting`] no longer triggers in tests [#14347](https://github.com/rust-lang/rust-clippy/pull/14347)
* [`option_if_let_else`] now handles cases when value is partially moved [#14209](https://github.com/rust-lang/rust-clippy/pull/14209)
* [`blocks_in_conditions`] no longer triggers when the condition contains a `return` [#14338](https://github.com/rust-lang/rust-clippy/pull/14338)
* [`undocumented_unsafe_blocks`] no longer triggers on trait/impl items [#13888](https://github.com/rust-lang/rust-clippy/pull/13888)
* [`manual_slice_fill`] no longer triggers due to missing index checks [#14193](https://github.com/rust-lang/rust-clippy/pull/14193)
* [`useless_asref`] no longer suggests using `.clone()` if the target type doesn't implement `Clone` [#14174](https://github.com/rust-lang/rust-clippy/pull/14174)
* [`unnecessary_safety_comment`] no longer triggers on desugared assign [#14371](https://github.com/rust-lang/rust-clippy/pull/14371)
* [`unnecessary_map_or`] no longer consumes the comparison value if it does not implement `Copy` [#14207](https://github.com/rust-lang/rust-clippy/pull/14207)
* [`let_and_return`] no longer triggers involving short-lived block temporary variables [#14180](https://github.com/rust-lang/rust-clippy/pull/14180)
* [`manual_async_fn`] no longer emits suggestions inside macros [#14142](https://github.com/rust-lang/rust-clippy/pull/14142)
* [`use_self`] skips analysis inside macro expansions of a `impl Self` block [#13128](https://github.com/rust-lang/rust-clippy/pull/13128)
* [`double_ended_iterator_last`] no longer triggers on non-reference immutable receiver [#14140](https://github.com/rust-lang/rust-clippy/pull/14140)
### ICE Fixes
* [`macro_use_imports`] Fix ICE when checking attributes
[#14317](https://github.com/rust-lang/rust-clippy/pull/14317)
* [`doc_nested_refdefs`] Fix ICE by avoiding invalid ranges
[#14308](https://github.com/rust-lang/rust-clippy/pull/14308)
* [`just_underscores_and_digits`] Fix ICE in error recovery scenario
[#14168](https://github.com/rust-lang/rust-clippy/pull/14168)
* [`declare_interior_mutable_const`], [`borrow_interior_mutable_const`] Fix ICE by properly resolving `<T as Trait>::AssocT` projections
[#14125](https://github.com/rust-lang/rust-clippy/pull/14125)
### Documentation Improvements
* [`struct_excessive_bools`] Documentation improved with rationale
[#14351](https://github.com/rust-lang/rust-clippy/pull/14351)
### Others
* Use edition=2021 in `rustc_tools_util`
[#14211](https://github.com/rust-lang/rust-clippy/pull/14211)
* Fix rustc_tools_util's `version.host_compiler` release channel, expose the rustc version, and add tests
[#14123](https://github.com/rust-lang/rust-clippy/pull/14123)
* Make UI test annotations mandatory
[#11421](https://github.com/rust-lang/rust-clippy/pull/11421)
[#14388](https://github.com/rust-lang/rust-clippy/pull/14388)
[#14393](https://github.com/rust-lang/rust-clippy/pull/14393)
## Rust 1.86
@ -5583,6 +5681,7 @@ Released 2018-09-13
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
[`cloned_ref_to_slice_refs`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_ref_to_slice_refs
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
@ -5594,6 +5693,7 @@ Released 2018-09-13
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`confusing_method_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#confusing_method_to_numeric_cast
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
@ -6383,6 +6483,7 @@ Released 2018-09-13
[`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement
[`allow-comparison-to-zero`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-comparison-to-zero
[`allow-dbg-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-dbg-in-tests
[`allow-exact-repetitions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-exact-repetitions
[`allow-expect-in-consts`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-consts
[`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests
[`allow-indexing-slicing-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-indexing-slicing-in-tests
@ -6435,6 +6536,7 @@ Released 2018-09-13
[`max-suggested-slice-pattern-length`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-suggested-slice-pattern-length
[`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
[`missing-docs-allow-unused`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-allow-unused
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
[`module-items-ordered-within-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groupings

View file

@ -77,7 +77,7 @@ debugging to find the actual problem behind the issue.
[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
an AST expression).
[`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed

View file

@ -28,7 +28,7 @@ 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 }
tempfile = { version = "3.20", optional = true }
termize = "0.1"
color-print = "0.3.4"
anstream = "0.6.18"
@ -47,7 +47,6 @@ pulldown-cmark = { version = "0.11", default-features = false, features = ["html
askama = { version = "0.13", default-features = false, features = ["alloc", "config", "derive"] }
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
if_chain = "1.0"
quote = "1.0.25"
syn = { version = "2.0", features = ["full"] }
@ -73,3 +72,10 @@ harness = false
[[test]]
name = "dogfood"
harness = false
# quine-mc_cluskey makes up a significant part of the runtime in dogfood
# due to the number of conditions in the clippy_lints crate
# and enabling optimizations for that specific dependency helps a bit
# without increasing total build times.
[profile.dev.package.quine-mc_cluskey]
opt-level = 3

View file

@ -416,7 +416,7 @@ In our example, `is_foo_fn` looks like:
fn is_foo_fn(fn_kind: FnKind<'_>) -> bool {
match fn_kind {
FnKind::Fn(_, ident, ..) => {
FnKind::Fn(_, _, Fn { ident, .. }) => {
// check if `fn` name is `foo`
ident.name.as_str() == "foo"
}

View file

@ -145,42 +145,32 @@ unclear to you.
If you are hacking on Clippy and want to install it from source, do the
following:
First, take note of the toolchain
[override](https://rust-lang.github.io/rustup/overrides.html) in
`/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
> show active-toolchain`.
From the Clippy project root, run the following command to build the Clippy
binaries and copy them into the toolchain directory. This will override the
currently installed Clippy component.
binaries and copy them into the toolchain directory. This will create a new
toolchain called `clippy` by default, see `cargo dev setup toolchain --help`
for other options.
```terminal
cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin"
cargo dev setup toolcahin
```
Now you may run `cargo clippy` in any project, using the toolchain where you
just installed Clippy.
Now you may run `cargo +clippy clippy` in any project using the new toolchain.
```terminal
cd my-project
cargo +nightly-2021-07-01 clippy
cargo +clippy clippy
```
...or `clippy-driver`
```terminal
clippy-driver +nightly-2021-07-01 <filename>
clippy-driver +clippy <filename>
```
If you need to restore the default Clippy installation, run the following (from
the Clippy project root).
If you no longer need the toolchain it can be uninstalled using `rustup`:
```terminal
rustup component remove clippy
rustup component add clippy
rustup toolchain uninstall clippy
```
> **DO NOT** install using `cargo install --path . --force` since this will

View file

@ -86,7 +86,7 @@ arguments have to be checked separately.
```rust
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{paths, match_def_path};
use clippy_utils::paths;
use rustc_span::symbol::sym;
use rustc_hir::LangItem;
@ -108,7 +108,7 @@ impl LateLintPass<'_> for MyStructLint {
// 3. Using the type path
// This method should be avoided if possible
if match_def_path(cx, def_id, &paths::RESULT) {
if paths::RESULT.matches_ty(cx, ty) {
// The type is a `core::result::Result`
}
}

View file

@ -51,7 +51,9 @@ Once you've got the correct commit range, run
util/fetch_prs_between.sh commit1 commit2 > changes.txt
```
and open that file in your editor of choice.
where `commit2` is the commit hash from the previous command and `commit1`
is the commit hash from the current CHANGELOG file.
Open `changes.txt` file in your editor of choice.
When updating the changelog it's also a good idea to make sure that `commit1` is
already correct in the current changelog.
@ -60,8 +62,8 @@ already correct in the current changelog.
The above script should have dumped all the relevant PRs to the file you
specified. It should have filtered out most of the irrelevant PRs already, but
it's a good idea to do a manual cleanup pass where you look for more irrelevant
PRs. If you're not sure about some PRs, just leave them in for the review and
it's a good idea to do a manual cleanup pass and choose valuable PRs.
If you're not sure about some PRs, just leave them in for the review and
ask for feedback.
With the PRs filtered, you can start to take each PR and move the `changelog: `
@ -74,10 +76,9 @@ The order should roughly be:
2. Moves or deprecations of lints
3. Changes that expand what code existing lints cover
4. False positive fixes
5. Suggestion fixes/improvements
6. ICE fixes
7. Documentation improvements
8. Others
5. ICE fixes
6. Documentation improvements
7. Others
As section headers, we use:
@ -91,7 +92,6 @@ As section headers, we use:
### Enhancements
### False Positive Fixes
### Suggestion Fixes/Improvements
### ICE Fixes
### Documentation Improvements
### Others
@ -112,7 +112,16 @@ that label in the changelog. If you can, remove the `beta-accepted` labels
### 4. Update `clippy::version` attributes
Next, make sure to check that the `#[clippy::version]` attributes for the added
lints contain the correct version.
lints contain the correct version.
In order to find lints that need a version update, go through the lints in the
"New Lints" section and run the following command for each lint name:
```
grep -rB1 "pub $LINT_NAME" .
```
The version shown should match the version of the release the changelog is
written for. If not, update the version to the changelog version.
[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md
[forge]: https://forge.rust-lang.org/

View file

@ -73,22 +73,24 @@ impl LateLintPass<'_> for CheckDropTraitLint {
## Using Type Path
If neither diagnostic item nor a language item is available, we can use
[`clippy_utils::paths`][paths] with the `match_trait_method` to determine trait
implementation.
[`clippy_utils::paths`][paths] to determine get a trait's `DefId`.
> **Note**: This approach should be avoided if possible, the best thing to do would be to make a PR to [`rust-lang/rust`][rust] adding a diagnostic item.
Below, we check if the given `expr` implements the `Iterator`'s trait method `cloned` :
Below, we check if the given `expr` implements [`core::iter::Step`](https://doc.rust-lang.org/std/iter/trait.Step.html):
```rust
use clippy_utils::{match_trait_method, paths};
use clippy_utils::{implements_trait, paths};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckTokioAsyncReadExtTrait {
impl LateLintPass<'_> for CheckIterStep {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if match_trait_method(cx, expr, &paths::CORE_ITER_CLONED) {
println!("`expr` implements `CORE_ITER_CLONED` trait!");
let ty = cx.typeck_results().expr_ty(expr);
if let Some(trait_def_id) = paths::ITER_STEP.first(cx)
&& implements_trait(cx, ty, trait_def_id, &[])
{
println!("`expr` implements the `core::iter::Step` trait!");
}
}
}

View file

@ -71,6 +71,16 @@ Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
* [`dbg_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro)
## `allow-exact-repetitions`
Whether an item should be allowed to have the same name as its containing module
**Default Value:** `true`
---
**Affected lints:**
* [`module_name_repetitions`](https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions)
## `allow-expect-in-consts`
Whether `expect` should be allowed in code always evaluated at compile time
@ -734,6 +744,16 @@ Minimum chars an ident can have, anything below or equal to this will be linted.
* [`min_ident_chars`](https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars)
## `missing-docs-allow-unused`
Whether to allow fields starting with an underscore to skip documentation requirements
**Default Value:** `false`
---
**Affected lints:**
* [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
## `missing-docs-in-crate-items`
Whether to **only** check for missing documentation in items visible within the current
crate. For example, `pub(crate)` items.
@ -839,6 +859,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`same_item_push`](https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push)
* [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current)
* [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind)
* [`to_digit_is_some`](https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some)
* [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref)
* [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions)
* [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)

View file

@ -7,14 +7,11 @@ 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

@ -360,6 +360,9 @@ define_Conf! {
/// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
#[lints(dbg_macro)]
allow_dbg_in_tests: bool = false,
/// Whether an item should be allowed to have the same name as its containing module
#[lints(module_name_repetitions)]
allow_exact_repetitions: bool = true,
/// Whether `expect` should be allowed in code always evaluated at compile time
#[lints(expect_used)]
allow_expect_in_consts: bool = true,
@ -675,6 +678,9 @@ define_Conf! {
/// Minimum chars an ident can have, anything below or equal to this will be linted.
#[lints(min_ident_chars)]
min_ident_chars_threshold: u64 = 1,
/// Whether to allow fields starting with an underscore to skip documentation requirements
#[lints(missing_docs_in_private_items)]
missing_docs_allow_unused: bool = false,
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
#[lints(missing_docs_in_private_items)]
@ -756,6 +762,7 @@ define_Conf! {
same_item_push,
seek_from_current,
seek_rewind,
to_digit_is_some,
transmute_ptr_to_ref,
tuple_array_conversions,
type_repetition_in_bounds,

View file

@ -1,4 +1,4 @@
#![feature(rustc_private, array_windows, let_chains)]
#![feature(rustc_private)]
#![warn(
trivial_casts,
trivial_numeric_casts,
@ -12,6 +12,7 @@
rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic
)]
#![deny(clippy::derive_deserialize_allowing_unknown)]
extern crate rustc_data_structures;
extern crate rustc_errors;

View file

@ -1,16 +1,18 @@
use clippy_utils::paths::{PathNS, find_crates, lookup_path};
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::DefKind;
use rustc_hir::def_id::DefIdMap;
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize, ser};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Rename {
pub path: String,
pub rename: String,
@ -58,7 +60,7 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<R
// `DisallowedPathEnum` is an implementation detail to enable the `Deserialize` implementation just
// above. `DisallowedPathEnum` is not meant to be used outside of this file.
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[serde(untagged, deny_unknown_fields)]
enum DisallowedPathEnum {
Simple(String),
WithReason {
@ -133,6 +135,7 @@ impl DisallowedPathEnum {
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
tcx: TyCtxt<'_>,
disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
ns: PathNS,
def_kind_predicate: impl Fn(DefKind) -> bool,
predicate_description: &str,
allow_prim_tys: bool,
@ -145,57 +148,47 @@ pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
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 sym_path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
let mut resolutions = lookup_path(tcx, ns, &sym_path);
resolutions.retain(|&def_id| def_kind_predicate(tcx.def_kind(def_id)));
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,
});
let (prim_ty, found_prim_ty) = if let &[name] = sym_path.as_slice()
&& let Some(prim) = PrimTy::from_name(name)
{
(allow_prim_tys.then_some(prim), true)
} else {
(None, 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)
),
);
if resolutions.is_empty()
&& prim_ty.is_none()
&& !disallowed_path.allow_invalid
// Don't warn about unloaded crates:
// https://github.com/rust-lang/rust-clippy/pull/14397#issuecomment-2848328221
&& (sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty())
{
// Relookup the path in an arbitrary namespace to get a good `expected, found` message
let found_def_ids = lookup_path(tcx, PathNS::Arbitrary, &sym_path);
let message = if let Some(&def_id) = found_def_ids.first() {
let (article, description) = tcx.article_and_description(def_id);
format!("expected a {predicate_description}, found {article} {description}")
} 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}"),
);
}
format!("expected a {predicate_description}, found a primitive type")
} else {
format!("`{path}` does not refer to a reachable {predicate_description}")
};
tcx.sess
.dcx()
.struct_span_warn(disallowed_path.span(), message)
.with_help("add `allow-invalid = true` to the entry to suppress this warning")
.emit();
}
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!(),
}
for def_id in resolutions {
def_ids.insert(def_id, (path, disallowed_path));
}
if let Some(ty) = prim_ty {
prim_tys.insert(ty, (path, disallowed_path));
}
}

View file

@ -0,0 +1,174 @@
use crate::update_lints::{
DeprecatedLint, DeprecatedLints, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints,
};
use crate::utils::{UpdateMode, Version};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{fs, io};
/// Runs the `deprecate` command
///
/// This does the following:
/// * Adds an entry to `deprecated_lints.rs`.
/// * Removes the lint declaration (and the entire file if applicable)
///
/// # Panics
///
/// If a file path could not read from or written to
pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
let prefixed_name = if name.starts_with("clippy::") {
name.to_owned()
} else {
format!("clippy::{name}")
};
let stripped_name = &prefixed_name[8..];
let mut lints = find_lint_decls();
let DeprecatedLints {
renamed: renamed_lints,
deprecated: mut deprecated_lints,
file: mut deprecated_file,
contents: mut deprecated_contents,
deprecated_end,
..
} = read_deprecated_lints();
let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else {
eprintln!("error: failed to find lint `{name}`");
return;
};
let mod_path = {
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
if mod_path.is_dir() {
mod_path = mod_path.join("mod");
}
mod_path.set_extension("rs");
mod_path
};
if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) {
deprecated_contents.insert_str(
deprecated_end as usize,
&format!(
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
clippy_version.rust_display(),
prefixed_name,
reason,
),
);
deprecated_file.replace_contents(deprecated_contents.as_bytes());
drop(deprecated_file);
deprecated_lints.push(DeprecatedLint {
name: prefixed_name,
reason: reason.into(),
});
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{name}` has successfully been deprecated");
println!("note: you must run `cargo uitest` to update the test results");
} else {
eprintln!("error: lint not found");
}
}
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
}
fn remove_test_assets(name: &str) {
let test_file_stem = format!("tests/ui/{name}");
let path = Path::new(&test_file_stem);
// Some lints have their own directories, delete them
if path.is_dir() {
let _ = fs::remove_dir_all(path);
return;
}
// Remove all related test files
let _ = fs::remove_file(path.with_extension("rs"));
let _ = fs::remove_file(path.with_extension("stderr"));
let _ = fs::remove_file(path.with_extension("fixed"));
}
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
content
.find("declare_lint_pass!")
.unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
});
let mut impl_lint_pass_end = content[impl_lint_pass_start..]
.find(']')
.expect("failed to find `impl_lint_pass` terminator");
impl_lint_pass_end += impl_lint_pass_start;
if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) {
let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
for c in content[lint_name_end..impl_lint_pass_end].chars() {
// Remove trailing whitespace
if c == ',' || c.is_whitespace() {
lint_name_end += 1;
} else {
break;
}
}
content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
}
}
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");
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()));
}
remove_test_assets(name);
remove_lint(name, lints);
return Ok(true);
}
Ok(false)
}

View file

@ -1,4 +1,4 @@
use crate::utils::{clippy_project_root, exit_if_err};
use crate::utils::exit_if_err;
use std::process::Command;
/// # Panics
@ -8,8 +8,7 @@ use std::process::Command;
pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) {
let mut cmd = Command::new("cargo");
cmd.current_dir(clippy_project_root())
.args(["test", "--test", "dogfood"])
cmd.args(["test", "--test", "dogfood"])
.args(["--features", "internal"])
.args(["--", "dogfood_clippy", "--nocapture"]);

View file

@ -1,4 +1,3 @@
use crate::utils::clippy_project_root;
use itertools::Itertools;
use rustc_lexer::{TokenKind, tokenize};
use shell_escape::escape;
@ -104,15 +103,8 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
Field,
}
let path: PathBuf = [
clippy_project_root().as_path(),
"clippy_config".as_ref(),
"src".as_ref(),
"conf.rs".as_ref(),
]
.into_iter()
.collect();
let text = fs::read_to_string(&path)?;
let path = "clippy_config/src/conf.rs";
let text = fs::read_to_string(path)?;
let (pre, conf) = text
.split_once("define_Conf! {\n")
@ -203,7 +195,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
| (State::Lints, TokenKind::Comma | TokenKind::OpenParen | TokenKind::CloseParen) => {},
_ => {
return Err(Error::Parse(
path,
PathBuf::from(path),
offset_to_line(&text, conf_offset + i),
format!("unexpected token `{}`", &conf[i..i + t.len as usize]),
));
@ -213,7 +205,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
if !matches!(state, State::Field) {
return Err(Error::Parse(
path,
PathBuf::from(path),
offset_to_line(&text, conf_offset + conf.len()),
"incomplete field".into(),
));
@ -260,18 +252,16 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
if check {
return Err(Error::CheckFailed);
}
fs::write(&path, new_text.as_bytes())?;
fs::write(path, new_text.as_bytes())?;
}
Ok(())
}
fn run_rustfmt(context: &FmtContext) -> Result<(), Error> {
let project_root = clippy_project_root();
// if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
// format because rustfmt would also format the entire rustc repo as it is a local
// dependency
if fs::read_to_string(project_root.join("Cargo.toml"))
if fs::read_to_string("Cargo.toml")
.expect("Failed to read clippy Cargo.toml")
.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
{
@ -280,12 +270,12 @@ fn run_rustfmt(context: &FmtContext) -> Result<(), Error> {
check_for_rustfmt(context)?;
cargo_fmt(context, project_root.as_path())?;
cargo_fmt(context, &project_root.join("clippy_dev"))?;
cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
cargo_fmt(context, &project_root.join("lintcheck"))?;
cargo_fmt(context, ".".as_ref())?;
cargo_fmt(context, "clippy_dev".as_ref())?;
cargo_fmt(context, "rustc_tools_util".as_ref())?;
cargo_fmt(context, "lintcheck".as_ref())?;
let chunks = WalkDir::new(project_root.join("tests"))
let chunks = WalkDir::new("tests")
.into_iter()
.filter_map(|entry| {
let entry = entry.expect("failed to find tests");

View file

@ -1,5 +1,4 @@
#![feature(let_chains)]
#![feature(rustc_private)]
#![feature(rustc_private, if_let_guard, let_chains)]
#![warn(
trivial_casts,
trivial_numeric_casts,
@ -15,11 +14,13 @@ extern crate rustc_driver;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;
pub mod deprecate_lint;
pub mod dogfood;
pub mod fmt;
pub mod lint;
pub mod new_lint;
pub mod release;
pub mod rename_lint;
pub mod serve;
pub mod setup;
pub mod sync;

View file

@ -3,11 +3,18 @@
#![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{Args, Parser, Subcommand};
use clippy_dev::{dogfood, fmt, lint, new_lint, release, serve, setup, sync, update_lints, utils};
use clippy_dev::{
deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
};
use std::convert::Infallible;
use std::env;
fn main() {
let dev = Dev::parse();
let clippy = utils::ClippyInfo::search_for_manifest();
if let Err(e) = env::set_current_dir(&clippy.path) {
panic!("error setting current directory to `{}`: {e}", clippy.path.display());
}
match dev.command {
DevCommand::Bless => {
@ -20,22 +27,14 @@ fn main() {
allow_no_vcs,
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
DevCommand::Fmt { check, verbose } => fmt::run(check, verbose),
DevCommand::UpdateLints { print_only, check } => {
if print_only {
update_lints::print_lints();
} else if check {
update_lints::update(utils::UpdateMode::Check);
} else {
update_lints::update(utils::UpdateMode::Change);
}
},
DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
DevCommand::NewLint {
pass,
name,
category,
r#type,
msrv,
} => match new_lint::create(pass, &name, &category, r#type.as_deref(), msrv) {
} => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
Ok(()) => update_lints::update(utils::UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {e}"),
},
@ -79,13 +78,18 @@ fn main() {
old_name,
new_name,
uplift,
} => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift),
DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, &reason),
} => rename_lint::rename(
clippy.version,
&old_name,
new_name.as_ref().unwrap_or(&old_name),
uplift,
),
DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason),
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
SyncSubcommand::UpdateNightly => sync::update_nightly(),
},
DevCommand::Release(ReleaseCommand { subcommand }) => match subcommand {
ReleaseSubcommand::BumpVersion => release::bump_version(),
ReleaseSubcommand::BumpVersion => release::bump_version(clippy.version),
},
}
}
@ -135,11 +139,6 @@ enum DevCommand {
/// * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod` {n}
/// * all lints are registered in the lint store
UpdateLints {
#[arg(long)]
/// Print a table of lints to STDOUT
///
/// This does not include deprecated and internal lints. (Does not modify any files)
print_only: bool,
#[arg(long)]
/// Checks that `cargo dev update_lints` has been run. Used on CI.
check: bool,

View file

@ -1,4 +1,4 @@
use crate::utils::{clippy_project_root, clippy_version};
use crate::utils::{RustSearcher, Token, Version};
use clap::ValueEnum;
use indoc::{formatdoc, writedoc};
use std::fmt::{self, Write as _};
@ -22,11 +22,11 @@ impl fmt::Display for Pass {
}
struct LintData<'a> {
clippy_version: Version,
pass: Pass,
name: &'a str,
category: &'a str,
ty: Option<&'a str>,
project_root: PathBuf,
}
trait Context {
@ -50,18 +50,25 @@ impl<T> Context for io::Result<T> {
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
pub fn create(pass: Pass, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> {
pub fn create(
clippy_version: Version,
pass: Pass,
name: &str,
category: &str,
mut ty: Option<&str>,
msrv: bool,
) -> io::Result<()> {
if category == "cargo" && ty.is_none() {
// `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo`
ty = Some("cargo");
}
let lint = LintData {
clippy_version,
pass,
name,
category,
ty,
project_root: clippy_project_root(),
};
create_lint(&lint, msrv).context("Unable to create lint implementation")?;
@ -88,7 +95,7 @@ fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
} else {
let lint_contents = get_lint_file_contents(lint, enable_msrv);
let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())?;
write_file(&lint_path, lint_contents.as_bytes())?;
println!("Generated lint file: `{lint_path}`");
Ok(())
@ -115,8 +122,7 @@ fn create_test(lint: &LintData<'_>, msrv: bool) -> io::Result<()> {
}
if lint.category == "cargo" {
let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
let test_dir = lint.project_root.join(&relative_test_dir);
let test_dir = format!("tests/ui-cargo/{}", lint.name);
fs::create_dir(&test_dir)?;
create_project_layout(
@ -134,11 +140,11 @@ fn create_test(lint: &LintData<'_>, msrv: bool) -> io::Result<()> {
false,
)?;
println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`");
println!("Generated test directories: `{test_dir}/pass`, `{test_dir}/fail`");
} else {
let test_path = format!("tests/ui/{}.rs", lint.name);
let test_contents = get_test_file_contents(lint.name, msrv);
write_file(lint.project_root.join(&test_path), test_contents)?;
write_file(&test_path, test_contents)?;
println!("Generated test file: `{test_path}`");
}
@ -193,11 +199,6 @@ fn to_camel_case(name: &str) -> String {
.collect()
}
pub(crate) fn get_stabilization_version() -> String {
let (minor, patch) = clippy_version();
format!("{minor}.{patch}.0")
}
fn get_test_file_contents(lint_name: &str, msrv: bool) -> String {
let mut test = formatdoc!(
r"
@ -292,7 +293,11 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
);
}
let _: fmt::Result = writeln!(result, "{}", get_lint_declaration(&name_upper, category));
let _: fmt::Result = writeln!(
result,
"{}",
get_lint_declaration(lint.clippy_version, &name_upper, category)
);
if enable_msrv {
let _: fmt::Result = writedoc!(
@ -330,7 +335,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
result
}
fn get_lint_declaration(name_upper: &str, category: &str) -> String {
fn get_lint_declaration(version: Version, name_upper: &str, category: &str) -> String {
let justification_heading = if category == "restriction" {
"Why restrict this?"
} else {
@ -355,9 +360,8 @@ fn get_lint_declaration(name_upper: &str, category: &str) -> String {
pub {name_upper},
{category},
"default lint description"
}}
"#,
get_stabilization_version(),
}}"#,
version.rust_display(),
)
}
@ -371,7 +375,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
_ => {},
}
let ty_dir = lint.project_root.join(format!("clippy_lints/src/{ty}"));
let ty_dir = PathBuf::from(format!("clippy_lints/src/{ty}"));
assert!(
ty_dir.exists() && ty_dir.is_dir(),
"Directory `{}` does not exist!",
@ -441,95 +445,25 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
#[allow(clippy::too_many_lines)]
fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> {
use super::update_lints::{LintDeclSearchResult, match_tokens};
use rustc_lexer::TokenKind;
let lint_name_upper = lint.name.to_uppercase();
let mut file_contents = fs::read_to_string(path)?;
assert!(
!file_contents.contains(&lint_name_upper),
!file_contents.contains(&format!("pub {lint_name_upper},")),
"Lint `{}` already defined in `{}`",
lint.name,
path.display()
);
let mut offset = 0usize;
let mut last_decl_curly_offset = None;
let mut lint_context = None;
let mut iter = rustc_lexer::tokenize(&file_contents).map(|t| {
let range = offset..offset + t.len as usize;
offset = range.end;
LintDeclSearchResult {
token_kind: t.kind,
content: &file_contents[range.clone()],
range,
}
});
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) {
let mut iter = iter
.by_ref()
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
match content {
"declare_clippy_lint" => {
// matches `!{`
match_tokens!(iter, Bang OpenBrace);
if let Some(LintDeclSearchResult { range, .. }) =
iter.find(|result| result.token_kind == TokenKind::CloseBrace)
{
last_decl_curly_offset = Some(range.end);
}
},
"impl" => {
let mut token = iter.next();
match token {
// matches <'foo>
Some(LintDeclSearchResult {
token_kind: TokenKind::Lt,
..
}) => {
match_tokens!(iter, Lifetime { .. } Gt);
token = iter.next();
},
None => break,
_ => {},
}
if let Some(LintDeclSearchResult {
token_kind: TokenKind::Ident,
content,
..
}) = token
{
// Get the appropriate lint context struct
lint_context = match content {
"LateLintPass" => Some("LateContext"),
"EarlyLintPass" => Some("EarlyContext"),
_ => continue,
};
}
},
_ => {},
}
}
drop(iter);
let last_decl_curly_offset =
last_decl_curly_offset.unwrap_or_else(|| panic!("No lint declarations found in `{}`", path.display()));
let lint_context =
lint_context.unwrap_or_else(|| panic!("No lint pass implementation found in `{}`", path.display()));
let (lint_context, lint_decl_end) = parse_mod_file(path, &file_contents);
// Add the lint declaration to `mod.rs`
file_contents.replace_range(
// Remove the trailing newline, which should always be present
last_decl_curly_offset..=last_decl_curly_offset,
&format!("\n\n{}", get_lint_declaration(&lint_name_upper, lint.category)),
file_contents.insert_str(
lint_decl_end,
&format!(
"\n\n{}",
get_lint_declaration(lint.clippy_version, &lint_name_upper, lint.category)
),
);
// Add the lint to `impl_lint_pass`/`declare_lint_pass`
@ -580,6 +514,41 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
Ok(lint_context)
}
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
#[allow(clippy::enum_glob_use)]
use Token::*;
let mut context = None;
let mut decl_end = None;
let mut searcher = RustSearcher::new(contents);
while let Some(name) = searcher.find_capture_token(CaptureIdent) {
match name {
"declare_clippy_lint" => {
if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) {
decl_end = Some(searcher.pos());
}
},
"impl" => {
let mut capture = "";
if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) {
match capture {
"LateLintPass" => context = Some("LateContext"),
"EarlyLintPass" => context = Some("EarlyContext"),
_ => {},
}
}
},
_ => {},
}
}
(
context.unwrap_or_else(|| panic!("No lint pass implementation found in `{}`", path.display())),
decl_end.unwrap_or_else(|| panic!("No lint declarations found in `{}`", path.display())) as usize,
)
}
#[test]
fn test_camel_case() {
let s = "a_lint";

View file

@ -1,27 +1,27 @@
use crate::utils::{FileUpdater, Version, update_text_region_fn};
use std::fmt::Write;
use std::path::Path;
use crate::utils::{UpdateMode, clippy_version, replace_region_in_file};
const CARGO_TOML_FILES: [&str; 4] = [
static CARGO_TOML_FILES: &[&str] = &[
"clippy_config/Cargo.toml",
"clippy_lints/Cargo.toml",
"clippy_utils/Cargo.toml",
"Cargo.toml",
];
pub fn bump_version() {
let (minor, mut patch) = clippy_version();
patch += 1;
for file in &CARGO_TOML_FILES {
replace_region_in_file(
UpdateMode::Change,
Path::new(file),
"# begin autogenerated version\n",
"# end autogenerated version",
|res| {
writeln!(res, "version = \"0.{minor}.{patch}\"").unwrap();
},
pub fn bump_version(mut version: Version) {
version.minor += 1;
let mut updater = FileUpdater::default();
for file in CARGO_TOML_FILES {
updater.update_file(
file,
&mut update_text_region_fn(
"# begin autogenerated version\n",
"# end autogenerated version",
|dst| {
writeln!(dst, "version = \"{}\"", version.toml_display()).unwrap();
},
),
);
}
}

View file

@ -0,0 +1,194 @@
use crate::update_lints::{
DeprecatedLints, RenamedLint, find_lint_decls, gen_renamed_lints_test_fn, generate_lint_files,
read_deprecated_lints,
};
use crate::utils::{FileUpdater, StringReplacer, UpdateMode, Version, try_rename_file};
use std::ffi::OsStr;
use std::path::Path;
use walkdir::WalkDir;
/// Runs the `rename_lint` command.
///
/// This does the following:
/// * Adds an entry to `renamed_lints.rs`.
/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
/// * Renames the lint struct to the new name.
/// * Renames the module containing the lint struct to the new name if it shares a name with the
/// lint.
///
/// # Panics
/// Panics for the following conditions:
/// * If a file path could not read from or then written to
/// * If either lint name has a prefix
/// * If `old_name` doesn't name an existing lint.
/// * If `old_name` names a deprecated or renamed lint.
#[allow(clippy::too_many_lines)]
pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) {
if let Some((prefix, _)) = old_name.split_once("::") {
panic!("`{old_name}` should not contain the `{prefix}` prefix");
}
if let Some((prefix, _)) = new_name.split_once("::") {
panic!("`{new_name}` should not contain the `{prefix}` prefix");
}
let mut updater = FileUpdater::default();
let mut lints = find_lint_decls();
let DeprecatedLints {
renamed: mut renamed_lints,
deprecated: deprecated_lints,
file: mut deprecated_file,
contents: mut deprecated_contents,
renamed_end,
..
} = read_deprecated_lints();
let mut old_lint_index = None;
let mut found_new_name = false;
for (i, lint) in lints.iter().enumerate() {
if lint.name == old_name {
old_lint_index = Some(i);
} else if lint.name == new_name {
found_new_name = true;
}
}
let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{old_name}`"));
let lint = RenamedLint {
old_name: format!("clippy::{old_name}"),
new_name: if uplift {
new_name.into()
} else {
format!("clippy::{new_name}")
},
};
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
// case.
assert!(
!renamed_lints.iter().any(|l| lint.old_name == l.old_name),
"`{old_name}` has already been renamed"
);
assert!(
!deprecated_lints.iter().any(|l| lint.old_name == l.name),
"`{old_name}` has already been deprecated"
);
// Update all lint level attributes. (`clippy::lint_name`)
let replacements = &[(&*lint.old_name, &*lint.new_name)];
let replacer = StringReplacer::new(replacements);
for file in WalkDir::new(".").into_iter().map(Result::unwrap).filter(|f| {
let name = f.path().file_name();
let ext = f.path().extension();
(ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
&& name != Some(OsStr::new("rename.rs"))
&& name != Some(OsStr::new("deprecated_lints.rs"))
}) {
updater.update_file(file.path(), &mut replacer.replace_ident_fn());
}
deprecated_contents.insert_str(
renamed_end as usize,
&format!(
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
clippy_version.rust_display(),
lint.old_name,
lint.new_name,
),
);
deprecated_file.replace_contents(deprecated_contents.as_bytes());
drop(deprecated_file);
renamed_lints.push(lint);
renamed_lints.sort_by(|lhs, rhs| {
lhs.new_name
.starts_with("clippy::")
.cmp(&rhs.new_name.starts_with("clippy::"))
.reverse()
.then_with(|| lhs.old_name.cmp(&rhs.old_name))
});
if uplift {
updater.update_file("tests/ui/rename.rs", &mut gen_renamed_lints_test_fn(&renamed_lints));
println!(
"`{old_name}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually."
);
} else if found_new_name {
updater.update_file("tests/ui/rename.rs", &mut gen_renamed_lints_test_fn(&renamed_lints));
println!(
"`{new_name}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually."
);
} else {
// Rename the lint struct and source files sharing a name with the lint.
let lint = &mut lints[old_lint_index];
let old_name_upper = old_name.to_uppercase();
let new_name_upper = new_name.to_uppercase();
lint.name = new_name.into();
// Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
if try_rename_file(
Path::new(&format!("tests/ui/{old_name}.rs")),
Path::new(&format!("tests/ui/{new_name}.rs")),
) {
try_rename_file(
Path::new(&format!("tests/ui/{old_name}.stderr")),
Path::new(&format!("tests/ui/{new_name}.stderr")),
);
try_rename_file(
Path::new(&format!("tests/ui/{old_name}.fixed")),
Path::new(&format!("tests/ui/{new_name}.fixed")),
);
}
// Try to rename the file containing the lint if the file name matches the lint's name.
let replacements;
let replacements = if lint.module == old_name
&& try_rename_file(
Path::new(&format!("clippy_lints/src/{old_name}.rs")),
Path::new(&format!("clippy_lints/src/{new_name}.rs")),
) {
// Edit the module name in the lint list. Note there could be multiple lints.
for lint in lints.iter_mut().filter(|l| l.module == old_name) {
lint.module = new_name.into();
}
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
replacements.as_slice()
} else if !lint.module.contains("::")
// Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
&& try_rename_file(
Path::new(&format!("clippy_lints/src/{}/{old_name}.rs", lint.module)),
Path::new(&format!("clippy_lints/src/{}/{new_name}.rs", lint.module)),
)
{
// Edit the module name in the lint list. Note there could be multiple lints, or none.
let renamed_mod = format!("{}::{old_name}", lint.module);
for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
lint.module = format!("{}::{new_name}", lint.module);
}
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
replacements.as_slice()
} else {
replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
&replacements[0..1]
};
// Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
// renamed.
let replacer = StringReplacer::new(replacements);
for file in WalkDir::new("clippy_lints/src") {
let file = file.expect("error reading `clippy_lints/src`");
if file
.path()
.as_os_str()
.to_str()
.is_some_and(|x| x.ends_with("*.rs") && x["clippy_lints/src/".len()..] != *"deprecated_lints.rs")
{
updater.update_file(file.path(), &mut replacer.replace_ident_fn());
}
}
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("{old_name} has been successfully renamed");
}
println!("note: `cargo uitest` still needs to be run to update the test results");
}

View file

@ -1,33 +1,18 @@
use std::fmt::Write;
use std::path::Path;
use crate::utils::{FileUpdater, update_text_region_fn};
use chrono::offset::Utc;
use crate::utils::{UpdateMode, replace_region_in_file};
use std::fmt::Write;
pub fn update_nightly() {
// Update rust-toolchain nightly version
let date = Utc::now().format("%Y-%m-%d").to_string();
replace_region_in_file(
UpdateMode::Change,
Path::new("rust-toolchain.toml"),
let update = &mut update_text_region_fn(
"# begin autogenerated nightly\n",
"# end autogenerated nightly",
|res| {
writeln!(res, "channel = \"nightly-{date}\"").unwrap();
|dst| {
writeln!(dst, "channel = \"nightly-{date}\"").unwrap();
},
);
// Update clippy_utils nightly version
replace_region_in_file(
UpdateMode::Change,
Path::new("clippy_utils/README.md"),
"<!-- begin autogenerated nightly -->\n",
"<!-- end autogenerated nightly -->",
|res| {
writeln!(res, "```").unwrap();
writeln!(res, "nightly-{date}").unwrap();
writeln!(res, "```").unwrap();
},
);
let mut updater = FileUpdater::default();
updater.update_file("rust-toolchain.toml", update);
updater.update_file("clippy_utils/README.md", update);
}

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,113 @@
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
use core::fmt::{self, Display};
use core::slice;
use core::str::FromStr;
use rustc_lexer::{self as lexer, FrontmatterAllowed};
use std::env;
use std::fs::{self, OpenOptions};
use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process::{self, ExitStatus};
use std::{fs, io};
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
#[derive(Clone, Copy)]
pub enum FileAction {
Open,
Read,
Write,
Create,
Rename,
}
impl FileAction {
fn as_str(self) -> &'static str {
match self {
Self::Open => "opening",
Self::Read => "reading",
Self::Write => "writing",
Self::Create => "creating",
Self::Rename => "renaming",
}
}
}
#[cold]
#[track_caller]
pub fn panic_file(err: &impl Display, action: FileAction, path: &Path) -> ! {
panic!("error {} `{}`: {}", action.as_str(), path.display(), *err)
}
/// Wrapper around `std::fs::File` which panics with a path on failure.
pub struct File<'a> {
pub inner: fs::File,
pub path: &'a Path,
}
impl<'a> File<'a> {
/// Opens a file panicking on failure.
#[track_caller]
pub fn open(path: &'a (impl AsRef<Path> + ?Sized), options: &mut OpenOptions) -> Self {
let path = path.as_ref();
match options.open(path) {
Ok(inner) => Self { inner, path },
Err(e) => panic_file(&e, FileAction::Open, path),
}
}
/// Opens a file if it exists, panicking on any other failure.
#[track_caller]
pub fn open_if_exists(path: &'a (impl AsRef<Path> + ?Sized), options: &mut OpenOptions) -> Option<Self> {
let path = path.as_ref();
match options.open(path) {
Ok(inner) => Some(Self { inner, path }),
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
Err(e) => panic_file(&e, FileAction::Open, path),
}
}
/// Opens and reads a file into a string, panicking of failure.
#[track_caller]
pub fn open_read_to_cleared_string<'dst>(
path: &'a (impl AsRef<Path> + ?Sized),
dst: &'dst mut String,
) -> &'dst mut String {
Self::open(path, OpenOptions::new().read(true)).read_to_cleared_string(dst)
}
/// Read the entire contents of a file to the given buffer.
#[track_caller]
pub fn read_append_to_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String {
match self.inner.read_to_string(dst) {
Ok(_) => {},
Err(e) => panic_file(&e, FileAction::Read, self.path),
}
dst
}
#[track_caller]
pub fn read_to_cleared_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String {
dst.clear();
self.read_append_to_string(dst)
}
/// Replaces the entire contents of a file.
#[track_caller]
pub fn replace_contents(&mut self, data: &[u8]) {
let res = match self.inner.seek(SeekFrom::Start(0)) {
Ok(_) => match self.inner.write_all(data) {
Ok(()) => self.inner.set_len(data.len() as u64),
Err(e) => Err(e),
},
Err(e) => Err(e),
};
if let Err(e) = res {
panic_file(&e, FileAction::Write, self.path);
}
}
}
/// Returns the path to the `cargo-clippy` binary
///
/// # Panics
@ -14,34 +115,107 @@ static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
/// Panics if the path of current executable could not be retrieved.
#[must_use]
pub fn cargo_clippy_path() -> PathBuf {
let mut path = std::env::current_exe().expect("failed to get current executable name");
let mut path = env::current_exe().expect("failed to get current executable name");
path.set_file_name(CARGO_CLIPPY_EXE);
path
}
/// Returns the path to the Clippy project directory
///
/// # Panics
///
/// Panics if the current directory could not be retrieved, there was an error reading any of the
/// Cargo.toml files or ancestor directory is the clippy root directory
#[must_use]
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
&& err.kind() == io::ErrorKind::NotFound
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version {
pub major: u16,
pub minor: u16,
}
impl FromStr for Version {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(s) = s.strip_prefix("0.")
&& let Some((major, minor)) = s.split_once('.')
&& let Ok(major) = major.parse()
&& let Ok(minor) = minor.parse()
{
continue;
}
let content = result.unwrap();
if content.contains("[package]\nname = \"clippy\"") {
return path.to_path_buf();
Ok(Self { major, minor })
} else {
Err(())
}
}
}
impl Version {
/// Displays the version as a rust version. i.e. `x.y.0`
#[must_use]
pub fn rust_display(self) -> impl Display {
struct X(Version);
impl Display for X {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.0", self.0.major, self.0.minor)
}
}
X(self)
}
/// Displays the version as it should appear in clippy's toml files. i.e. `0.x.y`
#[must_use]
pub fn toml_display(self) -> impl Display {
struct X(Version);
impl Display for X {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0.{}.{}", self.0.major, self.0.minor)
}
}
X(self)
}
}
pub struct ClippyInfo {
pub path: PathBuf,
pub version: Version,
}
impl ClippyInfo {
#[must_use]
pub fn search_for_manifest() -> Self {
let mut path = env::current_dir().expect("error reading the working directory");
let mut buf = String::new();
loop {
path.push("Cargo.toml");
if let Some(mut file) = File::open_if_exists(&path, OpenOptions::new().read(true)) {
let mut in_package = false;
let mut is_clippy = false;
let mut version: Option<Version> = None;
// Ad-hoc parsing to avoid dependencies. We control all the file so this
// isn't actually a problem
for line in file.read_to_cleared_string(&mut buf).lines() {
if line.starts_with('[') {
in_package = line.starts_with("[package]");
} else if in_package && let Some((name, value)) = line.split_once('=') {
match name.trim() {
"name" => is_clippy = value.trim() == "\"clippy\"",
"version"
if let Some(value) = value.trim().strip_prefix('"')
&& let Some(value) = value.strip_suffix('"') =>
{
version = value.parse().ok();
},
_ => {},
}
}
}
if is_clippy {
let Some(version) = version else {
panic!("error reading clippy version from {}", file.path.display());
};
path.pop();
return ClippyInfo { path, version };
}
}
path.pop();
assert!(
path.pop(),
"error finding project root, please run from inside the clippy directory"
);
}
}
panic!("error: Can't determine root of project. Please run inside a Clippy working dir.");
}
/// # Panics
@ -57,86 +231,396 @@ pub fn exit_if_err(status: io::Result<ExitStatus>) {
}
}
pub(crate) fn clippy_version() -> (u32, u32) {
fn parse_manifest(contents: &str) -> Option<(u32, u32)> {
let version = contents
.lines()
.filter_map(|l| l.split_once('='))
.find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
return None;
};
let (minor, patch) = version.split_once('.')?;
Some((minor.parse().ok()?, patch.parse().ok()?))
#[derive(Clone, Copy)]
pub enum UpdateStatus {
Unchanged,
Changed,
}
impl UpdateStatus {
#[must_use]
pub fn from_changed(value: bool) -> Self {
if value { Self::Changed } else { Self::Unchanged }
}
#[must_use]
pub fn is_changed(self) -> bool {
matches!(self, Self::Changed)
}
let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy)]
pub enum UpdateMode {
Check,
Change,
Check,
}
impl UpdateMode {
#[must_use]
pub fn from_check(check: bool) -> Self {
if check { Self::Check } else { Self::Change }
}
}
pub(crate) fn exit_with_failure() {
println!(
"Not all lints defined properly. \
Please run `cargo dev update_lints` to make sure all lints are defined properly."
);
process::exit(1);
#[derive(Default)]
pub struct FileUpdater {
src_buf: String,
dst_buf: String,
}
impl FileUpdater {
fn update_file_checked_inner(
&mut self,
tool: &str,
mode: UpdateMode,
path: &Path,
update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus,
) {
let mut file = File::open(path, OpenOptions::new().read(true).write(true));
file.read_to_cleared_string(&mut self.src_buf);
self.dst_buf.clear();
match (mode, update(path, &self.src_buf, &mut self.dst_buf)) {
(UpdateMode::Check, UpdateStatus::Changed) => {
eprintln!(
"the contents of `{}` are out of date\nplease run `{tool}` to update",
path.display()
);
process::exit(1);
},
(UpdateMode::Change, UpdateStatus::Changed) => file.replace_contents(self.dst_buf.as_bytes()),
(UpdateMode::Check | UpdateMode::Change, UpdateStatus::Unchanged) => {},
}
}
/// Replaces a region in a file delimited by two lines matching regexes.
///
/// `path` is the relative path to the file on which you want to perform the replacement.
///
/// See `replace_region_in_text` for documentation of the other options.
///
/// # Panics
///
/// Panics if the path could not read or then written
pub(crate) fn replace_region_in_file(
update_mode: UpdateMode,
path: &Path,
start: &str,
end: &str,
write_replacement: impl FnMut(&mut String),
) {
let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display()));
let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) {
Ok(x) => x,
Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()),
};
fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) {
let mut file = File::open(path, OpenOptions::new().read(true).write(true));
file.read_to_cleared_string(&mut self.src_buf);
self.dst_buf.clear();
if update(path, &self.src_buf, &mut self.dst_buf).is_changed() {
file.replace_contents(self.dst_buf.as_bytes());
}
}
match update_mode {
UpdateMode::Check if contents != new_contents => exit_with_failure(),
UpdateMode::Check => (),
UpdateMode::Change => {
if let Err(e) = fs::write(path, new_contents.as_bytes()) {
panic!("Cannot write to `{}`: {e}", path.display());
}
},
pub fn update_file_checked(
&mut self,
tool: &str,
mode: UpdateMode,
path: impl AsRef<Path>,
update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus,
) {
self.update_file_checked_inner(tool, mode, path.as_ref(), update);
}
#[expect(clippy::type_complexity)]
pub fn update_files_checked(
&mut self,
tool: &str,
mode: UpdateMode,
files: &mut [(
impl AsRef<Path>,
&mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus,
)],
) {
for (path, update) in files {
self.update_file_checked_inner(tool, mode, path.as_ref(), update);
}
}
pub fn update_file(
&mut self,
path: impl AsRef<Path>,
update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus,
) {
self.update_file_inner(path.as_ref(), update);
}
}
/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
/// were found, or the missing delimiter if not.
pub(crate) fn replace_region_in_text<'a>(
text: &str,
start: &'a str,
end: &'a str,
mut write_replacement: impl FnMut(&mut String),
) -> Result<String, &'a str> {
let (text_start, rest) = text.split_once(start).ok_or(start)?;
let (_, text_end) = rest.split_once(end).ok_or(end)?;
let mut res = String::with_capacity(text.len() + 4096);
res.push_str(text_start);
res.push_str(start);
write_replacement(&mut res);
res.push_str(end);
res.push_str(text_end);
Ok(res)
pub fn update_text_region(
path: &Path,
start: &str,
end: &str,
src: &str,
dst: &mut String,
insert: &mut impl FnMut(&mut String),
) -> UpdateStatus {
let Some((src_start, src_end)) = src.split_once(start) else {
panic!("`{}` does not contain `{start}`", path.display());
};
let Some((replaced_text, src_end)) = src_end.split_once(end) else {
panic!("`{}` does not contain `{end}`", path.display());
};
dst.push_str(src_start);
dst.push_str(start);
let new_start = dst.len();
insert(dst);
let changed = dst[new_start..] != *replaced_text;
dst.push_str(end);
dst.push_str(src_end);
UpdateStatus::from_changed(changed)
}
pub fn update_text_region_fn(
start: &str,
end: &str,
mut insert: impl FnMut(&mut String),
) -> impl FnMut(&Path, &str, &mut String) -> UpdateStatus {
move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert)
}
#[must_use]
pub fn is_ident_char(c: u8) -> bool {
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
}
pub struct StringReplacer<'a> {
searcher: AhoCorasick,
replacements: &'a [(&'a str, &'a str)],
}
impl<'a> StringReplacer<'a> {
#[must_use]
pub fn new(replacements: &'a [(&'a str, &'a str)]) -> Self {
Self {
searcher: AhoCorasickBuilder::new()
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
.build(replacements.iter().map(|&(x, _)| x))
.unwrap(),
replacements,
}
}
/// Replace substrings if they aren't bordered by identifier characters.
pub fn replace_ident_fn(&self) -> impl Fn(&Path, &str, &mut String) -> UpdateStatus {
move |_, src, dst| {
let mut pos = 0;
let mut changed = false;
for m in self.searcher.find_iter(src) {
if !is_ident_char(src.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
&& !is_ident_char(src.as_bytes().get(m.end()).copied().unwrap_or(0))
{
changed = true;
dst.push_str(&src[pos..m.start()]);
dst.push_str(self.replacements[m.pattern()].1);
pos = m.end();
}
}
dst.push_str(&src[pos..]);
UpdateStatus::from_changed(changed)
}
}
}
#[derive(Clone, Copy)]
pub enum Token {
/// Matches any number of doc comments.
AnyDoc,
Ident(&'static str),
CaptureIdent,
LitStr,
CaptureLitStr,
Bang,
CloseBrace,
CloseBracket,
CloseParen,
/// This will consume the first colon even if the second doesn't exist.
DoubleColon,
Comma,
Eq,
Lifetime,
Lt,
Gt,
OpenBrace,
OpenBracket,
OpenParen,
Pound,
}
pub struct RustSearcher<'txt> {
text: &'txt str,
cursor: lexer::Cursor<'txt>,
pos: u32,
// Either the next token or a zero-sized whitespace sentinel.
next_token: lexer::Token,
}
impl<'txt> RustSearcher<'txt> {
#[must_use]
pub fn new(text: &'txt str) -> Self {
Self {
text,
cursor: lexer::Cursor::new(text, FrontmatterAllowed::Yes),
pos: 0,
// Sentinel value indicating there is no read token.
next_token: lexer::Token {
len: 0,
kind: lexer::TokenKind::Whitespace,
},
}
}
#[must_use]
pub fn peek_text(&self) -> &'txt str {
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
}
#[must_use]
pub fn peek(&self) -> lexer::TokenKind {
self.next_token.kind
}
#[must_use]
pub fn pos(&self) -> u32 {
self.pos
}
#[must_use]
pub fn at_end(&self) -> bool {
self.next_token.kind == lexer::TokenKind::Eof
}
pub fn step(&mut self) {
// `next_len` is zero for the sentinel value and the eof marker.
self.pos += self.next_token.len;
self.next_token = self.cursor.advance_token();
}
/// Consumes the next token if it matches the requested value and captures the value if
/// requested. Returns true if a token was matched.
fn read_token(&mut self, token: Token, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool {
loop {
match (token, self.next_token.kind) {
// Has to be the first match arm so the empty sentinel token will be handled.
// This will also skip all whitespace/comments preceding any tokens.
(
_,
lexer::TokenKind::Whitespace
| lexer::TokenKind::LineComment { doc_style: None }
| lexer::TokenKind::BlockComment {
doc_style: None,
terminated: true,
},
) => {
self.step();
if self.at_end() {
// `AnyDoc` always matches.
return matches!(token, Token::AnyDoc);
}
},
(
Token::AnyDoc,
lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. },
) => {
self.step();
if self.at_end() {
// `AnyDoc` always matches.
return true;
}
},
(Token::AnyDoc, _) => return true,
(Token::Bang, lexer::TokenKind::Bang)
| (Token::CloseBrace, lexer::TokenKind::CloseBrace)
| (Token::CloseBracket, lexer::TokenKind::CloseBracket)
| (Token::CloseParen, lexer::TokenKind::CloseParen)
| (Token::Comma, lexer::TokenKind::Comma)
| (Token::Eq, lexer::TokenKind::Eq)
| (Token::Lifetime, lexer::TokenKind::Lifetime { .. })
| (Token::Lt, lexer::TokenKind::Lt)
| (Token::Gt, lexer::TokenKind::Gt)
| (Token::OpenBrace, lexer::TokenKind::OpenBrace)
| (Token::OpenBracket, lexer::TokenKind::OpenBracket)
| (Token::OpenParen, lexer::TokenKind::OpenParen)
| (Token::Pound, lexer::TokenKind::Pound)
| (
Token::LitStr,
lexer::TokenKind::Literal {
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
..
},
) => {
self.step();
return true;
},
(Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => {
self.step();
return true;
},
(Token::DoubleColon, lexer::TokenKind::Colon) => {
self.step();
if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) {
self.step();
return true;
}
return false;
},
(
Token::CaptureLitStr,
lexer::TokenKind::Literal {
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
..
},
)
| (Token::CaptureIdent, lexer::TokenKind::Ident) => {
**captures.next().unwrap() = self.peek_text();
self.step();
return true;
},
_ => return false,
}
}
}
#[must_use]
pub fn find_token(&mut self, token: Token) -> bool {
let mut capture = [].iter_mut();
while !self.read_token(token, &mut capture) {
self.step();
if self.at_end() {
return false;
}
}
true
}
#[must_use]
pub fn find_capture_token(&mut self, token: Token) -> Option<&'txt str> {
let mut res = "";
let mut capture = &mut res;
let mut capture = slice::from_mut(&mut capture).iter_mut();
while !self.read_token(token, &mut capture) {
self.step();
if self.at_end() {
return None;
}
}
Some(res)
}
#[must_use]
pub fn match_tokens(&mut self, tokens: &[Token], captures: &mut [&mut &'txt str]) -> bool {
let mut captures = captures.iter_mut();
tokens.iter().all(|&t| self.read_token(t, &mut captures))
}
}
#[expect(clippy::must_use_candidate)]
pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
match OpenOptions::new().create_new(true).write(true).open(new_name) {
Ok(file) => drop(file),
Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
Err(e) => panic_file(&e, FileAction::Create, new_name),
}
match fs::rename(old_name, new_name) {
Ok(()) => true,
Err(e) => {
drop(fs::remove_file(new_name));
if e.kind() == io::ErrorKind::NotFound {
false
} else {
panic_file(&e, FileAction::Rename, old_name);
}
},
}
}
pub fn write_file(path: &Path, contents: &str) {
fs::write(path, contents).unwrap_or_else(|e| panic_file(&e, FileAction::Write, path));
}

View file

@ -468,7 +468,7 @@ declare_clippy_lint! {
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
#[clippy::version = "1.85.0"]
#[clippy::version = "1.88.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,
"ignored tests without messages"

View file

@ -26,16 +26,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
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"
name,
sym::ambiguous_glob_reexports
| sym::dead_code
| sym::deprecated
| sym::hidden_glob_reexports
| sym::unreachable_pub
| sym::unused
| sym::unused_braces
| sym::unused_import_braces
| sym::unused_imports
)
{
return;
@ -43,16 +43,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
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"
name,
sym::wildcard_imports
| sym::enum_glob_use
| sym::redundant_pub_crate
| sym::macro_use_imports
| sym::unsafe_removed_from_name
| sym::module_name_repetitions
| sym::single_component_path_imports
| sym::disallowed_types
| sym::unused_trait_names
)
{
return;

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPathWithoutReplacement, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths};
use clippy_utils::paths::{self, PathNS};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, DefIdMap};
use rustc_lint::{LateContext, LateLintPass};
@ -182,6 +182,7 @@ impl AwaitHolding {
let (def_ids, _) = create_disallowed_map(
tcx,
&conf.await_holding_invalid_types,
PathNS::Type,
crate::disallowed_types::def_kind_predicate,
"type",
false,
@ -275,12 +276,10 @@ fn emit_invalid_type(
}
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
cx.tcx.is_diagnostic_item(sym::MutexGuard, def_id)
|| cx.tcx.is_diagnostic_item(sym::RwLockReadGuard, def_id)
|| cx.tcx.is_diagnostic_item(sym::RwLockWriteGuard, def_id)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
match cx.tcx.get_diagnostic_name(def_id) {
Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
}
}
fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sugg::Sugg;
use clippy_utils::sym;
use clippy_utils::ty::{implements_trait, is_copy};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -73,10 +74,9 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return;
};
let macro_name = cx.tcx.item_name(macro_call.def_id);
let eq_macro = match macro_name.as_str() {
"assert_eq" | "debug_assert_eq" => true,
"assert_ne" | "debug_assert_ne" => false,
let eq_macro = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::assert_eq_macro | sym::debug_assert_eq_macro) => true,
Some(sym::assert_ne_macro | sym::debug_assert_ne_macro) => false,
_ => return,
};
let Some((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@ -115,6 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
return;
}
let macro_name = cx.tcx.item_name(macro_call.def_id);
let macro_name = macro_name.as_str();
let non_eq_mac = &macro_name[..macro_name.len() - 3];
span_lint_and_then(

View file

@ -0,0 +1,82 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArg, Ty};
use rustc_span::def_id::DefId;
use rustc_span::{Symbol, sym};
use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
match ty.kind() {
ty::Char => Some("char"),
ty::Int(int) => Some(int.name_str()),
ty::Uint(uint) => Some(uint.name_str()),
ty::Float(float) => Some(float.name_str()),
_ => None,
}
}
fn get_const_name_and_ty_name(
cx: &LateContext<'_>,
method_name: Symbol,
method_def_id: DefId,
generics: &[GenericArg<'_>],
) -> Option<(&'static str, &'static str)> {
let method_name = method_name.as_str();
let diagnostic_name = cx.tcx.get_diagnostic_name(method_def_id);
let ty_name = if diagnostic_name.is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max) {
// We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
if let [ty] = generics
&& let Some(ty) = ty.as_type()
{
get_primitive_ty_name(ty)?
} else {
return None;
}
} else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
&& let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
&& ["min", "max", "minimum", "maximum", "min_value", "max_value"].contains(&method_name)
{
ty_name
} else {
return None;
};
let const_name = if method_name.starts_with("max") { "MAX" } else { "MIN" };
Some((const_name, ty_name))
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We allow casts from any function type to any function type.
match cast_to.kind() {
ty::FnDef(..) | ty::FnPtr(..) => return,
_ => { /* continue to checks */ },
}
if let ty::FnDef(def_id, generics) = cast_from.kind()
&& let Some(method_name) = cx.tcx.opt_item_name(*def_id)
&& let Some((const_name, ty_name)) = get_const_name_and_ty_name(cx, method_name, *def_id, generics.as_slice())
{
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
span_lint_and_then(
cx,
CONFUSING_METHOD_TO_NUMERIC_CAST,
expr.span,
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
|diag| {
diag.span_suggestion_verbose(
expr.span,
"did you mean to use the associated constant?",
format!("{ty_name}::{const_name} as {cast_to}"),
applicability,
);
},
);
}
}

View file

@ -1,7 +1,6 @@
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 clippy_utils::{expr_or_init, 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};
@ -55,7 +54,7 @@ fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) ->
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)
&& paths::ALIGN_OF.matches(cx, fun_id)
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
&& let [GenericArg::Type(generic_ty)] = args.args
{
@ -71,12 +70,10 @@ fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>)
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
})
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

@ -14,6 +14,7 @@ mod cast_sign_loss;
mod cast_slice_different_sizes;
mod cast_slice_from_raw_parts;
mod char_lit_as_u8;
mod confusing_method_to_numeric_cast;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
@ -780,12 +781,38 @@ declare_clippy_lint! {
/// let aligned = std::ptr::dangling::<u32>();
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
/// ```
#[clippy::version = "1.87.0"]
#[clippy::version = "1.88.0"]
pub MANUAL_DANGLING_PTR,
style,
"casting small constant literals to pointers to create dangling pointers"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
///
/// ### Why restrict this?
/// Casting a function pointer to an integer can have surprising results and can occur
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything
/// low-level with function pointers then you can opt out of casting functions to integers in
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
/// pointer casts in your code.
///
/// ### Example
/// ```no_run
/// let _ = u16::max as usize;
/// ```
///
/// Use instead:
/// ```no_run
/// let _ = u16::MAX as usize;
/// ```
#[clippy::version = "1.86.0"]
pub CONFUSING_METHOD_TO_NUMERIC_CAST,
suspicious,
"casting a primitive method pointer to any integer type"
}
pub struct Casts {
msrv: Msrv,
}
@ -823,6 +850,7 @@ impl_lint_pass!(Casts => [
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
CONFUSING_METHOD_TO_NUMERIC_CAST,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -847,6 +875,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
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);

View file

@ -0,0 +1,100 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::{is_in_const_context, is_mutable, is_trait_method};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for slice references with cloned references such as `&[f.clone()]`.
///
/// ### Why is this bad
///
/// A reference does not need to be owned in order to used as a slice.
///
/// ### Known problems
///
/// This lint does not know whether or not a clone implementation has side effects.
///
/// ### Example
///
/// ```ignore
/// let data = 10;
/// let data_ref = &data;
/// take_slice(&[data_ref.clone()]);
/// ```
/// Use instead:
/// ```ignore
/// use std::slice;
/// let data = 10;
/// let data_ref = &data;
/// take_slice(slice::from_ref(data_ref));
/// ```
#[clippy::version = "1.87.0"]
pub CLONED_REF_TO_SLICE_REFS,
perf,
"cloning a reference for slice references"
}
pub struct ClonedRefToSliceRefs<'a> {
msrv: &'a Msrv,
}
impl<'a> ClonedRefToSliceRefs<'a> {
pub fn new(conf: &'a Conf) -> Self {
Self { msrv: &conf.msrv }
}
}
impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]);
impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if self.msrv.meets(cx, {
if is_in_const_context(cx) {
msrvs::CONST_SLICE_FROM_REF
} else {
msrvs::SLICE_FROM_REF
}
})
// `&[foo.clone()]` expressions
&& let ExprKind::AddrOf(_, mutability, arr) = &expr.kind
// mutable references would have a different meaning
&& mutability.is_not()
// check for single item arrays
&& let ExprKind::Array([item]) = &arr.kind
// check for clones
&& let ExprKind::MethodCall(_, val, _, _) = item.kind
&& is_trait_method(cx, item, sym::Clone)
// check for immutability or purity
&& (!is_mutable(cx, val) || is_const_evaluatable(cx, val))
// get appropriate crate for `slice::from_ref`
&& let Some(builtin_crate) = clippy_utils::std_or_core(cx)
{
let mut sugg = Sugg::hir(cx, val, "_");
if !cx.typeck_results().expr_ty(val).is_ref() {
sugg = sugg.addr();
}
span_lint_and_sugg(
cx,
CLONED_REF_TO_SLICE_REFS,
expr.span,
format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"),
"try",
format!("{builtin_crate}::slice::from_ref({sugg})"),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -1,11 +1,11 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -78,14 +78,14 @@ declare_clippy_lint! {
}
pub struct CollapsibleIf {
let_chains_enabled: bool,
msrv: Msrv,
lint_commented_code: bool,
}
impl CollapsibleIf {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
pub fn new(conf: &'static Conf) -> Self {
Self {
let_chains_enabled: tcx.features().let_chains(),
msrv: conf.msrv,
lint_commented_code: conf.lint_commented_code,
}
}
@ -127,7 +127,7 @@ impl CollapsibleIf {
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)
&& self.eligible_condition(cx, check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
@ -163,8 +163,9 @@ impl CollapsibleIf {
}
}
pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool {
!matches!(cond.kind, ExprKind::Let(..))
|| (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS))
}
}
@ -180,7 +181,7 @@ impl LateLintPass<'_> for CollapsibleIf {
{
Self::check_collapsible_else_if(cx, then.span, else_);
} else if else_.is_none()
&& self.eligible_condition(cond)
&& self.eligible_condition(cx, cond)
&& let ExprKind::Block(then, None) = then.kind
{
self.check_collapsible_if_if(cx, expr, cond, then);
@ -202,13 +203,12 @@ fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
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
}
},
[
Stmt {
kind: StmtKind::Semi(expr),
..
},
] if block.expr.is_none() => Some(expr),
_ => None,
}
}

View file

@ -64,6 +64,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,
crate::casts::CHAR_LIT_AS_U8_INFO,
crate::casts::CONFUSING_METHOD_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
@ -75,6 +76,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::ZERO_PTR_INFO,
crate::cfg_not_test::CFG_NOT_TEST_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
@ -705,13 +707,9 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS_INFO,
crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO,
crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO,
crate::transmute::TRANSMUTE_FLOAT_TO_INT_INFO,
crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO,
crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO,
crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO,
crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO,
crate::transmute::TRANSMUTE_NULL_TO_FN_INFO,
crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO,
crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO,
crate::transmute::TRANSMUTE_PTR_TO_REF_INFO,
crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO,

View file

@ -2,18 +2,18 @@
// Prefer to use those when possible.
macro_rules! declare_with_version {
($name:ident($name_version:ident): &[$ty:ty] = &[$(
($name:ident($name_version:ident) = [$(
#[clippy::version = $version:literal]
$e:expr,
)*]) => {
pub static $name: &[$ty] = &[$($e),*];
pub static $name: &[(&str, &str)] = &[$($e),*];
#[allow(unused)]
pub static $name_version: &[&str] = &[$($version),*];
};
}
#[rustfmt::skip]
declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [
#[clippy::version = "pre 1.29.0"]
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
#[clippy::version = "pre 1.29.0"]
@ -44,11 +44,10 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
("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`
]}
#[rustfmt::skip]
declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
declare_with_version! { RENAMED(RENAMED_VERSION) = [
#[clippy::version = ""]
("clippy::almost_complete_letter_range", "clippy::almost_complete_range"),
#[clippy::version = ""]
@ -187,5 +186,12 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"),
#[clippy::version = ""]
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
// end renamed lints. used by `cargo dev rename_lint`
#[clippy::version = "1.88.0"]
("clippy::transmute_int_to_float", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_int_to_char", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_float_to_int", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
]}

View file

@ -2,7 +2,7 @@ use std::ops::ControlFlow;
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths};
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
@ -377,7 +377,7 @@ fn check_unsafe_derive_deserialize<'tcx>(
}
if let Some(trait_def_id) = trait_ref.trait_def_id()
&& match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
&& paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
&& let ty::Adt(def, _) = ty.kind()
&& let Some(local_def_id) = def.did().as_local()
&& let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)

View file

@ -1,8 +1,8 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace;
use clippy_utils::paths::PathNS;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefIdMap;
@ -76,6 +76,7 @@ impl DisallowedMacros {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_macros,
PathNS::Macro,
|def_kind| matches!(def_kind, DefKind::Macro(_)),
"macro",
false,

View file

@ -1,6 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::PathNS;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind};
@ -66,6 +67,7 @@ impl DisallowedMethods {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_methods,
PathNS::Value,
|def_kind| {
matches!(
def_kind,

View file

@ -1,6 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::PathNS;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
@ -60,7 +61,14 @@ pub struct DisallowedTypes {
impl DisallowedTypes {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
let (def_ids, prim_tys) = create_disallowed_map(
tcx,
&conf.disallowed_types,
PathNS::Type,
def_kind_predicate,
"type",
true,
);
Self { def_ids, prim_tys }
}

View file

@ -93,7 +93,7 @@ declare_clippy_lint! {
/// ```no_run
/// //! <code>[first](x)second</code>
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub DOC_LINK_CODE,
nursery,
"link with code back-to-back with other code"

View file

@ -759,12 +759,12 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
let recv_ty = cx.typeck_results().expr_ty(receiver);
if recv_ty.is_floating_point() && !is_no_std_crate(cx) && is_inherent_method_call(cx, expr) {
match path.ident.name.as_str() {
"ln" => check_ln1p(cx, expr, receiver),
"log" => check_log_base(cx, expr, receiver, args),
"powf" => check_powf(cx, expr, receiver, args),
"powi" => check_powi(cx, expr, receiver, args),
"sqrt" => check_hypot(cx, expr, receiver),
match path.ident.name {
sym::ln => check_ln1p(cx, expr, receiver),
sym::log => check_log_base(cx, expr, receiver, args),
sym::powf => check_powf(cx, expr, receiver, args),
sym::powi => check_powi(cx, expr, receiver, args),
sym::sqrt => check_hypot(cx, expr, receiver),
_ => {},
}
}

View file

@ -9,8 +9,8 @@ mod too_many_arguments;
mod too_many_lines;
use clippy_config::Conf;
use clippy_utils::def_path_def_ids;
use clippy_utils::msrvs::Msrv;
use clippy_utils::paths::{PathNS, lookup_path_str};
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_lint::{LateContext, LateLintPass};
@ -469,7 +469,7 @@ impl Functions {
trait_ids: conf
.allow_renamed_params_for
.iter()
.flat_map(|p| def_path_def_ids(tcx, &p.split("::").collect::<Vec<_>>()))
.flat_map(|p| lookup_path_str(tcx, PathNS::Type, p))
.collect(),
msrv: conf.msrv,
}

View file

@ -69,7 +69,7 @@ declare_clippy_lint! {
///
/// let result = a.saturating_sub(b);
/// ```
#[clippy::version = "1.44.0"]
#[clippy::version = "1.83.0"]
pub INVERTED_SATURATING_SUB,
correctness,
"Check if a variable is smaller than another one and still subtract from it even if smaller"

View file

@ -5,7 +5,7 @@ use clippy_utils::macros::span_is_local;
use clippy_utils::source::is_present_in_source;
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::Symbol;
@ -162,6 +162,7 @@ pub struct ItemNameRepetitions {
enum_threshold: u64,
struct_threshold: u64,
avoid_breaking_exported_api: bool,
allow_exact_repetitions: bool,
allow_private_module_inception: bool,
allowed_prefixes: FxHashSet<String>,
}
@ -173,6 +174,7 @@ impl ItemNameRepetitions {
enum_threshold: conf.enum_variant_name_threshold,
struct_threshold: conf.struct_field_name_threshold,
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
allow_exact_repetitions: conf.allow_exact_repetitions,
allow_private_module_inception: conf.allow_private_module_inception,
allowed_prefixes: conf.allowed_prefixes.iter().map(|s| to_camel_case(s)).collect(),
}
@ -405,6 +407,7 @@ fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>
if count_match_start(item_name, name).char_count == item_name_chars
&& name.chars().nth(item_name_chars).is_some_and(|c| !c.is_lowercase())
&& name.chars().nth(item_name_chars + 1).is_some_and(|c| !c.is_numeric())
&& !check_enum_tuple_path_match(name, variant.data)
{
span_lint_hir(
cx,
@ -420,7 +423,9 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
let name = variant.ident.name.as_str();
let item_name_chars = item_name.chars().count();
if count_match_end(item_name, name).char_count == item_name_chars {
if count_match_end(item_name, name).char_count == item_name_chars
&& !check_enum_tuple_path_match(name, variant.data)
{
span_lint_hir(
cx,
ENUM_VARIANT_NAMES,
@ -431,6 +436,27 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
}
}
/// Checks if an enum tuple variant contains a single field
/// whose qualified path contains the variant's name.
fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>) -> bool {
// Only check single-field tuple variants
let VariantData::Tuple(fields, ..) = variant_data else {
return false;
};
if fields.len() != 1 {
return false;
}
// Check if field type is a path and contains the variant name
match fields[0].ty.kind {
TyKind::Path(QPath::Resolved(_, path)) => path
.segments
.iter()
.any(|segment| segment.ident.name.as_str() == variant_name),
TyKind::Path(QPath::TypeRelative(_, segment)) => segment.ident.name.as_str() == variant_name,
_ => false,
}
}
impl LateLintPass<'_> for ItemNameRepetitions {
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
let Some(_ident) = item.kind.ident() else { return };
@ -462,11 +488,21 @@ impl LateLintPass<'_> for ItemNameRepetitions {
}
// 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()
{
// name. Having the same name is only accepted if `allow_exact_repetition` is set to `true`.
let both_are_public =
cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public();
if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel {
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name is the same as its containing module's name",
);
}
if both_are_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();

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
use clippy_utils::ty::{implements_trait, is_must_use_ty};
use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths};
use rustc_hir::{LetStmt, LocalSource, PatKind};
use rustc_lint::{LateContext, LateLintPass};
@ -129,12 +129,6 @@ declare_clippy_lint! {
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]);
const SYNC_GUARD_PATHS: [&[&str]; 3] = [
&paths::PARKING_LOT_MUTEX_GUARD,
&paths::PARKING_LOT_RWLOCK_READ_GUARD,
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
];
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) {
if matches!(local.source, LocalSource::Normal)
@ -144,7 +138,9 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
{
let init_ty = cx.typeck_results().expr_ty(init);
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)),
GenericArgKind::Type(inner_ty) => inner_ty
.ty_adt_def()
.is_some_and(|adt| paths::PARKING_LOT_GUARDS.iter().any(|path| path.matches(cx, adt.did()))),
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
});
if contains_sync_guard {

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_errors::Applicability;
use rustc_hir::{LetStmt, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -32,13 +33,19 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
&& !local.span.in_external_macro(cx.tcx.sess.source_map())
&& !is_from_proc_macro(cx, ty)
{
span_lint_and_help(
span_lint_and_then(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration",
|diag| {
diag.span_suggestion_verbose(
ty.span.with_lo(local.pat.span.hi()),
"remove the explicit type `_` declaration",
"",
Applicability::MachineApplicable,
);
},
);
}
}

View file

@ -1,5 +1,4 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(macro_metavar_expr_concat)]
#![feature(f128)]
@ -7,7 +6,6 @@
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iter_partition_in_place)]
#![feature(let_chains)]
#![feature(never_type)]
#![feature(round_char_boundary)]
#![feature(rustc_private)]
@ -96,6 +94,7 @@ mod cargo;
mod casts;
mod cfg_not_test;
mod checked_conversions;
mod cloned_ref_to_slice_refs;
mod cognitive_complexity;
mod collapsible_if;
mod collection_is_never_read;
@ -731,7 +730,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf)));
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
store.register_early_pass(|| Box::new(precedence::Precedence));
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
@ -748,7 +747,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(conf)));
store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
store.register_late_pass(|_| Box::new(exit::Exit));
store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome));
store.register_late_pass(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf)));
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf)));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf)));
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
@ -944,5 +943,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf)));
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -88,7 +88,7 @@ declare_clippy_lint! {
/// x.chars()
/// }
/// ```
#[clippy::version = "1.84.0"]
#[clippy::version = "1.87.0"]
pub ELIDABLE_LIFETIME_NAMES,
pedantic,
"lifetime name that can be replaced with the anonymous lifetime"

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{HasSession, snippet_with_applicability};
use clippy_utils::ty::implements_trait;
use clippy_utils::ty::{implements_trait, is_slice_like};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{higher, peel_blocks_with_stmt, span_contains_comment};
use rustc_ast::ast::LitKind;
@ -58,6 +58,8 @@ pub(super) fn check<'tcx>(
&& let Res::Local(idx_hir) = idx_path.res
&& !is_local_used(cx, assignval, idx_hir)
&& msrv.meets(cx, msrvs::SLICE_FILL)
&& let slice_ty = cx.typeck_results().expr_ty(slice).peel_refs()
&& is_slice_like(cx, slice_ty)
{
sugg(cx, body, expr, slice.span, assignval.span);
}

View file

@ -778,7 +778,7 @@ declare_clippy_lint! {
/// let _ = s[idx..];
/// }
/// ```
#[clippy::version = "1.83.0"]
#[clippy::version = "1.88.0"]
pub CHAR_INDICES_AS_BYTE_INDICES,
correctness,
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"

View file

@ -36,7 +36,7 @@ declare_clippy_lint! {
/// a.abs_diff(b)
/// # ;
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.88.0"]
pub MANUAL_ABS_DIFF,
complexity,
"using an if-else pattern instead of `abs_diff`"

View file

@ -1,6 +1,7 @@
use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
@ -10,7 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{Ty, UintTy};
use rustc_session::declare_lint_pass;
use rustc_span::{Span, sym};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -47,9 +48,9 @@ enum MatchType<'a, 'b> {
fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> {
if let MethodCall(path, expr, _, _) = kind {
let is_lower = match path.ident.name.as_str() {
"to_ascii_lowercase" => true,
"to_ascii_uppercase" => false,
let is_lower = match path.ident.name {
sym::to_ascii_lowercase => true,
sym::to_ascii_uppercase => false,
_ => return None,
};
let ty_raw = cx.typeck_results().expr_ty(expr);

View file

@ -4,11 +4,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks};
use clippy_utils::{
MaybePath, is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, path_res, peel_blocks,
};
use rustc_ast::BindingMode;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_span::Span;
@ -91,14 +94,15 @@ impl<'tcx> QuestionMark {
let Some((idx, diverging_arm)) = diverging_arm_opt else {
return;
};
let pat_arm = &arms[1 - idx];
// If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
// However, if it arrives in second position, its pattern may cover some cases already covered
// by the diverging one.
// TODO: accept the non-diverging arm as a second position if patterns are disjointed.
if idx == 0 {
if idx == 0 && !is_arms_disjointed(cx, diverging_arm, pat_arm) {
return;
}
let pat_arm = &arms[1 - idx];
let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else {
return;
};
@ -110,6 +114,63 @@ impl<'tcx> QuestionMark {
}
}
/// Checks if the patterns of the arms are disjointed. Currently, we only support patterns of simple
/// enum variants without nested patterns or bindings.
///
/// TODO: Support more complex patterns.
fn is_arms_disjointed(cx: &LateContext<'_>, arm1: &Arm<'_>, arm2: &Arm<'_>) -> bool {
if arm1.guard.is_some() || arm2.guard.is_some() {
return false;
}
if !is_enum_variant(cx, arm1.pat) || !is_enum_variant(cx, arm2.pat) {
return false;
}
true
}
/// Returns `true` if the given pattern is a variant of an enum.
pub fn is_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
struct Pat<'hir>(&'hir rustc_hir::Pat<'hir>);
impl<'hir> MaybePath<'hir> for Pat<'hir> {
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
match self.0.kind {
PatKind::Struct(ref qpath, fields, _)
if fields
.iter()
.all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))) =>
{
Some(qpath)
},
PatKind::TupleStruct(ref qpath, pats, _)
if pats
.iter()
.all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))) =>
{
Some(qpath)
},
PatKind::Expr(&PatExpr {
kind: PatExprKind::Path(ref qpath),
..
}) => Some(qpath),
_ => None,
}
}
fn hir_id(&self) -> HirId {
self.0.hir_id
}
}
let res = path_res(cx, &Pat(pat));
matches!(
res,
Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(CtorOf::Variant, _), _)
)
}
fn emit_manual_let_else(
cx: &LateContext<'_>,
span: Span,

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::msrvs::Msrv;
use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym};
use clippy_utils::{is_none_arm, msrvs, paths, peel_hir_expr_refs, sym};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal};
@ -80,26 +80,26 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
check_map(cx, callee, span, self.msrv);
}
},
ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() {
"unwrap_or" => {
ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name {
sym::unwrap_or => {
if is_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
},
"unwrap_or_else" => {
sym::unwrap_or_else => {
if returns_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
},
_ => {},
},
ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() {
"map_or" => {
ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name {
sym::map_or => {
if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
},
"map_or_else" => {
sym::map_or_else => {
if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
@ -220,5 +220,5 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
clippy_utils::is_expr_path_def_path(cx, expr, &["core", "slice", "raw", "from_ref"])
paths::SLICE_FROM_REF.matches_path(cx, expr)
}

View file

@ -1,5 +1,6 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
use rustc_ast::{BindingMode, ByRef};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, QPath};
@ -16,7 +17,7 @@ use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<HirId> {
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
&& let PatKind::Binding(_, pat_id, _, _) = pat.kind
&& let PatKind::Binding(BindingMode(ByRef::No, _), pat_id, _, _) = pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.

View file

@ -62,7 +62,7 @@ declare_clippy_lint! {
/// let mut an_option = Some(0);
/// let taken = an_option.replace(1);
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub MEM_REPLACE_OPTION_WITH_SOME,
style,
"replacing an `Option` with `Some` instead of `replace()`"

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
use clippy_utils::sym;
use clippy_utils::ty::is_type_lang_item;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -21,8 +22,8 @@ pub(super) fn check<'tcx>(
) {
if let ExprKind::MethodCall(path_segment, ..) = recv.kind
&& matches!(
path_segment.ident.name.as_str(),
"to_lowercase" | "to_uppercase" | "to_ascii_lowercase" | "to_ascii_uppercase"
path_segment.ident.name,
sym::to_lowercase | sym::to_uppercase | sym::to_ascii_lowercase | sym::to_ascii_uppercase
)
{
return;

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{expr_or_init, paths};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
@ -8,13 +9,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args
if let [error_kind, error] = args
&& !expr.span.from_expansion()
&& !error_kind.span.from_expansion()
&& clippy_utils::is_expr_path_def_path(cx, path, &clippy_utils::paths::IO_ERROR_NEW)
&& clippy_utils::is_expr_path_def_path(
cx,
clippy_utils::expr_or_init(cx, error_kind),
&clippy_utils::paths::IO_ERRORKIND_OTHER,
)
&& let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind
&& paths::IO_ERROR_NEW.matches_path(cx, path)
&& paths::IO_ERRORKIND_OTHER_CTOR.matches_path(cx, expr_or_init(cx, error_kind))
&& msrv.meets(cx, msrvs::IO_ERROR_OTHER)
{
span_lint_and_then(

View file

@ -1,13 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::{get_parent_expr, sym};
use rustc_ast::{LitKind, StrStyle};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{Span, Symbol, sym};
use rustc_span::{Span, Symbol};
use super::MANUAL_C_STR_LITERALS;
@ -71,15 +71,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
&& cx.tcx.sess.edition() >= Edition2021
&& msrv.meets(cx, msrvs::C_STR_LITERALS)
{
match fn_name.as_str() {
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
match fn_name {
sym::from_bytes_with_nul | sym::from_bytes_with_nul_unchecked
if !arg.span.from_expansion()
&& let ExprKind::Lit(lit) = arg.kind
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
{
check_from_bytes(cx, expr, arg, name);
check_from_bytes(cx, expr, arg, fn_name);
},
"from_ptr" => check_from_ptr(cx, expr, arg),
sym::from_ptr => check_from_ptr(cx, expr, arg),
_ => {},
}
}
@ -106,13 +106,13 @@ fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
}
}
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: Symbol) {
let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(method, ..) = parent.kind
&& [sym::unwrap, sym::expect].contains(&method.ident.name)
{
(parent.span, Applicability::MachineApplicable)
} else if method == "from_bytes_with_nul_unchecked" {
} else if method == sym::from_bytes_with_nul_unchecked {
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
(expr.span, Applicability::MachineApplicable)
} else {

View file

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, path_def_id};
use clippy_utils::{path_res, sym};
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_lint::LateContext;
use rustc_middle::ty::layout::LayoutOf;
@ -79,16 +80,15 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
}
let ty = cx.typeck_results().expr_ty(expr);
let ty_str = ty.to_string();
// `std::T::MAX` `std::T::MIN` constants
if let Some(id) = path_def_id(cx, expr) {
if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
return Some(MinMax::Max);
}
if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
return Some(MinMax::Min);
// `T::MAX` and `T::MIN` constants
if let hir::ExprKind::Path(hir::QPath::TypeRelative(base, seg)) = expr.kind
&& let Res::PrimTy(_) = path_res(cx, base)
{
match seg.ident.name {
sym::MAX => return Some(MinMax::Max),
sym::MIN => return Some(MinMax::Min),
_ => {},
}
}

View file

@ -4428,7 +4428,7 @@ declare_clippy_lint! {
/// let file = BufReader::new(std::fs::File::open("./bytes.txt").unwrap());
/// file.bytes();
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub UNBUFFERED_BYTES,
perf,
"calling .bytes() is very inefficient when data is not in memory"
@ -4453,7 +4453,7 @@ declare_clippy_lint! {
/// values.contains(&10)
/// }
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub MANUAL_CONTAINS,
perf,
"unnecessary `iter().any()` on slices that can be replaced with `contains()`"
@ -4709,6 +4709,8 @@ impl_lint_pass!(Methods => [
]);
/// Extracts a method call name, args, and `Span` of the method name.
/// This ensures that neither the receiver nor any of the arguments
/// come from expansion.
pub fn method_call<'tcx>(
recv: &'tcx Expr<'tcx>,
) -> Option<(&'tcx str, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> {
@ -4907,6 +4909,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
impl Methods {
#[allow(clippy::too_many_lines)]
fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Handle method calls whose receiver and arguments may not come from expansion
if let Some((name, recv, args, span, call_span)) = method_call(expr) {
match (name, args) {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
@ -5049,29 +5052,12 @@ impl Methods {
Some(("err", recv, [], err_span, _)) => {
err_expect::check(cx, expr, recv, span, err_span, self.msrv);
},
_ => unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
),
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("expect_err", [_]) => {
("expect_err", [_]) | ("unwrap_err" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("extend", [arg]) => {
string_extend_chars::check(cx, expr, recv, arg);
@ -5437,27 +5423,6 @@ impl Methods {
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_or", [u_arg]) => {
match method_call(recv) {
@ -5486,9 +5451,6 @@ impl Methods {
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or_else", [u_arg]) => {
match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
@ -5526,6 +5488,56 @@ impl Methods {
_ => {},
}
}
// Handle method calls whose receiver and arguments may come from expansion
if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind {
match (path.ident.name.as_str(), args) {
("expect", [_]) if !matches!(method_call(recv), Some(("ok" | "err", _, [], _, _))) => {
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("expect_err", [_]) => {
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("unwrap", []) => {
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
_ => {},
}
}
}
}

View file

@ -7,9 +7,8 @@ use rustc_span::Span;
use super::NEEDLESS_CHARACTER_ITERATION;
use super::utils::get_last_chain_binding_hir_id;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::CHAR_IS_ASCII;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{match_def_path, path_to_local_id, peel_blocks, sym};
use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym};
fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
while let ExprKind::AddrOf(_, _, e) = expr.kind {
@ -76,9 +75,7 @@ fn handle_expr(
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
// `is_ascii`, then only `.all()` should warn.
if revert != is_all
&& let ExprKind::Path(path) = fn_path.kind
&& let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
&& match_def_path(cx, fn_def_id, &CHAR_IS_ASCII)
&& is_path_diagnostic_item(cx, fn_path, sym::char_is_ascii)
&& path_to_local_id(peels_expr_ref(arg), first_param)
&& let Some(snippet) = before_chars.get_source_text(cx)
{

View file

@ -357,20 +357,20 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
if let Some(hir_id) = self.current_statement_hir_id {
self.hir_id_uses_map.insert(hir_id, self.uses.len());
}
match method_name.ident.name.as_str() {
"into_iter" => self.uses.push(Some(IterFunction {
match method_name.ident.name {
sym::into_iter => self.uses.push(Some(IterFunction {
func: IterFunctionKind::IntoIter(expr.hir_id),
span: expr.span,
})),
"len" => self.uses.push(Some(IterFunction {
sym::len => self.uses.push(Some(IterFunction {
func: IterFunctionKind::Len,
span: expr.span,
})),
"is_empty" => self.uses.push(Some(IterFunction {
sym::is_empty => self.uses.push(Some(IterFunction {
func: IterFunctionKind::IsEmpty,
span: expr.span,
})),
"contains" => self.uses.push(Some(IterFunction {
sym::contains => self.uses.push(Some(IterFunction {
func: IterFunctionKind::Contains(args[0].span),
span: expr.span,
})),

View file

@ -1,8 +1,8 @@
use rustc_data_structures::fx::FxHashMap;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
use clippy_utils::{match_any_def_paths, paths};
use clippy_utils::paths;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_ast::ast::LitKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
@ -13,7 +13,7 @@ use rustc_span::{Span, sym};
use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS};
fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || match_type(cx, ty, &paths::TOKIO_IO_OPEN_OPTIONS)
is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty)
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
@ -126,14 +126,14 @@ fn get_open_options(
&& let ExprKind::Path(path) = callee.kind
&& let Some(did) = cx.qpath_res(&path, callee.hir_id).opt_def_id()
{
let std_file_options = [sym::file_options, sym::open_options_new];
let is_std_options = matches!(
cx.tcx.get_diagnostic_name(did),
Some(sym::file_options | sym::open_options_new)
);
let tokio_file_options: &[&[&str]] = &[&paths::TOKIO_IO_OPEN_OPTIONS_NEW, &paths::TOKIO_FILE_OPTIONS];
let is_std_options = std_file_options
.into_iter()
.any(|sym| cx.tcx.is_diagnostic_item(sym, did));
is_std_options || match_any_def_paths(cx, did, tokio_file_options).is_some()
is_std_options
|| paths::TOKIO_IO_OPEN_OPTIONS_NEW.matches(cx, did)
|| paths::TOKIO_FILE_OPTIONS.matches(cx, did)
} else {
false
}

View file

@ -9,7 +9,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability};
use clippy_utils::ty::get_type_diagnostic_name;
use clippy_utils::visitors::for_each_unconsumed_temporary;
use clippy_utils::{is_expr_final_block_expr, peel_blocks};
use clippy_utils::{get_parent_expr, peel_blocks};
use super::RETURN_AND_THEN;
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
recv: &'tcx hir::Expr<'tcx>,
arg: &'tcx hir::Expr<'_>,
) {
if !is_expr_final_block_expr(cx.tcx, expr) {
if cx.tcx.hir_get_fn_id_for_return_block(expr.hir_id).is_none() {
return;
}
@ -55,12 +55,24 @@ pub(super) fn check<'tcx>(
None => &body_snip,
};
let sugg = format!(
"let {} = {}?;\n{}",
arg_snip,
recv_snip,
reindent_multiline(inner, false, indent_of(cx, expr.span))
);
// If suggestion is going to get inserted as part of a `return` expression, it must be blockified.
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
let base_indent = indent_of(cx, parent_expr.span);
let inner_indent = base_indent.map(|i| i + 4);
format!(
"{}\n{}\n{}",
reindent_multiline(&format!("{{\nlet {arg_snip} = {recv_snip}?;"), true, inner_indent),
reindent_multiline(inner, false, inner_indent),
reindent_multiline("}", false, base_indent),
)
} else {
format!(
"let {} = {}?;\n{}",
arg_snip,
recv_snip,
reindent_multiline(inner, false, indent_of(cx, expr.span))
)
};
span_lint_and_sugg(
cx,

View file

@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::{Descend, for_each_expr};
use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
use clippy_utils::{is_diag_item_method, path_to_local_id, paths};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{
@ -288,7 +288,7 @@ fn parse_iter_usage<'tcx>(
match (name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
("next_tuple", []) => {
return if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE)
return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did)
&& let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind()
&& cx.tcx.is_diagnostic_item(sym::Option, adt_def.did())
&& let ty::Tuple(subs) = subs.type_at(0).kind()

View file

@ -5,7 +5,8 @@ use clippy_utils::{is_trait_method, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty;
use rustc_middle::ty::GenericArgKind;
use rustc_span::sym;
use rustc_span::symbol::Ident;
use std::iter;

View file

@ -1,9 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth};
use clippy_utils::{
get_parent_expr, is_diag_trait_item, match_def_path, path_to_local_id, peel_blocks, strip_pat_refs,
};
use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, LangItem};
use rustc_lint::LateContext;
@ -81,8 +79,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
applicability,
);
}
} else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
|| match_def_path(cx, def_id, &["core", "result", "Result", call_name])
} else if let Some(impl_id) = cx.tcx.opt_parent(def_id)
&& let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def()
&& (cx.tcx.lang_items().option_type() == Some(adt.did()) || cx.tcx.is_diagnostic_item(sym::Result, adt.did()))
{
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();

View file

@ -155,9 +155,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
return;
}
let mir = cx.tcx.mir_drops_elaborated_and_const_checked(def_id);
let mir = cx.tcx.optimized_mir(def_id);
if let Ok(()) = is_min_const_fn(cx, &mir.borrow(), self.msrv)
if let Ok(()) = is_min_const_fn(cx, mir, self.msrv)
&& let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) =
cx.tcx.hir_node_by_def_id(def_id)
{

View file

@ -48,6 +48,8 @@ pub struct MissingDoc {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
crate_items_only: bool,
/// Whether to allow fields starting with an underscore to skip documentation requirements
allow_unused: bool,
/// Stack of whether #[doc(hidden)] is set
/// at each level which has lint attributes.
doc_hidden_stack: Vec<bool>,
@ -59,6 +61,7 @@ impl MissingDoc {
pub fn new(conf: &'static Conf) -> Self {
Self {
crate_items_only: conf.missing_docs_in_crate_items,
allow_unused: conf.missing_docs_allow_unused,
doc_hidden_stack: vec![false],
prev_span: None,
}
@ -260,11 +263,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
if !sf.is_positional() {
if !(sf.is_positional()
|| is_from_proc_macro(cx, sf)
|| self.allow_unused && sf.ident.as_str().starts_with('_'))
{
let attrs = cx.tcx.hir_attrs(sf.hir_id);
if !is_from_proc_macro(cx, sf) {
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
self.prev_span = Some(sf.span);
}

View file

@ -1,6 +1,6 @@
use clippy_config::Conf;
use clippy_utils::def_path_def_ids;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::{PathNS, lookup_path_str};
use clippy_utils::source::SpanRangeExt;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -56,8 +56,12 @@ impl ImportRename {
renames: conf
.enforced_import_renames
.iter()
.map(|x| (x.path.split("::").collect::<Vec<_>>(), Symbol::intern(&x.rename)))
.flat_map(|(path, rename)| def_path_def_ids(tcx, &path).map(move |id| (id, rename)))
.map(|x| (&x.path, Symbol::intern(&x.rename)))
.flat_map(|(path, rename)| {
lookup_path_str(tcx, PathNS::Arbitrary, path)
.into_iter()
.map(move |id| (id, rename))
})
.collect(),
}
}

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sym;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
use rustc_lint::{LateContext, LateLintPass};
@ -42,10 +43,9 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
let Some(macro_call) = root_macro_call_first_node(cx, e) else {
return;
};
let macro_name = cx.tcx.item_name(macro_call.def_id);
if !matches!(
macro_name.as_str(),
"debug_assert" | "debug_assert_eq" | "debug_assert_ne"
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::debug_assert_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
) {
return;
}
@ -60,7 +60,10 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
cx,
DEBUG_ASSERT_WITH_MUT_CALL,
span,
format!("do not call a function with mutable arguments inside of `{macro_name}!`"),
format!(
"do not call a function with mutable arguments inside of `{}!`",
cx.tcx.item_name(macro_call.def_id)
),
);
}
}
@ -96,10 +99,6 @@ impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id)
&& adj

View file

@ -1,13 +1,14 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::paths::{self, PathNS, find_crates, lookup_path_str};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{def_path_def_ids, fn_def_id, is_no_std_crate, path_def_id};
use clippy_utils::{fn_def_id, is_no_std_crate, path_def_id, sym};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_hir::{self as hir, BodyId, Expr, ExprKind, Item, ItemKind};
use rustc_hir::{self as hir, BodyId, Expr, ExprKind, HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -62,10 +63,7 @@ static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[
pub struct NonStdLazyStatic {
msrv: Msrv,
lazy_static_lazy_static: Vec<DefId>,
once_cell_crate: Vec<CrateNum>,
once_cell_sync_lazy: Vec<DefId>,
once_cell_sync_lazy_new: Vec<DefId>,
once_cell_crates: Vec<CrateNum>,
sugg_map: FxIndexMap<DefId, Option<String>>,
lazy_type_defs: FxIndexMap<DefId, LazyInfo>,
uses_other_once_cell_types: bool,
@ -76,10 +74,7 @@ impl NonStdLazyStatic {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv,
lazy_static_lazy_static: Vec::new(),
once_cell_crate: Vec::new(),
once_cell_sync_lazy: Vec::new(),
once_cell_sync_lazy_new: Vec::new(),
once_cell_crates: Vec::new(),
sugg_map: FxIndexMap::default(),
lazy_type_defs: FxIndexMap::default(),
uses_other_once_cell_types: false,
@ -95,17 +90,15 @@ fn can_use_lazy_cell(cx: &LateContext<'_>, msrv: Msrv) -> bool {
impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
fn check_crate(&mut self, cx: &LateContext<'hir>) {
// Fetch def_ids for external paths
self.lazy_static_lazy_static = def_path_def_ids(cx.tcx, &["lazy_static", "lazy_static"]).collect();
self.once_cell_sync_lazy = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy"]).collect();
self.once_cell_sync_lazy_new = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy", "new"]).collect();
// And CrateNums for `once_cell` crate
self.once_cell_crate = self.once_cell_sync_lazy.iter().map(|d| d.krate).collect();
// Add CrateNums for `once_cell` crate
self.once_cell_crates = find_crates(cx.tcx, sym::once_cell)
.iter()
.map(|def_id| def_id.krate)
.collect();
// Convert hardcoded fn replacement list into a map with def_id
for (path, sugg) in FUNCTION_REPLACEMENTS {
let path_vec: Vec<&str> = path.split("::").collect();
for did in def_path_def_ids(cx.tcx, &path_vec) {
for did in lookup_path_str(cx.tcx, PathNS::Value, path) {
self.sugg_map.insert(did, sugg.map(ToOwned::to_owned));
}
}
@ -114,7 +107,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) {
if let ItemKind::Static(..) = item.kind
&& let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span)
&& self.lazy_static_lazy_static.contains(&macro_call.def_id)
&& paths::LAZY_STATIC.matches(cx, macro_call.def_id)
&& can_use_lazy_cell(cx, self.msrv)
{
span_lint(
@ -130,7 +123,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
return;
}
if let Some(lazy_info) = LazyInfo::from_item(self, cx, item)
if let Some(lazy_info) = LazyInfo::from_item(cx, item)
&& can_use_lazy_cell(cx, self.msrv)
{
self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info);
@ -155,9 +148,9 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
if let rustc_hir::TyKind::Path(qpath) = ty.peel_refs().kind
&& let Some(ty_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id()
// Is from `once_cell` crate
&& self.once_cell_crate.contains(&ty_def_id.krate)
&& self.once_cell_crates.contains(&ty_def_id.krate)
// And is NOT `once_cell::sync::Lazy`
&& !self.once_cell_sync_lazy.contains(&ty_def_id)
&& !paths::ONCE_CELL_SYNC_LAZY.matches(cx, ty_def_id)
{
self.uses_other_once_cell_types = true;
}
@ -180,6 +173,8 @@ struct LazyInfo {
/// // ^^^^
/// ```
ty_span_no_args: Span,
/// Item on which the lint must be generated.
item_hir_id: HirId,
/// `Span` and `DefId` of calls on `Lazy` type.
/// i.e.:
/// ```ignore
@ -190,12 +185,12 @@ struct LazyInfo {
}
impl LazyInfo {
fn from_item(state: &NonStdLazyStatic, cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
// Check if item is a `once_cell:sync::Lazy` static.
if let ItemKind::Static(_, ty, _, body_id) = item.kind
&& let Some(path_def_id) = path_def_id(cx, ty)
&& let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
&& state.once_cell_sync_lazy.contains(&path_def_id)
&& paths::ONCE_CELL_SYNC_LAZY.matches(cx, path_def_id)
{
let ty_span_no_args = path_span_without_args(path);
let body = cx.tcx.hir_body(body_id);
@ -204,7 +199,7 @@ impl LazyInfo {
let mut new_fn_calls = FxIndexMap::default();
for_each_expr::<(), ()>(cx, body, |ex| {
if let Some((fn_did, call_span)) = fn_def_id_and_span_from_body(cx, ex, body_id)
&& state.once_cell_sync_lazy_new.contains(&fn_did)
&& paths::ONCE_CELL_SYNC_LAZY_NEW.matches(cx, fn_did)
{
new_fn_calls.insert(call_span, fn_did);
}
@ -213,6 +208,7 @@ impl LazyInfo {
Some(LazyInfo {
ty_span_no_args,
item_hir_id: item.hir_id(),
calls_span_and_id: new_fn_calls,
})
} else {
@ -236,9 +232,10 @@ impl LazyInfo {
}
}
span_lint_and_then(
span_lint_hir_and_then(
cx,
NON_STD_LAZY_STATICS,
self.item_hir_id,
self.ty_span_no_args,
"this type has been superseded by `LazyLock` in the standard library",
|diag| {

View file

@ -1,20 +1,18 @@
use clippy_utils::ast_utils::is_useless_with_eq_exprs;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
use clippy_utils::{eq_expr_value, is_in_test_function};
use clippy_utils::{eq_expr_value, is_in_test_function, sym};
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use super::EQ_OP;
pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
let name = cx.tcx.item_name(macro_call.def_id);
if let Some(macro_call) = first_node_macro_backtrace(cx, e).find(|macro_call| {
matches!(
name.as_str(),
"assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne"
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
)
.then(|| (macro_call, name))
}) && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
&& eq_expr_value(cx, lhs, rhs)
&& macro_call.is_local()
@ -24,7 +22,10 @@ pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
cx,
EQ_OP,
lhs.span.to(rhs.span),
format!("identical args used in this `{macro_name}!` macro call"),
format!(
"identical args used in this `{}!` macro call",
cx.tcx.item_name(macro_call.def_id)
),
);
}
}

View file

@ -1,6 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use super::INTEGER_DIVISION;
@ -13,7 +15,8 @@ pub(crate) fn check<'tcx>(
) {
if op == hir::BinOpKind::Div
&& cx.typeck_results().expr_ty(left).is_integral()
&& cx.typeck_results().expr_ty(right).is_integral()
&& let right_ty = cx.typeck_results().expr_ty(right)
&& (right_ty.is_integral() || is_type_diagnostic_item(cx, right_ty, sym::NonZero))
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, INTEGER_DIVISION, expr.span, "integer division", |diag| {

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, for_each_expr};
use clippy_utils::{is_inside_always_const_context, return_ty};
@ -69,10 +69,11 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir
return ControlFlow::Continue(Descend::Yes);
};
if !is_inside_always_const_context(cx.tcx, e.hir_id)
&& matches!(
cx.tcx.item_name(macro_call.def_id).as_str(),
"panic" | "assert" | "assert_eq" | "assert_ne"
)
&& (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)
))
{
panics.push(macro_call.span);
ControlFlow::Continue(Descend::No)

View file

@ -113,8 +113,8 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
);
return;
}
match cx.tcx.item_name(macro_call.def_id).as_str() {
"todo" => {
match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::todo_macro) => {
span_lint(
cx,
TODO,
@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
"`todo` should not be present in production code",
);
},
"unimplemented" => {
Some(sym::unimplemented_macro) => {
span_lint(
cx,
UNIMPLEMENTED,
@ -130,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
"`unimplemented` should not be present in production code",
);
},
"unreachable" => {
Some(sym::unreachable_macro) => {
span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
},
_ => {},

View file

@ -2,8 +2,9 @@ use std::fmt::Display;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::paths::PathLookup;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths, sym};
use clippy_utils::{path_def_id, paths};
use rustc_ast::ast::{LitKind, StrStyle};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId};
@ -121,17 +122,9 @@ impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX, REGEX_CREATION_IN_LOOPS]
impl<'tcx> LateLintPass<'tcx> for Regex {
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
// We don't use `match_def_path` here because that relies on matching the exact path, which changed
// between regex 1.8 and 1.9
//
// `def_path_res_with_base` will resolve through re-exports but is relatively heavy, so we only
// perform the operation once and store the results
let regex_crates = find_crates(cx.tcx, sym::regex);
let mut resolve = |path: &[&str], kind: RegexKind| {
for res in def_path_res_with_base(cx.tcx, regex_crates.clone(), &path[1..]) {
if let Some(id) = res.opt_def_id() {
self.definitions.insert(id, kind);
}
let mut resolve = |path: &PathLookup, kind: RegexKind| {
for &id in path.get(cx) {
self.definitions.insert(id, kind);
}
};

View file

@ -5,7 +5,7 @@ use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
span_find_starting_semi,
span_find_starting_semi, sym,
};
use core::ops::ControlFlow;
use rustc_ast::MetaItemInner;
@ -22,7 +22,7 @@ use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::edition::Edition;
use rustc_span::{BytePos, Pos, Span, sym};
use rustc_span::{BytePos, Pos, Span};
use std::borrow::Cow;
use std::fmt::Display;
@ -411,8 +411,8 @@ fn check_final_expr<'tcx>(
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
&& tool.ident.name == sym::clippy
&& matches!(
lint_name.ident.name.as_str(),
"needless_return" | "style" | "all" | "warnings"
lint_name.ident.name,
sym::needless_return | sym::style | sym::all | sym::warnings
)
{
// This is an expectation of the `needless_return` lint

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{get_trait_def_id, paths};
use clippy_utils::paths;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -32,9 +32,7 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi {
}) = item.kind
{
let did = trait_ref.path.res.def_id();
if let Some(visit_did) = get_trait_def_id(cx.tcx, &paths::SERDE_DE_VISITOR)
&& did == visit_did
{
if paths::SERDE_DE_VISITOR.matches(cx, did) {
let mut seen_str = None;
let mut seen_string = None;
for item in *items {

View file

@ -30,7 +30,7 @@ declare_clippy_lint! {
/// param * 2
/// }
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub SINGLE_OPTION_MAP,
nursery,
"Checks for functions with method calls to `.map(_)` on an arg of type `Option` as the outermost expression."

View file

@ -3,7 +3,7 @@ use clippy_utils::higher::VecArgs;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, is_no_std_crate};
use clippy_utils::{is_no_std_crate, paths};
use rustc_ast::{LitIntType, LitKind, UintTy};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr};
@ -100,7 +100,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
&& let Some(start_snippet) = start.span.get_source_text(cx)
&& let Some(end_snippet) = end.span.get_source_text(cx)
{
let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx.tcx, &["core", "iter", "Step"])
let should_emit_every_value = if let Some(step_def_id) = paths::ITER_STEP.only(cx)
&& implements_trait(cx, ty, step_def_id, &[])
{
true

View file

@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
if let ExprKind::MethodCall(path, recv, [], _) = &e.kind
&& path.ident.name == sym::into_bytes
&& let ExprKind::MethodCall(path, recv, [], _) = &recv.kind
&& matches!(path.ident.name.as_str(), "to_owned" | "to_string")
&& matches!(path.ident.name, sym::to_owned | sym::to_string)
&& let ExprKind::Lit(lit) = &recv.kind
&& let LitKind::Str(lit_content, _) = &lit.node
&& lit_content.as_str().is_ascii()

View file

@ -1,11 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, sym};
use clippy_utils::{is_in_const_context, paths, sym};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -33,34 +34,35 @@ declare_clippy_lint! {
"`char.is_digit()` is clearer"
}
declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
impl_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
pub(crate) struct ToDigitIsSome {
msrv: Msrv,
}
impl ToDigitIsSome {
pub(crate) fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let hir::ExprKind::MethodCall(is_some_path, to_digit_expr, [], _) = &expr.kind
&& is_some_path.ident.name == sym::is_some
{
let match_result = match &to_digit_expr.kind {
let match_result = match to_digit_expr.kind {
hir::ExprKind::MethodCall(to_digits_path, char_arg, [radix_arg], _) => {
if to_digits_path.ident.name == sym::to_digit
&& let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg)
&& *char_arg_ty.kind() == ty::Char
&& cx.typeck_results().expr_ty_adjusted(char_arg).is_char()
{
Some((true, *char_arg, radix_arg))
Some((true, char_arg, radix_arg))
} else {
None
}
},
hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => {
if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind
&& let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id)
&& let Some(to_digits_def_id) = to_digits_call_res.opt_def_id()
&& match_def_path(
cx,
to_digits_def_id,
&["core", "char", "methods", "<impl char>", "to_digit"],
)
{
if paths::CHAR_TO_DIGIT.matches_path(cx, to_digits_call) {
Some((false, char_arg, radix_arg))
} else {
None
@ -69,7 +71,9 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
_ => None,
};
if let Some((is_method_call, char_arg, radix_arg)) = match_result {
if let Some((is_method_call, char_arg, radix_arg)) = match_result
&& (!is_in_const_context(cx) || self.msrv.meets(cx, msrvs::CONST_CHAR_IS_DIGIT))
{
let mut applicability = Applicability::MachineApplicable;
let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability);
let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability);

View file

@ -15,7 +15,7 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{BytePos, Span};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -282,18 +282,18 @@ impl TraitBounds {
.iter()
.copied()
.chain(p.bounds.iter())
.filter_map(get_trait_info_from_bound)
.map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability))
.map(|bound| snippet_with_applicability(cx, bound.span(), "_", &mut applicability))
.join(" + ");
let hint_string = format!(
"consider combining the bounds: `{}: {trait_bounds}`",
snippet(cx, p.bounded_ty.span, "_"),
);
let ty_name = snippet(cx, p.bounded_ty.span, "_");
span_lint_and_help(
cx,
TYPE_REPETITION_IN_BOUNDS,
bound.span,
"this type has already been used as a bound predicate",
format!("type `{ty_name}` has already been used as a bound predicate"),
None,
hint_string,
);
@ -395,15 +395,7 @@ impl Hash for ComparableTraitRef<'_, '_> {
fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
if let GenericBound::Trait(t) = bound {
let trait_path = t.trait_ref.path;
let trait_span = {
let path_span = trait_path.span;
if let BoundPolarity::Maybe(_) = t.modifiers.polarity {
path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?`
} else {
path_span
}
};
Some((trait_path.res, trait_path.segments, trait_span))
Some((trait_path.res, trait_path.segments, t.span))
} else {
None
}

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_normalizable;
use clippy_utils::{eq_expr_value, path_to_local, sym};
use rustc_abi::WrappingRange;
use rustc_errors::Applicability;
@ -84,8 +83,6 @@ pub(super) fn check<'tcx>(
&& path.ident.name == sym::then_some
&& is_local_with_projections(transmutable)
&& binops_with_local(cx, transmutable, receiver)
&& is_normalizable(cx, cx.param_env, from_ty)
&& is_normalizable(cx, cx.param_env, to_ty)
// we only want to lint if the target type has a niche that is larger than the one of the source type
// e.g. `u8` to `NonZero<u8>` should lint, but `NonZero<u8>` to `u8` should not
&& let Ok(from_layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(from_ty))

View file

@ -1,13 +1,9 @@
mod crosspointer_transmute;
mod eager_transmute;
mod missing_transmute_annotations;
mod transmute_float_to_int;
mod transmute_int_to_bool;
mod transmute_int_to_char;
mod transmute_int_to_float;
mod transmute_int_to_non_zero;
mod transmute_null_to_fn;
mod transmute_num_to_bytes;
mod transmute_ptr_to_ptr;
mod transmute_ptr_to_ref;
mod transmute_ref_to_ref;
@ -141,40 +137,6 @@ declare_clippy_lint! {
"transmutes from a pointer to a reference type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from an integer to a `char`.
///
/// ### Why is this bad?
/// Not every integer is a Unicode scalar value.
///
/// ### Known problems
/// - [`from_u32`] which this lint suggests using is slower than `transmute`
/// as it needs to validate the input.
/// If you are certain that the input is always a valid Unicode scalar value,
/// use [`from_u32_unchecked`] which is as fast as `transmute`
/// but has a semantically meaningful name.
/// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`.
///
/// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html
/// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html
///
/// ### Example
/// ```no_run
/// let x = 1_u32;
/// unsafe {
/// let _: char = std::mem::transmute(x); // where x: u32
/// }
///
/// // should be:
/// let _ = std::char::from_u32(x).unwrap();
/// ```
#[clippy::version = "pre 1.29.0"]
pub TRANSMUTE_INT_TO_CHAR,
complexity,
"transmutes from an integer to a `char`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a `&[u8]` to a `&str`.
@ -232,29 +194,6 @@ declare_clippy_lint! {
"transmutes from an integer to a `bool`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from an integer to a float.
///
/// ### Why is this bad?
/// Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive
/// and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let _: f32 = std::mem::transmute(1_u32); // where x: u32
/// }
///
/// // should be:
/// let _: f32 = f32::from_bits(1_u32);
/// ```
#[clippy::version = "pre 1.29.0"]
pub TRANSMUTE_INT_TO_FLOAT,
complexity,
"transmutes from an integer to a float"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from `T` to `NonZero<T>`, and suggests the `new_unchecked`
@ -280,52 +219,6 @@ declare_clippy_lint! {
"transmutes from an integer to a non-zero wrapper"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a float to an integer.
///
/// ### Why is this bad?
/// Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive
/// and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let _: u32 = std::mem::transmute(1f32);
/// }
///
/// // should be:
/// let _: u32 = 1f32.to_bits();
/// ```
#[clippy::version = "1.41.0"]
pub TRANSMUTE_FLOAT_TO_INT,
complexity,
"transmutes from a float to an integer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a number to an array of `u8`
///
/// ### Why this is bad?
/// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
/// is intuitive and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let x: [u8; 8] = std::mem::transmute(1i64);
/// }
///
/// // should be
/// let x: [u8; 8] = 0i64.to_ne_bytes();
/// ```
#[clippy::version = "1.58.0"]
pub TRANSMUTE_NUM_TO_BYTES,
complexity,
"transmutes from a number to an array of `u8`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a pointer to a pointer, or
@ -581,13 +474,9 @@ impl_lint_pass!(Transmute => [
TRANSMUTE_PTR_TO_PTR,
USELESS_TRANSMUTE,
WRONG_TRANSMUTE,
TRANSMUTE_INT_TO_CHAR,
TRANSMUTE_BYTES_TO_STR,
TRANSMUTE_INT_TO_BOOL,
TRANSMUTE_INT_TO_FLOAT,
TRANSMUTE_INT_TO_NON_ZERO,
TRANSMUTE_FLOAT_TO_INT,
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
@ -632,14 +521,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg)
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| (unsound_collection_transmute::check(cx, e, from_ty, to_ty)
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty))
| (eager_transmute::check(cx, e, arg, from_ty, to_ty));

View file

@ -1,66 +0,0 @@
use super::TRANSMUTE_FLOAT_TO_INT;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_ast as ast;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_float_to_int` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
mut arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Float(float_ty), ty::Int(_) | ty::Uint(_))
if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) =>
{
span_lint_and_then(
cx,
TRANSMUTE_FLOAT_TO_INT,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let mut sugg = sugg::Sugg::hir(cx, arg, "..");
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
arg = inner_expr;
}
if let ExprKind::Lit(lit) = &arg.kind
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
&& let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!("{sugg}{}", float_ty.name_str()).into();
match sugg {
sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
_ => sugg = sugg::Sugg::NonParen(op),
}
}
sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_paren()).into());
// cast the result of `to_bits` if `to_ty` is signed
sugg = if let ty::Int(int_ty) = to_ty.kind() {
sugg.as_ty(int_ty.name_str().to_string())
} else {
sugg
};
diag.span_suggestion(e.span, "consider using", sugg, Applicability::Unspecified);
},
);
true
},
_ => false,
}
}

View file

@ -1,47 +0,0 @@
use super::TRANSMUTE_INT_TO_CHAR;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{std_or_core, sugg};
use rustc_ast as ast;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_int_to_char` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) if !const_context => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_CHAR,
e.span,
format!("transmute from a `{from_ty}` to a `char`"),
|diag| {
let Some(top_crate) = std_or_core(cx) else { return };
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(_) = from_ty.kind() {
arg.as_ty(ast::UintTy::U32.name_str())
} else {
arg
};
diag.span_suggestion(
e.span,
"consider using",
format!("{top_crate}::char::from_u32({arg}).unwrap()"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -1,50 +0,0 @@
use super::TRANSMUTE_INT_TO_FLOAT;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_int_to_float` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_FLOAT,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(int_ty) = from_ty.kind() {
arg.as_ty(format!(
"u{}",
int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string())
))
} else {
arg
};
diag.span_suggestion(
e.span,
"consider using",
format!("{to_ty}::from_bits({arg})"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -1,50 +0,0 @@
use super::TRANSMUTE_NUM_TO_BYTES;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, UintTy};
/// Checks for `transmute_int_to_float` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
return false;
}
if matches!(from_ty.kind(), ty::Float(_)) && const_context && !msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV)
{
return false;
}
span_lint_and_then(
cx,
TRANSMUTE_NUM_TO_BYTES,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
diag.span_suggestion(
e.span,
"consider using `to_ne_bytes()`",
format!("{arg}.to_ne_bytes()"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -385,7 +385,7 @@ declare_clippy_lint! {
/// ```no_run
/// let right: std::borrow::Cow<'_, [u8]>;
/// ```
#[clippy::version = "1.85.0"]
#[clippy::version = "1.87.0"]
pub OWNED_COW,
style,
"needlessly owned Cow type"

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sym;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@ -7,11 +8,12 @@ use super::UNIT_CMP;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
let macro_name = cx.tcx.item_name(macro_call.def_id);
let result = match macro_name.as_str() {
"assert_eq" | "debug_assert_eq" => "succeed",
"assert_ne" | "debug_assert_ne" => "fail",
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
{
let result = match diag_name {
sym::assert_eq_macro | sym::debug_assert_eq_macro => "succeed",
sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail",
_ => return,
};
let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@ -24,7 +26,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
cx,
UNIT_CMP,
macro_call.span,
format!("`{macro_name}` of unit values detected. This will always {result}"),
format!(
"`{}` of unit values detected. This will always {result}",
cx.tcx.item_name(macro_call.def_id)
),
);
}
return;

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::is_def_id_trait_method;
use rustc_hir::def::DefKind;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Node, YieldSource};
use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_session::impl_lint_pass;
@ -116,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
span: Span,
def_id: LocalDefId,
) {
if !span.from_expansion() && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) {
if !span.from_expansion()
&& fn_kind.asyncness().is_async()
&& !is_def_id_trait_method(cx, def_id)
&& !is_default_trait_impl(cx, def_id)
{
let mut visitor = AsyncFnVisitor {
cx,
found_await: false,
@ -189,3 +193,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
}
}
}
fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
matches!(
cx.tcx.hir_node_by_def_id(def_id),
Node::TraitItem(TraitItem {
defaultness: Defaultness::Default { .. },
..
})
)
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks};
use clippy_utils::{is_res_lang_ctor, paths, peel_blocks};
use hir::{ExprKind, HirId, PatKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -93,14 +93,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
return;
}
let async_paths: [&[&str]; 4] = [
let async_paths = [
&paths::TOKIO_IO_ASYNCREADEXT,
&paths::TOKIO_IO_ASYNCWRITEEXT,
&paths::FUTURES_IO_ASYNCREADEXT,
&paths::FUTURES_IO_ASYNCWRITEEXT,
];
if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) {
if async_paths.into_iter().any(|path| path.matches(cx, trait_id)) {
return;
}
}
@ -226,7 +226,7 @@ fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if is_panic(cx, macro_call.def_id) {
return !cx.tcx.hir_is_inside_const_context(expr.hir_id);
}
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
cx.tcx.is_diagnostic_item(sym::unreachable_macro, macro_call.def_id)
}
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
@ -291,19 +291,28 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
},
};
match (
is_trait_method(cx, call, sym::IoRead),
is_trait_method(cx, call, sym::IoWrite),
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
) {
(true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
(_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
(_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
(_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
_ => None,
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(call.hir_id)
&& let Some(trait_def_id) = cx.tcx.trait_of_item(method_def_id)
{
if let Some(diag_name) = cx.tcx.get_diagnostic_name(trait_def_id) {
match diag_name {
sym::IoRead => Some(IoOp::SyncRead(vectorized)),
sym::IoWrite => Some(IoOp::SyncWrite(vectorized)),
_ => None,
}
} else if paths::FUTURES_IO_ASYNCREADEXT.matches(cx, trait_def_id)
|| paths::TOKIO_IO_ASYNCREADEXT.matches(cx, trait_def_id)
{
Some(IoOp::AsyncRead(vectorized))
} else if paths::TOKIO_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
|| paths::FUTURES_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
{
Some(IoOp::AsyncWrite(vectorized))
} else {
None
}
} else {
None
}
}

View file

@ -4,15 +4,15 @@ use clippy_utils::usage::is_potentially_local_place;
use clippy_utils::{higher, path_to_local, sym};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, PathSegment, UnOp};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceWithHirId};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -111,7 +111,7 @@ struct UnwrapInfo<'tcx> {
/// The check, like `x.is_ok()`
check: &'tcx Expr<'tcx>,
/// The check's name, like `is_ok`
check_name: &'tcx PathSegment<'tcx>,
check_name: Symbol,
/// The branch where the check takes place, like `if x.is_ok() { .. }`
branch: &'tcx Expr<'tcx>,
/// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
@ -133,12 +133,12 @@ fn collect_unwrap_info<'tcx>(
invert: bool,
is_entire_condition: bool,
) -> Vec<UnwrapInfo<'tcx>> {
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::Option) && ["is_some", "is_none"].contains(&method_name)
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some)
}
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::Result) && ["is_ok", "is_err"].contains(&method_name)
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
}
if let ExprKind::Binary(op, left, right) = &expr.kind {
@ -155,14 +155,10 @@ fn collect_unwrap_info<'tcx>(
} else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind
&& let Some(local_id) = path_to_local(receiver)
&& let ty = cx.typeck_results().expr_ty(receiver)
&& let name = method_name.ident.as_str()
&& let name = method_name.ident.name
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name))
{
let unwrappable = match name {
"is_some" | "is_ok" => true,
"is_err" | "is_none" => false,
_ => unreachable!(),
};
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
let safe_to_unwrap = unwrappable != invert;
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
UnwrappableKind::Option
@ -174,7 +170,7 @@ fn collect_unwrap_info<'tcx>(
local_id,
if_expr,
check: expr,
check_name: method_name,
check_name: name,
branch,
safe_to_unwrap,
kind,
@ -184,12 +180,12 @@ fn collect_unwrap_info<'tcx>(
Vec::new()
}
/// A HIR visitor delegate that checks if a local variable of type `Option<_>` is mutated,
/// *except* for if `Option::as_mut` is called.
/// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated,
/// *except* for if `.as_mut()` is called.
/// The reason for why we allow that one specifically is that `.as_mut()` cannot change
/// the option to `None`, and that is important because this lint relies on the fact that
/// the variant, and that is important because this lint relies on the fact that
/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
/// the option is changed to None between `is_some` and `unwrap`.
/// the option is changed to None between `is_some` and `unwrap`, ditto for `Result`.
/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
struct MutationVisitor<'tcx> {
is_mutated: bool,
@ -198,13 +194,13 @@ struct MutationVisitor<'tcx> {
}
/// Checks if the parent of the expression pointed at by the given `HirId` is a call to
/// `Option::as_mut`.
/// `.as_mut()`.
///
/// Used by the mutation visitor to specifically allow `.as_mut()` calls.
/// In particular, the `HirId` that the visitor receives is the id of the local expression
/// (i.e. the `x` in `x.as_mut()`), and that is the reason for why we care about its parent
/// expression: that will be where the actual method call is.
fn is_option_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
if let Node::Expr(mutating_expr) = tcx.parent_hir_node(expr_id)
&& let ExprKind::MethodCall(path, _, [], _) = mutating_expr.kind
{
@ -218,14 +214,16 @@ impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> {
fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::Mutable = bk
&& is_potentially_local_place(self.local_id, &cat.place)
&& !is_option_as_mut_use(self.tcx, diag_expr_id)
&& !is_as_mut_use(self.tcx, diag_expr_id)
{
self.is_mutated = true;
}
}
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {
self.is_mutated = true;
fn mutate(&mut self, cat: &PlaceWithHirId<'tcx>, _: HirId) {
if is_potentially_local_place(self.local_id, &cat.place) {
self.is_mutated = true;
}
}
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
@ -276,12 +274,10 @@ enum AsRefKind {
/// If it isn't, the expression itself is returned.
fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Option<AsRefKind>) {
if let ExprKind::MethodCall(path, recv, [], _) = expr.kind {
if path.ident.name == sym::as_ref {
(recv, Some(AsRefKind::AsRef))
} else if path.ident.name == sym::as_mut {
(recv, Some(AsRefKind::AsMut))
} else {
(expr, None)
match path.ident.name {
sym::as_ref => (recv, Some(AsRefKind::AsRef)),
sym::as_mut => (recv, Some(AsRefKind::AsMut)),
_ => (expr, None),
}
} else {
(expr, None)
@ -296,6 +292,10 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
if expr.span.in_external_macro(self.cx.tcx.sess.source_map()) {
return;
}
// Skip checking inside closures since they are visited through `Unwrap::check_fn()` already.
if matches!(expr.kind, ExprKind::Closure(_)) {
return;
}
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
walk_expr(self, cond);
self.visit_branch(expr, cond, then, false);
@ -332,8 +332,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
expr.span,
format!(
"called `{}` on `{unwrappable_variable_name}` after checking its variant with `{}`",
method_name.ident.name,
unwrappable.check_name.ident.as_str(),
method_name.ident.name, unwrappable.check_name,
),
|diag| {
if is_entire_condition {

View file

@ -1,16 +1,18 @@
use clippy_utils::{get_attr, higher};
use clippy_utils::{MaybePath, get_attr, higher, path_def_id};
use itertools::Itertools;
use rustc_ast::LitIntType;
use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::{
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind, ExprKind,
FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr, TyKind,
FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::{Ident, Symbol};
use std::cell::Cell;
use std::fmt::{Display, Formatter, Write as _};
use std::fmt::{Display, Formatter};
declare_lint_pass!(
/// ### What it does
@ -148,6 +150,15 @@ fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_,
}
}
fn paths_static_name(cx: &LateContext<'_>, id: DefId) -> String {
cx.get_def_path(id)
.iter()
.map(Symbol::as_str)
.filter(|s| !s.starts_with('<'))
.join("_")
.to_uppercase()
}
struct Binding<T> {
name: String,
value: T,
@ -257,11 +268,44 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
}
fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
fn qpath<'p>(&self, qpath: &Binding<&QPath<'_>>, has_hir_id: &Binding<&impl MaybePath<'p>>) {
if let QPath::LangItem(lang_item, ..) = *qpath.value {
chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
} else if let Ok(path) = path_to_string(qpath.value) {
chain!(self, "match_qpath({qpath}, &[{}])", path);
} else if let Some(def_id) = self.cx.qpath_res(qpath.value, has_hir_id.value.hir_id()).opt_def_id()
&& !def_id.is_local()
{
bind!(self, def_id);
chain!(
self,
"let Some({def_id}) = cx.qpath_res({qpath}, {has_hir_id}.hir_id).opt_def_id()"
);
if let Some(name) = self.cx.tcx.get_diagnostic_name(def_id.value) {
chain!(self, "cx.tcx.is_diagnostic_item(sym::{name}, {def_id})");
} else {
chain!(
self,
"paths::{}.matches(cx, {def_id}) // Add the path to `clippy_utils::paths` if needed",
paths_static_name(self.cx, def_id.value)
);
}
}
}
fn maybe_path<'p>(&self, path: &Binding<&impl MaybePath<'p>>) {
if let Some(id) = path_def_id(self.cx, path.value)
&& !id.is_local()
{
if let Some(lang) = self.cx.tcx.lang_items().from_def_id(id) {
chain!(self, "is_path_lang_item(cx, {path}, LangItem::{}", lang.name());
} else if let Some(name) = self.cx.tcx.get_diagnostic_name(id) {
chain!(self, "is_path_diagnostic_item(cx, {path}, sym::{name})");
} else {
chain!(
self,
"paths::{}.matches_path(cx, {path}) // Add the path to `clippy_utils::paths` if needed",
paths_static_name(self.cx, id)
);
}
}
}
@ -270,7 +314,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ConstArgKind::Path(ref qpath) => {
bind!(self, qpath);
chain!(self, "let ConstArgKind::Path(ref {qpath}) = {const_arg}.kind");
self.qpath(qpath);
},
ConstArgKind::Anon(anon_const) => {
bind!(self, anon_const);
@ -394,12 +437,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
bind!(self, let_expr);
kind!("Let({let_expr})");
self.pat(field!(let_expr.pat));
// Does what ExprKind::Cast does, only adds a clause for the type
// if it's a path
if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
bind!(self, qpath);
chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
self.qpath(qpath);
if let Some(ty) = let_expr.value.ty {
bind!(self, ty);
chain!(self, "let Some({ty}) = {let_expr}.ty");
self.maybe_path(ty);
}
self.expr(field!(let_expr.init));
},
@ -451,11 +492,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ExprKind::Cast(expr, cast_ty) => {
bind!(self, expr, cast_ty);
kind!("Cast({expr}, {cast_ty})");
if let TyKind::Path(ref qpath) = cast_ty.value.kind {
bind!(self, qpath);
chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
self.qpath(qpath);
}
self.maybe_path(cast_ty);
self.expr(expr);
},
ExprKind::Type(expr, _ty) => {
@ -561,10 +598,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
self.expr(object);
self.expr(index);
},
ExprKind::Path(ref qpath) => {
bind!(self, qpath);
kind!("Path(ref {qpath})");
self.qpath(qpath);
ExprKind::Path(_) => {
self.maybe_path(expr);
},
ExprKind::AddrOf(kind, mutability, inner) => {
bind!(self, inner);
@ -608,7 +643,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
});
kind!("Struct({qpath}, {fields}, {base})");
self.qpath(qpath);
self.qpath(qpath, expr);
self.slice(fields, |field| {
self.ident(field!(field.ident));
self.expr(field!(field.expr));
@ -648,7 +683,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
self.expr(expr);
}
fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>) {
fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>, pat: &Binding<&hir::Pat<'_>>) {
let kind = |kind| chain!(self, "let PatExprKind::{kind} = {lit}.kind");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
@ -657,15 +692,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatExprKind::Lit { lit, negated } => {
bind!(self, lit);
bind!(self, negated);
kind!("Lit{{ref {lit}, {negated} }}");
kind!("Lit {{ ref {lit}, {negated} }}");
self.lit(lit);
},
PatExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
PatExprKind::Path(ref qpath) => {
bind!(self, qpath);
kind!("Path(ref {qpath})");
self.qpath(qpath);
},
PatExprKind::Path(_) => self.maybe_path(pat),
}
}
@ -697,7 +728,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::Struct(ref qpath, fields, ignore) => {
bind!(self, qpath, fields);
kind!("Struct(ref {qpath}, {fields}, {ignore})");
self.qpath(qpath);
self.qpath(qpath, pat);
self.slice(fields, |field| {
self.ident(field!(field.ident));
self.pat(field!(field.pat));
@ -711,7 +742,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
bind!(self, qpath, fields);
kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
self.qpath(qpath);
self.qpath(qpath, pat);
self.slice(fields, |pat| self.pat(pat));
},
PatKind::Tuple(fields, skip_pos) => {
@ -743,13 +774,13 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::Expr(lit_expr) => {
bind!(self, lit_expr);
kind!("Expr({lit_expr})");
self.pat_expr(lit_expr);
self.pat_expr(lit_expr, pat);
},
PatKind::Range(start, end, end_kind) => {
opt_bind!(self, start, end);
kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
start.if_some(|e| self.pat_expr(e));
end.if_some(|e| self.pat_expr(e));
start.if_some(|e| self.pat_expr(e, pat));
end.if_some(|e| self.pat_expr(e, pat));
},
PatKind::Slice(start, middle, end) => {
bind!(self, start, end);
@ -797,32 +828,3 @@ fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
let attrs = cx.tcx.hir_attrs(hir_id);
get_attr(cx.sess(), attrs, "author").count() > 0
}
fn path_to_string(path: &QPath<'_>) -> Result<String, ()> {
fn inner(s: &mut String, path: &QPath<'_>) -> Result<(), ()> {
match *path {
QPath::Resolved(_, path) => {
for (i, segment) in path.segments.iter().enumerate() {
if i > 0 {
*s += ", ";
}
write!(s, "{:?}", segment.ident.as_str()).unwrap();
}
},
QPath::TypeRelative(ty, segment) => match &ty.kind {
TyKind::Path(inner_path) => {
inner(s, inner_path)?;
*s += ", ";
write!(s, "{:?}", segment.ident.as_str()).unwrap();
},
other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
},
QPath::LangItem(..) => return Err(()),
}
Ok(())
}
let mut s = String::new();
inner(&mut s, path)?;
Ok(s)
}

View file

@ -498,9 +498,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
None => return,
},
LitKind::Char => (
match lit.symbol.as_str() {
"\"" => "\\\"",
"\\'" => "'",
match lit.symbol {
sym::DOUBLE_QUOTE => "\\\"",
sym::BACKSLASH_SINGLE_QUOTE => "'",
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
Some(stripped) => stripped,
None => return,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::{is_normalizable, is_type_diagnostic_item, ty_from_hir_ty};
use clippy_utils::ty::{is_type_diagnostic_item, ty_from_hir_ty};
use rustc_hir::{self as hir, AmbigArg, HirId, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf as _;
@ -54,8 +54,6 @@ impl LateLintPass<'_> for ZeroSizedMapValues {
// Fixes https://github.com/rust-lang/rust-clippy/issues/7447 because of
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_middle/src/ty/sty.rs#L968
&& !ty.has_escaping_bound_vars()
// Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`.
&& is_normalizable(cx, cx.param_env, ty)
&& let Ok(layout) = cx.layout_of(ty)
&& layout.is_zst()
{

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