Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
0bb1b5bd3b
263 changed files with 5325 additions and 4470 deletions
17
.github/workflows/clippy_changelog.yml
vendored
17
.github/workflows/clippy_changelog.yml
vendored
|
|
@ -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.
|
||||
|
|
|
|||
104
CHANGELOG.md
104
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
Cargo.toml
10
Cargo.toml
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
174
clippy_dev/src/deprecate_lint.rs
Normal file
174
clippy_dev/src/deprecate_lint.rs
Normal 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)
|
||||
}
|
||||
|
|
@ -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"]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
194
clippy_dev/src/rename_lint.rs
Normal file
194
clippy_dev/src/rename_lint.rs
Normal 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");
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = ¯o_name[..macro_name.len() - 3];
|
||||
span_lint_and_then(
|
||||
|
|
|
|||
82
clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
Normal file
82
clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
Normal 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
100
clippy_lints/src/cloned_ref_to_slice_refs.rs
Normal file
100
clippy_lints/src/cloned_ref_to_slice_refs.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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`"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()`"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(¯o_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| {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 { .. },
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue