Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
f074034590
170 changed files with 3143 additions and 980 deletions
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -3,11 +3,15 @@ Thank you for making Clippy better!
|
|||
We're collecting our changelog from pull request descriptions.
|
||||
If your PR only includes internal changes, you can just write
|
||||
`changelog: none`. Otherwise, please write a short comment
|
||||
explaining your change. Also, it's helpful for us that
|
||||
the lint name is put into brackets `[]` and backticks `` ` ` ``,
|
||||
e.g. ``[`lint_name`]``.
|
||||
explaining your change.
|
||||
|
||||
If your PR fixes an issue, you can add "fixes #issue_number" into this
|
||||
It's also helpful for us that the lint name is put within backticks (`` ` ` ``),
|
||||
and then encapsulated by square brackets (`[]`), for example:
|
||||
```
|
||||
changelog: [`lint_name`]: your change
|
||||
```
|
||||
|
||||
If your PR fixes an issue, you can add `fixes #issue_number` into this
|
||||
PR description. This way the issue will be automatically closed when
|
||||
your PR is merged.
|
||||
|
||||
|
|
6
.github/workflows/clippy_bors.yml
vendored
6
.github/workflows/clippy_bors.yml
vendored
|
@ -144,7 +144,7 @@ jobs:
|
|||
OS: ${{ runner.os }}
|
||||
|
||||
metadata_collection:
|
||||
needs: base
|
||||
needs: changelog
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -264,7 +264,7 @@ jobs:
|
|||
name: bors test finished
|
||||
if: github.event.pusher.name == 'bors' && success()
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changelog, base, integration_build, integration]
|
||||
needs: [changelog, base, metadata_collection, integration_build, integration]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as successful
|
||||
|
@ -274,7 +274,7 @@ jobs:
|
|||
name: bors test finished
|
||||
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changelog, base, integration_build, integration]
|
||||
needs: [changelog, base, metadata_collection, integration_build, integration]
|
||||
|
||||
steps:
|
||||
- name: Mark the job as a failure
|
||||
|
|
172
CHANGELOG.md
172
CHANGELOG.md
|
@ -1,16 +1,173 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Changelog Update](doc/changelog_update.md) if you want to update this
|
||||
See [Changelog Update](book/src/development/infrastructure/changelog_update.md) if you want to update this
|
||||
document.
|
||||
|
||||
## Unreleased / In Rust Nightly
|
||||
|
||||
[d0cf3481...master](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...master)
|
||||
[7c21f91b...master](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...master)
|
||||
|
||||
## Rust 1.62
|
||||
|
||||
Current stable, released 2022-06-30
|
||||
|
||||
[d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`large_include_file`]
|
||||
[#8727](https://github.com/rust-lang/rust-clippy/pull/8727)
|
||||
* [`cast_abs_to_unsigned`]
|
||||
[#8635](https://github.com/rust-lang/rust-clippy/pull/8635)
|
||||
* [`err_expect`]
|
||||
[#8606](https://github.com/rust-lang/rust-clippy/pull/8606)
|
||||
* [`unnecessary_owned_empty_strings`]
|
||||
[#8660](https://github.com/rust-lang/rust-clippy/pull/8660)
|
||||
* [`empty_structs_with_brackets`]
|
||||
[#8594](https://github.com/rust-lang/rust-clippy/pull/8594)
|
||||
* [`crate_in_macro_def`]
|
||||
[#8576](https://github.com/rust-lang/rust-clippy/pull/8576)
|
||||
* [`needless_option_take`]
|
||||
[#8665](https://github.com/rust-lang/rust-clippy/pull/8665)
|
||||
* [`bytes_count_to_len`]
|
||||
[#8711](https://github.com/rust-lang/rust-clippy/pull/8711)
|
||||
* [`is_digit_ascii_radix`]
|
||||
[#8624](https://github.com/rust-lang/rust-clippy/pull/8624)
|
||||
* [`await_holding_invalid_type`]
|
||||
[#8707](https://github.com/rust-lang/rust-clippy/pull/8707)
|
||||
* [`trim_split_whitespace`]
|
||||
[#8575](https://github.com/rust-lang/rust-clippy/pull/8575)
|
||||
* [`pub_use`]
|
||||
[#8670](https://github.com/rust-lang/rust-clippy/pull/8670)
|
||||
* [`format_push_string`]
|
||||
[#8626](https://github.com/rust-lang/rust-clippy/pull/8626)
|
||||
* [`empty_drop`]
|
||||
[#8571](https://github.com/rust-lang/rust-clippy/pull/8571)
|
||||
* [`drop_non_drop`]
|
||||
[#8630](https://github.com/rust-lang/rust-clippy/pull/8630)
|
||||
* [`forget_non_drop`]
|
||||
[#8630](https://github.com/rust-lang/rust-clippy/pull/8630)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Move [`only_used_in_recursion`] to `nursery` (now allow-by-default)
|
||||
[#8783](https://github.com/rust-lang/rust-clippy/pull/8783)
|
||||
* Move [`stable_sort_primitive`] to `pedantic` (now allow-by-default)
|
||||
[#8716](https://github.com/rust-lang/rust-clippy/pull/8716)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Remove overlap between [`manual_split_once`] and [`needless_splitn`]
|
||||
[#8631](https://github.com/rust-lang/rust-clippy/pull/8631)
|
||||
* [`map_identity`]: Now checks for needless `map_err`
|
||||
[#8487](https://github.com/rust-lang/rust-clippy/pull/8487)
|
||||
* [`extra_unused_lifetimes`]: Now checks for impl lifetimes
|
||||
[#8737](https://github.com/rust-lang/rust-clippy/pull/8737)
|
||||
* [`cast_possible_truncation`]: Now catches more cases with larger shift or divide operations
|
||||
[#8687](https://github.com/rust-lang/rust-clippy/pull/8687)
|
||||
* [`identity_op`]: Now checks for modulo expressions
|
||||
[#8519](https://github.com/rust-lang/rust-clippy/pull/8519)
|
||||
* [`panic`]: No longer lint in constant context
|
||||
[#8592](https://github.com/rust-lang/rust-clippy/pull/8592)
|
||||
* [`manual_split_once`]: Now lints manual iteration of `splitn`
|
||||
[#8717](https://github.com/rust-lang/rust-clippy/pull/8717)
|
||||
* [`self_named_module_files`], [`mod_module_files`]: Now handle relative module paths
|
||||
[#8611](https://github.com/rust-lang/rust-clippy/pull/8611)
|
||||
* [`unsound_collection_transmute`]: Now has better size and alignment checks
|
||||
[#8648](https://github.com/rust-lang/rust-clippy/pull/8648)
|
||||
* [`unnested_or_patterns`]: Ignore cases, where the suggestion would be longer
|
||||
[#8619](https://github.com/rust-lang/rust-clippy/pull/8619)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`rest_pat_in_fully_bound_structs`]: Now ignores structs marked with `#[non_exhaustive]`
|
||||
[#8690](https://github.com/rust-lang/rust-clippy/pull/8690)
|
||||
* [`needless_late_init`]: No longer lints `if let` statements, `let mut` bindings or instances that
|
||||
changes the drop order significantly
|
||||
[#8617](https://github.com/rust-lang/rust-clippy/pull/8617)
|
||||
* [`unnecessary_cast`]: No longer lints to casts to aliased or non-primitive types
|
||||
[#8596](https://github.com/rust-lang/rust-clippy/pull/8596)
|
||||
* [`init_numbered_fields`]: No longer lints type aliases
|
||||
[#8780](https://github.com/rust-lang/rust-clippy/pull/8780)
|
||||
* [`needless_option_as_deref`]: No longer lints for `as_deref_mut` on `Option` values that can't be moved
|
||||
[#8646](https://github.com/rust-lang/rust-clippy/pull/8646)
|
||||
* [`mistyped_literal_suffixes`]: Now ignores float literals without an exponent
|
||||
[#8742](https://github.com/rust-lang/rust-clippy/pull/8742)
|
||||
* [`undocumented_unsafe_blocks`]: Now ignores unsafe blocks from proc-macros and works better for sub-expressions
|
||||
[#8450](https://github.com/rust-lang/rust-clippy/pull/8450)
|
||||
* [`same_functions_in_if_condition`]: Now allows different constants, even if they have the same value
|
||||
[#8673](https://github.com/rust-lang/rust-clippy/pull/8673)
|
||||
* [`needless_match`]: Now checks for more complex types and ignores type coercion
|
||||
[#8549](https://github.com/rust-lang/rust-clippy/pull/8549)
|
||||
* [`assertions_on_constants`]: Now ignores constants from `cfg!` macros
|
||||
[#8614](https://github.com/rust-lang/rust-clippy/pull/8614)
|
||||
* [`indexing_slicing`]: Fix false positives with constant indices in
|
||||
[#8588](https://github.com/rust-lang/rust-clippy/pull/8588)
|
||||
* [`iter_with_drain`]: Now ignores iterator references
|
||||
[#8668](https://github.com/rust-lang/rust-clippy/pull/8668)
|
||||
* [`useless_attribute`]: Now allows [`redundant_pub_crate`] on `use` items
|
||||
[#8743](https://github.com/rust-lang/rust-clippy/pull/8743)
|
||||
* [`cast_ptr_alignment`]: Now ignores expressions, when used for unaligned reads and writes
|
||||
[#8632](https://github.com/rust-lang/rust-clippy/pull/8632)
|
||||
* [`wrong_self_convention`]: Now allows `&mut self` and no self as arguments for `is_*` methods
|
||||
[#8738](https://github.com/rust-lang/rust-clippy/pull/8738)
|
||||
* [`mut_from_ref`]: Only lint in unsafe code
|
||||
[#8647](https://github.com/rust-lang/rust-clippy/pull/8647)
|
||||
* [`redundant_pub_crate`]: Now allows macro exports
|
||||
[#8736](https://github.com/rust-lang/rust-clippy/pull/8736)
|
||||
* [`needless_match`]: Ignores cases where the else block expression is different
|
||||
[#8700](https://github.com/rust-lang/rust-clippy/pull/8700)
|
||||
* [`transmute_int_to_char`]: Now allows transmutations in `const` code
|
||||
[#8610](https://github.com/rust-lang/rust-clippy/pull/8610)
|
||||
* [`manual_non_exhaustive`]: Ignores cases, where the enum value is used
|
||||
[#8645](https://github.com/rust-lang/rust-clippy/pull/8645)
|
||||
* [`redundant_closure`]: Now ignores coerced closure
|
||||
[#8431](https://github.com/rust-lang/rust-clippy/pull/8431)
|
||||
* [`identity_op`]: Is now ignored in cases where extra brackets would be needed
|
||||
[#8730](https://github.com/rust-lang/rust-clippy/pull/8730)
|
||||
* [`let_unit_value`]: Now ignores cases which are used for type inference
|
||||
[#8563](https://github.com/rust-lang/rust-clippy/pull/8563)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`manual_split_once`]: Fixed incorrect suggestions for single result accesses
|
||||
[#8631](https://github.com/rust-lang/rust-clippy/pull/8631)
|
||||
* [`bytes_nth`]: Fix typos in the diagnostic message
|
||||
[#8403](https://github.com/rust-lang/rust-clippy/pull/8403)
|
||||
* [`mistyped_literal_suffixes`]: Now suggests the correct integer types
|
||||
[#8742](https://github.com/rust-lang/rust-clippy/pull/8742)
|
||||
* [`unnecessary_to_owned`]: Fixed suggestion based on the configured msrv
|
||||
[#8692](https://github.com/rust-lang/rust-clippy/pull/8692)
|
||||
* [`single_element_loop`]: Improve lint for Edition 2021 arrays
|
||||
[#8616](https://github.com/rust-lang/rust-clippy/pull/8616)
|
||||
* [`manual_bits`]: Now includes a cast for proper type conversion, when needed
|
||||
[#8677](https://github.com/rust-lang/rust-clippy/pull/8677)
|
||||
* [`option_map_unit_fn`], [`result_map_unit_fn`]: Fix some incorrect suggestions
|
||||
[#8584](https://github.com/rust-lang/rust-clippy/pull/8584)
|
||||
* [`collapsible_else_if`]: Add whitespace in suggestion
|
||||
[#8729](https://github.com/rust-lang/rust-clippy/pull/8729)
|
||||
* [`transmute_bytes_to_str`]: Now suggest `from_utf8_unchecked` in `const` context
|
||||
[#8612](https://github.com/rust-lang/rust-clippy/pull/8612)
|
||||
* [`map_clone`]: Improve message and suggestion based on the msrv
|
||||
[#8688](https://github.com/rust-lang/rust-clippy/pull/8688)
|
||||
* [`needless_late_init`]: Now shows the `let` statement where it was first initialized
|
||||
[#8779](https://github.com/rust-lang/rust-clippy/pull/8779)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`only_used_in_recursion`]
|
||||
[#8691](https://github.com/rust-lang/rust-clippy/pull/8691)
|
||||
* [`cast_slice_different_sizes`]
|
||||
[#8720](https://github.com/rust-lang/rust-clippy/pull/8720)
|
||||
* [`iter_overeager_cloned`]
|
||||
[#8602](https://github.com/rust-lang/rust-clippy/pull/8602)
|
||||
* [`undocumented_unsafe_blocks`]
|
||||
[#8686](https://github.com/rust-lang/rust-clippy/pull/8686)
|
||||
|
||||
## Rust 1.61
|
||||
|
||||
Current stable, released 2022-05-19
|
||||
Released 2022-05-19
|
||||
|
||||
[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
|
||||
|
||||
|
@ -207,7 +364,7 @@ Released 2022-04-07
|
|||
|
||||
* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
|
||||
[#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
|
||||
* [`chars_next_cmp`]: Correctly excapes the suggestion
|
||||
* [`chars_next_cmp`]: Correctly escapes the suggestion
|
||||
[#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
|
||||
* [`explicit_write`]: Add suggestions for `write!`s with format arguments
|
||||
[#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
|
||||
|
@ -1420,7 +1577,7 @@ Released 2021-03-25
|
|||
* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool
|
||||
[#6469](https://github.com/rust-lang/rust-clippy/pull/6469)
|
||||
|
||||
[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md
|
||||
[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/proposals/roadmap-2021.md
|
||||
[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3
|
||||
|
||||
## Rust 1.50
|
||||
|
@ -2368,7 +2525,7 @@ Released 2019-09-26
|
|||
* [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
|
||||
* [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766)
|
||||
* [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222)
|
||||
* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307)
|
||||
* Move `{unnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307)
|
||||
* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308)
|
||||
* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262)
|
||||
* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337)
|
||||
|
@ -3269,7 +3426,7 @@ Released 2018-09-13
|
|||
[`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
|
||||
[configuration file]: ./rust-clippy#configuration
|
||||
[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665
|
||||
[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
|
||||
[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md
|
||||
[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md
|
||||
|
||||
<!-- lint disable no-unused-definitions -->
|
||||
|
@ -3484,6 +3641,7 @@ Released 2018-09-13
|
|||
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
|
||||
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
|
||||
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
|
||||
[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked
|
||||
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
|
||||
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
|
||||
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
|
||||
|
|
|
@ -16,7 +16,6 @@ All contributors are expected to follow the [Rust Code of Conduct].
|
|||
- [The Clippy book](#the-clippy-book)
|
||||
- [High level approach](#high-level-approach)
|
||||
- [Finding something to fix/improve](#finding-something-to-fiximprove)
|
||||
- [Writing code](#writing-code)
|
||||
- [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
|
||||
- [IntelliJ Rust](#intellij-rust)
|
||||
- [Rust Analyzer](#rust-analyzer)
|
||||
|
|
|
@ -41,6 +41,7 @@ filetime = "0.2"
|
|||
rustc-workspace-hack = "1.0"
|
||||
|
||||
# UI test dependencies
|
||||
clap = { version = "3.1", features = ["derive"] }
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
derive-new = "0.5"
|
||||
if_chain = "1.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Clippy Book
|
||||
|
||||
This is the source for the Clippy Book. See the
|
||||
[book](src/infrastructure/book.md) for more information.
|
||||
[book](src/development/infrastructure/book.md) for more information.
|
||||
|
|
|
@ -4,8 +4,8 @@ This document explains the basics for hacking on Clippy. Besides others, this
|
|||
includes how to build and test Clippy. For a more in depth description on the
|
||||
codebase take a look at [Adding Lints] or [Common Tools].
|
||||
|
||||
[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
|
||||
[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md
|
||||
[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md
|
||||
[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md
|
||||
|
||||
- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy)
|
||||
- [Get the Code](#get-the-code)
|
||||
|
@ -59,7 +59,7 @@ cargo uitest
|
|||
# only run UI tests starting with `test_`
|
||||
TESTNAME="test_" cargo uitest
|
||||
# only run dogfood tests
|
||||
cargo test --test dogfood
|
||||
cargo dev dogfood
|
||||
```
|
||||
|
||||
If the output of a [UI test] differs from the expected output, you can update
|
||||
|
@ -91,10 +91,14 @@ cargo dev fmt
|
|||
cargo dev update_lints
|
||||
# create a new lint and register it
|
||||
cargo dev new_lint
|
||||
# deprecate a lint and attempt to remove code relating to it
|
||||
cargo dev deprecate
|
||||
# automatically formatting all code before each commit
|
||||
cargo dev setup git-hook
|
||||
# (experimental) Setup Clippy to work with IntelliJ-Rust
|
||||
cargo dev setup intellij
|
||||
# runs the `dogfood` tests
|
||||
cargo dev dogfood
|
||||
```
|
||||
|
||||
More about intellij command usage and reasons
|
||||
|
|
|
@ -95,6 +95,9 @@ As section headers, we use:
|
|||
Please also be sure to update the Beta/Unreleased sections at the top with the
|
||||
relevant commit ranges.
|
||||
|
||||
If you have the time, it would be appreciated if you double-check, that the
|
||||
`#[clippy::version]` attributes for the added lints contains the correct version.
|
||||
|
||||
[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md
|
||||
[forge]: https://forge.rust-lang.org/
|
||||
[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy
|
||||
|
|
|
@ -56,7 +56,7 @@ For more information on configuring lint levels, see the [rustc documentation].
|
|||
Clippy has lint groups which are allow-by-default. This means, that you will
|
||||
have to enable the lints in those groups manually.
|
||||
|
||||
For a full list of all lints with their description and examples, please refere
|
||||
For a full list of all lints with their description and examples, please refer
|
||||
to [Clippy's lint list]. The two most important allow-by-default groups are
|
||||
described below:
|
||||
|
||||
|
|
33
clippy_dev/src/dogfood.rs
Normal file
33
clippy_dev/src/dogfood.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use crate::clippy_project_root;
|
||||
use std::process::Command;
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if unable to run the dogfood test
|
||||
pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool) {
|
||||
let mut cmd = Command::new("cargo");
|
||||
|
||||
cmd.current_dir(clippy_project_root())
|
||||
.args(["test", "--test", "dogfood"])
|
||||
.args(["--features", "internal"])
|
||||
.args(["--", "dogfood_clippy"]);
|
||||
|
||||
let mut dogfood_args = Vec::new();
|
||||
if fix {
|
||||
dogfood_args.push("--fix");
|
||||
}
|
||||
|
||||
if allow_dirty {
|
||||
dogfood_args.push("--allow-dirty");
|
||||
}
|
||||
|
||||
if allow_staged {
|
||||
dogfood_args.push("--allow-staged");
|
||||
}
|
||||
|
||||
cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" "));
|
||||
|
||||
let output = cmd.output().expect("failed to run command");
|
||||
|
||||
println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
}
|
|
@ -11,6 +11,7 @@ extern crate rustc_lexer;
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub mod bless;
|
||||
pub mod dogfood;
|
||||
pub mod fmt;
|
||||
pub mod lint;
|
||||
pub mod new_lint;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue};
|
||||
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
|
||||
use clippy_dev::{bless, dogfood, fmt, lint, new_lint, serve, setup, update_lints};
|
||||
use indoc::indoc;
|
||||
|
||||
fn main() {
|
||||
|
@ -13,6 +13,13 @@ fn main() {
|
|||
Some(("bless", matches)) => {
|
||||
bless::bless(matches.contains_id("ignore-timestamp"));
|
||||
},
|
||||
Some(("dogfood", matches)) => {
|
||||
dogfood::dogfood(
|
||||
matches.contains_id("fix"),
|
||||
matches.contains_id("allow-dirty"),
|
||||
matches.contains_id("allow-staged"),
|
||||
);
|
||||
},
|
||||
Some(("fmt", matches)) => {
|
||||
fmt::run(matches.contains_id("check"), matches.contains_id("verbose"));
|
||||
},
|
||||
|
@ -104,6 +111,17 @@ fn get_clap_config() -> ArgMatches {
|
|||
.long("ignore-timestamp")
|
||||
.help("Include files updated before clippy was built"),
|
||||
),
|
||||
Command::new("dogfood").about("Runs the dogfood test").args([
|
||||
Arg::new("fix").long("fix").help("Apply the suggestions when possible"),
|
||||
Arg::new("allow-dirty")
|
||||
.long("allow-dirty")
|
||||
.help("Fix code even if the working directory has changes")
|
||||
.requires("fix"),
|
||||
Arg::new("allow-staged")
|
||||
.long("allow-staged")
|
||||
.help("Fix code even if the working directory has staged changes")
|
||||
.requires("fix"),
|
||||
]),
|
||||
Command::new("fmt")
|
||||
.about("Run rustfmt on all projects and tests")
|
||||
.args([
|
||||
|
|
|
@ -553,7 +553,7 @@ fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<S
|
|||
pos = m.end();
|
||||
}
|
||||
result.push_str(&contents[pos..]);
|
||||
edited.then(|| result)
|
||||
edited.then_some(result)
|
||||
}
|
||||
|
||||
fn round_to_fifty(count: usize) -> usize {
|
||||
|
|
|
@ -18,7 +18,7 @@ quine-mc_cluskey = "0.2"
|
|||
regex-syntax = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
tempfile = { version = "3.2", optional = true }
|
||||
tempfile = { version = "3.3.0", optional = true }
|
||||
toml = "0.5"
|
||||
unicode-normalization = "0.1"
|
||||
unicode-script = { version = "0.5", default-features = false }
|
||||
|
|
|
@ -161,7 +161,7 @@ declare_clippy_lint! {
|
|||
/// baz().await; // Lint violation
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub AWAIT_HOLDING_INVALID_TYPE,
|
||||
suspicious,
|
||||
"holding a type across an await point which is not allowed to be held as per the configuration"
|
||||
|
|
|
@ -22,7 +22,7 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
/// let x = &12;
|
||||
/// let addr_x = &x as *const _ as usize;
|
||||
/// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggerd.
|
||||
/// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggered.
|
||||
/// // But if we fix it, assert will fail.
|
||||
/// assert_ne!(addr_x, addr_y);
|
||||
/// ```
|
||||
|
|
|
@ -500,7 +500,7 @@ declare_clippy_lint! {
|
|||
/// let x: i32 = -42;
|
||||
/// let y: u32 = x.unsigned_abs();
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub CAST_ABS_TO_UNSIGNED,
|
||||
suspicious,
|
||||
"casting the result of `abs()` to an unsigned integer can panic"
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed,
|
||||
search_same, ContainsName, HirEqInterExpr, SpanlessEq,
|
||||
capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
|
||||
is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
|
||||
};
|
||||
use core::iter;
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind};
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::hygiene::walk_chain;
|
||||
|
@ -214,7 +217,7 @@ fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&
|
|||
fn lint_branches_sharing_code<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
conds: &[&'tcx Expr<'_>],
|
||||
blocks: &[&Block<'tcx>],
|
||||
blocks: &[&'tcx Block<'_>],
|
||||
expr: &'tcx Expr<'_>,
|
||||
) {
|
||||
// We only lint ifs with multiple blocks
|
||||
|
@ -340,6 +343,21 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the statement modifies or moves any of the given locals.
|
||||
fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool {
|
||||
for_each_expr(s, |e| {
|
||||
if let Some(id) = path_to_local(e)
|
||||
&& locals.contains(&id)
|
||||
&& !capture_local_usage(cx, e).is_imm_ref()
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Checks if the given statement should be considered equal to the statement in the same position
|
||||
/// for each block.
|
||||
fn eq_stmts(
|
||||
|
@ -365,18 +383,52 @@ fn eq_stmts(
|
|||
.all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt)))
|
||||
}
|
||||
|
||||
fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn scan_block_for_eq<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
conds: &[&'tcx Expr<'_>],
|
||||
block: &'tcx Block<'_>,
|
||||
blocks: &[&'tcx Block<'_>],
|
||||
) -> BlockEq {
|
||||
let mut eq = SpanlessEq::new(cx);
|
||||
let mut eq = eq.inter_expr();
|
||||
let mut moved_locals = Vec::new();
|
||||
|
||||
let mut cond_locals = HirIdSet::default();
|
||||
for &cond in conds {
|
||||
let _: Option<!> = for_each_expr(cond, |e| {
|
||||
if let Some(id) = path_to_local(e) {
|
||||
cond_locals.insert(id);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
}
|
||||
|
||||
let mut local_needs_ordered_drop = false;
|
||||
let start_end_eq = block
|
||||
.stmts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals))
|
||||
.find(|&(i, stmt)| {
|
||||
if let StmtKind::Local(l) = stmt.kind
|
||||
&& needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id))
|
||||
{
|
||||
local_needs_ordered_drop = true;
|
||||
return true;
|
||||
}
|
||||
modifies_any_local(cx, stmt, &cond_locals)
|
||||
|| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
|
||||
})
|
||||
.map_or(block.stmts.len(), |(i, _)| i);
|
||||
|
||||
if local_needs_ordered_drop {
|
||||
return BlockEq {
|
||||
start_end_eq,
|
||||
end_begin_eq: None,
|
||||
moved_locals,
|
||||
};
|
||||
}
|
||||
|
||||
// Walk backwards through the final expression/statements so long as their hashes are equal. Note
|
||||
// `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block
|
||||
// to match those in other blocks. e.g. If each block ends with the following the hash value will be
|
||||
|
|
|
@ -43,7 +43,7 @@ declare_clippy_lint! {
|
|||
/// #[allow(clippy::crate_in_macro_def)]
|
||||
/// macro_rules! ok { ... crate::foo ... }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub CRATE_IN_MACRO_DEF,
|
||||
suspicious,
|
||||
"using `crate` in a macro definition"
|
||||
|
|
|
@ -8,8 +8,8 @@ use rustc_data_structures::fx::FxIndexMap;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_ty, Visitor};
|
||||
use rustc_hir::{
|
||||
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem,
|
||||
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
|
||||
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId,
|
||||
ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
|
||||
TraitItemKind, TyKind, UnOp,
|
||||
};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
|
@ -717,20 +717,36 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
|
|||
|
||||
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
|
||||
ExprKind::Ret(_) => {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
|
||||
.skip_binder()
|
||||
.output();
|
||||
Some(if !output.is_ref() {
|
||||
Position::Other(precedence)
|
||||
} else if output.has_placeholders() || output.has_opaque_types() {
|
||||
Position::ReborrowStable(precedence)
|
||||
} else {
|
||||
Position::DerefStable(precedence)
|
||||
})
|
||||
let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap());
|
||||
Some(
|
||||
if let Node::Expr(Expr {
|
||||
kind: ExprKind::Closure(&Closure { fn_decl, .. }),
|
||||
..
|
||||
}) = cx.tcx.hir().get(owner_id)
|
||||
{
|
||||
match fn_decl.output {
|
||||
FnRetTy::Return(ty) => binding_ty_auto_deref_stability(ty, precedence),
|
||||
FnRetTy::DefaultReturn(_) => Position::Other(precedence),
|
||||
}
|
||||
} else {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(cx.tcx.hir().local_def_id(owner_id))
|
||||
.skip_binder()
|
||||
.output();
|
||||
if !output.is_ref() {
|
||||
Position::Other(precedence)
|
||||
} else if output.has_placeholders() || output.has_opaque_types() {
|
||||
Position::ReborrowStable(precedence)
|
||||
} else {
|
||||
Position::DerefStable(precedence)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
ExprKind::Call(func, _) if func.hir_id == child_id => {
|
||||
(child_id == e.hir_id).then_some(Position::Callee)
|
||||
},
|
||||
ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee),
|
||||
ExprKind::Call(func, args) => args
|
||||
.iter()
|
||||
.position(|arg| arg.hir_id == child_id)
|
||||
|
@ -756,9 +772,14 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
|
|||
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
|
||||
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
|
||||
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
|
||||
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
|
||||
|| cx.tcx.mk_substs([].iter())
|
||||
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
|
||||
&& let subs = match cx
|
||||
.typeck_results()
|
||||
.node_substs_opt(parent.hir_id)
|
||||
.and_then(|subs| subs.get(1..))
|
||||
{
|
||||
Some(subs) => cx.tcx.mk_substs(subs.iter().copied()),
|
||||
None => cx.tcx.mk_substs([].iter()),
|
||||
} && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
|
||||
// Trait methods taking `&self`
|
||||
sub_ty
|
||||
} else {
|
||||
|
|
|
@ -189,7 +189,7 @@ declare_clippy_lint! {
|
|||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
style,
|
||||
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
|
||||
|
|
|
@ -116,7 +116,7 @@ declare_clippy_lint! {
|
|||
/// let x = Foo;
|
||||
/// std::mem::drop(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub DROP_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::drop` with a value which does not implement `Drop`"
|
||||
|
@ -136,7 +136,7 @@ declare_clippy_lint! {
|
|||
/// let x = Foo;
|
||||
/// std::mem::forget(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub FORGET_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::forget` with a value which does not implement `Drop`"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Level};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{FileName, Span};
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -79,21 +79,29 @@ impl EarlyLintPass for DuplicateMod {
|
|||
}
|
||||
|
||||
fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
|
||||
for Modules { local_path, spans, lint_levels } in self.modules.values() {
|
||||
for Modules {
|
||||
local_path,
|
||||
spans,
|
||||
lint_levels,
|
||||
} in self.modules.values()
|
||||
{
|
||||
if spans.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point the lint would be emitted
|
||||
assert_eq!(spans.len(), lint_levels.len());
|
||||
let spans: Vec<_> = spans.into_iter().zip(lint_levels).filter_map(|(span, lvl)|{
|
||||
if let Some(id) = lvl.get_expectation_id() {
|
||||
cx.fulfill_expectation(id);
|
||||
}
|
||||
let spans: Vec<_> = spans
|
||||
.iter()
|
||||
.zip(lint_levels)
|
||||
.filter_map(|(span, lvl)| {
|
||||
if let Some(id) = lvl.get_expectation_id() {
|
||||
cx.fulfill_expectation(id);
|
||||
}
|
||||
|
||||
(!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
|
||||
})
|
||||
.collect();
|
||||
(!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if spans.len() < 2 {
|
||||
continue;
|
||||
|
|
|
@ -26,7 +26,7 @@ declare_clippy_lint! {
|
|||
/// ```rust
|
||||
/// struct S;
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub EMPTY_DROP,
|
||||
restriction,
|
||||
"empty `Drop` implementations"
|
||||
|
|
|
@ -650,7 +650,7 @@ fn find_insert_calls<'tcx>(
|
|||
let allow_insert_closure = s.allow_insert_closure;
|
||||
let is_single_insert = s.is_single_insert;
|
||||
let edits = s.edits;
|
||||
s.can_use_entry.then(|| InsertSearchResults {
|
||||
s.can_use_entry.then_some(InsertSearchResults {
|
||||
edits,
|
||||
allow_insert_closure,
|
||||
is_single_insert,
|
||||
|
|
|
@ -4,7 +4,8 @@ use clippy_utils::ty::implements_trait;
|
|||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
|
@ -67,6 +68,7 @@ fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: T
|
|||
impl<'tcx> LateLintPass<'tcx> for PatternEquality {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.sess(), expr.span);
|
||||
if let ExprKind::Let(let_expr) = expr.kind;
|
||||
if unary_pattern(let_expr.pat);
|
||||
let exp_ty = cx.typeck_results().expr_ty(let_expr.init);
|
||||
|
|
|
@ -14,6 +14,12 @@ declare_clippy_lint! {
|
|||
/// ### Why is this bad?
|
||||
/// Introduces an extra, avoidable heap allocation.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// `format!` returns a `String` but `write!` returns a `Result`.
|
||||
/// Thus you are forced to ignore the `Err` variant to achieve the same API.
|
||||
///
|
||||
/// While using `write!` in the suggested way should never fail, this isn't necessarily clear to the programmer.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let mut s = String::new();
|
||||
|
@ -27,9 +33,9 @@ declare_clippy_lint! {
|
|||
/// let mut s = String::new();
|
||||
/// let _ = write!(s, "0x{:X}", 1024);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub FORMAT_PUSH_STRING,
|
||||
perf,
|
||||
restriction,
|
||||
"`format!(..)` appended to existing `String`"
|
||||
}
|
||||
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
|
||||
|
|
|
@ -127,7 +127,7 @@ fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> {
|
|||
(!span.from_expansion()
|
||||
&& impl_item.generics.params.is_empty()
|
||||
&& !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
|
||||
.then(|| span)
|
||||
.then_some(span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
74
clippy_lints/src/invalid_utf8_in_unchecked.rs
Normal file
74
clippy_lints/src/invalid_utf8_in_unchecked.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{match_function_call, paths};
|
||||
use rustc_ast::{BorrowKind, LitKind};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `std::str::from_utf8_unchecked` with an invalid UTF-8 literal
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Creating such a `str` would result in undefined behavior
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # #[allow(unused)]
|
||||
/// unsafe {
|
||||
/// std::str::from_utf8_unchecked(b"cl\x82ippy");
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.64.0"]
|
||||
pub INVALID_UTF8_IN_UNCHECKED,
|
||||
correctness,
|
||||
"using a non UTF-8 literal in `std::std::from_utf8_unchecked`"
|
||||
}
|
||||
declare_lint_pass!(InvalidUtf8InUnchecked => [INVALID_UTF8_IN_UNCHECKED]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for InvalidUtf8InUnchecked {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let Some([arg]) = match_function_call(cx, expr, &paths::STR_FROM_UTF8_UNCHECKED) {
|
||||
match &arg.kind {
|
||||
ExprKind::Lit(Spanned { node: lit, .. }) => {
|
||||
if let LitKind::ByteStr(bytes) = &lit
|
||||
&& std::str::from_utf8(bytes).is_err()
|
||||
{
|
||||
lint(cx, expr.span);
|
||||
}
|
||||
},
|
||||
ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => {
|
||||
let elements = args.iter().map(|e|{
|
||||
match &e.kind {
|
||||
ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
|
||||
LitKind::Byte(b) => Some(*b),
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
LitKind::Int(b, _) => Some(*b as u8),
|
||||
_ => None
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}).collect::<Option<Vec<_>>>();
|
||||
|
||||
if let Some(elements) = elements
|
||||
&& std::str::from_utf8(&elements).is_err()
|
||||
{
|
||||
lint(cx, expr.span);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lint(cx: &LateContext<'_>, span: Span) {
|
||||
span_lint(
|
||||
cx,
|
||||
INVALID_UTF8_IN_UNCHECKED,
|
||||
span,
|
||||
"non UTF-8 literal in `std::str::from_utf8_unchecked`",
|
||||
);
|
||||
}
|
|
@ -30,7 +30,7 @@ declare_clippy_lint! {
|
|||
/// For types that implement `Copy`, the suggestion to `Box` a variant's
|
||||
/// data would require removing the trait impl. The types can of course
|
||||
/// still be `Clone`, but that is worse ergonomically. Depending on the
|
||||
/// use case it may be possible to store the large data in an auxillary
|
||||
/// use case it may be possible to store the large data in an auxiliary
|
||||
/// structure (e.g. Arena or ECS).
|
||||
///
|
||||
/// The lint will ignore generic types if the layout depends on the
|
||||
|
|
|
@ -71,7 +71,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
|
||||
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
|
||||
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
|
||||
LintId::of(format_push_string::FORMAT_PUSH_STRING),
|
||||
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
|
||||
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
|
||||
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
|
||||
|
@ -92,6 +91,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
|
||||
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
|
||||
LintId::of(int_plus_one::INT_PLUS_ONE),
|
||||
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
|
||||
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
|
||||
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
|
||||
LintId::of(len_zero::COMPARISON_TO_EMPTY),
|
||||
|
|
|
@ -29,6 +29,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
|||
LintId::of(infinite_iter::INFINITE_ITER),
|
||||
LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
|
||||
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
|
||||
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
|
||||
LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
|
||||
LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
|
||||
LintId::of(loops::ITER_NEXT_LOOP),
|
||||
|
|
|
@ -196,6 +196,7 @@ store.register_lints(&[
|
|||
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
|
||||
int_plus_one::INT_PLUS_ONE,
|
||||
invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
|
||||
invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED,
|
||||
items_after_statements::ITEMS_AFTER_STATEMENTS,
|
||||
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
|
||||
large_const_arrays::LARGE_CONST_ARRAYS,
|
||||
|
|
|
@ -28,6 +28,8 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
|
|||
LintId::of(strings::STRING_LIT_AS_BYTES),
|
||||
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
|
||||
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
|
||||
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
|
||||
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
|
||||
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
|
||||
LintId::of(unused_rounding::UNUSED_ROUNDING),
|
||||
LintId::of(use_self::USE_SELF),
|
||||
|
|
|
@ -86,8 +86,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
|
|||
LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
|
||||
LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
|
||||
LintId::of(strings::STRING_ADD_ASSIGN),
|
||||
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
|
||||
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
|
||||
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
|
||||
LintId::of(types::LINKEDLIST),
|
||||
LintId::of(types::OPTION_OPTION),
|
||||
|
|
|
@ -7,7 +7,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
|
|||
LintId::of(escape::BOXED_LOCAL),
|
||||
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
|
||||
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
|
||||
LintId::of(format_push_string::FORMAT_PUSH_STRING),
|
||||
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
|
||||
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
|
||||
LintId::of(loops::MANUAL_MEMCPY),
|
||||
|
|
|
@ -21,6 +21,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
|||
LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
|
||||
LintId::of(exit::EXIT),
|
||||
LintId::of(float_literal::LOSSY_FLOAT_LITERAL),
|
||||
LintId::of(format_push_string::FORMAT_PUSH_STRING),
|
||||
LintId::of(if_then_some_else_none::IF_THEN_SOME_ELSE_NONE),
|
||||
LintId::of(implicit_return::IMPLICIT_RETURN),
|
||||
LintId::of(indexing_slicing::INDEXING_SLICING),
|
||||
|
|
|
@ -255,6 +255,7 @@ mod init_numbered_fields;
|
|||
mod inline_fn_without_body;
|
||||
mod int_plus_one;
|
||||
mod invalid_upcast_comparisons;
|
||||
mod invalid_utf8_in_unchecked;
|
||||
mod items_after_statements;
|
||||
mod iter_not_returning_iterator;
|
||||
mod large_const_arrays;
|
||||
|
@ -913,6 +914,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv)));
|
||||
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
|
||||
store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ pub(super) fn check<'tcx>(
|
|||
if let Some((name, ty, initializer)) = initialize_visitor.get_result();
|
||||
if is_integer_const(cx, initializer, 0);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let span = expr.span.with_hi(arg.span.hi());
|
||||
|
||||
let int_name = match ty.map(Ty::kind) {
|
||||
// usize or inferred
|
||||
|
@ -42,7 +43,7 @@ pub(super) fn check<'tcx>(
|
|||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
expr.span.with_hi(arg.span.hi()),
|
||||
span,
|
||||
&format!("the variable `{}` is used as a loop counter", name),
|
||||
"consider using",
|
||||
format!(
|
||||
|
@ -63,11 +64,11 @@ pub(super) fn check<'tcx>(
|
|||
span_lint_and_then(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
expr.span.with_hi(arg.span.hi()),
|
||||
span,
|
||||
&format!("the variable `{}` is used as a loop counter", name),
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
expr.span.with_hi(arg.span.hi()),
|
||||
span,
|
||||
"consider using",
|
||||
format!(
|
||||
"for ({}, {}) in (0_{}..).zip({})",
|
||||
|
|
|
@ -139,7 +139,7 @@ fn last_stmt_and_ret<'tcx>(
|
|||
if_chain! {
|
||||
// This should be the loop
|
||||
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
|
||||
// This should be the funciton body
|
||||
// This should be the function body
|
||||
if let Some((_, Node::Block(block))) = parent_iter.next();
|
||||
if let Some((last_stmt, last_ret)) = extract(block);
|
||||
if last_stmt.hir_id == node_hir;
|
||||
|
|
|
@ -51,22 +51,32 @@ pub(super) fn check<'tcx>(
|
|||
_ => ""
|
||||
};
|
||||
|
||||
let sugg = format!("{arg_snippet}{copied}.flatten()");
|
||||
|
||||
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
|
||||
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
|
||||
// to refer to the correct relative position of the suggestion.
|
||||
let help_msg = if sugg.contains('\n') {
|
||||
"remove the `if let` statement in the for loop and then..."
|
||||
} else {
|
||||
"...and remove the `if let` statement in the for loop"
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_FLATTEN,
|
||||
span,
|
||||
&msg,
|
||||
|diag| {
|
||||
let sugg = format!("{}{}.flatten()", arg_snippet, copied);
|
||||
diag.span_suggestion(
|
||||
arg.span,
|
||||
"try",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
applicability,
|
||||
);
|
||||
diag.span_help(
|
||||
inner_expr.span,
|
||||
"...and remove the `if let` statement in the for loop",
|
||||
help_msg,
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ use rustc_lint::LateContext;
|
|||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
|
||||
let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
|
||||
([stmt, stmts @ ..], expr) => {
|
||||
if let StmtKind::Local(&Local { init: Some(e), els: None, .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
|
||||
if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
|
||||
(e, !stmts.is_empty() || expr.is_some())
|
||||
} else {
|
||||
return;
|
||||
|
|
|
@ -3,13 +3,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{
|
||||
get_enclosing_loop_or_closure, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used,
|
||||
get_enclosing_loop_or_multi_call_closure, is_refutable, is_trait_method, match_def_path, paths,
|
||||
visitors::is_res_used,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{Closure, def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
|
||||
use rustc_hir::{def::Res, Closure, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter::OnlyBodies;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_span::{symbol::sym, Symbol};
|
||||
|
||||
|
@ -249,6 +251,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
|
|||
used_iter: bool,
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
|
||||
type NestedFilter = OnlyBodies;
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if self.used_iter {
|
||||
return;
|
||||
|
@ -283,6 +290,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
|
|||
used_after: bool,
|
||||
}
|
||||
impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
|
||||
type NestedFilter = OnlyBodies;
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx Local<'_>) {
|
||||
if !self.after_loop {
|
||||
l.pat.each_binding_or_first(&mut |_, id, _, _| {
|
||||
|
@ -320,10 +332,7 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) {
|
||||
// The iterator expression will be used on the next iteration (for loops), or on the next call (for
|
||||
// closures) unless it is declared within the enclosing expression. TODO: Check for closures
|
||||
// used where an `FnOnce` type is expected.
|
||||
if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
|
||||
let local_id = match iter_expr.path {
|
||||
Res::Local(id) => id,
|
||||
_ => return true,
|
||||
|
|
|
@ -35,7 +35,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
|
@ -6,8 +6,8 @@ use if_chain::if_chain;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId,
|
||||
IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
|
||||
AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound,
|
||||
HirId, IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
|
|
@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
|
|||
(matches!(v.data, hir::VariantData::Unit(_))
|
||||
&& v.ident.as_str().starts_with('_')
|
||||
&& is_doc_hidden(cx.tcx.hir().attrs(v.id)))
|
||||
.then(|| (id, v.span))
|
||||
.then_some((id, v.span))
|
||||
});
|
||||
if let Some((id, span)) = iter.next()
|
||||
&& iter.next().is_none()
|
||||
|
|
|
@ -71,7 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
|
|||
&& let Some(const3) = check_for_unsigned_int_constant(cx, right)
|
||||
// Also ensures the const is nonzero since zero can't be a divisor
|
||||
&& const1 == const2 && const2 == const3
|
||||
&& let Some(hir_id) = path_to_local(expr3) {
|
||||
&& let Some(hir_id) = path_to_local(expr3)
|
||||
&& let Some(Node::Pat(_)) = cx.tcx.hir().find(hir_id) {
|
||||
// Apply only to params or locals with annotated types
|
||||
match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
|
||||
Some(Node::Param(..)) => (),
|
||||
|
|
|
@ -105,7 +105,7 @@ fn check<'tcx>(
|
|||
|
||||
// Determine which binding mode to use.
|
||||
let explicit_ref = some_pat.contains_explicit_ref_binding();
|
||||
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
|
||||
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
|
||||
|
||||
let as_ref_str = match binding_ref {
|
||||
Some(Mutability::Mut) => ".as_mut()",
|
||||
|
|
|
@ -38,7 +38,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
normalized_pats[i + 1..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
|
||||
.find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j))
|
||||
.unwrap_or(normalized_pats.len())
|
||||
})
|
||||
.collect();
|
||||
|
@ -55,7 +55,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
.zip(forwards_blocking_idxs[..i].iter().copied().rev())
|
||||
.skip_while(|&(_, forward_block)| forward_block > i)
|
||||
.find_map(|((j, other), forward_block)| {
|
||||
(forward_block == i || pat.has_overlapping_values(other)).then(|| j)
|
||||
(forward_block == i || pat.has_overlapping_values(other)).then_some(j)
|
||||
})
|
||||
.unwrap_or(0)
|
||||
})
|
||||
|
@ -365,7 +365,7 @@ impl<'a> NormalizedPat<'a> {
|
|||
(Self::Slice(pats, None), Self::Slice(front, Some(back)))
|
||||
| (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
|
||||
// Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
|
||||
// then the minium length required will be greater than the length of `pats`.
|
||||
// then the minimum length required will be greater than the length of `pats`.
|
||||
if pats.len() < front.len() + back.len() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1062,7 +1062,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar
|
|||
let start = scrutinee_span.hi();
|
||||
let mut arm_spans = arms.iter().map(|arm| {
|
||||
let data = arm.span.data();
|
||||
(data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
|
||||
(data.ctxt == SyntaxContext::root()).then_some((data.lo, data.hi))
|
||||
});
|
||||
let end = e.span.hi();
|
||||
|
||||
|
@ -1096,7 +1096,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar
|
|||
parent: None,
|
||||
}
|
||||
.span();
|
||||
(!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
|
||||
(!span_contains_cfg(cx, span)).then_some(next_start).ok_or(())
|
||||
});
|
||||
match found {
|
||||
Ok(start) => {
|
||||
|
|
|
@ -89,6 +89,10 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
|
|||
source: MatchSource,
|
||||
) -> Option<(Vec<FoundSigDrop>, &'static str)> {
|
||||
let mut helper = SigDropHelper::new(cx);
|
||||
let scrutinee = match (source, &scrutinee.kind) {
|
||||
(MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
|
||||
_ => scrutinee,
|
||||
};
|
||||
helper.find_sig_drop(scrutinee).map(|drops| {
|
||||
let message = if source == MatchSource::Normal {
|
||||
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
|
||||
|
|
|
@ -8,6 +8,7 @@ use rustc_hir as hir;
|
|||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
@ -49,35 +50,18 @@ fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>
|
|||
is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
|
||||
}
|
||||
|
||||
/// lint use of `filter().map()` for `Iterators`
|
||||
fn lint_filter_some_map_unwrap(
|
||||
/// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
|
||||
fn is_filter_some_map_unwrap(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
filter_recv: &hir::Expr<'_>,
|
||||
filter_arg: &hir::Expr<'_>,
|
||||
map_arg: &hir::Expr<'_>,
|
||||
target_span: Span,
|
||||
methods_span: Span,
|
||||
) {
|
||||
) -> bool {
|
||||
let iterator = is_trait_method(cx, expr, sym::Iterator);
|
||||
let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option);
|
||||
if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) {
|
||||
let msg = "`filter` for `Some` followed by `unwrap`";
|
||||
let help = "consider using `flatten` instead";
|
||||
let sugg = format!(
|
||||
"{}",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),)
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_FILTER_MAP,
|
||||
methods_span,
|
||||
msg,
|
||||
help,
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
(iterator || option) && is_option_filter_map(cx, filter_arg, map_arg)
|
||||
}
|
||||
|
||||
/// lint use of `filter().map()` or `find().map()` for `Iterators`
|
||||
|
@ -93,15 +77,20 @@ pub(super) fn check<'tcx>(
|
|||
map_span: Span,
|
||||
is_find: bool,
|
||||
) {
|
||||
lint_filter_some_map_unwrap(
|
||||
cx,
|
||||
expr,
|
||||
filter_recv,
|
||||
filter_arg,
|
||||
map_arg,
|
||||
map_span,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
);
|
||||
if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_FILTER_MAP,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
"`filter` for `Some` followed by `unwrap`",
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if is_trait_method(cx, map_recv, sym::Iterator);
|
||||
|
||||
|
@ -118,7 +107,7 @@ pub(super) fn check<'tcx>(
|
|||
// closure ends with is_some() or is_ok()
|
||||
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
|
||||
if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind;
|
||||
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
|
||||
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def();
|
||||
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
|
||||
Some(false)
|
||||
} else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
|
||||
|
@ -137,6 +126,19 @@ pub(super) fn check<'tcx>(
|
|||
if let ExprKind::MethodCall(seg, [map_arg, ..], _) = map_body.value.kind;
|
||||
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
|
||||
|
||||
// .filter(..).map(|y| f(y).copied().unwrap())
|
||||
// ~~~~
|
||||
let map_arg_peeled = match map_arg.kind {
|
||||
ExprKind::MethodCall(method, [original_arg], _) if acceptable_methods(method) => {
|
||||
original_arg
|
||||
},
|
||||
_ => map_arg,
|
||||
};
|
||||
|
||||
// .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
|
||||
let simple_equal = path_to_local_id(filter_arg, filter_param_id)
|
||||
&& path_to_local_id(map_arg_peeled, map_param_id);
|
||||
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
// in `filter(|x| ..)`, replace `*x` with `x`
|
||||
let a_path = if_chain! {
|
||||
|
@ -145,25 +147,12 @@ pub(super) fn check<'tcx>(
|
|||
then { expr_path } else { a }
|
||||
};
|
||||
// let the filter closure arg and the map closure arg be equal
|
||||
if_chain! {
|
||||
if path_to_local_id(a_path, filter_param_id);
|
||||
if path_to_local_id(b, map_param_id);
|
||||
if cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b);
|
||||
then {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
if match map_arg.kind {
|
||||
ExprKind::MethodCall(method, [original_arg], _) => {
|
||||
acceptable_methods(method)
|
||||
&& SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, original_arg)
|
||||
},
|
||||
_ => SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg)
|
||||
path_to_local_id(a_path, filter_param_id)
|
||||
&& path_to_local_id(b, map_param_id)
|
||||
&& cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
|
||||
};
|
||||
|
||||
if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
|
||||
then {
|
||||
let span = filter_span.with_hi(expr.span.hi());
|
||||
let (filter_name, lint) = if is_find {
|
||||
|
@ -171,10 +160,22 @@ pub(super) fn check<'tcx>(
|
|||
} else {
|
||||
("filter", MANUAL_FILTER_MAP)
|
||||
};
|
||||
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
|
||||
let to_opt = if is_result { ".ok()" } else { "" };
|
||||
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
|
||||
snippet(cx, map_arg.span, ".."), to_opt);
|
||||
let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
|
||||
let (to_opt, deref) = if is_result {
|
||||
(".ok()", String::new())
|
||||
} else {
|
||||
let derefs = cx.typeck_results()
|
||||
.expr_adjustments(map_arg)
|
||||
.iter()
|
||||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count();
|
||||
|
||||
("", "*".repeat(derefs))
|
||||
};
|
||||
let sugg = format!(
|
||||
"{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
|
||||
snippet(cx, map_arg.span, ".."),
|
||||
);
|
||||
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<RepeatKind> {
|
|||
Some(RepeatKind::String)
|
||||
} else {
|
||||
let ty = ty.peel_refs();
|
||||
(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then(|| RepeatKind::String)
|
||||
(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then_some(RepeatKind::String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -369,7 +369,7 @@ declare_clippy_lint! {
|
|||
/// let x: Result<u32, &str> = Ok(10);
|
||||
/// x.expect_err("Testing expect_err");
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub ERR_EXPECT,
|
||||
style,
|
||||
r#"using `.err().expect("")` when `.expect_err("")` can be used"#
|
||||
|
@ -2196,12 +2196,9 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Finds usages of [`char::is_digit`]
|
||||
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
|
||||
/// can be replaced with [`is_ascii_digit`]
|
||||
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
|
||||
/// [`is_ascii_hexdigit`]
|
||||
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
|
||||
/// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
|
||||
/// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
|
||||
/// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `is_digit(..)` is slower and requires specifying the radix.
|
||||
|
@ -2218,7 +2215,7 @@ declare_clippy_lint! {
|
|||
/// c.is_ascii_digit();
|
||||
/// c.is_ascii_hexdigit();
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub IS_DIGIT_ASCII_RADIX,
|
||||
style,
|
||||
"use of `char::is_digit(..)` with literal radix of 10 or 16"
|
||||
|
@ -2238,7 +2235,7 @@ declare_clippy_lint! {
|
|||
/// let x = Some(3);
|
||||
/// x.as_ref();
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub NEEDLESS_OPTION_TAKE,
|
||||
complexity,
|
||||
"using `.as_ref().take()` on a temporary value"
|
||||
|
@ -2740,6 +2737,12 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("take", []) => needless_option_take::check(cx, expr, recv),
|
||||
("then", [arg]) => {
|
||||
if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
|
||||
return;
|
||||
}
|
||||
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
|
||||
},
|
||||
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
|
||||
implicit_clone::check(cx, name, expr, recv);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
|
||||
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
|
||||
use clippy_utils::ty::{implements_trait, match_type};
|
||||
use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
|
||||
use if_chain::if_chain;
|
||||
|
@ -28,10 +28,10 @@ pub(super) fn check<'tcx>(
|
|||
cx: &LateContext<'_>,
|
||||
name: &str,
|
||||
fun: &hir::Expr<'_>,
|
||||
self_expr: &hir::Expr<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
or_has_args: bool,
|
||||
span: Span,
|
||||
method_span: Span,
|
||||
) -> bool {
|
||||
let is_default_default = || is_trait_item(cx, fun, sym::Default);
|
||||
|
||||
|
@ -52,24 +52,14 @@ pub(super) fn check<'tcx>(
|
|||
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let hint = "unwrap_or_default()";
|
||||
let sugg_span = span;
|
||||
|
||||
let sugg: String = format!(
|
||||
"{}.{}",
|
||||
snippet_with_applicability(cx, self_expr.span, "..", &mut applicability),
|
||||
hint
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OR_FUN_CALL,
|
||||
sugg_span,
|
||||
method_span.with_hi(span.hi()),
|
||||
&format!("use of `{}` followed by a call to `{}`", name, path),
|
||||
"try this",
|
||||
sugg,
|
||||
applicability,
|
||||
"unwrap_or_default()".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
true
|
||||
|
@ -171,7 +161,7 @@ pub(super) fn check<'tcx>(
|
|||
match inner_arg.kind {
|
||||
hir::ExprKind::Call(fun, or_args) => {
|
||||
let or_has_args = !or_args.is_empty();
|
||||
if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) {
|
||||
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
|
||||
let fun_span = if or_has_args { None } else { Some(fun.span) };
|
||||
check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,9 @@ pub(super) fn check<'tcx>(
|
|||
) {
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
|
||||
|
||||
if is_option || is_result {
|
||||
if is_option || is_result || is_bool {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let body_expr = &body.value;
|
||||
|
@ -33,8 +34,10 @@ pub(super) fn check<'tcx>(
|
|||
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
|
||||
let msg = if is_option {
|
||||
"unnecessary closure used to substitute value for `Option::None`"
|
||||
} else {
|
||||
} else if is_result {
|
||||
"unnecessary closure used to substitute value for `Result::Err`"
|
||||
} else {
|
||||
"unnecessary closure used with `bool::then`"
|
||||
};
|
||||
let applicability = if body
|
||||
.params
|
||||
|
|
|
@ -301,7 +301,7 @@ fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
|
|||
use rustc_span::hygiene::MacroKind;
|
||||
if expr.span.from_expansion() {
|
||||
let data = expr.span.ctxt().outer_expn_data();
|
||||
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr|MacroKind::Derive, _))
|
||||
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for type parameters which are positioned inconsistently between
|
||||
/// a type definition and impl block. Specifically, a paramater in an impl
|
||||
/// a type definition and impl block. Specifically, a parameter in an impl
|
||||
/// block which has the same name as a parameter in the type def, but is in
|
||||
/// a different place.
|
||||
///
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::ast;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::{self, DefIdTree};
|
||||
|
@ -57,6 +58,20 @@ impl MissingDoc {
|
|||
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
||||
}
|
||||
|
||||
fn has_include(meta: Option<MetaItem>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(meta) = meta;
|
||||
if let MetaItemKind::List(list) = meta.kind;
|
||||
if let Some(meta) = list.get(0);
|
||||
if let Some(name) = meta.ident();
|
||||
then {
|
||||
name.name == sym::include
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_missing_docs_attrs(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
|
@ -80,7 +95,9 @@ impl MissingDoc {
|
|||
return;
|
||||
}
|
||||
|
||||
let has_doc = attrs.iter().any(|a| a.doc_str().is_some());
|
||||
let has_doc = attrs
|
||||
.iter()
|
||||
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
|
||||
if !has_doc {
|
||||
span_lint(
|
||||
cx,
|
||||
|
|
|
@ -95,10 +95,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, '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) {
|
||||
if adj
|
||||
|
|
|
@ -88,15 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
|
|||
// shouldn't be implemented when it is hidden in docs
|
||||
return;
|
||||
}
|
||||
if impl_item
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. }))
|
||||
{
|
||||
// when the result of `new()` depends on a type parameter we should not require
|
||||
// an
|
||||
// impl of `Default`
|
||||
if !impl_item.generics.params.is_empty() {
|
||||
// when the result of `new()` depends on a parameter we should not require
|
||||
// an impl of `Default`
|
||||
return;
|
||||
}
|
||||
if_chain! {
|
||||
|
|
|
@ -31,7 +31,7 @@ declare_clippy_lint! {
|
|||
/// and friends since the string is already preprocessed when Clippy lints
|
||||
/// can see it.
|
||||
///
|
||||
/// # Example
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
|
||||
/// let two = "\033\0"; // \033 intended as null-3-3
|
||||
|
|
|
@ -11,8 +11,8 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
|
||||
use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor};
|
||||
use rustc_hir::{
|
||||
Arm, Closure, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
|
||||
QPath, Stmt, StmtKind, TyKind, UnOp,
|
||||
Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path,
|
||||
PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
|
|
|
@ -146,7 +146,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
|
|||
});
|
||||
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
|
||||
match some_captures.get(local_id)
|
||||
.or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id)))
|
||||
.or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id)))
|
||||
{
|
||||
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
|
||||
Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,
|
||||
|
|
|
@ -502,7 +502,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio
|
|||
.iter()
|
||||
.filter_map(get_rptr_lm)
|
||||
.filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region)
|
||||
.map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
|
||||
.map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span))
|
||||
.collect();
|
||||
if let Some(args) = args
|
||||
&& !args.is_empty()
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt};
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_node, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks,
|
||||
peel_blocks_with_stmt,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, PatKind};
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, PathSegment, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{sym, symbol::Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -39,135 +42,190 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
|
||||
|
||||
impl QuestionMark {
|
||||
/// Checks if the given expression on the given context matches the following structure:
|
||||
enum IfBlockType<'hir> {
|
||||
/// An `if x.is_xxx() { a } else { b } ` expression.
|
||||
///
|
||||
/// ```ignore
|
||||
/// if option.is_none() {
|
||||
/// return None;
|
||||
/// }
|
||||
/// ```
|
||||
/// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)
|
||||
IfIs(
|
||||
&'hir Expr<'hir>,
|
||||
Ty<'hir>,
|
||||
Symbol,
|
||||
&'hir Expr<'hir>,
|
||||
Option<&'hir Expr<'hir>>,
|
||||
),
|
||||
/// An `if let Xxx(a) = b { c } else { d }` expression.
|
||||
///
|
||||
/// ```ignore
|
||||
/// if result.is_err() {
|
||||
/// return result;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If it matches, it will suggest to use the question mark operator instead
|
||||
fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
|
||||
if let ExprKind::MethodCall(segment, args, _) = &cond.kind;
|
||||
if let Some(subject) = args.get(0);
|
||||
if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
|
||||
(Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
|
||||
let mut replacement: Option<String> = None;
|
||||
if let Some(else_inner) = r#else {
|
||||
if eq_expr_value(cx, subject, peel_blocks(else_inner)) {
|
||||
replacement = Some(format!("Some({}?)", receiver_str));
|
||||
}
|
||||
} else if Self::moves_by_default(cx, subject)
|
||||
&& !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
|
||||
{
|
||||
replacement = Some(format!("{}.as_ref()?;", receiver_str));
|
||||
/// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
|
||||
/// if_else (d)
|
||||
IfLet(
|
||||
&'hir QPath<'hir>,
|
||||
Ty<'hir>,
|
||||
Symbol,
|
||||
&'hir Expr<'hir>,
|
||||
&'hir Expr<'hir>,
|
||||
Option<&'hir Expr<'hir>>,
|
||||
),
|
||||
}
|
||||
|
||||
/// Checks if the given expression on the given context matches the following structure:
|
||||
///
|
||||
/// ```ignore
|
||||
/// if option.is_none() {
|
||||
/// return None;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore
|
||||
/// if result.is_err() {
|
||||
/// return result;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If it matches, it will suggest to use the question mark operator instead
|
||||
fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
|
||||
if !is_else_clause(cx.tcx, expr);
|
||||
if let ExprKind::MethodCall(segment, args, _) = &cond.kind;
|
||||
if let Some(caller) = args.get(0);
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller);
|
||||
let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else);
|
||||
if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
|
||||
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx.at(caller.span), cx.param_env) &&
|
||||
!matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
|
||||
let sugg = if let Some(else_inner) = r#else {
|
||||
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
|
||||
format!("Some({}?)", receiver_str)
|
||||
} else {
|
||||
replacement = Some(format!("{}?;", receiver_str));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
format!("{}{}?;", receiver_str, if by_ref { ".as_ref()" } else { "" })
|
||||
};
|
||||
|
||||
if let Some(replacement_str) = replacement {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
QUESTION_MARK,
|
||||
expr.span,
|
||||
"this block may be rewritten with the `?` operator",
|
||||
"replace it with",
|
||||
replacement_str,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
QUESTION_MARK,
|
||||
expr.span,
|
||||
"this block may be rewritten with the `?` operator",
|
||||
"replace it with",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
|
||||
= higher::IfLet::hir(cx, expr);
|
||||
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
|
||||
if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
|
||||
(Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
|
||||
|
||||
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
|
||||
fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr);
|
||||
if !is_else_clause(cx.tcx, expr);
|
||||
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
|
||||
if let PatKind::Binding(annot, bind_id, ident, _) = fields[0].kind;
|
||||
let caller_ty = cx.typeck_results().expr_ty(let_expr);
|
||||
let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else);
|
||||
if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
|
||||
|| is_early_return(sym::Result, cx, &if_block);
|
||||
if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none();
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
|
||||
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
|
||||
if path_to_local_id(peel_blocks(if_then), bind_id);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
|
||||
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
|
||||
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
|
||||
let sugg = format!(
|
||||
"{}{}?{}",
|
||||
receiver_str,
|
||||
if by_ref { ".as_ref()" } else { "" },
|
||||
if requires_semi { ";" } else { "" }
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
QUESTION_MARK,
|
||||
expr.span,
|
||||
"this block may be rewritten with the `?` operator",
|
||||
"replace it with",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
QUESTION_MARK,
|
||||
expr.span,
|
||||
"this if-let-else may be rewritten with the `?` operator",
|
||||
"replace it with",
|
||||
replacement,
|
||||
applicability,
|
||||
);
|
||||
fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool {
|
||||
match *if_block {
|
||||
IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => {
|
||||
// If the block could be identified as `if x.is_none()/is_err()`,
|
||||
// we then only need to check the if_then return to see if it is none/err.
|
||||
is_type_diagnostic_item(cx, caller_ty, smbl)
|
||||
&& expr_return_none_or_err(smbl, cx, if_then, caller, None)
|
||||
&& match smbl {
|
||||
sym::Option => call_sym == sym!(is_none),
|
||||
sym::Result => call_sym == sym!(is_err),
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
IfBlockType::IfLet(qpath, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => {
|
||||
is_type_diagnostic_item(cx, let_expr_ty, smbl)
|
||||
&& match smbl {
|
||||
sym::Option => {
|
||||
// We only need to check `if let Some(x) = option` not `if let None = option`,
|
||||
// because the later one will be suggested as `if option.is_none()` thus causing conflict.
|
||||
is_lang_ctor(cx, qpath, OptionSome)
|
||||
&& if_else.is_some()
|
||||
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None)
|
||||
},
|
||||
sym::Result => {
|
||||
(is_lang_ctor(cx, qpath, ResultOk)
|
||||
&& if_else.is_some()
|
||||
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym)))
|
||||
|| is_lang_ctor(cx, qpath, ResultErr)
|
||||
&& expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_return_none_or_err(
|
||||
smbl: Symbol,
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
cond_expr: &Expr<'_>,
|
||||
err_sym: Option<Symbol>,
|
||||
) -> bool {
|
||||
match peel_blocks_with_stmt(expr).kind {
|
||||
ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym),
|
||||
ExprKind::Path(ref qpath) => match smbl {
|
||||
sym::Option => is_lang_ctor(cx, qpath, OptionNone),
|
||||
sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
|
||||
_ => false,
|
||||
},
|
||||
ExprKind::Call(call_expr, args_expr) => {
|
||||
if_chain! {
|
||||
if smbl == sym::Result;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind;
|
||||
if let Some(segment) = path.segments.first();
|
||||
if let Some(err_sym) = err_sym;
|
||||
if let Some(arg) = args_expr.first();
|
||||
if let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind;
|
||||
if let Some(PathSegment { ident, .. }) = arg_path.segments.first();
|
||||
then {
|
||||
return segment.ident.name == sym::Err && err_sym == ident.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
|
||||
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr)
|
||||
}
|
||||
|
||||
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
|
||||
Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
|
||||
}
|
||||
|
||||
fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expression);
|
||||
|
||||
!expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env)
|
||||
}
|
||||
|
||||
fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expression);
|
||||
|
||||
is_type_diagnostic_item(cx, expr_ty, sym::Option)
|
||||
}
|
||||
|
||||
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expression);
|
||||
|
||||
is_type_diagnostic_item(cx, expr_ty, sym::Result)
|
||||
}
|
||||
|
||||
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
match peel_blocks_with_stmt(expression).kind {
|
||||
ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr),
|
||||
ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
|
||||
match peel_blocks_with_stmt(expr).kind {
|
||||
ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr),
|
||||
ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
|
||||
_ => false,
|
||||
}
|
||||
false
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
Self::check_is_none_or_err_and_early_return(cx, expr);
|
||||
Self::check_if_let_some_or_err_and_early_return(cx, expr);
|
||||
check_is_none_or_err_and_early_return(cx, expr);
|
||||
check_if_let_some_or_err_and_early_return(cx, expr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -396,7 +396,7 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args:
|
|||
// `.iter()` and `.len()` called on same `Path`
|
||||
if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
|
||||
if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
|
||||
if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments);
|
||||
then {
|
||||
span_lint(cx,
|
||||
RANGE_ZIP_WITH_LEN,
|
||||
|
|
|
@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
if matches!(cx.tcx.def_kind(id.def_id), DefKind::Impl)
|
||||
&& let item = cx.tcx.hir().item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
|
|
@ -99,7 +99,7 @@ declare_clippy_lint! {
|
|||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Shadow {
|
||||
bindings: Vec<FxHashMap<Symbol, Vec<ItemLocalId>>>,
|
||||
bindings: Vec<(FxHashMap<Symbol, Vec<ItemLocalId>>, LocalDefId)>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]);
|
||||
|
@ -121,7 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
|
|||
|
||||
let HirId { owner, local_id } = id;
|
||||
// get (or insert) the list of items for this owner and symbol
|
||||
let data = self.bindings.last_mut().unwrap();
|
||||
let (ref mut data, scope_owner) = *self.bindings.last_mut().unwrap();
|
||||
let items_with_name = data.entry(ident.name).or_default();
|
||||
|
||||
// check other bindings with the same name, most recently seen first
|
||||
|
@ -131,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
|
|||
return;
|
||||
}
|
||||
|
||||
if is_shadow(cx, owner, prev, local_id) {
|
||||
if is_shadow(cx, scope_owner, prev, local_id) {
|
||||
let prev_hir_id = HirId { owner, local_id: prev };
|
||||
lint_shadow(cx, pat, prev_hir_id, ident.span);
|
||||
// only lint against the "nearest" shadowed binding
|
||||
|
@ -144,11 +144,9 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
|
|||
|
||||
fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
|
||||
let hir = cx.tcx.hir();
|
||||
if !matches!(
|
||||
hir.body_owner_kind(hir.body_owner_def_id(body.id())),
|
||||
BodyOwnerKind::Closure
|
||||
) {
|
||||
self.bindings.push(FxHashMap::default());
|
||||
let owner_id = hir.body_owner_def_id(body.id());
|
||||
if !matches!(hir.body_owner_kind(owner_id), BodyOwnerKind::Closure) {
|
||||
self.bindings.push((FxHashMap::default(), owner_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ fn is_ptr_to_ref(cx: &LateContext<'_>, e: &Expr<'_>, ctxt: SyntaxContext) -> (bo
|
|||
&& let ExprKind::Unary(UnOp::Deref, derefed_expr) = borrowed_expr.kind
|
||||
&& cx.typeck_results().expr_ty(derefed_expr).is_unsafe_ptr()
|
||||
{
|
||||
(true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then(|| derefed_expr.span))
|
||||
(true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then_some(derefed_expr.span))
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
|
||||
use clippy_utils::{SpanlessEq, SpanlessHash};
|
||||
use core::hash::{Hash, Hasher};
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::unhash::UnhashMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{
|
||||
GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitItem, Ty, TyKind,
|
||||
WherePredicate,
|
||||
GenericArg, GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath,
|
||||
TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use std::fmt::Write as _;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -35,8 +35,8 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
#[clippy::version = "1.38.0"]
|
||||
pub TYPE_REPETITION_IN_BOUNDS,
|
||||
pedantic,
|
||||
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
|
||||
nursery,
|
||||
"types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -63,10 +63,26 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// fn func<T>(arg: T) where T: Clone + Default {}
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// fn foo<T: Default + Default>(bar: T) {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn foo<T: Default>(bar: T) {}
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// fn foo<T>(bar: T) where T: Default + Default {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn foo<T>(bar: T) where T: Default {}
|
||||
/// ```
|
||||
#[clippy::version = "1.47.0"]
|
||||
pub TRAIT_DUPLICATION_IN_BOUNDS,
|
||||
pedantic,
|
||||
"Check if the same trait bounds are specified twice during a function declaration"
|
||||
nursery,
|
||||
"check if the same trait bounds are specified more than once during a generic declaration"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -87,6 +103,19 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
|
|||
fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
|
||||
self.check_type_repetition(cx, gen);
|
||||
check_trait_bound_duplication(cx, gen);
|
||||
check_bounds_or_where_duplication(cx, gen);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
// special handling for self trait bounds as these are not considered generics
|
||||
// ie. trait Foo: Display {}
|
||||
if let Item {
|
||||
kind: ItemKind::Trait(_, _, _, bounds, ..),
|
||||
..
|
||||
} = item
|
||||
{
|
||||
rollup_traits(cx, bounds, "these bounds contain repeated elements");
|
||||
}
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) {
|
||||
|
@ -178,30 +207,18 @@ impl TraitBounds {
|
|||
);
|
||||
|
||||
then {
|
||||
let mut hint_string = format!(
|
||||
"consider combining the bounds: `{}:",
|
||||
snippet(cx, p.bounded_ty.span, "_")
|
||||
let trait_bounds = v
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(p.bounds.iter())
|
||||
.filter_map(get_trait_info_from_bound)
|
||||
.map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability))
|
||||
.join(" + ");
|
||||
let hint_string = format!(
|
||||
"consider combining the bounds: `{}: {}`",
|
||||
snippet(cx, p.bounded_ty.span, "_"),
|
||||
trait_bounds,
|
||||
);
|
||||
for b in v.iter() {
|
||||
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
|
||||
let path = &poly_trait_ref.trait_ref.path;
|
||||
let _ = write!(hint_string,
|
||||
" {} +",
|
||||
snippet_with_applicability(cx, path.span, "..", &mut applicability)
|
||||
);
|
||||
}
|
||||
}
|
||||
for b in p.bounds.iter() {
|
||||
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
|
||||
let path = &poly_trait_ref.trait_ref.path;
|
||||
let _ = write!(hint_string,
|
||||
" {} +",
|
||||
snippet_with_applicability(cx, path.span, "..", &mut applicability)
|
||||
);
|
||||
}
|
||||
}
|
||||
hint_string.truncate(hint_string.len() - 2);
|
||||
hint_string.push('`');
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
TYPE_REPETITION_IN_BOUNDS,
|
||||
|
@ -253,10 +270,107 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
struct ComparableTraitRef(Res, Vec<Res>);
|
||||
|
||||
fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
|
||||
if gen.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
for predicate in gen.predicates {
|
||||
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate {
|
||||
let msg = if predicate.in_where_clause() {
|
||||
"these where clauses contain repeated elements"
|
||||
} else {
|
||||
"these bounds contain repeated elements"
|
||||
};
|
||||
rollup_traits(cx, bound_predicate.bounds, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
|
||||
if let GenericBound::Trait(t, _) = bound {
|
||||
Some((t.trait_ref.path.res, t.trait_ref.path.segments, t.span))
|
||||
if let GenericBound::Trait(t, tbm) = bound {
|
||||
let trait_path = t.trait_ref.path;
|
||||
let trait_span = {
|
||||
let path_span = trait_path.span;
|
||||
if let TraitBoundModifier::Maybe = tbm {
|
||||
path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?`
|
||||
} else {
|
||||
path_span
|
||||
}
|
||||
};
|
||||
Some((trait_path.res, trait_path.segments, trait_span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
|
||||
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
|
||||
ComparableTraitRef(
|
||||
trait_ref.path.res,
|
||||
trait_ref
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|segment| {
|
||||
// get trait bound type arguments
|
||||
Some(segment.args?.args.iter().filter_map(|arg| {
|
||||
if_chain! {
|
||||
if let GenericArg::Type(ty) = arg;
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
|
||||
then { return Some(path.res) }
|
||||
}
|
||||
None
|
||||
}))
|
||||
})
|
||||
.flatten()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) {
|
||||
let mut map = FxHashMap::default();
|
||||
let mut repeated_res = false;
|
||||
|
||||
let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
|
||||
if let GenericBound::Trait(t, _) = bound {
|
||||
Some((into_comparable_trait_ref(&t.trait_ref), t.span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
|
||||
let (comparable_bound, span_direct) = bound;
|
||||
if map.insert(comparable_bound, span_direct).is_some() {
|
||||
repeated_res = true;
|
||||
}
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if repeated_res;
|
||||
if let [first_trait, .., last_trait] = bounds;
|
||||
then {
|
||||
let all_trait_span = first_trait.span().to(last_trait.span());
|
||||
|
||||
let mut traits = map.values()
|
||||
.filter_map(|span| snippet_opt(cx, *span))
|
||||
.collect::<Vec<_>>();
|
||||
traits.sort_unstable();
|
||||
let traits = traits.join(" + ");
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRAIT_DUPLICATION_IN_BOUNDS,
|
||||
all_trait_span,
|
||||
msg,
|
||||
"try",
|
||||
traits,
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,19 +15,17 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
sym::String => "",
|
||||
_ => "<..>",
|
||||
};
|
||||
|
||||
let box_content = format!("{outer}{generic}", outer = item_type);
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
BOX_COLLECTION,
|
||||
hir_ty.span,
|
||||
&format!(
|
||||
"you seem to be trying to use `Box<{outer}{generic}>`. Consider using just `{outer}{generic}`",
|
||||
outer=item_type,
|
||||
generic = generic),
|
||||
"you seem to be trying to use `Box<{box_content}>`. Consider using just `{box_content}`"),
|
||||
None,
|
||||
&format!(
|
||||
"`{outer}{generic}` is already on the heap, `Box<{outer}{generic}>` makes an extra allocation",
|
||||
outer=item_type,
|
||||
generic = generic)
|
||||
"`{box_content}` is already on the heap, `Box<{box_content}>` makes an extra allocation")
|
||||
);
|
||||
true
|
||||
} else {
|
||||
|
@ -39,7 +37,18 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Symbol> {
|
||||
let param = qpath_generic_tys(qpath).next()?;
|
||||
let id = path_def_id(cx, param)?;
|
||||
cx.tcx
|
||||
.get_diagnostic_name(id)
|
||||
.filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec))
|
||||
cx.tcx.get_diagnostic_name(id).filter(|&name| {
|
||||
matches!(
|
||||
name,
|
||||
sym::HashMap
|
||||
| sym::String
|
||||
| sym::Vec
|
||||
| sym::HashSet
|
||||
| sym::VecDeque
|
||||
| sym::LinkedList
|
||||
| sym::BTreeMap
|
||||
| sym::BTreeSet
|
||||
| sym::BinaryHeap
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -265,14 +265,28 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
|
|||
}
|
||||
}
|
||||
|
||||
fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
|
||||
let body = cx.enclosing_body?;
|
||||
let map = cx.tcx.hir();
|
||||
let mut span = map.body(body).value.span;
|
||||
for (_, node) in map.parent_iter(body.hir_id) {
|
||||
match node {
|
||||
Node::Expr(e) => span = e.span,
|
||||
Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Some(span)
|
||||
}
|
||||
|
||||
fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
|
||||
let source_map = cx.sess().source_map();
|
||||
let ctxt = span.ctxt();
|
||||
if ctxt == SyntaxContext::root()
|
||||
&& let Some(body) = cx.enclosing_body
|
||||
&& let Some(search_span) = get_body_search_span(cx)
|
||||
{
|
||||
if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
&& let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
|
||||
&& let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
|
||||
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
|
|
|
@ -1,43 +1,40 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_parent_node;
|
||||
use clippy_utils::source::snippet_with_macro_callsite;
|
||||
use clippy_utils::visitors::for_each_value_source;
|
||||
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, Ty, TypeVisitable, TypeSuperVisitable, TypeVisitor};
|
||||
use rustc_middle::ty;
|
||||
|
||||
use super::LET_UNIT_VALUE;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
||||
if let StmtKind::Local(local) = stmt.kind
|
||||
&& let Some(init) = local.init
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
|
||||
if let Some(init) = local.init
|
||||
&& !local.pat.span.from_expansion()
|
||||
&& !in_external_macro(cx.sess(), stmt.span)
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& cx.typeck_results().pat_ty(local.pat).is_unit()
|
||||
{
|
||||
let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
|
||||
ControlFlow::Continue(())
|
||||
} else {
|
||||
ControlFlow::Break(())
|
||||
}).is_continue();
|
||||
|
||||
if needs_inferred {
|
||||
if !matches!(local.pat.kind, PatKind::Wild) {
|
||||
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
|
||||
|| matches!(local.pat.kind, PatKind::Tuple([], None)))
|
||||
&& expr_needs_inferred_result(cx, init)
|
||||
{
|
||||
if !matches!(local.pat.kind, PatKind::Wild | PatKind::Tuple([], None)) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LET_UNIT_VALUE,
|
||||
stmt.span,
|
||||
local.span,
|
||||
"this let-binding has unit value",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
local.pat.span,
|
||||
"use a wild (`_`) binding",
|
||||
"_",
|
||||
Applicability::MaybeIncorrect, // snippet
|
||||
);
|
||||
diag.span_suggestion(
|
||||
local.pat.span,
|
||||
"use a wild (`_`) binding",
|
||||
"_",
|
||||
Applicability::MaybeIncorrect, // snippet
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -45,15 +42,15 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
|||
span_lint_and_then(
|
||||
cx,
|
||||
LET_UNIT_VALUE,
|
||||
stmt.span,
|
||||
local.span,
|
||||
"this let-binding has unit value",
|
||||
|diag| {
|
||||
if let Some(expr) = &local.init {
|
||||
let snip = snippet_with_macro_callsite(cx, expr.span, "()");
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
local.span,
|
||||
"omit the `let` binding",
|
||||
format!("{};", snip),
|
||||
format!("{snip};"),
|
||||
Applicability::MachineApplicable, // snippet
|
||||
);
|
||||
}
|
||||
|
@ -63,48 +60,106 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
let id = match e.kind {
|
||||
/// Checks sub-expressions which create the value returned by the given expression for whether
|
||||
/// return value inference is needed. This checks through locals to see if they also need inference
|
||||
/// at this point.
|
||||
///
|
||||
/// e.g.
|
||||
/// ```rust,ignore
|
||||
/// let bar = foo();
|
||||
/// let x: u32 = if true { baz() } else { bar };
|
||||
/// ```
|
||||
/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the
|
||||
/// initialization of `bar`. If both `foo` and `baz` have a return type which require type
|
||||
/// inference then this function would return `true`.
|
||||
fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
|
||||
// The locals used for initialization which have yet to be checked.
|
||||
let mut locals_to_check = Vec::new();
|
||||
// All the locals which have been added to `locals_to_check`. Needed to prevent cycles.
|
||||
let mut seen_locals = HirIdSet::default();
|
||||
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
|
||||
return false;
|
||||
}
|
||||
while let Some(id) = locals_to_check.pop() {
|
||||
if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
|
||||
if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
|
||||
return false;
|
||||
}
|
||||
if let Some(e) = l.init {
|
||||
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
|
||||
return false;
|
||||
}
|
||||
} else if for_each_local_assignment(cx, id, |e| {
|
||||
if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
|
||||
ControlFlow::Continue(())
|
||||
} else {
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
})
|
||||
.is_break()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn each_value_source_needs_inference(
|
||||
cx: &LateContext<'_>,
|
||||
e: &Expr<'_>,
|
||||
locals_to_check: &mut Vec<HirId>,
|
||||
seen_locals: &mut HirIdSet,
|
||||
) -> bool {
|
||||
for_each_value_source(e, &mut |e| {
|
||||
if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) {
|
||||
ControlFlow::Continue(())
|
||||
} else {
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
})
|
||||
.is_continue()
|
||||
}
|
||||
|
||||
fn needs_inferred_result_ty(
|
||||
cx: &LateContext<'_>,
|
||||
e: &Expr<'_>,
|
||||
locals_to_check: &mut Vec<HirId>,
|
||||
seen_locals: &mut HirIdSet,
|
||||
) -> bool {
|
||||
let (id, args) = match e.kind {
|
||||
ExprKind::Call(
|
||||
Expr {
|
||||
kind: ExprKind::Path(ref path),
|
||||
hir_id,
|
||||
..
|
||||
},
|
||||
_,
|
||||
args,
|
||||
) => match cx.qpath_res(path, *hir_id) {
|
||||
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id,
|
||||
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args),
|
||||
_ => return false,
|
||||
},
|
||||
ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
|
||||
Some(id) => id,
|
||||
ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
|
||||
Some(id) => (id, args),
|
||||
None => return false,
|
||||
},
|
||||
ExprKind::Path(QPath::Resolved(None, path)) => {
|
||||
if let Res::Local(id) = path.res
|
||||
&& seen_locals.insert(id)
|
||||
{
|
||||
locals_to_check.push(id);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
_ => return false,
|
||||
};
|
||||
let sig = cx.tcx.fn_sig(id).skip_binder();
|
||||
if let ty::Param(output_ty) = *sig.output().kind() {
|
||||
sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
|
||||
sig.inputs().iter().zip(args).all(|(&ty, arg)| {
|
||||
!ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
|
||||
struct Visitor(u32);
|
||||
impl<'tcx> TypeVisitor<'tcx> for Visitor {
|
||||
type BreakTy = ();
|
||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
if let ty::Param(ty) = *ty.kind() {
|
||||
if ty.index == self.0 {
|
||||
ControlFlow::BREAK
|
||||
} else {
|
||||
ControlFlow::CONTINUE
|
||||
}
|
||||
} else {
|
||||
ty.super_visit_with(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
ty.visit_with(&mut Visitor(index)).is_break()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ mod unit_arg;
|
|||
mod unit_cmp;
|
||||
mod utils;
|
||||
|
||||
use rustc_hir::{Expr, Stmt};
|
||||
use rustc_hir::{Expr, Local};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
|
@ -98,9 +98,9 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
|
||||
|
||||
impl LateLintPass<'_> for UnitTypes {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
||||
let_unit_value::check(cx, stmt);
|
||||
impl<'tcx> LateLintPass<'tcx> for UnitTypes {
|
||||
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
|
||||
let_unit_value::check(cx, local);
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
|
|
|
@ -20,8 +20,8 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_hir::hir_id::CRATE_HIR_ID;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_hir::{
|
||||
BinOpKind, Block, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty, TyKind,
|
||||
UnOp,
|
||||
BinOpKind, Block, Closure, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty,
|
||||
TyKind, UnOp,
|
||||
};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
@ -730,8 +730,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
|
|||
if let ExprKind::Call(func, and_then_args) = expr.kind;
|
||||
if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
|
||||
if and_then_args.len() == 5;
|
||||
if let ExprKind::Closure { body, .. } = &and_then_args[4].kind;
|
||||
let body = cx.tcx.hir().body(*body);
|
||||
if let ExprKind::Closure(&Closure { body, .. }) = &and_then_args[4].kind;
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let only_expr = peel_blocks_with_stmt(&body.value);
|
||||
if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind;
|
||||
if let ExprKind::Path(..) = span_call_args[0].kind;
|
||||
|
|
|
@ -17,7 +17,7 @@ use if_chain::if_chain;
|
|||
use rustc_ast as ast;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{
|
||||
self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
|
||||
self as hir, def::DefKind, intravisit, intravisit::Visitor, Closure, ExprKind, Item, ItemKind, Mutability, QPath,
|
||||
};
|
||||
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
@ -843,7 +843,7 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
|
|||
fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
|
||||
DEFAULT_LINT_LEVELS
|
||||
.iter()
|
||||
.find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
|
||||
.find_map(|(group_name, group_level)| (*group_name == lint_group).then_some(*group_level))
|
||||
}
|
||||
|
||||
pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
|
||||
|
@ -958,7 +958,7 @@ fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hi
|
|||
}
|
||||
|
||||
fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
|
||||
if let ExprKind::Closure { body, .. } = closure_expr.kind {
|
||||
if let ExprKind::Closure(&Closure { body, .. }) = closure_expr.kind {
|
||||
let mut scanner = IsMultiSpanScanner::new(cx);
|
||||
intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body));
|
||||
return scanner.is_multi_part();
|
||||
|
@ -1018,7 +1018,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
|
|||
/// This visitor finds the highest applicability value in the visited expressions
|
||||
struct ApplicabilityResolver<'a, 'hir> {
|
||||
cx: &'a LateContext<'hir>,
|
||||
/// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
|
||||
/// This is the index of highest `Applicability` for `paths::APPLICABILITY_VALUES`
|
||||
applicability_index: Option<usize>,
|
||||
}
|
||||
|
||||
|
|
|
@ -515,7 +515,7 @@ impl Write {
|
|||
args.push(arg, span);
|
||||
}
|
||||
|
||||
parser.errors.is_empty().then(move || args)
|
||||
parser.errors.is_empty().then_some(args)
|
||||
}
|
||||
|
||||
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
|
||||
|
|
|
@ -169,7 +169,12 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
|
|||
(Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
|
||||
(Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm),
|
||||
(Closure(lb, lc, la, lm, lf, le, _), Closure(rb, rc, ra, rm, rf, re, _)) => {
|
||||
eq_closure_binder(lb, rb) && lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(le, re)
|
||||
eq_closure_binder(lb, rb)
|
||||
&& lc == rc
|
||||
&& la.is_async() == ra.is_async()
|
||||
&& lm == rm
|
||||
&& eq_fn_decl(lf, rf)
|
||||
&& eq_expr(le, re)
|
||||
},
|
||||
(Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
|
||||
(Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
|
||||
|
@ -564,8 +569,9 @@ pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
|
|||
pub fn eq_closure_binder(l: &ClosureBinder, r: &ClosureBinder) -> bool {
|
||||
match (l, r) {
|
||||
(ClosureBinder::NotPresent, ClosureBinder::NotPresent) => true,
|
||||
(ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) =>
|
||||
lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r)),
|
||||
(ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) => {
|
||||
lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -610,7 +616,7 @@ pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
|
|||
use Extern::*;
|
||||
match (l, r) {
|
||||
(None, None) | (Implicit(_), Implicit(_)) => true,
|
||||
(Explicit(l,_), Explicit(r,_)) => eq_str_lit(l, r),
|
||||
(Explicit(l, _), Explicit(r, _)) => eq_str_lit(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,13 +155,7 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
pub fn span_lint_hir(
|
||||
cx: &LateContext<'_>,
|
||||
lint: &'static Lint,
|
||||
hir_id: HirId,
|
||||
sp: Span,
|
||||
msg: &str,
|
||||
) {
|
||||
pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
|
||||
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
docs_link(&mut diag, lint);
|
||||
|
|
|
@ -6,9 +6,9 @@ use rustc_data_structures::fx::FxHasher;
|
|||
use rustc_hir::def::Res;
|
||||
use rustc_hir::HirIdMap;
|
||||
use rustc_hir::{
|
||||
ArrayLen, BinOpKind, Closure, Block, BodyId, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, HirId,
|
||||
InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath, Stmt,
|
||||
StmtKind, Ty, TyKind, TypeBinding,
|
||||
ArrayLen, BinOpKind, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard,
|
||||
HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath,
|
||||
Stmt, StmtKind, Ty, TyKind, TypeBinding,
|
||||
};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -102,7 +102,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> {
|
|||
impl HirEqInterExpr<'_, '_, '_> {
|
||||
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(&StmtKind::Local(l, ), &StmtKind::Local(r, )) => {
|
||||
(&StmtKind::Local(l), &StmtKind::Local(r)) => {
|
||||
// This additional check ensures that the type of the locals are equivalent even if the init
|
||||
// expression or type have some inferred parts.
|
||||
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
|
||||
|
@ -922,7 +922,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
std::mem::discriminant(&b.kind).hash(&mut self.s);
|
||||
|
||||
match &b.kind {
|
||||
StmtKind::Local(local, ) => {
|
||||
StmtKind::Local(local) => {
|
||||
self.hash_pat(local.pat);
|
||||
if let Some(init) = local.init {
|
||||
self.hash_expr(init);
|
||||
|
|
|
@ -93,7 +93,9 @@ use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{
|
|||
ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType,
|
||||
PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
|
||||
};
|
||||
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture};
|
||||
use rustc_middle::ty::{
|
||||
layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture,
|
||||
};
|
||||
use rustc_middle::ty::{FloatTy, IntTy, UintTy};
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::Session;
|
||||
|
@ -105,7 +107,7 @@ use rustc_span::{Span, DUMMY_SP};
|
|||
use rustc_target::abi::Integer;
|
||||
|
||||
use crate::consts::{constant, Constant};
|
||||
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
|
||||
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
|
||||
use crate::visitors::expr_visitor_no_bodies;
|
||||
|
||||
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
|
||||
|
@ -890,7 +892,7 @@ pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Captur
|
|||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
|
||||
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
|
||||
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
|
||||
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
|
||||
return CaptureKind::Ref(Mutability::Mut);
|
||||
},
|
||||
ExprKind::Field(..) => {
|
||||
|
@ -1016,7 +1018,7 @@ pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'
|
|||
captures: HirIdMap::default(),
|
||||
};
|
||||
v.visit_expr(expr);
|
||||
v.allow_closure.then(|| v.captures)
|
||||
v.allow_closure.then_some(v.captures)
|
||||
}
|
||||
|
||||
/// Returns the method names and argument list of nested method call expressions that make up
|
||||
|
@ -1197,16 +1199,54 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
|
|||
}
|
||||
|
||||
/// Gets the loop or closure enclosing the given expression, if any.
|
||||
pub fn get_enclosing_loop_or_closure<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
for (_, node) in tcx.hir().parent_iter(expr.hir_id) {
|
||||
pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'_>,
|
||||
) -> Option<&'tcx Expr<'tcx>> {
|
||||
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
match node {
|
||||
Node::Expr(
|
||||
e @ Expr {
|
||||
kind: ExprKind::Loop(..) | ExprKind::Closure { .. },
|
||||
..
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Closure { .. } => {
|
||||
if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
|
||||
&& subs.as_closure().kind() == ClosureKind::FnOnce
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let is_once = walk_to_expr_usage(cx, e, |node, id| {
|
||||
let Node::Expr(e) = node else {
|
||||
return None;
|
||||
};
|
||||
match e.kind {
|
||||
ExprKind::Call(f, _) if f.hir_id == id => Some(()),
|
||||
ExprKind::Call(f, args) => {
|
||||
let i = args.iter().position(|arg| arg.hir_id == id)?;
|
||||
let sig = expr_sig(cx, f)?;
|
||||
let predicates = sig
|
||||
.predicates_id()
|
||||
.map_or(cx.param_env, |id| cx.tcx.param_env(id))
|
||||
.caller_bounds();
|
||||
sig.input(i).and_then(|ty| {
|
||||
ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
|
||||
})
|
||||
},
|
||||
ExprKind::MethodCall(_, args, _) => {
|
||||
let i = args.iter().position(|arg| arg.hir_id == id)?;
|
||||
let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
|
||||
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i];
|
||||
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
if !is_once {
|
||||
return Some(e);
|
||||
}
|
||||
},
|
||||
) => return Some(e),
|
||||
Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
|
||||
ExprKind::Loop(..) => return Some(e),
|
||||
_ => (),
|
||||
},
|
||||
Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ macro_rules! msrv_aliases {
|
|||
|
||||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,62,0 { BOOL_THEN_SOME }
|
||||
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN }
|
||||
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
|
||||
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
|
||||
|
|
|
@ -163,6 +163,7 @@ pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"];
|
|||
pub const STR_CHARS: [&str; 4] = ["core", "str", "<impl str>", "chars"];
|
||||
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
|
||||
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
|
||||
pub const STR_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"];
|
||||
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
|
||||
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
|
||||
#[cfg(feature = "internal")]
|
||||
|
|
|
@ -353,7 +353,7 @@ pub fn snippet_with_context<'a>(
|
|||
/// span containing `m!(0)`.
|
||||
pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
|
||||
let outer_span = hygiene::walk_chain(span, outer);
|
||||
(outer_span.ctxt() == outer).then(|| outer_span)
|
||||
(outer_span.ctxt() == outer).then_some(outer_span)
|
||||
}
|
||||
|
||||
/// Removes block comments from the given `Vec` of lines.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite};
|
||||
use crate::ty::expr_sig;
|
||||
use crate::{get_parent_expr_for_hir, higher};
|
||||
use rustc_ast::util::parser::AssocOp;
|
||||
use rustc_ast::{ast, token};
|
||||
|
@ -18,7 +19,6 @@ use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
|
|||
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Display, Write as _};
|
||||
use std::iter;
|
||||
use std::ops::{Add, Neg, Not, Sub};
|
||||
|
||||
/// A helper type to build suggestion correctly handling parentheses.
|
||||
|
@ -861,23 +861,37 @@ impl<'tcx> DerefDelegate<'_, 'tcx> {
|
|||
|
||||
/// indicates whether the function from `parent_expr` takes its args by double reference
|
||||
fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
|
||||
let (call_args, inputs) = match parent_expr.kind {
|
||||
let ty = match parent_expr.kind {
|
||||
ExprKind::MethodCall(_, call_args, _) => {
|
||||
if let Some(method_did) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) {
|
||||
(call_args, self.cx.tcx.fn_sig(method_did).skip_binder().inputs())
|
||||
if let Some(sig) = self
|
||||
.cx
|
||||
.typeck_results()
|
||||
.type_dependent_def_id(parent_expr.hir_id)
|
||||
.map(|did| self.cx.tcx.fn_sig(did).skip_binder())
|
||||
{
|
||||
call_args
|
||||
.iter()
|
||||
.position(|arg| arg.hir_id == cmt_hir_id)
|
||||
.map(|i| sig.inputs()[i])
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, call_args) => {
|
||||
let typ = self.cx.typeck_results().expr_ty(func);
|
||||
(call_args, typ.fn_sig(self.cx.tcx).skip_binder().inputs())
|
||||
if let Some(sig) = expr_sig(self.cx, func) {
|
||||
call_args
|
||||
.iter()
|
||||
.position(|arg| arg.hir_id == cmt_hir_id)
|
||||
.and_then(|i| sig.input(i))
|
||||
.map(ty::Binder::skip_binder)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
iter::zip(call_args, inputs)
|
||||
.any(|(arg, ty)| arg.hir_id == cmt_hir_id && matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
|
||||
ty.map_or(false, |ty| matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
|
|||
/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
|
||||
/// * [Common tools for writing lints] for an example how to use this function and other options.
|
||||
///
|
||||
/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
|
||||
/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
|
||||
pub fn implements_trait<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
|
@ -501,7 +501,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(P
|
|||
/// A signature for a function like type.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ExprFnSig<'tcx> {
|
||||
Sig(Binder<'tcx, FnSig<'tcx>>),
|
||||
Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
|
||||
Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),
|
||||
Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>),
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
/// bounds only for variadic functions, otherwise this will panic.
|
||||
pub fn input(self, i: usize) -> Option<Binder<'tcx, Ty<'tcx>>> {
|
||||
match self {
|
||||
Self::Sig(sig) => {
|
||||
Self::Sig(sig, _) => {
|
||||
if sig.c_variadic() {
|
||||
sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose()
|
||||
} else {
|
||||
|
@ -527,7 +527,7 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
/// functions, otherwise this will panic.
|
||||
pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> {
|
||||
match self {
|
||||
Self::Sig(sig) => {
|
||||
Self::Sig(sig, _) => {
|
||||
if sig.c_variadic() {
|
||||
sig.inputs()
|
||||
.map_bound(|inputs| inputs.get(i).copied())
|
||||
|
@ -549,22 +549,29 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
/// specified.
|
||||
pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
|
||||
match self {
|
||||
Self::Sig(sig) | Self::Closure(_, sig) => Some(sig.output()),
|
||||
Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()),
|
||||
Self::Trait(_, output) => output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn predicates_id(&self) -> Option<DefId> {
|
||||
if let ExprFnSig::Sig(_, id) = *self { id } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
/// If the expression is function like, get the signature for it.
|
||||
pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> {
|
||||
if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) {
|
||||
Some(ExprFnSig::Sig(cx.tcx.fn_sig(id)))
|
||||
Some(ExprFnSig::Sig(cx.tcx.fn_sig(id), Some(id)))
|
||||
} else {
|
||||
ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs())
|
||||
}
|
||||
}
|
||||
|
||||
fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
|
||||
if ty.is_box() {
|
||||
return ty_sig(cx, ty.boxed_ty());
|
||||
}
|
||||
match *ty.kind() {
|
||||
ty::Closure(id, subs) => {
|
||||
let decl = id
|
||||
|
@ -572,8 +579,9 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>>
|
|||
.and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id)));
|
||||
Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
|
||||
},
|
||||
ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))),
|
||||
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)),
|
||||
ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))),
|
||||
ty::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)),
|
||||
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
|
||||
ty::Dynamic(bounds, _) => {
|
||||
let lang_items = cx.tcx.lang_items();
|
||||
match bounds.principal() {
|
||||
|
@ -789,3 +797,33 @@ pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx Va
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`.
|
||||
pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [Predicate<'_>]) -> bool {
|
||||
let ty::Param(ty) = *ty.kind() else {
|
||||
return false;
|
||||
};
|
||||
let lang = tcx.lang_items();
|
||||
let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id))
|
||||
= (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
predicates
|
||||
.iter()
|
||||
.try_fold(false, |found, p| {
|
||||
if let PredicateKind::Trait(p) = p.kind().skip_binder()
|
||||
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
|
||||
&& ty.index == self_ty.index
|
||||
{
|
||||
// This should use `super_traits_of`, but that's a private function.
|
||||
if p.trait_ref.def_id == fn_once_id {
|
||||
return Some(true);
|
||||
} else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(found)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use rustc_hir as hir;
|
|||
use rustc_hir::def::{CtorKind, DefKind, Res};
|
||||
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
|
||||
use rustc_hir::{
|
||||
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp,
|
||||
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp,
|
||||
UnsafeSource, Unsafety,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -13,6 +13,74 @@ use rustc_middle::hir::map::Map;
|
|||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{self, Ty, TypeckResults};
|
||||
use rustc_span::Span;
|
||||
|
||||
mod internal {
|
||||
/// Trait for visitor functions to control whether or not to descend to child nodes. Implemented
|
||||
/// for only two types. `()` always descends. `Descend` allows controlled descent.
|
||||
pub trait Continue {
|
||||
fn descend(&self) -> bool;
|
||||
}
|
||||
}
|
||||
use internal::Continue;
|
||||
|
||||
impl Continue for () {
|
||||
fn descend(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows for controlled descent when using visitor functions. Use `()` instead when always
|
||||
/// descending into child nodes.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Descend {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
impl From<bool> for Descend {
|
||||
fn from(from: bool) -> Self {
|
||||
if from { Self::Yes } else { Self::No }
|
||||
}
|
||||
}
|
||||
impl Continue for Descend {
|
||||
fn descend(&self) -> bool {
|
||||
matches!(self, Self::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given function once for each expression contained. This does not enter any bodies or
|
||||
/// nested items.
|
||||
pub fn for_each_expr<'tcx, B, C: Continue>(
|
||||
node: impl Visitable<'tcx>,
|
||||
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
|
||||
) -> Option<B> {
|
||||
struct V<B, F> {
|
||||
f: F,
|
||||
res: Option<B>,
|
||||
}
|
||||
impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<B, F> {
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
|
||||
if self.res.is_some() {
|
||||
return;
|
||||
}
|
||||
match (self.f)(e) {
|
||||
ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
|
||||
ControlFlow::Break(b) => self.res = Some(b),
|
||||
ControlFlow::Continue(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid unnecessary `walk_*` calls.
|
||||
fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
|
||||
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
|
||||
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
|
||||
// Avoid monomorphising all `visit_*` functions.
|
||||
fn visit_nested_item(&mut self, _: ItemId) {}
|
||||
}
|
||||
let mut v = V { f, res: None };
|
||||
node.visit(&mut v);
|
||||
v.res
|
||||
}
|
||||
|
||||
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
|
||||
/// bodies (i.e. closures) are visited.
|
||||
|
@ -617,3 +685,49 @@ pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx
|
|||
})
|
||||
.is_break()
|
||||
}
|
||||
|
||||
/// Runs the given function for each path expression referencing the given local which occur after
|
||||
/// the given expression.
|
||||
pub fn for_each_local_assignment<'tcx, B>(
|
||||
cx: &LateContext<'tcx>,
|
||||
local_id: HirId,
|
||||
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
|
||||
) -> ControlFlow<B> {
|
||||
struct V<'cx, 'tcx, F, B> {
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
local_id: HirId,
|
||||
res: ControlFlow<B>,
|
||||
f: F,
|
||||
}
|
||||
impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
|
||||
type NestedFilter = nested_filter::OnlyBodies;
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
|
||||
if let ExprKind::Assign(lhs, rhs, _) = e.kind
|
||||
&& self.res.is_continue()
|
||||
&& path_to_local_id(lhs, self.local_id)
|
||||
{
|
||||
self.res = (self.f)(rhs);
|
||||
self.visit_expr(rhs);
|
||||
} else {
|
||||
walk_expr(self, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(b) = get_enclosing_block(cx, local_id) {
|
||||
let mut v = V {
|
||||
cx,
|
||||
local_id,
|
||||
res: ControlFlow::Continue(()),
|
||||
f,
|
||||
};
|
||||
v.visit_block(b);
|
||||
v.res
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "lintcheck"
|
||||
version = "0.0.1"
|
||||
description = "tool to monitor impact of changes in Clippys lints on a part of the ecosystem"
|
||||
description = "tool to monitor impact of changes in Clippy's lints on a part of the ecosystem"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
|
@ -11,7 +11,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
cargo_metadata = "0.14"
|
||||
clap = "3.1"
|
||||
clap = "3.2"
|
||||
flate2 = "1.0"
|
||||
rayon = "1.5.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -70,7 +70,7 @@ is explicitly specified in the options.
|
|||
|
||||
### Fix mode
|
||||
You can run `./lintcheck/target/debug/lintcheck --fix` which will run Clippy with `--fix` and
|
||||
print a warning if Clippys suggestions fail to apply (if the resulting code does not build).
|
||||
print a warning if Clippy's suggestions fail to apply (if the resulting code does not build).
|
||||
This lets us spot bad suggestions or false positives automatically in some cases.
|
||||
|
||||
Please note that the target dir should be cleaned afterwards since clippy will modify
|
||||
|
|
|
@ -545,7 +545,7 @@ fn lintcheck_needs_rerun(lintcheck_logs_path: &Path) -> bool {
|
|||
fn main() {
|
||||
// assert that we launch lintcheck from the repo root (via cargo lintcheck)
|
||||
if std::fs::metadata("lintcheck/Cargo.toml").is_err() {
|
||||
eprintln!("lintcheck needs to be run from clippys repo root!\nUse `cargo lintcheck` alternatively.");
|
||||
eprintln!("lintcheck needs to be run from clippy's repo root!\nUse `cargo lintcheck` alternatively.");
|
||||
std::process::exit(3);
|
||||
}
|
||||
|
||||
|
@ -586,7 +586,7 @@ fn main() {
|
|||
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
|
||||
.expect("could not get clippy version!");
|
||||
|
||||
// download and extract the crates, then run clippy on them and collect clippys warnings
|
||||
// download and extract the crates, then run clippy on them and collect clippy's warnings
|
||||
// flatten into one big list of warnings
|
||||
|
||||
let crates = read_crates(&config.sources_toml_path);
|
||||
|
|
|
@ -394,6 +394,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
|
|||
"single_component_path_imports_nested_first.rs",
|
||||
"string_add.rs",
|
||||
"toplevel_ref_arg_non_rustfix.rs",
|
||||
"trait_duplication_in_bounds.rs",
|
||||
"unit_arg.rs",
|
||||
"unnecessary_clone.rs",
|
||||
"unnecessary_lazy_eval_unfixable.rs",
|
||||
|
|
|
@ -74,10 +74,16 @@ fn run_clippy_for_package(project: &str, args: &[&str]) {
|
|||
.env("CARGO_INCREMENTAL", "0")
|
||||
.arg("clippy")
|
||||
.arg("--all-targets")
|
||||
.arg("--all-features")
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
|
||||
.arg("--all-features");
|
||||
|
||||
if let Ok(dogfood_args) = std::env::var("__CLIPPY_DOGFOOD_ARGS") {
|
||||
for arg in dogfood_args.split_whitespace() {
|
||||
command.arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
command.arg("--").args(args);
|
||||
command.arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
|
||||
|
||||
if cfg!(feature = "internal") {
|
||||
// internal lints only exist if we build with the internal feature
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#![feature(once_cell)]
|
||||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use regex::RegexSet;
|
||||
|
||||
|
@ -14,42 +16,45 @@ struct Message {
|
|||
|
||||
impl Message {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let content: String = std::fs::read_to_string(&path).unwrap();
|
||||
// we don't want the first letter after "error: ", "help: " ... to be capitalized
|
||||
// also no punctuation (except for "?" ?) at the end of a line
|
||||
let regex_set: RegexSet = RegexSet::new(&[
|
||||
r"error: [A-Z]",
|
||||
r"help: [A-Z]",
|
||||
r"warning: [A-Z]",
|
||||
r"note: [A-Z]",
|
||||
r"try this: [A-Z]",
|
||||
r"error: .*[.!]$",
|
||||
r"help: .*[.!]$",
|
||||
r"warning: .*[.!]$",
|
||||
r"note: .*[.!]$",
|
||||
r"try this: .*[.!]$",
|
||||
])
|
||||
.unwrap();
|
||||
static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
|
||||
RegexSet::new(&[
|
||||
r"error: [A-Z]",
|
||||
r"help: [A-Z]",
|
||||
r"warning: [A-Z]",
|
||||
r"note: [A-Z]",
|
||||
r"try this: [A-Z]",
|
||||
r"error: .*[.!]$",
|
||||
r"help: .*[.!]$",
|
||||
r"warning: .*[.!]$",
|
||||
r"note: .*[.!]$",
|
||||
r"try this: .*[.!]$",
|
||||
])
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or
|
||||
// we want to ask a question ending in "?"
|
||||
let exceptions_set: RegexSet = RegexSet::new(&[
|
||||
r".*C-like enum variant discriminant is not portable to 32-bit targets",
|
||||
r".*did you mean `unix`?",
|
||||
r".*the arguments may be inverted...",
|
||||
r".*Intel x86 assembly syntax used",
|
||||
r".*AT&T x86 assembly syntax used",
|
||||
r".*remove .*the return type...",
|
||||
r"note: Clippy version: .*",
|
||||
r"the compiler unexpectedly panicked. this is a bug.",
|
||||
])
|
||||
.unwrap();
|
||||
static EXCEPTIONS_SET: LazyLock<RegexSet> = LazyLock::new(|| {
|
||||
RegexSet::new(&[
|
||||
r"\.\.\.$",
|
||||
r".*C-like enum variant discriminant is not portable to 32-bit targets",
|
||||
r".*Intel x86 assembly syntax used",
|
||||
r".*AT&T x86 assembly syntax used",
|
||||
r"note: Clippy version: .*",
|
||||
r"the compiler unexpectedly panicked. this is a bug.",
|
||||
])
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let content: String = std::fs::read_to_string(&path).unwrap();
|
||||
|
||||
let bad_lines = content
|
||||
.lines()
|
||||
.filter(|line| regex_set.matches(line).matched_any())
|
||||
.filter(|line| REGEX_SET.matches(line).matched_any())
|
||||
// ignore exceptions
|
||||
.filter(|line| !exceptions_set.matches(line).matched_any())
|
||||
.filter(|line| !EXCEPTIONS_SET.matches(line).matched_any())
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#[feature(lint_reasons)]
|
||||
#![feature(lint_reasons)]
|
||||
|
||||
mod a;
|
||||
|
||||
|
@ -25,5 +25,4 @@ mod d3;
|
|||
#[allow(clippy::duplicate_mod)]
|
||||
mod d4;
|
||||
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -24,6 +24,17 @@ LL | | mod c3;
|
|||
|
|
||||
= help: replace all but one `mod` item with `use` items
|
||||
|
||||
error: file is loaded as a module multiple times: `$DIR/d.rs`
|
||||
--> $DIR/main.rs:18:1
|
||||
|
|
||||
LL | mod d;
|
||||
| ^^^^^^ first loaded here
|
||||
LL | / #[path = "d.rs"]
|
||||
LL | | mod d2;
|
||||
| |_______^ loaded again here
|
||||
|
|
||||
= help: replace all but one `mod` item with `use` items
|
||||
|
||||
error: file is loaded as a module multiple times: `$DIR/from_other_module.rs`
|
||||
--> $DIR/main.rs:15:1
|
||||
|
|
||||
|
@ -38,16 +49,5 @@ LL | | mod m;
|
|||
|
|
||||
= help: replace all but one `mod` item with `use` items
|
||||
|
||||
error: file is loaded as a module multiple times: `$DIR/b.rs`
|
||||
--> $DIR/main.rs:18:1
|
||||
|
|
||||
LL | mod d;
|
||||
| ^^^^^^ first loaded here
|
||||
LL | / #[path = "d.rs"]
|
||||
LL | | mod d2;
|
||||
| |_______^ loaded again here
|
||||
|
|
||||
= help: replace all but one `mod` item with `use` items
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
|
|
@ -135,3 +135,8 @@ macro_rules! manual_rem_euclid {
|
|||
let _: i32 = ((value % 4) + 4) % 4;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! equatable_if_let {
|
||||
($a:ident) => {{ if let 2 = $a {} }};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
unused
|
||||
)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
|
||||
|
||||
macro_rules! boxit {
|
||||
($init:expr, $x:ty) => {
|
||||
|
@ -18,7 +18,7 @@ fn test_macro() {
|
|||
boxit!(Vec::new(), Vec<u8>);
|
||||
}
|
||||
|
||||
fn test(foo: Box<Vec<bool>>) {}
|
||||
fn test1(foo: Box<Vec<bool>>) {}
|
||||
|
||||
fn test2(foo: Box<dyn Fn(Vec<u32>)>) {
|
||||
// pass if #31 is fixed
|
||||
|
@ -29,6 +29,18 @@ fn test3(foo: Box<String>) {}
|
|||
|
||||
fn test4(foo: Box<HashMap<String, String>>) {}
|
||||
|
||||
fn test5(foo: Box<HashSet<i64>>) {}
|
||||
|
||||
fn test6(foo: Box<VecDeque<i32>>) {}
|
||||
|
||||
fn test7(foo: Box<LinkedList<i16>>) {}
|
||||
|
||||
fn test8(foo: Box<BTreeMap<i8, String>>) {}
|
||||
|
||||
fn test9(foo: Box<BTreeSet<u64>>) {}
|
||||
|
||||
fn test10(foo: Box<BinaryHeap<u32>>) {}
|
||||
|
||||
fn test_local_not_linted() {
|
||||
let _: Box<Vec<bool>>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
error: you seem to be trying to use `Box<Vec<..>>`. Consider using just `Vec<..>`
|
||||
--> $DIR/box_collection.rs:21:14
|
||||
--> $DIR/box_collection.rs:21:15
|
||||
|
|
||||
LL | fn test(foo: Box<Vec<bool>>) {}
|
||||
| ^^^^^^^^^^^^^^
|
||||
LL | fn test1(foo: Box<Vec<bool>>) {}
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::box-collection` implied by `-D warnings`
|
||||
= help: `Vec<..>` is already on the heap, `Box<Vec<..>>` makes an extra allocation
|
||||
|
@ -23,5 +23,53 @@ LL | fn test4(foo: Box<HashMap<String, String>>) {}
|
|||
|
|
||||
= help: `HashMap<..>` is already on the heap, `Box<HashMap<..>>` makes an extra allocation
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: you seem to be trying to use `Box<HashSet<..>>`. Consider using just `HashSet<..>`
|
||||
--> $DIR/box_collection.rs:32:15
|
||||
|
|
||||
LL | fn test5(foo: Box<HashSet<i64>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `HashSet<..>` is already on the heap, `Box<HashSet<..>>` makes an extra allocation
|
||||
|
||||
error: you seem to be trying to use `Box<VecDeque<..>>`. Consider using just `VecDeque<..>`
|
||||
--> $DIR/box_collection.rs:34:15
|
||||
|
|
||||
LL | fn test6(foo: Box<VecDeque<i32>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `VecDeque<..>` is already on the heap, `Box<VecDeque<..>>` makes an extra allocation
|
||||
|
||||
error: you seem to be trying to use `Box<LinkedList<..>>`. Consider using just `LinkedList<..>`
|
||||
--> $DIR/box_collection.rs:36:15
|
||||
|
|
||||
LL | fn test7(foo: Box<LinkedList<i16>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `LinkedList<..>` is already on the heap, `Box<LinkedList<..>>` makes an extra allocation
|
||||
|
||||
error: you seem to be trying to use `Box<BTreeMap<..>>`. Consider using just `BTreeMap<..>`
|
||||
--> $DIR/box_collection.rs:38:15
|
||||
|
|
||||
LL | fn test8(foo: Box<BTreeMap<i8, String>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `BTreeMap<..>` is already on the heap, `Box<BTreeMap<..>>` makes an extra allocation
|
||||
|
||||
error: you seem to be trying to use `Box<BTreeSet<..>>`. Consider using just `BTreeSet<..>`
|
||||
--> $DIR/box_collection.rs:40:15
|
||||
|
|
||||
LL | fn test9(foo: Box<BTreeSet<u64>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `BTreeSet<..>` is already on the heap, `Box<BTreeSet<..>>` makes an extra allocation
|
||||
|
||||
error: you seem to be trying to use `Box<BinaryHeap<..>>`. Consider using just `BinaryHeap<..>`
|
||||
--> $DIR/box_collection.rs:42:16
|
||||
|
|
||||
LL | fn test10(foo: Box<BinaryHeap<u32>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: `BinaryHeap<..>` is already on the heap, `Box<BinaryHeap<..>>` makes an extra allocation
|
||||
|
||||
error: aborting due to 9 previous errors
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue