mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-24 21:53:23 +00:00
Merge commit '83e42a2337dadac915c956d125f1d69132f36425' into clippyup
This commit is contained in:
parent
fe129a022a
commit
6b95029f17
145 changed files with 4737 additions and 1704 deletions
|
@ -11,3 +11,6 @@ target-dir = "target"
|
|||
|
||||
[unstable]
|
||||
binary-dep-depinfo = true
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
|
|
@ -11,6 +11,7 @@ trim_trailing_whitespace = true
|
|||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 120
|
||||
|
||||
[*.md]
|
||||
# double whitespace at end of line
|
||||
|
|
2
.github/workflows/clippy_bors.yml
vendored
2
.github/workflows/clippy_bors.yml
vendored
|
@ -180,6 +180,8 @@ jobs:
|
|||
|
||||
# Run
|
||||
- name: Build Integration Test
|
||||
env:
|
||||
CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: off
|
||||
run: cargo test --test integration --features integration --no-run
|
||||
|
||||
# Upload
|
||||
|
|
2
.github/workflows/remark.yml
vendored
2
.github/workflows/remark.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
|||
- name: Install mdbook
|
||||
run: |
|
||||
mkdir mdbook
|
||||
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.18/mdbook-v0.4.18-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
|
||||
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
|
||||
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||
|
||||
# Run
|
||||
|
|
|
@ -4441,6 +4441,7 @@ Released 2018-09-13
|
|||
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
|
||||
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
|
||||
[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
|
||||
[`clear_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#clear_with_drain
|
||||
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
|
||||
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
|
||||
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
|
||||
|
@ -4632,6 +4633,7 @@ Released 2018-09-13
|
|||
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
|
||||
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
|
||||
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
|
||||
[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures
|
||||
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
|
||||
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
|
||||
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
|
||||
|
@ -4645,6 +4647,7 @@ Released 2018-09-13
|
|||
[`let_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped
|
||||
[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
|
||||
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
|
||||
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
|
||||
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
|
||||
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
|
||||
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
|
||||
|
@ -4671,6 +4674,7 @@ Released 2018-09-13
|
|||
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
|
||||
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
|
||||
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
|
||||
[`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation
|
||||
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
|
||||
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
|
||||
[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new
|
||||
|
@ -4921,6 +4925,7 @@ Released 2018-09-13
|
|||
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
|
||||
[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
|
||||
[`suspicious_command_arg_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_command_arg_space
|
||||
[`suspicious_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_doc_comments
|
||||
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
|
||||
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
|
||||
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
|
||||
|
@ -4933,6 +4938,7 @@ Released 2018-09-13
|
|||
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
|
||||
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
|
||||
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
|
||||
[`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module
|
||||
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
|
||||
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
|
||||
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
|
||||
|
@ -4974,6 +4980,7 @@ Released 2018-09-13
|
|||
[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
|
||||
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
|
||||
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
|
||||
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
|
||||
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
|
||||
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
|
||||
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
|
||||
|
|
|
@ -11,7 +11,7 @@ Lints are divided into categories, each with a default [lint level](https://doc.
|
|||
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
|
||||
|
||||
| Category | Description | Default level |
|
||||
| --------------------- | ----------------------------------------------------------------------------------- | ------------- |
|
||||
|-----------------------|-------------------------------------------------------------------------------------|---------------|
|
||||
| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** |
|
||||
| `clippy::correctness` | code that is outright wrong or useless | **deny** |
|
||||
| `clippy::suspicious` | code that is most likely wrong or useless | **warn** |
|
||||
|
@ -130,7 +130,7 @@ for example.
|
|||
|
||||
You can add Clippy to Travis CI in the same way you use it locally:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
|
@ -253,7 +253,7 @@ rust-version = "1.30"
|
|||
|
||||
The MSRV can also be specified as an attribute, like below.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![clippy::msrv = "1.30.0"]
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ much Clippy is supposed to ~~annoy~~ help you by changing the lint level by
|
|||
category.
|
||||
|
||||
| Category | Description | Default level |
|
||||
| --------------------- | ----------------------------------------------------------------------------------- | ------------- |
|
||||
|-----------------------|-------------------------------------------------------------------------------------|---------------|
|
||||
| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** |
|
||||
| `clippy::correctness` | code that is outright wrong or useless | **deny** |
|
||||
| `clippy::suspicious` | code that is most likely wrong or useless | **warn** |
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
- [Development](development/README.md)
|
||||
- [Basics](development/basics.md)
|
||||
- [Adding Lints](development/adding_lints.md)
|
||||
- [Type Checking](development/type_checking.md)
|
||||
- [Common Tools](development/common_tools_writing_lints.md)
|
||||
- [Infrastructure](development/infrastructure/README.md)
|
||||
- [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
> **Note:** The configuration file is unstable and may be deprecated in the future.
|
||||
|
||||
Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a
|
||||
basic `variable = value` mapping eg.
|
||||
basic `variable = value` mapping e.g.
|
||||
|
||||
```toml
|
||||
avoid-breaking-exported-api = false
|
||||
|
@ -60,7 +60,7 @@ And to warn on `lint_name`, run
|
|||
cargo clippy -- -W clippy::lint_name
|
||||
```
|
||||
|
||||
This also works with lint groups. For example you can run Clippy with warnings for all lints enabled:
|
||||
This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled:
|
||||
|
||||
```terminal
|
||||
cargo clippy -- -W clippy::pedantic
|
||||
|
@ -84,7 +84,7 @@ msrv = "1.30.0"
|
|||
|
||||
The MSRV can also be specified as an attribute, like below.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![clippy::msrv = "1.30.0"]
|
||||
|
||||
|
@ -96,7 +96,28 @@ fn main() {
|
|||
You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
|
||||
is equivalent to `msrv = 1.30.0`.
|
||||
|
||||
Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly.
|
||||
Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
|
||||
|
||||
Lints that recognize this configuration option can be
|
||||
found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
|
||||
|
||||
### Disabling evaluation of certain code
|
||||
|
||||
> **Note:** This should only be used in cases where other solutions, like `#[allow(clippy::all)]`, are not sufficient.
|
||||
|
||||
Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with
|
||||
[conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the
|
||||
`cargo-clippy` feature is not set. You may need to provide a stub so that the code compiles:
|
||||
|
||||
```rust
|
||||
#[cfg(not(feature = "cargo-clippy"))]
|
||||
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
|
||||
|
||||
#[cfg(feature = "cargo-clippy")]
|
||||
fn my_big_function(_input: &str) -> Option<MyStruct> {
|
||||
None
|
||||
}
|
||||
```
|
||||
|
||||
This feature is not actually part of your crate, so specifying `--all-features` to other tools, e.g. `cargo test
|
||||
--all-features`, will not disable it.
|
||||
|
|
|
@ -5,7 +5,7 @@ making Clippy better by contributing to it. In that case, welcome to the
|
|||
project!
|
||||
|
||||
> _Note:_ If you're just interested in using Clippy, there's nothing to see from
|
||||
> this point onward and you should return to one of the earlier chapters.
|
||||
> this point onward, and you should return to one of the earlier chapters.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ because that's clearly a non-descriptive name.
|
|||
- [Cargo lints](#cargo-lints)
|
||||
- [Rustfix tests](#rustfix-tests)
|
||||
- [Testing manually](#testing-manually)
|
||||
- [Running directly](#running-directly)
|
||||
- [Lint declaration](#lint-declaration)
|
||||
- [Lint registration](#lint-registration)
|
||||
- [Lint passes](#lint-passes)
|
||||
|
@ -186,6 +187,15 @@ cargo dev lint input.rs
|
|||
from the working copy root. With tests in place, let's have a look at
|
||||
implementing our lint now.
|
||||
|
||||
## Running directly
|
||||
|
||||
While it's easier to just use `cargo dev lint`, it might be desirable to get
|
||||
`target/release/cargo-clippy` and `target/release/clippy-driver` to work as well in some cases.
|
||||
By default, they don't work because clippy dynamically links rustc. To help them find rustc,
|
||||
add the path printed by`rustc --print target-libdir` (ran inside this workspace so that the rustc version matches)
|
||||
to your library search path.
|
||||
On linux, this can be done by setting the `LD_LIBRARY_PATH` environment variable to that path.
|
||||
|
||||
## Lint declaration
|
||||
|
||||
Let's start by opening the new file created in the `clippy_lints` crate at
|
||||
|
@ -265,7 +275,7 @@ When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
|
|||
pass may have to be registered manually in the `register_plugins` function in
|
||||
`clippy_lints/src/lib.rs`:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
store.register_early_pass(|| Box::new(foo_functions::FooFunctions));
|
||||
```
|
||||
|
||||
|
@ -291,7 +301,7 @@ either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass].
|
|||
|
||||
In short, the `LateLintPass` has access to type information while the
|
||||
`EarlyLintPass` doesn't. If you don't need access to type information, use the
|
||||
`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed
|
||||
`EarlyLintPass`. The `EarlyLintPass` is also faster. However, linting speed
|
||||
hasn't really been a concern with Clippy so far.
|
||||
|
||||
Since we don't need type information for checking the function name, we used
|
||||
|
@ -308,7 +318,7 @@ implementation of the lint logic.
|
|||
|
||||
Let's start by implementing the `EarlyLintPass` for our `FooFunctions`:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
impl EarlyLintPass for FooFunctions {
|
||||
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
|
||||
// TODO: Emit lint here
|
||||
|
@ -327,10 +337,10 @@ variety of lint emission functions. They can all be found in
|
|||
[`clippy_utils/src/diagnostics.rs`][diagnostics].
|
||||
|
||||
`span_lint_and_help` seems most appropriate in this case. It allows us to
|
||||
provide an extra help message and we can't really suggest a better name
|
||||
provide an extra help message, and we can't really suggest a better name
|
||||
automatically. This is how it looks:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
impl EarlyLintPass for FooFunctions {
|
||||
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
|
||||
span_lint_and_help(
|
||||
|
@ -469,7 +479,7 @@ the value from `clippy.toml`. This can be accounted for using the
|
|||
`extract_msrv_attr!(LintContext)` macro and passing
|
||||
`LateContext`/`EarlyContext`.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualStrip {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
...
|
||||
|
@ -483,7 +493,7 @@ the lint's test file, `tests/ui/manual_strip.rs` in this example. It should
|
|||
have a case for the version below the MSRV and one with the same contents but
|
||||
for the MSRV version itself.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
...
|
||||
|
||||
#[clippy::msrv = "1.44"]
|
||||
|
@ -514,7 +524,7 @@ define_Conf! {
|
|||
|
||||
If you have trouble implementing your lint, there is also the internal `author`
|
||||
lint to generate Clippy code that detects the offending pattern. It does not
|
||||
work for all of the Rust syntax, but can give a good starting point.
|
||||
work for all the Rust syntax, but can give a good starting point.
|
||||
|
||||
The quickest way to use it, is the [Rust playground:
|
||||
play.rust-lang.org][author_example]. Put the code you want to lint into the
|
||||
|
@ -607,7 +617,7 @@ output in the `stdout` part.
|
|||
|
||||
## PR Checklist
|
||||
|
||||
Before submitting your PR make sure you followed all of the basic requirements:
|
||||
Before submitting your PR make sure you followed all the basic requirements:
|
||||
|
||||
<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` -->
|
||||
|
||||
|
@ -627,7 +637,7 @@ for some users. Adding a configuration is done in the following steps:
|
|||
|
||||
1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
/// Lint: LINT_NAME.
|
||||
///
|
||||
/// <The configuration field doc comment>
|
||||
|
@ -680,7 +690,7 @@ for some users. Adding a configuration is done in the following steps:
|
|||
configuration value is now cloned or copied into a local value that is then
|
||||
passed to the impl struct like this:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
// Default generated registration:
|
||||
store.register_*_pass(|| box module::StructName);
|
||||
|
||||
|
|
|
@ -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/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
|
||||
[Adding Lints]: adding_lints.md
|
||||
[Common Tools]: common_tools_writing_lints.md
|
||||
|
||||
- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy)
|
||||
- [Get the Code](#get-the-code)
|
||||
|
@ -125,7 +125,7 @@ We follow a rustc no merge-commit policy. See
|
|||
## Common Abbreviations
|
||||
|
||||
| Abbreviation | Meaning |
|
||||
| ------------ | -------------------------------------- |
|
||||
|--------------|----------------------------------------|
|
||||
| UB | Undefined Behavior |
|
||||
| FP | False Positive |
|
||||
| FN | False Negative |
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
You may need following tooltips to catch up with common operations.
|
||||
|
||||
- [Common tools for writing lints](#common-tools-for-writing-lints)
|
||||
- [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
|
||||
- [Retrieving the type of expression](#retrieving-the-type-of-expression)
|
||||
- [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
|
||||
- [Checking for a specific type](#checking-for-a-specific-type)
|
||||
- [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
|
||||
|
@ -16,7 +16,7 @@ Useful Rustc dev guide links:
|
|||
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
|
||||
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
|
||||
|
||||
## Retrieving the type of an expression
|
||||
## Retrieving the type of expression
|
||||
|
||||
Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for
|
||||
example to answer following questions:
|
||||
|
@ -45,7 +45,7 @@ impl LateLintPass<'_> for MyStructLint {
|
|||
}
|
||||
```
|
||||
|
||||
Similarly in [`TypeckResults`][TypeckResults] methods, you have the
|
||||
Similarly, in [`TypeckResults`][TypeckResults] methods, you have the
|
||||
[`pat_ty()`][pat_ty] method to retrieve a type from a pattern.
|
||||
|
||||
Two noticeable items here:
|
||||
|
@ -192,7 +192,7 @@ functions to deal with macros:
|
|||
- `span.from_expansion()`: detects if a span is from macro expansion or
|
||||
desugaring. Checking this is a common first step in a lint.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
if expr.span.from_expansion() {
|
||||
// just forget it
|
||||
return;
|
||||
|
@ -203,11 +203,11 @@ functions to deal with macros:
|
|||
if so, which macro call expanded it. It is sometimes useful to check if the
|
||||
context of two spans are equal.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
// expands to `1 + 0`, but don't lint
|
||||
1 + mac!()
|
||||
```
|
||||
```rust
|
||||
```rust,ignore
|
||||
if left.span.ctxt() != right.span.ctxt() {
|
||||
// the coder most likely cannot modify this expression
|
||||
return;
|
||||
|
@ -246,7 +246,7 @@ functions to deal with macros:
|
|||
`macro_rules!` with `a == $b`, `$b` is expanded to some expression with a
|
||||
different context from `a`.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
macro_rules! m {
|
||||
($a:expr, $b:expr) => {
|
||||
if $a.is_some() {
|
||||
|
|
|
@ -13,7 +13,7 @@ guide to Clippy that you're reading right now. The Clippy book is formatted with
|
|||
While not strictly necessary since the book source is simply Markdown text
|
||||
files, having mdBook locally will allow you to build, test and serve the book
|
||||
locally to view changes before you commit them to the repository. You likely
|
||||
already have `cargo` installed, so the easiest option is to simply:
|
||||
already have `cargo` installed, so the easiest option is to:
|
||||
|
||||
```shell
|
||||
cargo install mdbook
|
||||
|
@ -26,7 +26,7 @@ instructions for other options.
|
|||
|
||||
The book's
|
||||
[src](https://github.com/rust-lang/rust-clippy/tree/master/book/src)
|
||||
directory contains all of the markdown files used to generate the book. If you
|
||||
directory contains all the markdown files used to generate the book. If you
|
||||
want to see your changes in real time, you can use the mdBook `serve` command to
|
||||
run a web server locally that will automatically update changes as they are
|
||||
made. From the top level of your `rust-clippy` directory:
|
||||
|
|
|
@ -101,7 +101,7 @@ Look for the [`beta-accepted`] label and make sure to also include the PRs with
|
|||
that label in the changelog. If you can, remove the `beta-accepted` labels
|
||||
**after** the changelog PR was merged.
|
||||
|
||||
> _Note:_ Some of those PRs might even got backported to the previous `beta`.
|
||||
> _Note:_ Some of those PRs might even get backported to the previous `beta`.
|
||||
> Those have to be included in the changelog of the _previous_ release.
|
||||
|
||||
### 4. Update `clippy::version` attributes
|
||||
|
|
|
@ -44,7 +44,7 @@ $ git push origin backport_remerge # This can be pushed to your fork
|
|||
```
|
||||
|
||||
After this, open a PR to the master branch. In this PR, the commit hash of the
|
||||
`HEAD` of the `beta` branch must exists. In addition to that, no files should be
|
||||
`HEAD` of the `beta` branch must exist. In addition to that, no files should be
|
||||
changed by this PR.
|
||||
|
||||
## Update the `beta` branch
|
||||
|
|
|
@ -19,8 +19,7 @@ to beta. For reference, the first sync following this cadence was performed the
|
|||
2020-08-27.
|
||||
|
||||
This process is described in detail in the following sections. For general
|
||||
information about `subtree`s in the Rust repository see [Rust's
|
||||
`CONTRIBUTING.md`][subtree].
|
||||
information about `subtree`s in the Rust repository see [the rustc-dev-guide][subtree].
|
||||
|
||||
## Patching git-subtree to work with big repos
|
||||
|
||||
|
@ -47,7 +46,7 @@ sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subt
|
|||
|
||||
> _Note:_ If you are a Debian user, `dash` is the shell used by default for
|
||||
> scripts instead of `sh`. This shell has a hardcoded recursion limit set to
|
||||
> 1000. In order to make this process work, you need to force the script to run
|
||||
> 1,000. In order to make this process work, you need to force the script to run
|
||||
> `bash` instead. You can do this by editing the first line of the `git-subtree`
|
||||
> script and changing `sh` to `bash`.
|
||||
|
||||
|
@ -71,10 +70,10 @@ $ git remote add clippy-local /path/to/rust-clippy
|
|||
|
||||
## Performing the sync from [`rust-lang/rust`] to Clippy
|
||||
|
||||
Here is a TL;DR version of the sync process (all of the following commands have
|
||||
Here is a TL;DR version of the sync process (all the following commands have
|
||||
to be run inside the `rust` directory):
|
||||
|
||||
1. Clone the [`rust-lang/rust`] repository or make sure it is up to date.
|
||||
1. Clone the [`rust-lang/rust`] repository or make sure it is up-to-date.
|
||||
2. Checkout the commit from the latest available nightly. You can get it using
|
||||
`rustup check`.
|
||||
3. Sync the changes to the rust-copy of Clippy to your Clippy fork:
|
||||
|
@ -107,7 +106,7 @@ to be run inside the `rust` directory):
|
|||
|
||||
## Performing the sync from Clippy to [`rust-lang/rust`]
|
||||
|
||||
All of the following commands have to be run inside the `rust` directory.
|
||||
All the following commands have to be run inside the `rust` directory.
|
||||
|
||||
1. Make sure you have checked out the latest `master` of `rust-lang/rust`.
|
||||
2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy:
|
||||
|
@ -118,5 +117,5 @@ All of the following commands have to be run inside the `rust` directory.
|
|||
3. Open a PR to [`rust-lang/rust`]
|
||||
|
||||
[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
|
||||
[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree
|
||||
[subtree]: https://rustc-dev-guide.rust-lang.org/external-repos.html#external-dependencies-subtree
|
||||
[`rust-lang/rust`]: https://github.com/rust-lang/rust
|
||||
|
|
|
@ -6,6 +6,6 @@ or around Clippy in the long run.
|
|||
Besides adding more and more lints and improve the lints that Clippy already
|
||||
has, Clippy is also interested in making the experience of its users, developers
|
||||
and maintainers better over time. Projects that address bigger picture things
|
||||
like this usually take more time and it is useful to have a proposal for those
|
||||
like this usually take more time, and it is useful to have a proposal for those
|
||||
first. This is the place where such proposals are collected, so that we can
|
||||
refer to them when working on them.
|
||||
|
|
|
@ -52,8 +52,8 @@ In the following, plans to improve the usability are covered.
|
|||
|
||||
#### No Output After `cargo check`
|
||||
|
||||
Currently when `cargo clippy` is run after `cargo check`, it does not produce
|
||||
any output. This is especially problematic since `rust-analyzer` is on the rise
|
||||
Currently, when `cargo clippy` is run after `cargo check`, it does not produce
|
||||
any output. This is especially problematic since `rust-analyzer` is on the rise,
|
||||
and it uses `cargo check` for checking code. A fix is already implemented, but
|
||||
it still has to be pushed over the finish line. This also includes the
|
||||
stabilization of the `cargo clippy --fix` command or the support of multi-span
|
||||
|
@ -221,7 +221,7 @@ regarding the user facing issues.
|
|||
|
||||
Rust's roadmap process was established by [RFC 1728] in 2016. Since then every
|
||||
year a roadmap was published, that defined the bigger plans for the coming
|
||||
years. This years roadmap can be found [here][Rust Roadmap 2021].
|
||||
years. This year roadmap can be found [here][Rust Roadmap 2021].
|
||||
|
||||
[RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ lints. For non-trivial lints, it often requires nested pattern matching of AST /
|
|||
HIR nodes. For example, testing that an expression is a boolean literal requires
|
||||
the following checks:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
if let ast::ExprKind::Lit(lit) = &expr.node {
|
||||
if let ast::LitKind::Bool(_) = &lit.node {
|
||||
...
|
||||
|
@ -28,7 +28,7 @@ Writing this kind of matching code quickly becomes a complex task and the
|
|||
resulting code is often hard to comprehend. The code below shows a simplified
|
||||
version of the pattern matching required by the `collapsible_if` lint:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
// simplified version of the collapsible_if lint
|
||||
if let ast::ExprKind::If(check, then, None) = &expr.node {
|
||||
if then.stmts.len() == 1 {
|
||||
|
@ -111,7 +111,7 @@ expressions that are boolean literals with value `false`.
|
|||
|
||||
The pattern can then be used to implement lints in the following way:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
...
|
||||
|
||||
impl EarlyLintPass for MyAwesomeLint {
|
||||
|
@ -346,7 +346,7 @@ pattern!{
|
|||
one could get references to the nodes that matched the subpatterns in the
|
||||
following way:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
...
|
||||
fn check_expr(expr: &syntax::ast::Expr) {
|
||||
if let Some(result) = my_pattern(expr) {
|
||||
|
@ -372,7 +372,7 @@ matches arrays that consist of any number of literal expressions. Because those
|
|||
expressions are named `foo`, the result struct contains a `foo` attribute which
|
||||
is a vector of expressions:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
...
|
||||
if let Some(result) = my_pattern_seq(expr) {
|
||||
result.foo // type: Vec<&syntax::ast::Expr>
|
||||
|
@ -394,7 +394,7 @@ In the pattern above, the `bar` name is only defined if the pattern matches a
|
|||
boolean literal. If it matches an integer literal, the name isn't set. To
|
||||
account for this, the result struct's `bar` attribute is an option type:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
...
|
||||
if let Some(result) = my_pattern_alt(expr) {
|
||||
result.bar // type: Option<&bool>
|
||||
|
@ -404,7 +404,7 @@ if let Some(result) = my_pattern_alt(expr) {
|
|||
It's also possible to use a name in multiple alternation branches if they have
|
||||
compatible types:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pattern!{
|
||||
// matches if expression is a boolean or integer literal
|
||||
my_pattern_mult: Expr =
|
||||
|
@ -519,7 +519,7 @@ The `Alt`, `Seq` and `Opt` structs look like these:
|
|||
> Note: The current implementation can be found
|
||||
> [here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/matchers.rs#L35-L60).
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pub enum Alt<T> {
|
||||
Any,
|
||||
Elmt(Box<T>),
|
||||
|
@ -580,7 +580,7 @@ implementations is the `IsMatch` trait. It defines how to match *PatternTree*
|
|||
nodes against specific syntax tree nodes. A simplified implementation of the
|
||||
`IsMatch` trait is shown below:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pub trait IsMatch<O> {
|
||||
fn is_match(&self, other: &'o O) -> bool;
|
||||
}
|
||||
|
@ -619,7 +619,7 @@ approach (matching against the coarse pattern first and checking for additional
|
|||
properties later) might be slower than the current practice of checking for
|
||||
structure and additional properties in one pass. For example, the following lint
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pattern!{
|
||||
pat_if_without_else: Expr =
|
||||
If(
|
||||
|
@ -644,7 +644,7 @@ first matches against the pattern and then checks that the `then` block doesn't
|
|||
start with a comment. Using clippy's current approach, it's possible to check
|
||||
for these conditions earlier:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
||||
if_chain! {
|
||||
if let ast::ExprKind::If(ref check, ref then, None) = expr.node;
|
||||
|
@ -708,7 +708,7 @@ is similar to actual Rust syntax (probably like the `quote!` macro). For
|
|||
example, a pattern that matches `if` expressions that have `false` in their
|
||||
condition could look like this:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
if false {
|
||||
#[*]
|
||||
}
|
||||
|
@ -742,7 +742,7 @@ affects the structure of the resulting AST. `1 + 0 + 0` is parsed as `(1 + 0) +
|
|||
Another example of a problem would be named submatches. Take a look at this
|
||||
pattern:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn test() {
|
||||
1 #foo
|
||||
}
|
||||
|
@ -862,7 +862,7 @@ op b` and recommends changing it to `a op= b` requires that both occurrences of
|
|||
`a` are the same. Using `=#...` as syntax for backreferences, the lint could be
|
||||
implemented like this:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pattern!{
|
||||
assign_op_pattern: Expr =
|
||||
Assign(_#target, Binary(_, =#target, _)
|
||||
|
|
144
book/src/development/type_checking.md
Normal file
144
book/src/development/type_checking.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
# Type Checking
|
||||
|
||||
When we work on a new lint or improve an existing lint, we might want
|
||||
to retrieve the type `Ty` of an expression `Expr` for a variety of
|
||||
reasons. This can be achieved by utilizing the [`LateContext`][LateContext]
|
||||
that is available for [`LateLintPass`][LateLintPass].
|
||||
|
||||
## `LateContext` and `TypeckResults`
|
||||
|
||||
The lint context [`LateContext`][LateContext] and [`TypeckResults`][TypeckResults]
|
||||
(returned by `LateContext::typeck_results`) are the two most useful data structures
|
||||
in `LateLintPass`. They allow us to jump to type definitions and other compilation
|
||||
stages such as HIR.
|
||||
|
||||
> Note: `LateContext.typeck_results`'s return value is [`TypeckResults`][TypeckResults]
|
||||
> and is created in the type checking step, it includes useful information such as types of
|
||||
> expressions, ways to resolve methods and so on.
|
||||
|
||||
`TypeckResults` contains useful methods such as [`expr_ty`][expr_ty],
|
||||
which gives us access to the underlying structure [`Ty`][Ty] of a given expression.
|
||||
|
||||
```rust
|
||||
pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx>
|
||||
```
|
||||
|
||||
As a side note, besides `expr_ty`, [`TypeckResults`][TypeckResults] contains a
|
||||
[`pat_ty()`][pat_ty] method that is useful for retrieving a type from a pattern.
|
||||
|
||||
## `Ty`
|
||||
|
||||
`Ty` struct contains the type information of an expression.
|
||||
Let's take a look at `rustc_middle`'s [`Ty`][Ty] struct to examine this struct:
|
||||
|
||||
```rust
|
||||
pub struct Ty<'tcx>(Interned<'tcx, WithStableHash<TyS<'tcx>>>);
|
||||
```
|
||||
|
||||
At a first glance, this struct looks quite esoteric. But at a closer look,
|
||||
we will see that this struct contains many useful methods for type checking.
|
||||
|
||||
For instance, [`is_char`][is_char] checks if the given `Ty` struct corresponds
|
||||
to the primitive character type.
|
||||
|
||||
### `is_*` Usage
|
||||
|
||||
In some scenarios, all we need to do is check if the `Ty` of an expression
|
||||
is a specific type, such as `char` type, so we could write the following:
|
||||
|
||||
```rust
|
||||
impl LateLintPass<'_> for MyStructLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
// Get type of `expr`
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
// Check if the `Ty` of this expression is of character type
|
||||
if ty.is_char() {
|
||||
println!("Our expression is a char!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Furthermore, if we examine the [source code][is_char_source] for `is_char`,
|
||||
we find something very interesting:
|
||||
|
||||
```rust
|
||||
#[inline]
|
||||
pub fn is_char(self) -> bool {
|
||||
matches!(self.kind(), Char)
|
||||
}
|
||||
```
|
||||
|
||||
Indeed, we just discovered `Ty`'s [`kind` method][kind], which provides us
|
||||
with [`TyKind`][TyKind] of a `Ty`.
|
||||
|
||||
## `TyKind`
|
||||
|
||||
`TyKind` defines the kinds of types in Rust's type system.
|
||||
Peeking into [`TyKind` documentation][TyKind], we will see that it is an
|
||||
enum of 27 variants, including items such as `Bool`, `Int`, `Ref`, etc.
|
||||
|
||||
### `kind` Usage
|
||||
|
||||
The `TyKind` of `Ty` can be returned by calling [`Ty.kind` method][kind].
|
||||
We often use this method to perform pattern matching in Clippy.
|
||||
|
||||
For instance, if we want to check for a `struct`, we could examine if the
|
||||
`ty.kind` corresponds to an [`Adt`][Adt] (algebraic data type) and if its
|
||||
[`AdtDef`][AdtDef] is a struct:
|
||||
|
||||
```rust
|
||||
impl LateLintPass<'_> for MyStructLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
// Get type of `expr`
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
// Match its kind to enter the type
|
||||
match ty.kind {
|
||||
ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `hir::Ty` and `ty::Ty`
|
||||
|
||||
We've been talking about [`ty::Ty`][middle_ty] this whole time without addressing [`hir::Ty`][hir_ty], but the latter
|
||||
is also important to understand.
|
||||
|
||||
`hir::Ty` would represent *what* an user wrote, while `ty::Ty` would understand the meaning of it (because it has more
|
||||
information).
|
||||
|
||||
**Example: `fn foo(x: u32) -> u32 { x }`**
|
||||
|
||||
Here the HIR sees the types without "thinking" about them, it knows that the function takes an `u32` and returns
|
||||
an `u32`. But at the `ty::Ty` level the compiler understands that they're the same type, in-depth lifetimes, etc...
|
||||
|
||||
you can use the [`hir_ty_to_ty`][hir_ty_to_ty] function to convert from a `hir::Ty` to a `ty::Ty`
|
||||
|
||||
## Useful Links
|
||||
|
||||
Below are some useful links to further explore the concepts covered
|
||||
in this chapter:
|
||||
|
||||
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
|
||||
- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
|
||||
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
|
||||
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
|
||||
|
||||
[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html#variant.Adt
|
||||
[AdtDef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/adt/struct.AdtDef.html
|
||||
[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
|
||||
[is_char]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.is_char
|
||||
[is_char_source]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_middle/ty/sty.rs.html#1831-1834
|
||||
[kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.kind
|
||||
[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
|
||||
[LateLintPass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
|
||||
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
|
||||
[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
|
||||
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
|
||||
[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
|
||||
[middle_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/struct.Ty.html
|
||||
[hir_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/struct.Ty.html
|
||||
[hir_ty_to_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir_analysis/fn.hir_ty_to_ty.html
|
|
@ -17,8 +17,8 @@ $ rustup component add clippy [--toolchain=<name>]
|
|||
|
||||
## From Source
|
||||
|
||||
Take a look at the [Basics] chapter in the Clippy developer guide to find step
|
||||
by step instructions on how to build and install Clippy from source.
|
||||
Take a look at the [Basics] chapter in the Clippy developer guide to find step-by-step
|
||||
instructions on how to build and install Clippy from source.
|
||||
|
||||
[Basics]: development/basics.md#install-from-source
|
||||
[Usage]: usage.md
|
||||
|
|
|
@ -54,6 +54,7 @@ Please use that command to update the file and do not edit it by hand.
|
|||
| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` |
|
||||
| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` |
|
||||
| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` |
|
||||
| [future-size-threshold](#future-size-threshold) | `16384` |
|
||||
|
||||
### arithmetic-side-effects-allowed
|
||||
Suppress checking of the passed type names in all types of operations.
|
||||
|
@ -130,6 +131,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
|
|||
* [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
|
||||
* [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
|
||||
* [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
|
||||
* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
|
||||
|
||||
|
||||
### msrv
|
||||
|
@ -193,7 +195,7 @@ The maximum cognitive complexity a function can have
|
|||
### disallowed-names
|
||||
The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
|
||||
`".."` can be used as part of the list to indicate, that the configured values should be appended to the
|
||||
default configuration of Clippy. By default any configuration will replace the default value.
|
||||
default configuration of Clippy. By default, any configuration will replace the default value.
|
||||
|
||||
**Default Value:** `["foo", "baz", "quux"]` (`Vec<String>`)
|
||||
|
||||
|
@ -203,7 +205,7 @@ default configuration of Clippy. By default any configuration will replace the d
|
|||
### doc-valid-idents
|
||||
The list of words this lint should not consider as identifiers needing ticks. The value
|
||||
`".."` can be used as part of the list to indicate, that the configured values should be appended to the
|
||||
default configuration of Clippy. By default any configuraction will replace the default value. For example:
|
||||
default configuration of Clippy. By default, any configuration will replace the default value. For example:
|
||||
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
|
||||
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
|
||||
|
||||
|
@ -413,7 +415,7 @@ For internal testing only, ignores the current `publish` settings in the Cargo m
|
|||
Enforce the named macros always use the braces specified.
|
||||
|
||||
A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
|
||||
is could be used with a full path two `MacroMatcher`s have to be added one with the full path
|
||||
could be used with a full path two `MacroMatcher`s have to be added one with the full path
|
||||
`crate_name::macro_name` and one with just the macro name.
|
||||
|
||||
**Default Value:** `[]` (`Vec<crate::nonstandard_macro_braces::MacroMatcher>`)
|
||||
|
@ -447,7 +449,7 @@ Whether to apply the raw pointer heuristic to determine if a type is `Send`.
|
|||
|
||||
### max-suggested-slice-pattern-length
|
||||
When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
|
||||
the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
|
||||
the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
|
||||
For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
|
||||
|
||||
**Default Value:** `3` (`u64`)
|
||||
|
@ -551,4 +553,12 @@ crate. For example, `pub(crate)` items.
|
|||
* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
|
||||
|
||||
|
||||
### future-size-threshold
|
||||
The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
|
||||
|
||||
**Default Value:** `16384` (`u64`)
|
||||
|
||||
* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ The different lint groups were defined in the [Clippy 1.0 RFC].
|
|||
The `clippy::correctness` group is the only lint group in Clippy which lints are
|
||||
deny-by-default and abort the compilation when triggered. This is for good
|
||||
reason: If you see a `correctness` lint, it means that your code is outright
|
||||
wrong or useless and you should try to fix it.
|
||||
wrong or useless, and you should try to fix it.
|
||||
|
||||
Lints in this category are carefully picked and should be free of false
|
||||
positives. So just `#[allow]`ing those lints is not recommended.
|
||||
|
@ -41,7 +41,7 @@ simplify your code. It mostly focuses on code that can be written in a shorter
|
|||
and more readable way, while preserving the semantics.
|
||||
|
||||
If you should see a complexity lint, it usually means that you can remove or
|
||||
replace some code and it is recommended to do so. However, if you need the more
|
||||
replace some code, and it is recommended to do so. However, if you need the more
|
||||
complex code for some expressiveness reason, it is recommended to allow
|
||||
complexity lints on a case-by-case basis.
|
||||
|
||||
|
@ -50,9 +50,9 @@ complexity lints on a case-by-case basis.
|
|||
The `clippy::perf` group gives you suggestions on how you can increase the
|
||||
performance of your code. Those lints are mostly about code that the compiler
|
||||
can't trivially optimize, but has to be written in a slightly different way to
|
||||
make the optimizer's job easier.
|
||||
make the optimizer job easier.
|
||||
|
||||
Perf lints are usually easy to apply and it is recommended to do so.
|
||||
Perf lints are usually easy to apply, and it is recommended to do so.
|
||||
|
||||
## Style
|
||||
|
||||
|
@ -91,7 +91,7 @@ and your use case.
|
|||
|
||||
Lints from this group will restrict you in some way. If you enable a restriction
|
||||
lint for your crate it is recommended to also fix code that this lint triggers
|
||||
on. However, those lints are really strict by design and you might want to
|
||||
on. However, those lints are really strict by design, and you might want to
|
||||
`#[allow]` them in some special cases, with a comment justifying that.
|
||||
|
||||
## Cargo
|
||||
|
|
|
@ -19,7 +19,7 @@ cargo clippy
|
|||
### Lint configuration
|
||||
|
||||
The above command will run the default set of lints, which are included in the
|
||||
lint group `clippy::all`. You might want to use even more lints or you might not
|
||||
lint group `clippy::all`. You might want to use even more lints, or you may not
|
||||
agree with every Clippy lint, and for that there are ways to configure lint
|
||||
levels.
|
||||
|
||||
|
@ -98,7 +98,7 @@ other of Clippy's lint groups.
|
|||
You can configure lint levels in source code the same way you can configure
|
||||
`rustc` lints:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
#![allow(clippy::style)]
|
||||
|
||||
#[warn(clippy::double_neg)]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![feature(lazy_cell)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(rustc_private)]
|
||||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
|
|
|
@ -369,9 +369,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
|
|||
}}
|
||||
todo!();
|
||||
}}
|
||||
"#,
|
||||
context_import = context_import,
|
||||
name_upper = name_upper,
|
||||
"#
|
||||
);
|
||||
} else {
|
||||
let _: fmt::Result = writedoc!(
|
||||
|
@ -385,9 +383,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
|
|||
pub(super) fn check(cx: &{context_import}) {{
|
||||
todo!();
|
||||
}}
|
||||
"#,
|
||||
context_import = context_import,
|
||||
name_upper = name_upper,
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -537,17 +537,13 @@ fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
|
|||
/// Nothing. This lint has been deprecated.
|
||||
///
|
||||
/// ### Deprecation reason
|
||||
/// {}
|
||||
#[clippy::version = \"{}\"]
|
||||
pub {},
|
||||
\"{}\"
|
||||
/// {deprecation_reason}
|
||||
#[clippy::version = \"{version}\"]
|
||||
pub {name},
|
||||
\"{reason}\"
|
||||
}}
|
||||
|
||||
",
|
||||
deprecation_reason,
|
||||
version,
|
||||
name,
|
||||
reason,
|
||||
"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ keywords = ["clippy", "lint", "plugin"]
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = { version = "0.7", default-features = false }
|
||||
cargo_metadata = "0.15.3"
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
||||
|
|
|
@ -7,7 +7,7 @@ use rustc_ast::ast::LitKind;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
|
||||
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{LateContext, LateLintPass, Level};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::source_map::Span;
|
||||
|
@ -430,23 +430,25 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
let nonminimal_bool_lint = |suggestions: Vec<_>| {
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
NONMINIMAL_BOOL,
|
||||
e.hir_id,
|
||||
e.span,
|
||||
"this boolean expression can be simplified",
|
||||
|diag| {
|
||||
diag.span_suggestions(
|
||||
e.span,
|
||||
"try",
|
||||
suggestions.into_iter(),
|
||||
// nonminimal_bool can produce minimal but
|
||||
// not human readable expressions (#3141)
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
);
|
||||
if self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, e.hir_id).0 != Level::Allow {
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
NONMINIMAL_BOOL,
|
||||
e.hir_id,
|
||||
e.span,
|
||||
"this boolean expression can be simplified",
|
||||
|diag| {
|
||||
diag.span_suggestions(
|
||||
e.span,
|
||||
"try",
|
||||
suggestions.into_iter(),
|
||||
// nonminimal_bool can produce minimal but
|
||||
// not human readable expressions (#3141)
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
if improvements.is_empty() {
|
||||
let mut visitor = NotSimplificationVisitor { cx: self.cx };
|
||||
|
@ -498,6 +500,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
|
|||
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind &&
|
||||
!inner.span.from_expansion() &&
|
||||
let Some(suggestion) = simplify_not(self.cx, inner)
|
||||
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
self.cx,
|
||||
|
|
|
@ -2,8 +2,9 @@ use clippy_utils::consts::{constant, Constant};
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::expr_or_init;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
|
||||
use rustc_errors::{Applicability, SuggestionStyle};
|
||||
use rustc_errors::{Applicability, Diagnostic, SuggestionStyle};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -163,19 +164,34 @@ pub(super) fn check(
|
|||
_ => return,
|
||||
};
|
||||
|
||||
let name_of_cast_from = snippet(cx, cast_expr.span, "..");
|
||||
let cast_to_snip = snippet(cx, cast_to_span, "..");
|
||||
let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})");
|
||||
|
||||
span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| {
|
||||
diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...");
|
||||
diag.span_suggestion_with_style(
|
||||
expr.span,
|
||||
"... or use `try_from` and handle the error accordingly",
|
||||
suggestion,
|
||||
Applicability::Unspecified,
|
||||
// always show the suggestion in a separate line
|
||||
SuggestionStyle::ShowAlways,
|
||||
);
|
||||
if !cast_from.is_floating_point() {
|
||||
offer_suggestion(cx, expr, cast_expr, cast_to_span, diag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn offer_suggestion(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
cast_expr: &Expr<'_>,
|
||||
cast_to_span: Span,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
let cast_to_snip = snippet(cx, cast_to_span, "..");
|
||||
let suggestion = if cast_to_snip == "_" {
|
||||
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
|
||||
} else {
|
||||
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
|
||||
};
|
||||
|
||||
diag.span_suggestion_with_style(
|
||||
expr.span,
|
||||
"... or use `try_from` and handle the error accordingly",
|
||||
suggestion,
|
||||
Applicability::Unspecified,
|
||||
// always show the suggestion in a separate line
|
||||
SuggestionStyle::ShowAlways,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use clippy_utils::visitors::for_each_expr_with_closures;
|
||||
use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_hir::{Block, ExprKind, HirId, Local, Node, PatKind};
|
||||
use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::sym;
|
||||
|
@ -44,7 +44,8 @@ declare_clippy_lint! {
|
|||
}
|
||||
declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
|
||||
|
||||
static COLLECTIONS: [Symbol; 10] = [
|
||||
// Add `String` here when it is added to diagnostic items
|
||||
static COLLECTIONS: [Symbol; 9] = [
|
||||
sym::BTreeMap,
|
||||
sym::BTreeSet,
|
||||
sym::BinaryHeap,
|
||||
|
@ -52,7 +53,6 @@ static COLLECTIONS: [Symbol; 10] = [
|
|||
sym::HashSet,
|
||||
sym::LinkedList,
|
||||
sym::Option,
|
||||
sym::String,
|
||||
sym::Vec,
|
||||
sym::VecDeque,
|
||||
];
|
||||
|
@ -60,8 +60,7 @@ static COLLECTIONS: [Symbol; 10] = [
|
|||
impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
|
||||
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
|
||||
// Look for local variables whose type is a container. Search surrounding bock for read access.
|
||||
let ty = cx.typeck_results().pat_ty(local.pat);
|
||||
if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
|
||||
if match_acceptable_type(cx, local, &COLLECTIONS)
|
||||
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
|
||||
&& let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
|
||||
&& has_no_read_access(cx, local_id, enclosing_block)
|
||||
|
@ -71,6 +70,13 @@ impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
|
|||
}
|
||||
}
|
||||
|
||||
fn match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool {
|
||||
let ty = cx.typeck_results().pat_ty(local.pat);
|
||||
collections.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
|
||||
// String type is a lang item but not a diagnostic item for now so we need a separate check
|
||||
|| is_type_lang_item(cx, ty, LangItem::String)
|
||||
}
|
||||
|
||||
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
|
||||
let mut has_access = false;
|
||||
let mut has_read_access = false;
|
||||
|
@ -95,9 +101,9 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
|||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
// Method call on `id` in a statement ignores any return value, so it's not a read access:
|
||||
// Look for method call with receiver `id`. It might be a non-read access:
|
||||
//
|
||||
// id.foo(...); // Not reading `id`.
|
||||
// id.foo(args)
|
||||
//
|
||||
// Only assuming this for "official" methods defined on the type. For methods defined in extension
|
||||
// traits (identified as local, based on the orphan rule), pessimistically assume that they might
|
||||
|
@ -105,11 +111,24 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
|||
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
|
||||
&& path_to_local_id(receiver, id)
|
||||
&& let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id)
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
||||
&& !method_def_id.is_local()
|
||||
{
|
||||
return ControlFlow::Continue(());
|
||||
// The method call is a statement, so the return value is not used. That's not a read access:
|
||||
//
|
||||
// id.foo(args);
|
||||
if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
// The method call is not a statement, so its return value is used somehow but its type is the
|
||||
// unit type, so this is not a real read access. Examples:
|
||||
//
|
||||
// let y = x.clear();
|
||||
// println!("{:?}", x.clear());
|
||||
if cx.typeck_results().expr_ty(parent).is_unit() {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
}
|
||||
|
||||
// Any other access to `id` is a read access. Stop searching.
|
||||
|
|
|
@ -218,6 +218,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
|
||||
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
|
||||
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
|
||||
crate::large_futures::LARGE_FUTURES_INFO,
|
||||
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
|
||||
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
|
||||
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
|
||||
|
@ -231,6 +232,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::let_with_type_underscore::LET_WITH_TYPE_UNDERSCORE_INFO,
|
||||
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
|
||||
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
|
||||
crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO,
|
||||
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
|
||||
crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
|
||||
crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
|
||||
|
@ -267,6 +269,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
|
||||
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
|
||||
crate::manual_retain::MANUAL_RETAIN_INFO,
|
||||
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
|
||||
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
|
||||
crate::manual_strip::MANUAL_STRIP_INFO,
|
||||
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
|
||||
|
@ -307,6 +310,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
|
||||
crate::methods::CHARS_LAST_CMP_INFO,
|
||||
crate::methods::CHARS_NEXT_CMP_INFO,
|
||||
crate::methods::CLEAR_WITH_DRAIN_INFO,
|
||||
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
|
||||
crate::methods::CLONE_DOUBLE_REF_INFO,
|
||||
crate::methods::CLONE_ON_COPY_INFO,
|
||||
|
@ -565,6 +569,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::strings::STR_TO_STRING_INFO,
|
||||
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
|
||||
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
|
||||
crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO,
|
||||
crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
|
||||
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
|
||||
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,
|
||||
|
@ -574,6 +579,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO,
|
||||
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
|
||||
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
|
||||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
|
@ -616,6 +622,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::unit_types::UNIT_CMP_INFO,
|
||||
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
|
||||
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
|
||||
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
|
||||
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
|
||||
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
|
||||
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
|
||||
|
|
|
@ -32,7 +32,7 @@ declare_clippy_lint! {
|
|||
/// ### Example
|
||||
/// ```rust
|
||||
/// // Assuming that `clippy.toml` contains the following line:
|
||||
/// // allowed-locales = ["Latin", "Cyrillic"]
|
||||
/// // allowed-scripts = ["Latin", "Cyrillic"]
|
||||
/// let counter = 10; // OK, latin is allowed.
|
||||
/// let счётчик = 10; // OK, cyrillic is allowed.
|
||||
/// let zähler = 10; // OK, it's still latin.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::FormatArgsExpn;
|
||||
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, match_function_call, paths};
|
||||
use if_chain::if_chain;
|
||||
|
@ -8,7 +8,7 @@ use rustc_hir::def::Res;
|
|||
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{sym, ExpnId};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -43,23 +43,22 @@ declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
// match call to unwrap
|
||||
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind;
|
||||
if unwrap_fun.ident.name == sym::unwrap;
|
||||
// match call to unwrap
|
||||
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind
|
||||
&& unwrap_fun.ident.name == sym::unwrap
|
||||
// match call to write_fmt
|
||||
if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind);
|
||||
if write_fun.ident.name == sym!(write_fmt);
|
||||
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind)
|
||||
&& write_fun.ident.name == sym!(write_fmt)
|
||||
// match calls to std::io::stdout() / std::io::stderr ()
|
||||
if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
|
||||
&& let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
|
||||
Some("stdout")
|
||||
} else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
|
||||
Some("stderr")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
|
||||
then {
|
||||
}
|
||||
{
|
||||
find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
|
||||
let calling_macro =
|
||||
// ordering is important here, since `writeln!` uses `write!` internally
|
||||
if is_expn_of(write_call.span, "writeln").is_some() {
|
||||
|
@ -92,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args.inputs_span(),
|
||||
format_args_inputs_span(format_args),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
|
@ -104,8 +103,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
"try this",
|
||||
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::trait_ref_of_method;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor};
|
||||
use rustc_hir::{
|
||||
BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
|
||||
BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
|
||||
PredicateOrigin, Ty, TyKind, WherePredicate,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
@ -53,13 +53,19 @@ impl ExtraUnusedTypeParameters {
|
|||
}
|
||||
}
|
||||
|
||||
/// Don't lint external macros or functions with empty bodies. Also, don't lint public items if
|
||||
/// the `avoid_breaking_exported_api` config option is set.
|
||||
fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool {
|
||||
/// Don't lint external macros or functions with empty bodies. Also, don't lint exported items
|
||||
/// if the `avoid_breaking_exported_api` config option is set.
|
||||
fn is_empty_exported_or_macro(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
def_id: LocalDefId,
|
||||
body_id: BodyId,
|
||||
) -> bool {
|
||||
let body = cx.tcx.hir().body(body_id).value;
|
||||
let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none());
|
||||
let is_exported = cx.effective_visibilities.is_exported(def_id);
|
||||
in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty
|
||||
in_external_macro(cx.sess(), span) || fn_empty || (is_exported && self.avoid_breaking_exported_api)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,85 +75,129 @@ impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]);
|
|||
/// trait bounds those parameters have.
|
||||
struct TypeWalker<'cx, 'tcx> {
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
/// Collection of all the function's type parameters.
|
||||
/// Collection of the function's type parameters. Once the function has been walked, this will
|
||||
/// contain only unused type parameters.
|
||||
ty_params: FxHashMap<DefId, Span>,
|
||||
/// Collection of any (inline) trait bounds corresponding to each type parameter.
|
||||
bounds: FxHashMap<DefId, Span>,
|
||||
/// Collection of any inline trait bounds corresponding to each type parameter.
|
||||
inline_bounds: FxHashMap<DefId, Span>,
|
||||
/// Collection of any type parameters with trait bounds that appear in a where clause.
|
||||
where_bounds: FxHashSet<DefId>,
|
||||
/// The entire `Generics` object of the function, useful for querying purposes.
|
||||
generics: &'tcx Generics<'tcx>,
|
||||
/// The value of this will remain `true` if *every* parameter:
|
||||
/// 1. Is a type parameter, and
|
||||
/// 2. Goes unused in the function.
|
||||
/// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic
|
||||
/// parameters are present, this will be set to `false`.
|
||||
all_params_unused: bool,
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> {
|
||||
fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self {
|
||||
let mut all_params_unused = true;
|
||||
let ty_params = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|param| {
|
||||
if let GenericParamKind::Type { synthetic, .. } = param.kind {
|
||||
(!synthetic).then_some((param.def_id.into(), param.span))
|
||||
} else {
|
||||
if !param.is_elided_lifetime() {
|
||||
all_params_unused = false;
|
||||
}
|
||||
None
|
||||
}
|
||||
.filter_map(|param| match param.kind {
|
||||
GenericParamKind::Type { synthetic, .. } if !synthetic => Some((param.def_id.into(), param.span)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
cx,
|
||||
ty_params,
|
||||
bounds: FxHashMap::default(),
|
||||
inline_bounds: FxHashMap::default(),
|
||||
where_bounds: FxHashSet::default(),
|
||||
generics,
|
||||
all_params_unused,
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_param_used(&mut self, def_id: DefId) {
|
||||
if self.ty_params.remove(&def_id).is_some() {
|
||||
self.all_params_unused = false;
|
||||
}
|
||||
fn get_bound_span(&self, param: &'tcx GenericParam<'tcx>) -> Span {
|
||||
self.inline_bounds
|
||||
.get(¶m.def_id.to_def_id())
|
||||
.map_or(param.span, |bound_span| param.span.with_hi(bound_span.hi()))
|
||||
}
|
||||
|
||||
fn emit_help(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
|
||||
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, None, help);
|
||||
}
|
||||
|
||||
fn emit_sugg(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
|
||||
let suggestions: Vec<(Span, String)> = spans.iter().copied().zip(std::iter::repeat(String::new())).collect();
|
||||
span_lint_and_then(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, |diag| {
|
||||
diag.multipart_suggestion(help, suggestions, Applicability::MachineApplicable);
|
||||
});
|
||||
}
|
||||
|
||||
fn emit_lint(&self) {
|
||||
let (msg, help) = match self.ty_params.len() {
|
||||
let explicit_params = self
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let extra_params = explicit_params
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, param)| self.ty_params.contains_key(¶m.def_id.to_def_id()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (msg, help) = match extra_params.len() {
|
||||
0 => return,
|
||||
1 => (
|
||||
"type parameter goes unused in function definition",
|
||||
format!(
|
||||
"type parameter `{}` goes unused in function definition",
|
||||
extra_params[0].1.name.ident()
|
||||
),
|
||||
"consider removing the parameter",
|
||||
),
|
||||
_ => (
|
||||
"type parameters go unused in function definition",
|
||||
format!(
|
||||
"type parameters go unused in function definition: {}",
|
||||
extra_params
|
||||
.iter()
|
||||
.map(|(_, param)| param.name.ident().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
"consider removing the parameters",
|
||||
),
|
||||
};
|
||||
|
||||
let source_map = self.cx.sess().source_map();
|
||||
let span = if self.all_params_unused {
|
||||
self.generics.span.into() // Remove the entire list of generics
|
||||
// If any parameters are bounded in where clauses, don't try to form a suggestion.
|
||||
// Otherwise, the leftover where bound would produce code that wouldn't compile.
|
||||
if extra_params
|
||||
.iter()
|
||||
.any(|(_, param)| self.where_bounds.contains(¶m.def_id.to_def_id()))
|
||||
{
|
||||
let spans = extra_params
|
||||
.iter()
|
||||
.map(|(_, param)| self.get_bound_span(param))
|
||||
.collect::<Vec<_>>();
|
||||
self.emit_help(spans, &msg, help);
|
||||
} else {
|
||||
MultiSpan::from_spans(
|
||||
self.ty_params
|
||||
let spans = if explicit_params.len() == extra_params.len() {
|
||||
vec![self.generics.span] // Remove the entire list of generics
|
||||
} else {
|
||||
let mut end: Option<LocalDefId> = None;
|
||||
extra_params
|
||||
.iter()
|
||||
.map(|(def_id, &span)| {
|
||||
// Extend the span past any trait bounds, and include the comma at the end.
|
||||
let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi);
|
||||
let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false);
|
||||
let comma_span = source_map.span_through_char(comma_range, ',');
|
||||
span.with_hi(comma_span.hi())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
.rev()
|
||||
.map(|(idx, param)| {
|
||||
if let Some(next) = explicit_params.get(idx + 1) && end != Some(next.def_id) {
|
||||
// Extend the current span forward, up until the next param in the list.
|
||||
param.span.until(next.span)
|
||||
} else {
|
||||
// Extend the current span back to include the comma following the previous
|
||||
// param. If the span of the next param in the list has already been
|
||||
// extended, we continue the chain. This is why we're iterating in reverse.
|
||||
end = Some(param.def_id);
|
||||
|
||||
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help);
|
||||
// idx will never be 0, else we'd be removing the entire list of generics
|
||||
let prev = explicit_params[idx - 1];
|
||||
let prev_span = self.get_bound_span(prev);
|
||||
self.get_bound_span(param).with_lo(prev_span.hi())
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
self.emit_sugg(spans, &msg, help);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
|
|||
|
||||
fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
|
||||
if let Some((def_id, _)) = t.peel_refs().as_generic_param() {
|
||||
self.mark_param_used(def_id);
|
||||
self.ty_params.remove(&def_id);
|
||||
} else if let TyKind::OpaqueDef(id, _, _) = t.kind {
|
||||
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
|
||||
// `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're
|
||||
|
@ -176,9 +226,18 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
|
|||
|
||||
fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
|
||||
if let WherePredicate::BoundPredicate(predicate) = predicate {
|
||||
// Collect spans for any bounds on type parameters. We only keep bounds that appear in
|
||||
// the list of generics (not in a where-clause).
|
||||
// Collect spans for any bounds on type parameters.
|
||||
if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() {
|
||||
match predicate.origin {
|
||||
PredicateOrigin::GenericParam => {
|
||||
self.inline_bounds.insert(def_id, predicate.span);
|
||||
},
|
||||
PredicateOrigin::WhereClause => {
|
||||
self.where_bounds.insert(def_id);
|
||||
},
|
||||
PredicateOrigin::ImplTrait => (),
|
||||
}
|
||||
|
||||
// If the bound contains non-public traits, err on the safe side and don't lint the
|
||||
// corresponding parameter.
|
||||
if !predicate
|
||||
|
@ -187,12 +246,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
|
|||
.filter_map(bound_to_trait_def_id)
|
||||
.all(|id| self.cx.effective_visibilities.is_exported(id))
|
||||
{
|
||||
self.mark_param_used(def_id);
|
||||
} else if let PredicateOrigin::GenericParam = predicate.origin {
|
||||
self.bounds.insert(def_id, predicate.span);
|
||||
self.ty_params.remove(&def_id);
|
||||
}
|
||||
}
|
||||
// Only walk the right-hand side of where-bounds
|
||||
// Only walk the right-hand side of where bounds
|
||||
for bound in predicate.bounds {
|
||||
walk_param_bound(self, bound);
|
||||
}
|
||||
|
@ -207,7 +264,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
|
|||
impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Fn(_, generics, body_id) = item.kind
|
||||
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
|
||||
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
|
||||
{
|
||||
let mut walker = TypeWalker::new(cx, generics);
|
||||
walk_item(&mut walker, item);
|
||||
|
@ -219,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
|
|||
// Only lint on inherent methods, not trait methods.
|
||||
if let ImplItemKind::Fn(.., body_id) = item.kind
|
||||
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
|
||||
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
|
||||
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
|
||||
{
|
||||
let mut walker = TypeWalker::new(cx, item.generics);
|
||||
walk_impl_item(&mut walker, item);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
|
||||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -44,55 +43,53 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let (format_args, call_site) = if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
|
||||
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
|
||||
then {
|
||||
(format_args, macro_call.span)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
};
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if format_args.args.is_empty() {
|
||||
match *format_args.format_string.parts {
|
||||
[] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
|
||||
[_] => {
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let call_site = macro_call.span;
|
||||
|
||||
match (format_args.arguments.all_args(), &format_args.template[..]) {
|
||||
([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
|
||||
([], [_]) => {
|
||||
// Simulate macro expansion, converting {{ and }} to { and }.
|
||||
let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
|
||||
let Some(snippet) = snippet_opt(cx, format_args.span) else { return };
|
||||
let s_expand = snippet.replace("{{", "{").replace("}}", "}");
|
||||
let sugg = format!("{s_expand}.to_string()");
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
},
|
||||
[..] => {},
|
||||
([arg], [piece]) => {
|
||||
if let Ok(value) = find_format_arg_expr(expr, arg)
|
||||
&& let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& placeholder.format_trait == FormatTrait::Display
|
||||
&& placeholder.format_options == FormatOptions::default()
|
||||
&& match cx.typeck_results().expr_ty(value).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
}
|
||||
{
|
||||
let is_new_string = match value.kind {
|
||||
ExprKind::Binary(..) => true,
|
||||
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
|
||||
_ => false,
|
||||
};
|
||||
let sugg = if is_new_string {
|
||||
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
|
||||
} else {
|
||||
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
|
||||
format!("{}.to_string()", sugg.maybe_par())
|
||||
};
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
} else if let [arg] = &*format_args.args {
|
||||
let value = arg.param.value;
|
||||
if_chain! {
|
||||
if format_args.format_string.parts == [kw::Empty];
|
||||
if arg.format.is_default();
|
||||
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
};
|
||||
then {
|
||||
let is_new_string = match value.kind {
|
||||
ExprKind::Binary(..) => true,
|
||||
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
|
||||
_ => false,
|
||||
};
|
||||
let sugg = if is_new_string {
|
||||
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
|
||||
} else {
|
||||
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
|
||||
format!("{}.to_string()", sugg.maybe_par())
|
||||
};
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
use arrayvec::ArrayVec;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::is_diag_trait_item;
|
||||
use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred};
|
||||
use clippy_utils::macros::{
|
||||
is_assert_macro, is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam,
|
||||
FormatParamUsage,
|
||||
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
|
||||
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage,
|
||||
};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_lang_item};
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::{
|
||||
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
|
||||
FormatPlaceholder, FormatTrait,
|
||||
};
|
||||
use rustc_errors::{
|
||||
Applicability,
|
||||
SuggestionStyle::{CompletelyHidden, ShowCode},
|
||||
};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath};
|
||||
use rustc_hir::{Expr, ExprKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::edition::Edition::Edition2021;
|
||||
use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -184,72 +188,79 @@ impl FormatArgs {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let Some(format_args) = FormatArgsExpn::parse(cx, expr)
|
||||
&& let expr_expn_data = expr.span.ctxt().outer_expn_data()
|
||||
&& let outermost_expn_data = outermost_expn_data(expr_expn_data)
|
||||
&& let Some(macro_def_id) = outermost_expn_data.macro_def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
&& let ExpnKind::Macro(_, name) = outermost_expn_data.kind
|
||||
{
|
||||
for arg in &format_args.args {
|
||||
check_unused_format_specifier(cx, arg);
|
||||
if !arg.format.is_default() {
|
||||
continue;
|
||||
}
|
||||
if is_aliased(&format_args, arg.param.value.hir_id) {
|
||||
continue;
|
||||
}
|
||||
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
|
||||
check_to_string_in_format_args(cx, name, arg.param.value);
|
||||
}
|
||||
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
|
||||
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed);
|
||||
}
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
|
||||
if !is_format_macro(cx, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
let name = cx.tcx.item_name(macro_call.def_id);
|
||||
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
{
|
||||
let arg_expr = find_format_arg_expr(expr, arg);
|
||||
|
||||
check_unused_format_specifier(cx, placeholder, arg_expr);
|
||||
|
||||
if placeholder.format_trait != FormatTrait::Display
|
||||
|| placeholder.format_options != FormatOptions::default()
|
||||
|| is_aliased(format_args, index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(arg_hir_expr) = arg_expr {
|
||||
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
|
||||
check_to_string_in_format_args(cx, name, arg_hir_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
|
||||
check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
|
||||
let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs();
|
||||
fn check_unused_format_specifier(
|
||||
cx: &LateContext<'_>,
|
||||
placeholder: &FormatPlaceholder,
|
||||
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
|
||||
) {
|
||||
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());
|
||||
|
||||
if let Count::Implied(Some(mut span)) = arg.format.precision
|
||||
&& !span.is_empty()
|
||||
let is_format_args = match ty_or_ast_expr {
|
||||
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
|
||||
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
|
||||
};
|
||||
|
||||
let options = &placeholder.format_options;
|
||||
|
||||
let arg_span = match arg_expr {
|
||||
Ok(expr) => expr.span,
|
||||
Err(expr) => expr.span,
|
||||
};
|
||||
|
||||
if let Some(placeholder_span) = placeholder.span
|
||||
&& is_format_args
|
||||
&& *options != FormatOptions::default()
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
span,
|
||||
"empty precision specifier has no effect",
|
||||
|diag| {
|
||||
if param_ty.is_floating_point() {
|
||||
diag.note("a precision specifier is not required to format floats");
|
||||
}
|
||||
|
||||
if arg.format.is_default() {
|
||||
// If there's no other specifiers remove the `:` too
|
||||
span = arg.format_span();
|
||||
}
|
||||
|
||||
diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
arg.span,
|
||||
placeholder_span,
|
||||
"format specifiers have no effect on `format_args!()`",
|
||||
|diag| {
|
||||
let mut suggest_format = |spec, span| {
|
||||
let mut suggest_format = |spec| {
|
||||
let message = format!("for the {spec} to apply consider using `format!()`");
|
||||
|
||||
if let Some(mac_call) = root_macro_call(arg.param.value.span)
|
||||
if let Some(mac_call) = root_macro_call(arg_span)
|
||||
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
|
||||
&& arg.span.eq_ctxt(mac_call.span)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
cx.sess().source_map().span_until_char(mac_call.span, '!'),
|
||||
|
@ -257,25 +268,27 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
|
|||
"format",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else if let Some(span) = span {
|
||||
diag.span_help(span, message);
|
||||
} else {
|
||||
diag.help(message);
|
||||
}
|
||||
};
|
||||
|
||||
if !arg.format.width.is_implied() {
|
||||
suggest_format("width", arg.format.width.span());
|
||||
if options.width.is_some() {
|
||||
suggest_format("width");
|
||||
}
|
||||
|
||||
if !arg.format.precision.is_implied() {
|
||||
suggest_format("precision", arg.format.precision.span());
|
||||
if options.precision.is_some() {
|
||||
suggest_format("precision");
|
||||
}
|
||||
|
||||
diag.span_suggestion_verbose(
|
||||
arg.format_span(),
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
if let Some(format_span) = format_placeholder_format_span(placeholder) {
|
||||
diag.span_suggestion_verbose(
|
||||
format_span,
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -283,12 +296,12 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
|
|||
|
||||
fn check_uninlined_args(
|
||||
cx: &LateContext<'_>,
|
||||
args: &FormatArgsExpn<'_>,
|
||||
args: &rustc_ast::FormatArgs,
|
||||
call_site: Span,
|
||||
def_id: DefId,
|
||||
ignore_mixed: bool,
|
||||
) {
|
||||
if args.format_string.span.from_expansion() {
|
||||
if args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
|
||||
|
@ -303,7 +316,13 @@ fn check_uninlined_args(
|
|||
// we cannot remove any other arguments in the format string,
|
||||
// because the index numbers might be wrong after inlining.
|
||||
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
|
||||
if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() {
|
||||
for (pos, usage) in format_arg_positions(args) {
|
||||
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if fixes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -332,47 +351,40 @@ fn check_uninlined_args(
|
|||
}
|
||||
|
||||
fn check_one_arg(
|
||||
args: &FormatArgsExpn<'_>,
|
||||
param: &FormatParam<'_>,
|
||||
args: &rustc_ast::FormatArgs,
|
||||
pos: &FormatArgPosition,
|
||||
usage: FormatParamUsage,
|
||||
fixes: &mut Vec<(Span, String)>,
|
||||
ignore_mixed: bool,
|
||||
) -> bool {
|
||||
if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
|
||||
&& let [segment] = path.segments
|
||||
let index = pos.index.unwrap();
|
||||
let arg = &args.arguments.all_args()[index];
|
||||
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
|
||||
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
|
||||
&& let [segment] = path.segments.as_slice()
|
||||
&& segment.args.is_none()
|
||||
&& let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
|
||||
&& let Some(arg_span) = format_arg_removal_span(args, index)
|
||||
&& let Some(pos_span) = pos.span
|
||||
{
|
||||
let replacement = match param.usage {
|
||||
let replacement = match usage {
|
||||
FormatParamUsage::Argument => segment.ident.name.to_string(),
|
||||
FormatParamUsage::Width => format!("{}$", segment.ident.name),
|
||||
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
|
||||
};
|
||||
fixes.push((param.span, replacement));
|
||||
fixes.push((pos_span, replacement));
|
||||
fixes.push((arg_span, String::new()));
|
||||
true // successful inlining, continue checking
|
||||
} else {
|
||||
// Do not continue inlining (return false) in case
|
||||
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
|
||||
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
|
||||
param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_)))
|
||||
pos.kind != FormatArgPositionKind::Number
|
||||
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
|
||||
}
|
||||
}
|
||||
|
||||
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
|
||||
if expn_data.call_site.from_expansion() {
|
||||
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
|
||||
} else {
|
||||
expn_data
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_in_format_args(
|
||||
cx: &LateContext<'_>,
|
||||
call_site: Span,
|
||||
name: Symbol,
|
||||
arg: &Expr<'_>,
|
||||
) {
|
||||
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
|
||||
let expn_data = arg.span.ctxt().outer_expn_data();
|
||||
if expn_data.call_site.from_expansion() {
|
||||
return;
|
||||
|
@ -443,9 +455,33 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if `hir_id` is referred to by multiple format params
|
||||
fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
|
||||
args.params().filter(|param| param.value.hir_id == hir_id).at_most_one().is_err()
|
||||
fn format_arg_positions(
|
||||
format_args: &rustc_ast::FormatArgs,
|
||||
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
|
||||
format_args.template.iter().flat_map(|piece| match piece {
|
||||
FormatArgsPiece::Placeholder(placeholder) => {
|
||||
let mut positions = ArrayVec::<_, 3>::new();
|
||||
|
||||
positions.push((&placeholder.argument, FormatParamUsage::Argument));
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
|
||||
positions.push((position, FormatParamUsage::Width));
|
||||
}
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
|
||||
positions.push((position, FormatParamUsage::Precision));
|
||||
}
|
||||
|
||||
positions
|
||||
},
|
||||
FormatArgsPiece::Literal(_) => ArrayVec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the format argument at `index` is referred to by multiple format params
|
||||
fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool {
|
||||
format_arg_positions(format_args)
|
||||
.filter(|(position, _)| position.index == Ok(index))
|
||||
.at_most_one()
|
||||
.is_err()
|
||||
}
|
||||
|
||||
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
|
||||
|
@ -455,7 +491,11 @@ where
|
|||
let mut n_total = 0;
|
||||
let mut n_needed = 0;
|
||||
loop {
|
||||
if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
|
||||
if let Some(Adjustment {
|
||||
kind: Adjust::Deref(overloaded_deref),
|
||||
target,
|
||||
}) = iter.next()
|
||||
{
|
||||
n_total += 1;
|
||||
if overloaded_deref.is_some() {
|
||||
n_needed = n_total;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
|
||||
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{FormatArgsPiece, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{sym, symbol::kw, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -89,7 +91,7 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FormatTrait {
|
||||
struct FormatTraitNames {
|
||||
/// e.g. `sym::Display`
|
||||
name: Symbol,
|
||||
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
|
||||
|
@ -99,7 +101,7 @@ struct FormatTrait {
|
|||
#[derive(Default)]
|
||||
pub struct FormatImpl {
|
||||
// Whether we are inside Display or Debug trait impl - None for neither
|
||||
format_trait_impl: Option<FormatTrait>,
|
||||
format_trait_impl: Option<FormatTraitNames>,
|
||||
}
|
||||
|
||||
impl FormatImpl {
|
||||
|
@ -161,43 +163,57 @@ fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||||
if_chain! {
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
|
||||
if let macro_def_id = outer_macro.def_id;
|
||||
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
|
||||
if is_format_macro(cx, macro_def_id);
|
||||
then {
|
||||
for arg in format_args.args {
|
||||
if arg.format.r#trait != impl_trait.name {
|
||||
continue;
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
{
|
||||
find_format_args(cx, expr, outer_macro.expn, |format_args| {
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
}
|
||||
check_format_arg_self(cx, expr, &arg, impl_trait);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||||
// Since the argument to fmt is itself a reference: &self
|
||||
let reference = peel_ref_operators(cx, arg.param.value);
|
||||
let reference = peel_ref_operators(cx, arg);
|
||||
let map = cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTrait { name, .. } = impl_trait;
|
||||
let FormatTraitNames { name, .. } = impl_trait;
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
|
||||
|
@ -227,7 +243,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
|
|||
}
|
||||
}
|
||||
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
|
||||
if_chain! {
|
||||
if impl_item.ident.name == sym::fmt;
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
|
@ -241,7 +257,7 @@ fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Optio
|
|||
.and_then(|param| param.pat.simple_ident())
|
||||
.map(|ident| ident.name);
|
||||
|
||||
Some(FormatTrait {
|
||||
Some(FormatTraitNames {
|
||||
name,
|
||||
formatter_name,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use hir::FnSig;
|
||||
use rustc_ast::ast::Attribute;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefIdSet;
|
||||
use rustc_hir::{self as hir, def::Res, QPath};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::{
|
||||
lint::in_external_macro,
|
||||
|
@ -27,7 +29,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
|
|||
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
|
||||
} else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
|
@ -49,7 +51,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
|
|||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
|
||||
} else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
|
@ -72,7 +74,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
|
|||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
|
||||
} else if let hir::TraitFn::Provided(eid) = *eid {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
if attr.is_none() && is_public && !is_proc_macro(attrs) {
|
||||
|
@ -97,6 +99,7 @@ fn check_needless_must_use(
|
|||
item_span: Span,
|
||||
fn_header_span: Span,
|
||||
attr: &Attribute,
|
||||
sig: &FnSig<'_>,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), item_span) {
|
||||
return;
|
||||
|
@ -112,6 +115,15 @@ fn check_needless_must_use(
|
|||
},
|
||||
);
|
||||
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
||||
// Ignore async functions unless Future::Output type is a must_use type
|
||||
if sig.header.is_async() {
|
||||
let infcx = cx.tcx.infer_ctxt().build();
|
||||
if let Some(future_ty) = infcx.get_impl_future_output_ty(return_ty(cx, item_id))
|
||||
&& !is_must_use_ty(cx, future_ty) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DOUBLE_MUST_USE,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! lint when items are used after statements
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::ast::{Block, ItemKind, StmtKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use clippy_utils::diagnostics::span_lint_hir;
|
||||
use rustc_hir::{Block, ItemKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
|
@ -52,33 +52,34 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
|
||||
|
||||
impl EarlyLintPass for ItemsAfterStatements {
|
||||
fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) {
|
||||
if in_external_macro(cx.sess(), item.span) {
|
||||
impl LateLintPass<'_> for ItemsAfterStatements {
|
||||
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
if in_external_macro(cx.sess(), block.span) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip initial items and trailing semicolons
|
||||
let stmts = item
|
||||
// skip initial items
|
||||
let stmts = block
|
||||
.stmts
|
||||
.iter()
|
||||
.map(|stmt| &stmt.kind)
|
||||
.skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty));
|
||||
.skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)));
|
||||
|
||||
// lint on all further items
|
||||
for stmt in stmts {
|
||||
if let StmtKind::Item(ref it) = *stmt {
|
||||
if in_external_macro(cx.sess(), it.span) {
|
||||
if let StmtKind::Item(item_id) = stmt.kind {
|
||||
let item = cx.tcx.hir().item(item_id);
|
||||
if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) {
|
||||
return;
|
||||
}
|
||||
if let ItemKind::MacroDef(..) = it.kind {
|
||||
if let ItemKind::Macro(..) = item.kind {
|
||||
// do not lint `macro_rules`, but continue processing further statements
|
||||
continue;
|
||||
}
|
||||
span_lint(
|
||||
span_lint_hir(
|
||||
cx,
|
||||
ITEMS_AFTER_STATEMENTS,
|
||||
it.span,
|
||||
item.hir_id(),
|
||||
item.span,
|
||||
"adding items after statements is confusing, since items exist from the \
|
||||
start of the scope",
|
||||
);
|
||||
|
|
87
clippy_lints/src/large_futures.rs
Normal file
87
clippy_lints/src/large_futures.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// It checks for the size of a `Future` created by `async fn` or `async {}`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`,
|
||||
/// large size of a `Future` may cause stack overflows.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
|
||||
///
|
||||
/// async fn big_fut(arg: [u8; 1024]) {}
|
||||
///
|
||||
/// pub async fn test() {
|
||||
/// let fut = big_fut([0u8; 1024]);
|
||||
/// wait(fut).await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `Box::pin` the big future instead.
|
||||
///
|
||||
/// ```rust
|
||||
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
|
||||
///
|
||||
/// async fn big_fut(arg: [u8; 1024]) {}
|
||||
///
|
||||
/// pub async fn test() {
|
||||
/// let fut = Box::pin(big_fut([0u8; 1024]));
|
||||
/// wait(fut).await;
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.68.0"]
|
||||
pub LARGE_FUTURES,
|
||||
pedantic,
|
||||
"large future may lead to unexpected stack overflows"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LargeFuture {
|
||||
future_size_threshold: u64,
|
||||
}
|
||||
|
||||
impl LargeFuture {
|
||||
pub fn new(future_size_threshold: u64) -> Self {
|
||||
Self { future_size_threshold }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
|
||||
if let ExprKind::Call(func, [expr, ..]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(expr)
|
||||
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
|
||||
&& implements_trait(cx, ty, future_trait_def_id, &[])
|
||||
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
|
||||
&& let size = layout.layout.size()
|
||||
&& size >= Size::from_bytes(self.future_size_threshold)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
LARGE_FUTURES,
|
||||
expr.span,
|
||||
&format!("large future with a size of {} bytes", size.bytes()),
|
||||
"consider `Box::pin` on it",
|
||||
format!("Box::pin({})", snippet(cx, expr.span, "..")),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
#![feature(array_windows)]
|
||||
#![feature(binary_heap_into_iter_sorted)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(iter_intersperse)]
|
||||
#![feature(let_chains)]
|
||||
|
@ -162,6 +161,7 @@ mod items_after_statements;
|
|||
mod iter_not_returning_iterator;
|
||||
mod large_const_arrays;
|
||||
mod large_enum_variant;
|
||||
mod large_futures;
|
||||
mod large_include_file;
|
||||
mod large_stack_arrays;
|
||||
mod len_zero;
|
||||
|
@ -169,6 +169,7 @@ mod let_if_seq;
|
|||
mod let_underscore;
|
||||
mod let_with_type_underscore;
|
||||
mod lifetimes;
|
||||
mod lines_filter_map_ok;
|
||||
mod literal_representation;
|
||||
mod loops;
|
||||
mod macro_use;
|
||||
|
@ -183,6 +184,7 @@ mod manual_main_separator_str;
|
|||
mod manual_non_exhaustive;
|
||||
mod manual_rem_euclid;
|
||||
mod manual_retain;
|
||||
mod manual_slice_size_calculation;
|
||||
mod manual_string_new;
|
||||
mod manual_strip;
|
||||
mod map_unit_fn;
|
||||
|
@ -281,6 +283,7 @@ mod slow_vector_initialization;
|
|||
mod std_instead_of_core;
|
||||
mod strings;
|
||||
mod strlen_on_c_strings;
|
||||
mod suspicious_doc_comments;
|
||||
mod suspicious_operation_groupings;
|
||||
mod suspicious_trait_impl;
|
||||
mod suspicious_xor_used_as_pow;
|
||||
|
@ -288,6 +291,7 @@ mod swap;
|
|||
mod swap_ptr_to_ref;
|
||||
mod tabs_in_doc_comments;
|
||||
mod temporary_assignment;
|
||||
mod tests_outside_test_module;
|
||||
mod to_digit_is_some;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
|
@ -299,6 +303,7 @@ mod uninit_vec;
|
|||
mod unit_return_expecting_ord;
|
||||
mod unit_types;
|
||||
mod unnamed_address;
|
||||
mod unnecessary_box_returns;
|
||||
mod unnecessary_owned_empty_strings;
|
||||
mod unnecessary_self_imports;
|
||||
mod unnecessary_struct_initialization;
|
||||
|
@ -344,13 +349,17 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
|
||||
pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
|
||||
if let Ok((_, warnings)) = path {
|
||||
for warning in warnings {
|
||||
sess.warn(warning);
|
||||
}
|
||||
}
|
||||
let file_name = match path {
|
||||
Ok(Some(path)) => path,
|
||||
Ok(None) => return Conf::default(),
|
||||
Ok((Some(path), _)) => path,
|
||||
Ok((None, _)) => return Conf::default(),
|
||||
Err(error) => {
|
||||
sess.struct_err(format!("error finding Clippy's configuration file: {error}"))
|
||||
.emit();
|
||||
sess.err(format!("error finding Clippy's configuration file: {error}"));
|
||||
return Conf::default();
|
||||
},
|
||||
};
|
||||
|
@ -746,7 +755,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
|
||||
store.register_late_pass(|_| Box::new(returns::Return));
|
||||
store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
|
||||
store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
|
||||
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
|
||||
store.register_early_pass(|| Box::new(precedence::Precedence));
|
||||
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
|
||||
store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
|
||||
|
@ -808,6 +817,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
|
||||
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
|
||||
let future_size_threshold = conf.future_size_threshold;
|
||||
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
|
||||
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
|
||||
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
|
||||
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
|
||||
|
@ -934,11 +945,20 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
|
||||
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
|
||||
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
|
||||
store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock));
|
||||
store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
|
||||
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
|
||||
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
|
||||
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
|
||||
avoid_breaking_exported_api,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
|
||||
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
|
||||
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
100
clippy_lints/src/lines_filter_map_ok.rs
Normal file
100
clippy_lints/src/lines_filter_map_ok.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use clippy_utils::{
|
||||
diagnostics::span_lint_and_then, is_diag_item_method, is_trait_method, match_def_path, path_to_local_id, paths,
|
||||
ty::match_type,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detect uses of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
|
||||
/// when `lines` has type `std::io::Lines`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `Lines` instances might produce a never-ending stream of `Err`, in which case
|
||||
/// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
|
||||
/// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
|
||||
/// even in the absence of explicit loops in the user code.
|
||||
///
|
||||
/// This situation can arise when working with user-provided paths. On some platforms,
|
||||
/// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
|
||||
/// but any later attempt to read from `fs` will return an error.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
|
||||
/// instance in all cases. There two cases where the suggestion might not be
|
||||
/// appropriate or necessary:
|
||||
///
|
||||
/// - If the `Lines` instance can never produce any error, or if an error is produced
|
||||
/// only once just before terminating the iterator, using `map_while()` is not
|
||||
/// necessary but will not do any harm.
|
||||
/// - If the `Lines` instance can produce intermittent errors then recover and produce
|
||||
/// successful results, using `map_while()` would stop at the first error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
|
||||
/// // If "some-path" points to a directory, the next statement never terminates:
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub LINES_FILTER_MAP_OK,
|
||||
suspicious,
|
||||
"filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop"
|
||||
}
|
||||
declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
|
||||
|
||||
impl LateLintPass<'_> for LinesFilterMapOk {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind &&
|
||||
is_trait_method(cx, expr, sym::Iterator) &&
|
||||
(fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") &&
|
||||
match_type(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), &paths::STD_IO_LINES)
|
||||
{
|
||||
let lint = match &fm_arg.kind {
|
||||
// Detect `Result::ok`
|
||||
ExprKind::Path(qpath) =>
|
||||
cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
|
||||
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
|
||||
// Detect `|x| x.ok()`
|
||||
ExprKind::Closure(Closure { body, .. }) =>
|
||||
if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
|
||||
let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
|
||||
path_to_local_id(receiver, param.pat.hir_id) &&
|
||||
let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
{
|
||||
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if lint {
|
||||
span_lint_and_then(cx,
|
||||
LINES_FILTER_MAP_OK,
|
||||
fm_span,
|
||||
&format!("`{}()` will run forever if the iterator repeatedly produces an `Err`", fm_method.ident),
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
fm_receiver.span,
|
||||
"this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error");
|
||||
diag.span_suggestion(fm_span, "replace with", "map_while(Result::ok)", Applicability::MaybeIncorrect);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
clippy_lints/src/manual_slice_size_calculation.rs
Normal file
93
clippy_lints/src/manual_slice_size_calculation.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{expr_or_init, in_constant};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// When `a` is `&[T]`, detect `a.len() * size_of::<T>()` and suggest `size_of_val(a)`
|
||||
/// instead.
|
||||
///
|
||||
/// ### Why is this better?
|
||||
/// * Shorter to write
|
||||
/// * Removes the need for the human and the compiler to worry about overflow in the
|
||||
/// multiplication
|
||||
/// * Potentially faster at runtime as rust emits special no-wrapping flags when it
|
||||
/// calculates the byte length
|
||||
/// * Less turbofishing
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let data : &[i32] = &[1, 2, 3];
|
||||
/// let newlen = data.len() * std::mem::size_of::<i32>();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let data : &[i32] = &[1, 2, 3];
|
||||
/// let newlen = std::mem::size_of_val(data);
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub MANUAL_SLICE_SIZE_CALCULATION,
|
||||
complexity,
|
||||
"manual slice size calculation"
|
||||
}
|
||||
declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
// Does not apply inside const because size_of_value is not cost in stable.
|
||||
if !in_constant(cx, expr.hir_id)
|
||||
&& let ExprKind::Binary(ref op, left, right) = expr.kind
|
||||
&& BinOpKind::Mul == op.node
|
||||
&& let Some(_receiver) = simplify(cx, left, right)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
MANUAL_SLICE_SIZE_CALCULATION,
|
||||
expr.span,
|
||||
"manual slice size calculation",
|
||||
None,
|
||||
"consider using std::mem::size_of_value instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn simplify<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr1: &'tcx Expr<'tcx>,
|
||||
expr2: &'tcx Expr<'tcx>,
|
||||
) -> Option<&'tcx Expr<'tcx>> {
|
||||
let expr1 = expr_or_init(cx, expr1);
|
||||
let expr2 = expr_or_init(cx, expr2);
|
||||
|
||||
simplify_half(cx, expr1, expr2).or_else(|| simplify_half(cx, expr2, expr1))
|
||||
}
|
||||
|
||||
fn simplify_half<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr1: &'tcx Expr<'tcx>,
|
||||
expr2: &'tcx Expr<'tcx>,
|
||||
) -> Option<&'tcx Expr<'tcx>> {
|
||||
if
|
||||
// expr1 is `[T1].len()`?
|
||||
let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind
|
||||
&& method_path.ident.name == sym::len
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let ty::Slice(ty1) = receiver_ty.peel_refs().kind()
|
||||
// expr2 is `size_of::<T2>()`?
|
||||
&& let ExprKind::Call(func, _) = expr2.kind
|
||||
&& let ExprKind::Path(ref func_qpath) = func.kind
|
||||
&& let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
|
||||
&& let Some(ty2) = cx.typeck_results().node_substs(func.hir_id).types().next()
|
||||
// T1 == T2?
|
||||
&& *ty1 == ty2
|
||||
{
|
||||
Some(receiver)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_non_aggregate_primitive_type;
|
||||
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res};
|
||||
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
@ -101,40 +102,26 @@ declare_clippy_lint! {
|
|||
impl_lint_pass!(MemReplace =>
|
||||
[MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
|
||||
|
||||
fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
|
||||
// Check that second argument is `Option::None`
|
||||
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
|
||||
// Since this is a late pass (already type-checked),
|
||||
// and we already know that the second argument is an
|
||||
// `Option`, we do not need to check the first
|
||||
// argument's type. All that's left is to get
|
||||
// replacee's path.
|
||||
let replaced_path = match dest.kind {
|
||||
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
|
||||
if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
|
||||
replaced_path
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MEM_REPLACE_OPTION_WITH_NONE,
|
||||
expr_span,
|
||||
"replacing an `Option` with `None`",
|
||||
"consider `Option::take()` instead",
|
||||
format!(
|
||||
"{}.take()",
|
||||
snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
fn check_replace_option_with_none(cx: &LateContext<'_>, dest: &Expr<'_>, expr_span: Span) {
|
||||
// Since this is a late pass (already type-checked),
|
||||
// and we already know that the second argument is an
|
||||
// `Option`, we do not need to check the first
|
||||
// argument's type. All that's left is to get
|
||||
// the replacee's expr after peeling off the `&mut`
|
||||
let sugg_expr = peel_ref_operators(cx, dest);
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MEM_REPLACE_OPTION_WITH_NONE,
|
||||
expr_span,
|
||||
"replacing an `Option` with `None`",
|
||||
"consider `Option::take()` instead",
|
||||
format!(
|
||||
"{}.take()",
|
||||
Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_par()
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
|
||||
|
@ -200,10 +187,6 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
|
|||
if is_non_aggregate_primitive_type(expr_type) {
|
||||
return;
|
||||
}
|
||||
// disable lint for Option since it is covered in another lint
|
||||
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
|
||||
return;
|
||||
}
|
||||
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
@ -246,11 +229,13 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
|
|||
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
|
||||
then {
|
||||
check_replace_option_with_none(cx, src, dest, expr.span);
|
||||
check_replace_with_uninit(cx, src, dest, expr.span);
|
||||
if self.msrv.meets(msrvs::MEM_TAKE) {
|
||||
// Check that second argument is `Option::None`
|
||||
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
|
||||
check_replace_option_with_none(cx, dest, expr.span);
|
||||
} else if self.msrv.meets(msrvs::MEM_TAKE) {
|
||||
check_replace_with_default(cx, src, dest, expr.span);
|
||||
}
|
||||
check_replace_with_uninit(cx, src, dest, expr.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
53
clippy_lints/src/methods/clear_with_drain.rs
Normal file
53
clippy_lints/src/methods/clear_with_drain.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_range_full;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::CLEAR_WITH_DRAIN;
|
||||
|
||||
// Add `String` here when it is added to diagnostic items
|
||||
const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque];
|
||||
|
||||
const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet];
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: Option<&Expr<'_>>) {
|
||||
if let Some(arg) = arg {
|
||||
if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITH_ARG)
|
||||
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
|
||||
&& is_range_full(cx, arg, Some(container_path))
|
||||
{
|
||||
suggest(cx, expr, recv, span);
|
||||
}
|
||||
} else if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITHOUT_ARG) {
|
||||
suggest(cx, expr, recv, span);
|
||||
}
|
||||
}
|
||||
|
||||
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty))
|
||||
// String type is a lang item but not a diagnostic item for now so we need a separate check
|
||||
|| is_type_lang_item(cx, expr_ty, LangItem::String)
|
||||
}
|
||||
|
||||
fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) {
|
||||
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
|
||||
// Use `opt_item_name` while `String` is not a diagnostic item
|
||||
&& let Some(ty_name) = cx.tcx.opt_item_name(adt.did())
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CLEAR_WITH_DRAIN,
|
||||
span.with_hi(expr.span.hi()),
|
||||
&format!("`drain` used to clear a `{ty_name}`"),
|
||||
"try",
|
||||
"clear()".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
|
||||
use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -136,18 +136,19 @@ pub(super) fn check<'tcx>(
|
|||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
|
||||
let span = format_args.inputs_span();
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
&format!("use of `{name}` followed by a function call"),
|
||||
"try this",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
find_format_args(cx, arg_root, macro_call.expn, |format_args| {
|
||||
let span = format_args_inputs_span(format_args);
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
&format!("use of `{name}` followed by a function call"),
|
||||
"try this",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher::Range;
|
||||
use clippy_utils::is_integer_const;
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use clippy_utils::is_range_full;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -15,8 +13,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
|
|||
&& let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
|
||||
&& let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
|
||||
&& matches!(ty_name, sym::Vec | sym::VecDeque)
|
||||
&& let Some(range) = Range::hir(arg)
|
||||
&& is_full_range(cx, recv, range)
|
||||
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
|
||||
&& is_range_full(cx, arg, Some(container_path))
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -29,19 +27,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
|
|||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
|
||||
range.start.map_or(true, |e| is_integer_const(cx, e, 0))
|
||||
&& range.end.map_or(true, |e| {
|
||||
if range.limits == RangeLimits::HalfOpen
|
||||
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
|
||||
&& let ExprKind::MethodCall(name, self_arg, [], _) = e.kind
|
||||
&& name.ident.name == sym::len
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
|
||||
{
|
||||
container_path.res == path.res
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ mod chars_last_cmp;
|
|||
mod chars_last_cmp_with_unwrap;
|
||||
mod chars_next_cmp;
|
||||
mod chars_next_cmp_with_unwrap;
|
||||
mod clear_with_drain;
|
||||
mod clone_on_copy;
|
||||
mod clone_on_ref_ptr;
|
||||
mod cloned_instead_of_copied;
|
||||
|
@ -110,7 +111,7 @@ use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_
|
|||
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
@ -3190,6 +3191,31 @@ declare_clippy_lint! {
|
|||
"single command line argument that looks like it should be multiple arguments"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.drain(..)` for the sole purpose of clearing a container.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This creates an unnecessary iterator that is dropped immediately.
|
||||
///
|
||||
/// Calling `.clear()` also makes the intent clearer.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let mut v = vec![1, 2, 3];
|
||||
/// v.drain(..);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let mut v = vec![1, 2, 3];
|
||||
/// v.clear();
|
||||
/// ```
|
||||
#[clippy::version = "1.69.0"]
|
||||
pub CLEAR_WITH_DRAIN,
|
||||
nursery,
|
||||
"calling `drain` in order to `clear` a container"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
@ -3318,6 +3344,7 @@ impl_lint_pass!(Methods => [
|
|||
SEEK_TO_START_INSTEAD_OF_REWIND,
|
||||
NEEDLESS_COLLECT,
|
||||
SUSPICIOUS_COMMAND_ARG_SPACE,
|
||||
CLEAR_WITH_DRAIN,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
@ -3562,8 +3589,15 @@ impl Methods {
|
|||
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
|
||||
_ => {},
|
||||
},
|
||||
("drain", [arg]) => {
|
||||
iter_with_drain::check(cx, expr, recv, span, arg);
|
||||
("drain", ..) => {
|
||||
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
|
||||
&& matches!(kind, StmtKind::Semi(_))
|
||||
&& args.len() <= 1
|
||||
{
|
||||
clear_with_drain::check(cx, expr, recv, span, args.first());
|
||||
} else if let [arg] = args {
|
||||
iter_with_drain::check(cx, expr, recv, span, arg);
|
||||
}
|
||||
},
|
||||
("ends_with", [arg]) => {
|
||||
if let ExprKind::MethodCall(.., span) = expr.kind {
|
||||
|
|
|
@ -41,6 +41,7 @@ declare_clippy_lint! {
|
|||
/// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
|
||||
/// will suggest to make `b` const, too.
|
||||
///
|
||||
/// If you are marking a public function with `const`, removing it again will break API compatibility.
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # struct Foo {
|
||||
|
|
|
@ -154,10 +154,18 @@ impl ArithmeticSideEffects {
|
|||
Self::literal_integer(cx, actual_rhs),
|
||||
) {
|
||||
(None, None) => false,
|
||||
(None, Some(n)) | (Some(n), None) => match (&op.node, n) {
|
||||
(None, Some(n)) => match (&op.node, n) {
|
||||
// Division and module are always valid if applied to non-zero integers
|
||||
(hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
|
||||
// Addition or subtracting zeros is always a no-op
|
||||
// Adding or subtracting zeros is always a no-op
|
||||
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
|
||||
// Multiplication by 1 or 0 will never overflow
|
||||
| (hir::BinOpKind::Mul, 0 | 1)
|
||||
=> true,
|
||||
_ => false,
|
||||
},
|
||||
(Some(n), None) => match (&op.node, n) {
|
||||
// Adding or subtracting zeros is always a no-op
|
||||
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
|
||||
// Multiplication by 1 or 0 will never overflow
|
||||
| (hir::BinOpKind::Mul, 0 | 1)
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet};
|
||||
use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustc_ast::visit::Visitor as AstVisitor;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::{
|
||||
diagnostics::span_lint_and_sugg,
|
||||
peel_blocks,
|
||||
source::{snippet, walk_span_to_context},
|
||||
visitors::for_each_expr,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_hir::{AsyncGeneratorKind, Closure, Expr, ExprKind, GeneratorKind, MatchSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::{lint::in_external_macro, ty::UpvarCapture};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -14,106 +21,88 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// async fn f() -> i32 {
|
||||
/// 1 + 2
|
||||
/// }
|
||||
///
|
||||
/// let f = async {
|
||||
/// 1 + 2
|
||||
/// };
|
||||
/// let fut = async {
|
||||
/// f().await
|
||||
/// f.await
|
||||
/// };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// async fn f() -> i32 {
|
||||
/// 1 + 2
|
||||
/// }
|
||||
///
|
||||
/// let fut = f();
|
||||
/// let f = async {
|
||||
/// 1 + 2
|
||||
/// };
|
||||
/// let fut = f;
|
||||
/// ```
|
||||
#[clippy::version = "1.69.0"]
|
||||
pub REDUNDANT_ASYNC_BLOCK,
|
||||
nursery,
|
||||
complexity,
|
||||
"`async { future.await }` can be replaced by `future`"
|
||||
}
|
||||
declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]);
|
||||
|
||||
impl EarlyLintPass for RedundantAsyncBlock {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Async(_, block) = &expr.kind && block.stmts.len() == 1 &&
|
||||
let Some(Stmt { kind: StmtKind::Expr(last), .. }) = block.stmts.last() &&
|
||||
let ExprKind::Await(future) = &last.kind &&
|
||||
!future.span.from_expansion() &&
|
||||
!await_in_expr(future)
|
||||
impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let span = expr.span;
|
||||
if !in_external_macro(cx.tcx.sess, span) &&
|
||||
let Some(body_expr) = desugar_async_block(cx, expr) &&
|
||||
let Some(expr) = desugar_await(peel_blocks(body_expr)) &&
|
||||
// The await prefix must not come from a macro as its content could change in the future.
|
||||
expr.span.ctxt() == body_expr.span.ctxt() &&
|
||||
// An async block does not have immediate side-effects from a `.await` point-of-view.
|
||||
(!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) &&
|
||||
let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt())
|
||||
{
|
||||
if captures_value(last) {
|
||||
// If the async block captures variables then there is no equivalence.
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REDUNDANT_ASYNC_BLOCK,
|
||||
expr.span,
|
||||
span,
|
||||
"this async expression only awaits a single future",
|
||||
"you can reduce it to",
|
||||
snippet(cx, future.span, "..").into_owned(),
|
||||
snippet(cx, shortened_span, "..").into_owned(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether an expression contains `.await`
|
||||
fn await_in_expr(expr: &Expr) -> bool {
|
||||
let mut detector = AwaitDetector::default();
|
||||
detector.visit_expr(expr);
|
||||
detector.await_found
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AwaitDetector {
|
||||
await_found: bool,
|
||||
}
|
||||
|
||||
impl<'ast> AstVisitor<'ast> for AwaitDetector {
|
||||
fn visit_expr(&mut self, ex: &'ast Expr) {
|
||||
match (&ex.kind, self.await_found) {
|
||||
(ExprKind::Await(_), _) => self.await_found = true,
|
||||
(_, false) => rustc_ast::visit::walk_expr(self, ex),
|
||||
_ => (),
|
||||
}
|
||||
/// If `expr` is a desugared `async` block, return the original expression if it does not capture
|
||||
/// any variable by ref.
|
||||
fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let ExprKind::Closure(Closure { body, def_id, .. }) = expr.kind &&
|
||||
let body = cx.tcx.hir().body(*body) &&
|
||||
matches!(body.generator_kind, Some(GeneratorKind::Async(AsyncGeneratorKind::Block)))
|
||||
{
|
||||
cx
|
||||
.typeck_results()
|
||||
.closure_min_captures
|
||||
.get(def_id)
|
||||
.map_or(true, |m| {
|
||||
m.values().all(|places| {
|
||||
places
|
||||
.iter()
|
||||
.all(|place| matches!(place.info.capture_kind, UpvarCapture::ByValue))
|
||||
})
|
||||
})
|
||||
.then_some(body.value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether an expression may have captured a local variable.
|
||||
/// This is done by looking for paths with only one segment, except as
|
||||
/// a prefix of `.await` since this would be captured by value.
|
||||
///
|
||||
/// This function will sometimes return `true` even tough there are no
|
||||
/// captures happening: at the AST level, it is impossible to
|
||||
/// dinstinguish a function call from a call to a closure which comes
|
||||
/// from the local environment.
|
||||
fn captures_value(expr: &Expr) -> bool {
|
||||
let mut detector = CaptureDetector::default();
|
||||
detector.visit_expr(expr);
|
||||
detector.capture_found
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CaptureDetector {
|
||||
capture_found: bool,
|
||||
}
|
||||
|
||||
impl<'ast> AstVisitor<'ast> for CaptureDetector {
|
||||
fn visit_expr(&mut self, ex: &'ast Expr) {
|
||||
match (&ex.kind, self.capture_found) {
|
||||
(ExprKind::Await(fut), _) if matches!(fut.kind, ExprKind::Path(..)) => (),
|
||||
(ExprKind::Path(_, path), _) if path.segments.len() == 1 => self.capture_found = true,
|
||||
(_, false) => rustc_ast::visit::walk_expr(self, ex),
|
||||
_ => (),
|
||||
}
|
||||
/// If `expr` is a desugared `.await`, return the original expression if it does not come from a
|
||||
/// macro expansion.
|
||||
fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind &&
|
||||
let ExprKind::Call(_, [into_future_arg]) = match_value.kind &&
|
||||
let ctxt = expr.span.ctxt() &&
|
||||
for_each_expr(into_future_arg, |e|
|
||||
walk_span_to_context(e.span, ctxt)
|
||||
.map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))).is_none()
|
||||
{
|
||||
Some(into_future_arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, StaticItem, ConstItem};
|
||||
use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
@ -106,7 +106,7 @@ impl EarlyLintPass for RedundantStaticLifetimes {
|
|||
// #2438)
|
||||
}
|
||||
|
||||
if let ItemKind::Static(box StaticItem { ty: ref var_type,.. }) = item.kind {
|
||||
if let ItemKind::Static(box StaticItem { ty: ref var_type, .. }) = item.kind {
|
||||
Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use rustc_hir::intravisit::FnKind;
|
|||
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::source_map::Span;
|
||||
|
@ -175,7 +175,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
|||
} else {
|
||||
RetReplacement::Empty
|
||||
};
|
||||
check_final_expr(cx, body.value, vec![], replacement);
|
||||
check_final_expr(cx, body.value, vec![], replacement, None);
|
||||
},
|
||||
FnKind::ItemFn(..) | FnKind::Method(..) => {
|
||||
check_block_return(cx, &body.value.kind, sp, vec![]);
|
||||
|
@ -188,11 +188,11 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
|||
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
|
||||
if let ExprKind::Block(block, _) = expr_kind {
|
||||
if let Some(block_expr) = block.expr {
|
||||
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
|
||||
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
|
||||
} else if let Some(stmt) = block.stmts.iter().last() {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(expr) => {
|
||||
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
|
||||
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
StmtKind::Semi(semi_expr) => {
|
||||
// Remove ending semicolons and any whitespace ' ' in between.
|
||||
|
@ -202,7 +202,7 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>,
|
|||
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
|
||||
semi_spans.push(semi_span_to_remove);
|
||||
}
|
||||
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty);
|
||||
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
@ -216,6 +216,7 @@ fn check_final_expr<'tcx>(
|
|||
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
|
||||
* needless return */
|
||||
replacement: RetReplacement<'tcx>,
|
||||
match_ty_opt: Option<Ty<'_>>,
|
||||
) {
|
||||
let peeled_drop_expr = expr.peel_drop_temps();
|
||||
match &peeled_drop_expr.kind {
|
||||
|
@ -244,7 +245,22 @@ fn check_final_expr<'tcx>(
|
|||
RetReplacement::Expr(snippet, applicability)
|
||||
}
|
||||
} else {
|
||||
replacement
|
||||
match match_ty_opt {
|
||||
Some(match_ty) => {
|
||||
match match_ty.kind() {
|
||||
// If the code got till here with
|
||||
// tuple not getting detected before it,
|
||||
// then we are sure it's going to be Unit
|
||||
// type
|
||||
ty::Tuple(_) => RetReplacement::Unit,
|
||||
// We don't want to anything in this case
|
||||
// cause we can't predict what the user would
|
||||
// want here
|
||||
_ => return,
|
||||
}
|
||||
},
|
||||
None => replacement,
|
||||
}
|
||||
};
|
||||
|
||||
if !cx.tcx.hir().attrs(expr.hir_id).is_empty() {
|
||||
|
@ -268,8 +284,9 @@ fn check_final_expr<'tcx>(
|
|||
// note, if without else is going to be a type checking error anyways
|
||||
// (except for unit type functions) so we don't match it
|
||||
ExprKind::Match(_, arms, MatchSource::Normal) => {
|
||||
let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
|
||||
for arm in arms.iter() {
|
||||
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
|
||||
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
|
||||
}
|
||||
},
|
||||
// if it's a whole block, check it
|
||||
|
@ -293,6 +310,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>,
|
|||
if ret_span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable);
|
||||
let return_replacement = replacement.to_string();
|
||||
let sugg_help = replacement.sugg_help();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use rustc_ast::node_id::{NodeId, NodeMap};
|
||||
use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
|
||||
use rustc_ast::visit::{walk_expr, Visitor};
|
||||
use rustc_ast::{ptr::P, Crate, Expr, ExprKind, Item, ItemKind, MacroDef, ModKind, Ty, TyKind, UseTreeKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
@ -55,7 +56,7 @@ impl EarlyLintPass for SingleComponentPathImports {
|
|||
return;
|
||||
}
|
||||
|
||||
self.check_mod(cx, &krate.items);
|
||||
self.check_mod(&krate.items);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
|
@ -84,8 +85,43 @@ impl EarlyLintPass for SingleComponentPathImports {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ImportUsageVisitor {
|
||||
// keep track of imports reused with `self` keyword, such as `self::std` in the example below.
|
||||
// Removing the `use std;` would make this a compile error (#10549)
|
||||
// ```
|
||||
// use std;
|
||||
//
|
||||
// fn main() {
|
||||
// let _ = self::std::io::stdout();
|
||||
// }
|
||||
// ```
|
||||
imports_referenced_with_self: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ImportUsageVisitor {
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if let ExprKind::Path(_, path) = &expr.kind
|
||||
&& path.segments.len() > 1
|
||||
&& path.segments[0].ident.name == kw::SelfLower
|
||||
{
|
||||
self.imports_referenced_with_self.push(path.segments[1].ident.name);
|
||||
}
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, ty: &Ty) {
|
||||
if let TyKind::Path(_, path) = &ty.kind
|
||||
&& path.segments.len() > 1
|
||||
&& path.segments[0].ident.name == kw::SelfLower
|
||||
{
|
||||
self.imports_referenced_with_self.push(path.segments[1].ident.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SingleComponentPathImports {
|
||||
fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
|
||||
fn check_mod(&mut self, items: &[P<Item>]) {
|
||||
// keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example
|
||||
// below. Removing the `use crypto_hash;` would make this a compile error
|
||||
// ```
|
||||
|
@ -108,18 +144,16 @@ impl SingleComponentPathImports {
|
|||
// ```
|
||||
let mut macros = Vec::new();
|
||||
|
||||
let mut import_usage_visitor = ImportUsageVisitor::default();
|
||||
for item in items {
|
||||
self.track_uses(
|
||||
cx,
|
||||
item,
|
||||
&mut imports_reused_with_self,
|
||||
&mut single_use_usages,
|
||||
&mut macros,
|
||||
);
|
||||
self.track_uses(item, &mut imports_reused_with_self, &mut single_use_usages, &mut macros);
|
||||
import_usage_visitor.visit_item(item);
|
||||
}
|
||||
|
||||
for usage in single_use_usages {
|
||||
if !imports_reused_with_self.contains(&usage.name) {
|
||||
if !imports_reused_with_self.contains(&usage.name)
|
||||
&& !import_usage_visitor.imports_referenced_with_self.contains(&usage.name)
|
||||
{
|
||||
self.found.entry(usage.item_id).or_default().push(usage);
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +161,6 @@ impl SingleComponentPathImports {
|
|||
|
||||
fn track_uses(
|
||||
&mut self,
|
||||
cx: &EarlyContext<'_>,
|
||||
item: &Item,
|
||||
imports_reused_with_self: &mut Vec<Symbol>,
|
||||
single_use_usages: &mut Vec<SingleUse>,
|
||||
|
@ -139,7 +172,7 @@ impl SingleComponentPathImports {
|
|||
|
||||
match &item.kind {
|
||||
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
|
||||
self.check_mod(cx, items);
|
||||
self.check_mod(items);
|
||||
},
|
||||
ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
|
||||
macros.push(item.ident.name);
|
||||
|
|
94
clippy_lints/src/suspicious_doc_comments.rs
Normal file
94
clippy_lints/src/suspicious_doc_comments.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{token::CommentKind, AttrKind, AttrStyle, Attribute, Item};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Triple-slash comments (known as "outer doc comments") apply to items that follow it.
|
||||
/// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning.
|
||||
///
|
||||
/// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which
|
||||
/// applies to the parent item (i.e. the item that the comment is contained in,
|
||||
/// usually a module or crate).
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Inner doc comments can only appear before items, so there are certain cases where the suggestion
|
||||
/// made by this lint is not valid code. For example:
|
||||
/// ```rs
|
||||
/// fn foo() {}
|
||||
/// ///!
|
||||
/// fn bar() {}
|
||||
/// ```
|
||||
/// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment
|
||||
/// is not valid at that position.
|
||||
///
|
||||
/// ### Example
|
||||
/// In this example, the doc comment is attached to the *function*, rather than the *module*.
|
||||
/// ```rust
|
||||
/// pub mod util {
|
||||
/// ///! This module contains utility functions.
|
||||
///
|
||||
/// pub fn dummy() {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// pub mod util {
|
||||
/// //! This module contains utility functions.
|
||||
///
|
||||
/// pub fn dummy() {}
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub SUSPICIOUS_DOC_COMMENTS,
|
||||
suspicious,
|
||||
"suspicious usage of (outer) doc comments"
|
||||
}
|
||||
declare_lint_pass!(SuspiciousDocComments => [SUSPICIOUS_DOC_COMMENTS]);
|
||||
|
||||
const WARNING: &str = "this is an outer doc comment and does not apply to the parent module or crate";
|
||||
const HELP: &str = "use an inner doc comment to document the parent module or crate";
|
||||
|
||||
impl EarlyLintPass for SuspiciousDocComments {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
let replacements = collect_doc_comment_replacements(&item.attrs);
|
||||
|
||||
if let Some(((lo_span, _), (hi_span, _))) = replacements.first().zip(replacements.last()) {
|
||||
let span = lo_span.to(*hi_span);
|
||||
|
||||
span_lint_and_then(cx, SUSPICIOUS_DOC_COMMENTS, span, WARNING, |diag| {
|
||||
multispan_sugg_with_applicability(diag, HELP, Applicability::MaybeIncorrect, replacements);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_doc_comment_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if_chain! {
|
||||
if let AttrKind::DocComment(com_kind, sym) = attr.kind;
|
||||
if let AttrStyle::Outer = attr.style;
|
||||
if let Some(com) = sym.as_str().strip_prefix('!');
|
||||
then {
|
||||
let sugg = match com_kind {
|
||||
CommentKind::Line => format!("//!{com}"),
|
||||
CommentKind::Block => format!("/*!{com}*/")
|
||||
};
|
||||
Some((attr.span, sugg))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
71
clippy_lints/src/tests_outside_test_module.rs
Normal file
71
clippy_lints/src/tests_outside_test_module.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function};
|
||||
use rustc_hir::{intravisit::FnKind, Body, FnDecl};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{def_id::LocalDefId, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module
|
||||
/// (marked with `#[cfg(test)]`).
|
||||
/// ### Why is this bad?
|
||||
/// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`),
|
||||
/// having test functions outside of this module is confusing and may lead to them being "hidden".
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// #[test]
|
||||
/// fn my_cool_test() {
|
||||
/// // [...]
|
||||
/// }
|
||||
///
|
||||
/// #[cfg(test)]
|
||||
/// mod tests {
|
||||
/// // [...]
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[cfg(test)]
|
||||
/// mod tests {
|
||||
/// #[test]
|
||||
/// fn my_cool_test() {
|
||||
/// // [...]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub TESTS_OUTSIDE_TEST_MODULE,
|
||||
restriction,
|
||||
"A test function is outside the testing module."
|
||||
}
|
||||
|
||||
declare_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]);
|
||||
|
||||
impl LateLintPass<'_> for TestsOutsideTestModule {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
kind: FnKind<'_>,
|
||||
_: &FnDecl<'_>,
|
||||
body: &Body<'_>,
|
||||
sp: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
if_chain! {
|
||||
if !matches!(kind, FnKind::Closure);
|
||||
if is_in_test_function(cx.tcx, body.id().hir_id);
|
||||
if !is_in_cfg_test(cx.tcx, body.id().hir_id);
|
||||
then {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
TESTS_OUTSIDE_TEST_MODULE,
|
||||
sp,
|
||||
"this function marked with #[test] is outside a #[cfg(test)] module",
|
||||
None,
|
||||
"move it to a testing module marked with #[cfg(test)]",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,9 @@ use super::utils::check_cast;
|
|||
use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_ast::ExprPrecedence;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_hir::{Expr, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{cast::CastKind, Ty};
|
||||
|
||||
|
@ -19,7 +20,7 @@ pub(super) fn check<'tcx>(
|
|||
) -> bool {
|
||||
use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg = match check_cast(cx, e, from_ty, to_ty) {
|
||||
let mut sugg = match check_cast(cx, e, from_ty, to_ty) {
|
||||
Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => {
|
||||
Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app)
|
||||
.as_ty(to_ty.to_string())
|
||||
|
@ -39,6 +40,12 @@ pub(super) fn check<'tcx>(
|
|||
_ => return false,
|
||||
};
|
||||
|
||||
if let Node::Expr(parent) = cx.tcx.hir().get_parent(e.hir_id)
|
||||
&& parent.precedence().order() > ExprPrecedence::Cast.order()
|
||||
{
|
||||
sugg = format!("({sugg})");
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
||||
|
|
120
clippy_lints/src/unnecessary_box_returns.rs
Normal file
120
clippy_lints/src/unnecessary_box_returns.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Symbol;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks for a return type containing a `Box<T>` where `T` implements `Sized`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It's better to just return `T` in these cases. The caller may not need
|
||||
/// the value to be boxed, and it's expensive to free the memory once the
|
||||
/// `Box<T>` been dropped.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// fn foo() -> Box<String> {
|
||||
/// Box::new(String::from("Hello, world!"))
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn foo() -> String {
|
||||
/// String::from("Hello, world!")
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub UNNECESSARY_BOX_RETURNS,
|
||||
pedantic,
|
||||
"Needlessly returning a Box"
|
||||
}
|
||||
|
||||
pub struct UnnecessaryBoxReturns {
|
||||
avoid_breaking_exported_api: bool,
|
||||
}
|
||||
|
||||
impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]);
|
||||
|
||||
impl UnnecessaryBoxReturns {
|
||||
pub fn new(avoid_breaking_exported_api: bool) -> Self {
|
||||
Self {
|
||||
avoid_breaking_exported_api,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
|
||||
// we don't want to tell someone to break an exported function if they ask us not to
|
||||
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// functions which contain the word "box" are exempt from this lint
|
||||
if name.as_str().contains("box") {
|
||||
return;
|
||||
}
|
||||
|
||||
let FnRetTy::Return(return_ty_hir) = &decl.output else { return };
|
||||
|
||||
let return_ty = cx
|
||||
.tcx
|
||||
.erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder())
|
||||
.output();
|
||||
|
||||
if !return_ty.is_box() {
|
||||
return;
|
||||
}
|
||||
|
||||
let boxed_ty = return_ty.boxed_ty();
|
||||
|
||||
// it's sometimes useful to return Box<T> if T is unsized, so don't lint those
|
||||
if boxed_ty.is_sized(cx.tcx, cx.param_env) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNNECESSARY_BOX_RETURNS,
|
||||
return_ty_hir.span,
|
||||
format!("boxed return of the sized type `{boxed_ty}`").as_str(),
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion(
|
||||
return_ty_hir.span,
|
||||
"try",
|
||||
boxed_ty.to_string(),
|
||||
// the return value and function callers also needs to
|
||||
// be changed, so this can't be MachineApplicable
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
diagnostic.help("changing this also requires a change to the return expressions in this function");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for UnnecessaryBoxReturns {
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
|
||||
let TraitItemKind::Fn(signature, _) = &item.kind else { return };
|
||||
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) {
|
||||
// Ignore implementations of traits, because the lint should be on the
|
||||
// trait, not on the implmentation of it.
|
||||
let Node::Item(parent) = cx.tcx.hir().get_parent(item.hir_id()) else { return };
|
||||
let ItemKind::Impl(parent) = parent.kind else { return };
|
||||
if parent.of_trait.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ImplItemKind::Fn(signature, ..) = &item.kind else { return };
|
||||
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let ItemKind::Fn(signature, ..) = &item.kind else { return };
|
||||
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ declare_clippy_lint! {
|
|||
/// any field.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readibility suffers from unnecessary struct building.
|
||||
/// Readability suffers from unnecessary struct building.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
|
@ -25,9 +25,13 @@ declare_clippy_lint! {
|
|||
/// let a = S { s: String::from("Hello, world!") };
|
||||
/// let b = a;
|
||||
/// ```
|
||||
///
|
||||
/// ### Known Problems
|
||||
/// Has false positives when the base is a place expression that cannot be
|
||||
/// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub UNNECESSARY_STRUCT_INITIALIZATION,
|
||||
complexity,
|
||||
nursery,
|
||||
"struct built from a base that can be written mode concisely"
|
||||
}
|
||||
declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
|
||||
|
|
|
@ -10,8 +10,8 @@ use rustc_hir::{
|
|||
def::{CtorOf, DefKind, Res},
|
||||
def_id::LocalDefId,
|
||||
intravisit::{walk_inf, walk_ty, Visitor},
|
||||
Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl, ImplItemKind, Item,
|
||||
ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
|
||||
Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl,
|
||||
ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
@ -249,7 +249,7 @@ define_Conf! {
|
|||
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
|
||||
/// ```
|
||||
(arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
|
||||
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
|
||||
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS.
|
||||
///
|
||||
/// Suppress lints whenever the suggested change would cause breakage for other crates.
|
||||
(avoid_breaking_exported_api: bool = true),
|
||||
|
@ -275,13 +275,13 @@ define_Conf! {
|
|||
///
|
||||
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
|
||||
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
|
||||
/// default configuration of Clippy. By default any configuration will replace the default value.
|
||||
/// default configuration of Clippy. By default, any configuration will replace the default value.
|
||||
(disallowed_names: Vec<String> = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()),
|
||||
/// Lint: DOC_MARKDOWN.
|
||||
///
|
||||
/// The list of words this lint should not consider as identifiers needing ticks. The value
|
||||
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
|
||||
/// default configuration of Clippy. By default any configuraction will replace the default value. For example:
|
||||
/// default configuration of Clippy. By default, any configuration will replace the default value. For example:
|
||||
/// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
|
||||
/// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
|
||||
///
|
||||
|
@ -390,7 +390,7 @@ define_Conf! {
|
|||
/// Enforce the named macros always use the braces specified.
|
||||
///
|
||||
/// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
|
||||
/// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
|
||||
/// could be used with a full path two `MacroMatcher`s have to be added one with the full path
|
||||
/// `crate_name::macro_name` and one with just the macro name.
|
||||
(standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
|
||||
/// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
|
||||
|
@ -408,7 +408,7 @@ define_Conf! {
|
|||
/// Lint: INDEX_REFUTABLE_SLICE.
|
||||
///
|
||||
/// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
|
||||
/// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
|
||||
/// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
|
||||
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
|
||||
(max_suggested_slice_pattern_length: u64 = 3),
|
||||
/// Lint: AWAIT_HOLDING_INVALID_TYPE.
|
||||
|
@ -459,6 +459,10 @@ define_Conf! {
|
|||
/// Whether to **only** check for missing documentation in items visible within the current
|
||||
/// crate. For example, `pub(crate)` items.
|
||||
(missing_docs_in_crate_items: bool = false),
|
||||
/// Lint: LARGE_FUTURES.
|
||||
///
|
||||
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
|
||||
(future_size_threshold: u64 = 16 * 1024),
|
||||
}
|
||||
|
||||
/// Search for the configuration file.
|
||||
|
@ -466,7 +470,7 @@ define_Conf! {
|
|||
/// # Errors
|
||||
///
|
||||
/// Returns any unexpected filesystem error encountered when searching for the config file
|
||||
pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
||||
pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
|
||||
/// Possible filename to search for.
|
||||
const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
|
||||
|
||||
|
@ -474,9 +478,11 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
|||
// If neither of those exist, use ".".
|
||||
let mut current = env::var_os("CLIPPY_CONF_DIR")
|
||||
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
|
||||
.map_or_else(|| PathBuf::from("."), PathBuf::from);
|
||||
.map_or_else(|| PathBuf::from("."), PathBuf::from)
|
||||
.canonicalize()?;
|
||||
|
||||
let mut found_config: Option<PathBuf> = None;
|
||||
let mut warnings = vec![];
|
||||
|
||||
loop {
|
||||
for config_file_name in &CONFIG_FILE_NAMES {
|
||||
|
@ -487,12 +493,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
|||
Ok(md) if md.is_dir() => {},
|
||||
Ok(_) => {
|
||||
// warn if we happen to find two config files #8323
|
||||
if let Some(ref found_config_) = found_config {
|
||||
eprintln!(
|
||||
"Using config file `{}`\nWarning: `{}` will be ignored.",
|
||||
found_config_.display(),
|
||||
config_file.display(),
|
||||
);
|
||||
if let Some(ref found_config) = found_config {
|
||||
warnings.push(format!(
|
||||
"using config file `{}`, `{}` will be ignored",
|
||||
found_config.display(),
|
||||
config_file.display()
|
||||
));
|
||||
} else {
|
||||
found_config = Some(config_file);
|
||||
}
|
||||
|
@ -502,12 +508,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
|||
}
|
||||
|
||||
if found_config.is_some() {
|
||||
return Ok(found_config);
|
||||
return Ok((found_config, warnings));
|
||||
}
|
||||
|
||||
// If the current directory has no parent, we're done searching.
|
||||
if !current.pop() {
|
||||
return Ok(None);
|
||||
return Ok((None, warnings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
use clippy_utils::macros::collect_ast_format_args;
|
||||
use rustc_ast::{Expr, ExprKind};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::{Expr, ExprKind, FormatArgs};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::hygiene;
|
||||
use std::iter::once;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -15,9 +20,79 @@ declare_clippy_lint! {
|
|||
declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
|
||||
|
||||
impl EarlyLintPass for FormatArgsCollector {
|
||||
fn check_expr(&mut self, _: &EarlyContext<'_>, expr: &Expr) {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if let ExprKind::FormatArgs(args) = &expr.kind {
|
||||
if has_span_from_proc_macro(cx, args) {
|
||||
return;
|
||||
}
|
||||
|
||||
collect_ast_format_args(expr.span, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects if the format string or an argument has its span set by a proc macro to something inside
|
||||
/// a macro callsite, e.g.
|
||||
///
|
||||
/// ```ignore
|
||||
/// println!(some_proc_macro!("input {}"), a);
|
||||
/// ```
|
||||
///
|
||||
/// Where `some_proc_macro` expands to
|
||||
///
|
||||
/// ```ignore
|
||||
/// println!("output {}", a);
|
||||
/// ```
|
||||
///
|
||||
/// But with the span of `"output {}"` set to the macro input
|
||||
///
|
||||
/// ```ignore
|
||||
/// println!(some_proc_macro!("input {}"), a);
|
||||
/// // ^^^^^^^^^^
|
||||
/// ```
|
||||
fn has_span_from_proc_macro(cx: &EarlyContext<'_>, args: &FormatArgs) -> bool {
|
||||
let ctxt = args.span.ctxt();
|
||||
|
||||
// `format!("{} {} {c}", "one", "two", c = "three")`
|
||||
// ^^^^^ ^^^^^ ^^^^^^^
|
||||
let argument_span = args
|
||||
.arguments
|
||||
.explicit_args()
|
||||
.iter()
|
||||
.map(|argument| hygiene::walk_chain(argument.expr.span, ctxt));
|
||||
|
||||
// `format!("{} {} {c}", "one", "two", c = "three")`
|
||||
// ^^ ^^ ^^^^^^
|
||||
let between_spans = once(args.span)
|
||||
.chain(argument_span)
|
||||
.tuple_windows()
|
||||
.map(|(start, end)| start.between(end));
|
||||
|
||||
for between_span in between_spans {
|
||||
let mut seen_comma = false;
|
||||
|
||||
let Some(snippet) = snippet_opt(cx, between_span) else { return true };
|
||||
for token in tokenize(&snippet) {
|
||||
match token.kind {
|
||||
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
|
||||
TokenKind::Comma if !seen_comma => seen_comma = true,
|
||||
// named arguments, `start_val, name = end_val`
|
||||
// ^^^^^^^^^ between_span
|
||||
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
|
||||
// An unexpected token usually indicates that we crossed a macro boundary
|
||||
//
|
||||
// `println!(some_proc_macro!("input {}"), a)`
|
||||
// ^^^ between_span
|
||||
// `println!("{}", val!(x))`
|
||||
// ^^^^^^^ between_span
|
||||
_ => return true,
|
||||
}
|
||||
}
|
||||
|
||||
if !seen_comma {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
|
@ -463,12 +463,18 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
|||
&& let Some(value_string) = snippet_opt(cx, arg.expr.span)
|
||||
{
|
||||
let (replacement, replace_raw) = match lit.kind {
|
||||
LitKind::Str | LitKind::StrRaw(_) => extract_str_literal(&value_string),
|
||||
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
|
||||
Some(extracted) => extracted,
|
||||
None => return,
|
||||
},
|
||||
LitKind::Char => (
|
||||
match lit.symbol.as_str() {
|
||||
"\"" => "\\\"",
|
||||
"\\'" => "'",
|
||||
_ => &value_string[1..value_string.len() - 1],
|
||||
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
||||
Some(stripped) => stripped,
|
||||
None => return,
|
||||
},
|
||||
}
|
||||
.to_string(),
|
||||
false,
|
||||
|
@ -533,13 +539,13 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
|||
/// `r#"a"#` -> (`a`, true)
|
||||
///
|
||||
/// `"b"` -> (`b`, false)
|
||||
fn extract_str_literal(literal: &str) -> (String, bool) {
|
||||
fn extract_str_literal(literal: &str) -> Option<(String, bool)> {
|
||||
let (literal, raw) = match literal.strip_prefix('r') {
|
||||
Some(stripped) => (stripped.trim_matches('#'), true),
|
||||
None => (literal, false),
|
||||
};
|
||||
|
||||
(literal[1..literal.len() - 1].to_string(), raw)
|
||||
Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw))
|
||||
}
|
||||
|
||||
enum UnescapeErr {
|
||||
|
|
|
@ -286,8 +286,30 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
|
|||
match (l, r) {
|
||||
(ExternCrate(l), ExternCrate(r)) => l == r,
|
||||
(Use(l), Use(r)) => eq_use_tree(l, r),
|
||||
(Static(box ast::StaticItem { ty: lt, mutability: lm, expr: le}), Static(box ast::StaticItem { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(
|
||||
Static(box ast::StaticItem {
|
||||
ty: lt,
|
||||
mutability: lm,
|
||||
expr: le,
|
||||
}),
|
||||
Static(box ast::StaticItem {
|
||||
ty: rt,
|
||||
mutability: rm,
|
||||
expr: re,
|
||||
}),
|
||||
) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(
|
||||
Const(box ast::ConstItem {
|
||||
defaultness: ld,
|
||||
ty: lt,
|
||||
expr: le,
|
||||
}),
|
||||
Const(box ast::ConstItem {
|
||||
defaultness: rd,
|
||||
ty: rt,
|
||||
expr: re,
|
||||
}),
|
||||
) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(
|
||||
Fn(box ast::Fn {
|
||||
defaultness: ld,
|
||||
|
@ -451,7 +473,18 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
|
|||
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
|
||||
use AssocItemKind::*;
|
||||
match (l, r) {
|
||||
(Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(
|
||||
Const(box ast::ConstItem {
|
||||
defaultness: ld,
|
||||
ty: lt,
|
||||
expr: le,
|
||||
}),
|
||||
Const(box ast::ConstItem {
|
||||
defaultness: rd,
|
||||
ty: rt,
|
||||
expr: re,
|
||||
}),
|
||||
) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
|
||||
(
|
||||
Fn(box ast::Fn {
|
||||
defaultness: ld,
|
||||
|
|
|
@ -32,7 +32,6 @@ extern crate rustc_lexer;
|
|||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_mir_dataflow;
|
||||
extern crate rustc_parse_format;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
extern crate rustc_target;
|
||||
|
@ -77,7 +76,7 @@ use std::sync::OnceLock;
|
|||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, LitKind};
|
||||
use rustc_ast::ast::{self, LitKind, RangeLimits};
|
||||
use rustc_ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::unhash::UnhashMap;
|
||||
|
@ -95,6 +94,7 @@ use rustc_hir::{
|
|||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||
use rustc_middle::hir::place::PlaceBase;
|
||||
use rustc_middle::mir::ConstantKind;
|
||||
use rustc_middle::ty as rustc_ty;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
|
||||
use rustc_middle::ty::binding::BindingMode;
|
||||
|
@ -113,7 +113,8 @@ use rustc_span::symbol::{kw, Ident, Symbol};
|
|||
use rustc_span::Span;
|
||||
use rustc_target::abi::Integer;
|
||||
|
||||
use crate::consts::{constant, Constant};
|
||||
use crate::consts::{constant, miri_to_const, Constant};
|
||||
use crate::higher::Range;
|
||||
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
|
||||
use crate::visitors::for_each_expr;
|
||||
|
||||
|
@ -1490,6 +1491,68 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
|
||||
/// For the lower bound, this means that:
|
||||
/// - either there is none
|
||||
/// - or it is the smallest value that can be represented by the range's integer type
|
||||
/// For the upper bound, this means that:
|
||||
/// - either there is none
|
||||
/// - or it is the largest value that can be represented by the range's integer type and is
|
||||
/// inclusive
|
||||
/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to
|
||||
/// a method call on that same container (e.g. `v.drain(..v.len())`)
|
||||
/// If the given `Expr` is not some kind of range, the function returns `false`.
|
||||
pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if let Some(Range { start, end, limits }) = Range::hir(expr) {
|
||||
let start_is_none_or_min = start.map_or(true, |start| {
|
||||
if let rustc_ty::Adt(_, subst) = ty.kind()
|
||||
&& let bnd_ty = subst.type_at(0)
|
||||
&& let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx)
|
||||
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
|
||||
&& let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
|
||||
&& let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
|
||||
&& let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
|
||||
{
|
||||
start_const == min_const
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let end_is_none_or_max = end.map_or(true, |end| {
|
||||
match limits {
|
||||
RangeLimits::Closed => {
|
||||
if let rustc_ty::Adt(_, subst) = ty.kind()
|
||||
&& let bnd_ty = subst.type_at(0)
|
||||
&& let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx)
|
||||
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
|
||||
&& let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
|
||||
&& let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
|
||||
&& let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
|
||||
{
|
||||
end_const == max_const
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
RangeLimits::HalfOpen => {
|
||||
if let Some(container_path) = container_path
|
||||
&& let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
|
||||
&& name.ident.name == sym::len
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
|
||||
{
|
||||
container_path.res == path.res
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
return start_is_none_or_min && end_is_none_or_max;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks whether the given expression is a constant integer of the given value.
|
||||
/// unlike `is_integer_literal`, this version does const folding
|
||||
pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
|
||||
|
@ -2104,8 +2167,7 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
|
|||
.filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
|
||||
traits::impossible_predicates(
|
||||
cx.tcx,
|
||||
traits::elaborate(cx.tcx, predicates)
|
||||
.collect::<Vec<_>>(),
|
||||
traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
#![allow(clippy::similar_names)] // `expr` and `expn`
|
||||
|
||||
use crate::source::snippet_opt;
|
||||
use crate::visitors::{for_each_expr, Descend};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use itertools::{izip, Either, Itertools};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_ast::FormatArgs;
|
||||
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind};
|
||||
use rustc_lexer::unescape::unescape_literal;
|
||||
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_parse_format::{self as rpf, Alignment};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
|
||||
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
|
||||
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol};
|
||||
use std::cell::RefCell;
|
||||
use std::iter::{once, zip};
|
||||
use std::ops::ControlFlow;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
|
@ -226,11 +218,11 @@ pub enum PanicExpn<'a> {
|
|||
/// A single argument that implements `Display` - `panic!("{}", object)`
|
||||
Display(&'a Expr<'a>),
|
||||
/// Anything else - `panic!("error {}: {}", a, b)`
|
||||
Format(FormatArgsExpn<'a>),
|
||||
Format(&'a Expr<'a>),
|
||||
}
|
||||
|
||||
impl<'a> PanicExpn<'a> {
|
||||
pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
|
||||
pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
|
||||
let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
|
||||
let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
|
||||
let result = match path.segments.last().unwrap().ident.as_str() {
|
||||
|
@ -240,7 +232,7 @@ impl<'a> PanicExpn<'a> {
|
|||
let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
|
||||
Self::Display(e)
|
||||
},
|
||||
"panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
|
||||
"panic_fmt" => Self::Format(arg),
|
||||
// Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
|
||||
// `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
|
||||
"assert_failed" => {
|
||||
|
@ -252,7 +244,7 @@ impl<'a> PanicExpn<'a> {
|
|||
// `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
|
||||
let msg_arg = &rest[2];
|
||||
match msg_arg.kind {
|
||||
ExprKind::Call(_, [fmt_arg]) => Self::Format(FormatArgsExpn::parse(cx, fmt_arg)?),
|
||||
ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
|
||||
_ => Self::Empty,
|
||||
}
|
||||
},
|
||||
|
@ -304,7 +296,7 @@ fn find_assert_args_inner<'a, const N: usize>(
|
|||
let mut args = ArrayVec::new();
|
||||
let panic_expn = for_each_expr(expr, |e| {
|
||||
if args.is_full() {
|
||||
match PanicExpn::parse(cx, e) {
|
||||
match PanicExpn::parse(e) {
|
||||
Some(expn) => ControlFlow::Break(expn),
|
||||
None => ControlFlow::Continue(Descend::Yes),
|
||||
}
|
||||
|
@ -391,30 +383,82 @@ pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
|
|||
});
|
||||
}
|
||||
|
||||
/// Calls `callback` with an AST [`FormatArgs`] node if one is found
|
||||
/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
|
||||
/// descendant of `expn_id`
|
||||
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
|
||||
let format_args_expr = for_each_expr(start, |expr| {
|
||||
let ctxt = expr.span.ctxt();
|
||||
if ctxt == start.span.ctxt() {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
} else if ctxt.outer_expn().is_descendant_of(expn_id)
|
||||
&& macro_backtrace(expr.span)
|
||||
if ctxt.outer_expn().is_descendant_of(expn_id) {
|
||||
if macro_backtrace(expr.span)
|
||||
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
|
||||
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
|
||||
{
|
||||
ControlFlow::Break(expr)
|
||||
{
|
||||
ControlFlow::Break(expr)
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(format_args_expr) = format_args_expr {
|
||||
if let Some(expr) = format_args_expr {
|
||||
AST_FORMAT_ARGS.with(|ast_format_args| {
|
||||
ast_format_args.borrow().get(&format_args_expr.span).map(callback);
|
||||
ast_format_args.borrow().get(&expr.span).map(callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
|
||||
/// it cannot be found it will return the [`rustc_ast::Expr`].
|
||||
pub fn find_format_arg_expr<'hir, 'ast>(
|
||||
start: &'hir Expr<'hir>,
|
||||
target: &'ast FormatArgument,
|
||||
) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
|
||||
for_each_expr(start, |expr| {
|
||||
if expr.span == target.expr.span {
|
||||
ControlFlow::Break(expr)
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.ok_or(&target.expr)
|
||||
}
|
||||
|
||||
/// Span of the `:` and format specifiers
|
||||
///
|
||||
/// ```ignore
|
||||
/// format!("{:.}"), format!("{foo:.}")
|
||||
/// ^^ ^^
|
||||
/// ```
|
||||
pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
|
||||
let base = placeholder.span?.data();
|
||||
|
||||
// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
|
||||
// brace `{...|}`
|
||||
Some(Span::new(
|
||||
placeholder.argument.span?.hi(),
|
||||
base.hi - BytePos(1),
|
||||
base.ctxt,
|
||||
base.parent,
|
||||
))
|
||||
}
|
||||
|
||||
/// Span covering the format string and values
|
||||
///
|
||||
/// ```ignore
|
||||
/// format("{}.{}", 10, 11)
|
||||
/// // ^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
|
||||
match format_args.arguments.explicit_args() {
|
||||
[] => format_args.span,
|
||||
[.., last] => format_args
|
||||
.span
|
||||
.to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
|
||||
/// `10`
|
||||
///
|
||||
|
@ -436,251 +480,6 @@ pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option
|
|||
Some(current.with_lo(prev.hi()))
|
||||
}
|
||||
|
||||
/// The format string doesn't exist in the HIR, so we reassemble it from source code
|
||||
#[derive(Debug)]
|
||||
pub struct FormatString {
|
||||
/// Span of the whole format string literal, including `[r#]"`.
|
||||
pub span: Span,
|
||||
/// Snippet of the whole format string literal, including `[r#]"`.
|
||||
pub snippet: String,
|
||||
/// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
|
||||
pub style: Option<usize>,
|
||||
/// The unescaped value of the format string, e.g. `"val – {}"` for the literal
|
||||
/// `"val \u{2013} {}"`.
|
||||
pub unescaped: String,
|
||||
/// The format string split by format args like `{..}`.
|
||||
pub parts: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl FormatString {
|
||||
fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
|
||||
// format_args!(r"a {} b \", 1);
|
||||
//
|
||||
// expands to
|
||||
//
|
||||
// ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
|
||||
// &[::core::fmt::ArgumentV1::new_display(&1)]);
|
||||
//
|
||||
// where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
|
||||
let span = pieces.span;
|
||||
let snippet = snippet_opt(cx, span)?;
|
||||
|
||||
let (inner, style) = match tokenize(&snippet).next()?.kind {
|
||||
TokenKind::Literal { kind, .. } => {
|
||||
let style = match kind {
|
||||
LiteralKind::Str { .. } => None,
|
||||
LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let start = style.map_or(1, |n| 2 + n);
|
||||
let end = snippet.len() - style.map_or(1, |n| 1 + n);
|
||||
|
||||
(&snippet[start..end], style)
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mode = if style.is_some() {
|
||||
unescape::Mode::RawStr
|
||||
} else {
|
||||
unescape::Mode::Str
|
||||
};
|
||||
|
||||
let mut unescaped = String::with_capacity(inner.len());
|
||||
// Sometimes the original string comes from a macro which accepts a malformed string, such as in a
|
||||
// #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the
|
||||
// string from the span will not be possible, so we will just return None here.
|
||||
let mut unparsable = false;
|
||||
unescape_literal(inner, mode, &mut |_, ch| match ch {
|
||||
Ok(ch) => unescaped.push(ch),
|
||||
Err(e) if !e.is_fatal() => (),
|
||||
Err(_) => unparsable = true,
|
||||
});
|
||||
if unparsable {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parts = Vec::new();
|
||||
let _: Option<!> = for_each_expr(pieces, |expr| {
|
||||
if let ExprKind::Lit(lit) = &expr.kind
|
||||
&& let LitKind::Str(symbol, _) = lit.node
|
||||
{
|
||||
parts.push(symbol);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
Some(Self {
|
||||
span,
|
||||
snippet,
|
||||
style,
|
||||
unescaped,
|
||||
parts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatArgsValues<'tcx> {
|
||||
/// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
|
||||
/// `format!("{x} {} {}", 1, z + 2)`.
|
||||
value_args: Vec<&'tcx Expr<'tcx>>,
|
||||
/// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
|
||||
/// `value_args`
|
||||
pos_to_value_index: Vec<usize>,
|
||||
/// Used to check if a value is declared inline & to resolve `InnerSpan`s.
|
||||
format_string_span: SpanData,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatArgsValues<'tcx> {
|
||||
fn new_empty(format_string_span: SpanData) -> Self {
|
||||
Self {
|
||||
value_args: Vec::new(),
|
||||
pos_to_value_index: Vec::new(),
|
||||
format_string_span,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
|
||||
let mut pos_to_value_index = Vec::new();
|
||||
let mut value_args = Vec::new();
|
||||
let _: Option<!> = for_each_expr(args, |expr| {
|
||||
if expr.span.ctxt() == args.span.ctxt() {
|
||||
// ArgumentV1::new_<format_trait>(<val>)
|
||||
// ArgumentV1::from_usize(<val>)
|
||||
if let ExprKind::Call(callee, [val]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind
|
||||
{
|
||||
let val_idx = if val.span.ctxt() == expr.span.ctxt()
|
||||
&& let ExprKind::Field(_, field) = val.kind
|
||||
&& let Ok(idx) = field.name.as_str().parse()
|
||||
{
|
||||
// tuple index
|
||||
idx
|
||||
} else {
|
||||
// assume the value expression is passed directly
|
||||
pos_to_value_index.len()
|
||||
};
|
||||
|
||||
pos_to_value_index.push(val_idx);
|
||||
}
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
} else {
|
||||
// assume that any expr with a differing span is a value
|
||||
value_args.push(expr);
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
value_args,
|
||||
pos_to_value_index,
|
||||
format_string_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The positions of a format argument's value, precision and width
|
||||
///
|
||||
/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
struct ParamPosition {
|
||||
/// The position stored in `rt::v1::Argument::position`.
|
||||
value: usize,
|
||||
/// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
|
||||
width: Option<usize>,
|
||||
/// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
|
||||
precision: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ParamPosition {
|
||||
fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
|
||||
match field.ident.name {
|
||||
sym::position => {
|
||||
if let ExprKind::Lit(lit) = &field.expr.kind
|
||||
&& let LitKind::Int(pos, _) = lit.node
|
||||
{
|
||||
self.value = pos as usize;
|
||||
}
|
||||
},
|
||||
sym::precision => {
|
||||
self.precision = parse_count(field.expr);
|
||||
},
|
||||
sym::width => {
|
||||
self.width = parse_count(field.expr);
|
||||
},
|
||||
_ => walk_expr(self, field.expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_count(expr: &Expr<'_>) -> Option<usize> {
|
||||
// <::core::fmt::rt::v1::Count>::Param(1usize),
|
||||
if let ExprKind::Call(ctor, [val]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(_, path)) = ctor.kind
|
||||
&& path.ident.name == sym::Param
|
||||
&& let ExprKind::Lit(lit) = &val.kind
|
||||
&& let LitKind::Int(pos, _) = lit.node
|
||||
{
|
||||
Some(pos as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
|
||||
fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
|
||||
if let ExprKind::AddrOf(.., array) = fmt_arg.kind
|
||||
&& let ExprKind::Array(specs) = array.kind
|
||||
{
|
||||
Some(specs.iter().map(|spec| {
|
||||
if let ExprKind::Call(f, args) = spec.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind
|
||||
&& f.ident.name == sym::new
|
||||
&& let [position, _fill, _align, _flags, precision, width] = args
|
||||
&& let ExprKind::Lit(position) = &position.kind
|
||||
&& let LitKind::Int(position, _) = position.node {
|
||||
ParamPosition {
|
||||
value: position as usize,
|
||||
width: parse_count(width),
|
||||
precision: parse_count(precision),
|
||||
}
|
||||
} else {
|
||||
ParamPosition::default()
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
|
||||
fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
|
||||
Span::new(
|
||||
base.lo + BytePos::from_usize(inner.start),
|
||||
base.lo + BytePos::from_usize(inner.end),
|
||||
base.ctxt,
|
||||
base.parent,
|
||||
)
|
||||
}
|
||||
|
||||
/// How a format parameter is used in the format string
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum FormatParamKind {
|
||||
/// An implicit parameter , such as `{}` or `{:?}`.
|
||||
Implicit,
|
||||
/// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
|
||||
Numbered,
|
||||
/// A parameter with an asterisk precision. e.g. `{:.*}`.
|
||||
Starred,
|
||||
/// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
|
||||
Named(Symbol),
|
||||
/// An implicit named parameter, such as the `y` in `format!("{y}")`.
|
||||
NamedInline(Symbol),
|
||||
}
|
||||
|
||||
/// Where a format parameter is being used in the format string
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum FormatParamUsage {
|
||||
|
@ -692,467 +491,6 @@ pub enum FormatParamUsage {
|
|||
Precision,
|
||||
}
|
||||
|
||||
/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
|
||||
///
|
||||
/// ```
|
||||
/// let precision = 2;
|
||||
/// format!("{:.precision$}", 0.1234);
|
||||
/// ```
|
||||
///
|
||||
/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
|
||||
/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct FormatParam<'tcx> {
|
||||
/// The expression this parameter refers to.
|
||||
pub value: &'tcx Expr<'tcx>,
|
||||
/// How this parameter refers to its `value`.
|
||||
pub kind: FormatParamKind,
|
||||
/// Where this format param is being used - argument/width/precision
|
||||
pub usage: FormatParamUsage,
|
||||
/// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
|
||||
///
|
||||
/// ```text
|
||||
/// format!("{}, { }, {0}, {name}", ...);
|
||||
/// ^ ~~ ~ ~~~~
|
||||
/// ```
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatParam<'tcx> {
|
||||
fn new(
|
||||
mut kind: FormatParamKind,
|
||||
usage: FormatParamUsage,
|
||||
position: usize,
|
||||
inner: rpf::InnerSpan,
|
||||
values: &FormatArgsValues<'tcx>,
|
||||
) -> Option<Self> {
|
||||
let value_index = *values.pos_to_value_index.get(position)?;
|
||||
let value = *values.value_args.get(value_index)?;
|
||||
let span = span_from_inner(values.format_string_span, inner);
|
||||
|
||||
// if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
|
||||
// into the format string
|
||||
if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
|
||||
kind = FormatParamKind::NamedInline(name);
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
value,
|
||||
kind,
|
||||
usage,
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
|
||||
/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Count<'tcx> {
|
||||
/// Specified with a literal number, stores the value.
|
||||
Is(usize, Span),
|
||||
/// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
|
||||
/// `FormatParamKind::Numbered`.
|
||||
Param(FormatParam<'tcx>),
|
||||
/// Not specified.
|
||||
Implied(Option<Span>),
|
||||
}
|
||||
|
||||
impl<'tcx> Count<'tcx> {
|
||||
fn new(
|
||||
usage: FormatParamUsage,
|
||||
count: rpf::Count<'_>,
|
||||
position: Option<usize>,
|
||||
inner: Option<rpf::InnerSpan>,
|
||||
values: &FormatArgsValues<'tcx>,
|
||||
) -> Option<Self> {
|
||||
let span = inner.map(|inner| span_from_inner(values.format_string_span, inner));
|
||||
|
||||
Some(match count {
|
||||
rpf::Count::CountIs(val) => Self::Is(val, span?),
|
||||
rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
|
||||
FormatParamKind::Named(Symbol::intern(name)),
|
||||
usage,
|
||||
position?,
|
||||
inner?,
|
||||
values,
|
||||
)?),
|
||||
rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
|
||||
FormatParamKind::Numbered,
|
||||
usage,
|
||||
position?,
|
||||
inner?,
|
||||
values,
|
||||
)?),
|
||||
rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
|
||||
FormatParamKind::Starred,
|
||||
usage,
|
||||
position?,
|
||||
inner?,
|
||||
values,
|
||||
)?),
|
||||
rpf::Count::CountImplied => Self::Implied(span),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_implied(self) -> bool {
|
||||
matches!(self, Count::Implied(_))
|
||||
}
|
||||
|
||||
pub fn param(self) -> Option<FormatParam<'tcx>> {
|
||||
match self {
|
||||
Count::Param(param) => Some(param),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(self) -> Option<Span> {
|
||||
match self {
|
||||
Count::Is(_, span) => Some(span),
|
||||
Count::Param(param) => Some(param.span),
|
||||
Count::Implied(span) => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specification for the formatting of an argument in the format string. See
|
||||
/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSpec<'tcx> {
|
||||
/// Optionally specified character to fill alignment with.
|
||||
pub fill: Option<char>,
|
||||
/// Optionally specified alignment.
|
||||
pub align: Alignment,
|
||||
/// Whether all flag options are set to default (no flags specified).
|
||||
pub no_flags: bool,
|
||||
/// Represents either the maximum width or the integer precision.
|
||||
pub precision: Count<'tcx>,
|
||||
/// The minimum width, will be padded according to `width`/`align`
|
||||
pub width: Count<'tcx>,
|
||||
/// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
|
||||
/// `{:?}`.
|
||||
pub r#trait: Symbol,
|
||||
pub trait_span: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatSpec<'tcx> {
|
||||
fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
|
||||
Some(Self {
|
||||
fill: spec.fill,
|
||||
align: spec.align,
|
||||
no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(),
|
||||
precision: Count::new(
|
||||
FormatParamUsage::Precision,
|
||||
spec.precision,
|
||||
positions.precision,
|
||||
spec.precision_span,
|
||||
values,
|
||||
)?,
|
||||
width: Count::new(
|
||||
FormatParamUsage::Width,
|
||||
spec.width,
|
||||
positions.width,
|
||||
spec.width_span,
|
||||
values,
|
||||
)?,
|
||||
r#trait: match spec.ty {
|
||||
"" => sym::Display,
|
||||
"?" => sym::Debug,
|
||||
"o" => sym!(Octal),
|
||||
"x" => sym!(LowerHex),
|
||||
"X" => sym!(UpperHex),
|
||||
"p" => sym::Pointer,
|
||||
"b" => sym!(Binary),
|
||||
"e" => sym!(LowerExp),
|
||||
"E" => sym!(UpperExp),
|
||||
_ => return None,
|
||||
},
|
||||
trait_span: spec
|
||||
.ty_span
|
||||
.map(|span| span_from_inner(values.format_string_span, span)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
|
||||
/// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.r#trait == sym::Display && self.is_default_for_trait()
|
||||
}
|
||||
|
||||
/// Has no other formatting specifiers than setting the format trait. returns true for `{}`,
|
||||
/// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}`
|
||||
pub fn is_default_for_trait(&self) -> bool {
|
||||
self.width.is_implied() && self.precision.is_implied() && self.align == Alignment::AlignUnknown && self.no_flags
|
||||
}
|
||||
}
|
||||
|
||||
/// A format argument, such as `{}`, `{foo:?}`.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatArg<'tcx> {
|
||||
/// The parameter the argument refers to.
|
||||
pub param: FormatParam<'tcx>,
|
||||
/// How to format `param`.
|
||||
pub format: FormatSpec<'tcx>,
|
||||
/// span of the whole argument, `{..}`.
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatArg<'tcx> {
|
||||
/// Span of the `:` and format specifiers
|
||||
///
|
||||
/// ```ignore
|
||||
/// format!("{:.}"), format!("{foo:.}")
|
||||
/// ^^ ^^
|
||||
/// ```
|
||||
pub fn format_span(&self) -> Span {
|
||||
let base = self.span.data();
|
||||
|
||||
// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
|
||||
// brace `{...|}`
|
||||
Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed `format_args!` expansion.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatArgsExpn<'tcx> {
|
||||
/// The format string literal.
|
||||
pub format_string: FormatString,
|
||||
/// The format arguments, such as `{:?}`.
|
||||
pub args: Vec<FormatArg<'tcx>>,
|
||||
/// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
|
||||
/// include this added newline.
|
||||
pub newline: bool,
|
||||
/// Spans of the commas between the format string and explicit values, excluding any trailing
|
||||
/// comma
|
||||
///
|
||||
/// ```ignore
|
||||
/// format!("..", 1, 2, 3,)
|
||||
/// // ^ ^ ^
|
||||
/// ```
|
||||
comma_spans: Vec<Span>,
|
||||
/// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
|
||||
/// `format!("{x} {} {y}", 1, z + 2)`.
|
||||
explicit_values: Vec<&'tcx Expr<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatArgsExpn<'tcx> {
|
||||
/// Gets the spans of the commas inbetween the format string and explicit args, not including
|
||||
/// any trailing comma
|
||||
///
|
||||
/// ```ignore
|
||||
/// format!("{} {}", a, b)
|
||||
/// // ^ ^
|
||||
/// ```
|
||||
///
|
||||
/// Ensures that the format string and values aren't coming from a proc macro that sets the
|
||||
/// output span to that of its input
|
||||
fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
|
||||
// `format!("{} {} {c}", "one", "two", c = "three")`
|
||||
// ^^^^^ ^^^^^ ^^^^^^^
|
||||
let value_spans = explicit_values
|
||||
.iter()
|
||||
.map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
|
||||
|
||||
// `format!("{} {} {c}", "one", "two", c = "three")`
|
||||
// ^^ ^^ ^^^^^^
|
||||
let between_spans = once(fmt_span)
|
||||
.chain(value_spans)
|
||||
.tuple_windows()
|
||||
.map(|(start, end)| start.between(end));
|
||||
|
||||
let mut comma_spans = Vec::new();
|
||||
for between_span in between_spans {
|
||||
let mut offset = 0;
|
||||
let mut seen_comma = false;
|
||||
|
||||
for token in tokenize(&snippet_opt(cx, between_span)?) {
|
||||
match token.kind {
|
||||
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
|
||||
TokenKind::Comma if !seen_comma => {
|
||||
seen_comma = true;
|
||||
|
||||
let base = between_span.data();
|
||||
comma_spans.push(Span::new(
|
||||
base.lo + BytePos(offset),
|
||||
base.lo + BytePos(offset + 1),
|
||||
base.ctxt,
|
||||
base.parent,
|
||||
));
|
||||
},
|
||||
// named arguments, `start_val, name = end_val`
|
||||
// ^^^^^^^^^ between_span
|
||||
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
|
||||
// An unexpected token usually indicates the format string or a value came from a proc macro output
|
||||
// that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
|
||||
// emits a string literal with the span set to that of `"input"`
|
||||
_ => return None,
|
||||
}
|
||||
offset += token.len;
|
||||
}
|
||||
|
||||
if !seen_comma {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(comma_spans)
|
||||
}
|
||||
|
||||
pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
|
||||
let macro_name = macro_backtrace(expr.span)
|
||||
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
|
||||
.find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
|
||||
let newline = macro_name == sym::format_args_nl;
|
||||
|
||||
// ::core::fmt::Arguments::new_const(pieces)
|
||||
// ::core::fmt::Arguments::new_v1(pieces, args)
|
||||
// ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
|
||||
if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind
|
||||
&& matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted")
|
||||
{
|
||||
let format_string = FormatString::new(cx, pieces)?;
|
||||
|
||||
let mut parser = rpf::Parser::new(
|
||||
&format_string.unescaped,
|
||||
format_string.style,
|
||||
Some(format_string.snippet.clone()),
|
||||
// `format_string.unescaped` does not contain the appended newline
|
||||
false,
|
||||
rpf::ParseMode::Format,
|
||||
);
|
||||
|
||||
let parsed_args = parser
|
||||
.by_ref()
|
||||
.filter_map(|piece| match piece {
|
||||
rpf::Piece::NextArgument(a) => Some(a),
|
||||
rpf::Piece::String(_) => None,
|
||||
})
|
||||
.collect_vec();
|
||||
if !parser.errors.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let positions = if let Some(fmt_arg) = rest.get(1) {
|
||||
// If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
|
||||
// them.
|
||||
|
||||
Either::Left(parse_rt_fmt(fmt_arg)?)
|
||||
} else {
|
||||
// If no format specs are given, the positions are in the given order and there are
|
||||
// no `precision`/`width`s to consider.
|
||||
|
||||
Either::Right((0..).map(|n| ParamPosition {
|
||||
value: n,
|
||||
width: None,
|
||||
precision: None,
|
||||
}))
|
||||
};
|
||||
|
||||
let values = if let Some(args) = rest.first() {
|
||||
FormatArgsValues::new(args, format_string.span.data())
|
||||
} else {
|
||||
FormatArgsValues::new_empty(format_string.span.data())
|
||||
};
|
||||
|
||||
let args = izip!(positions, parsed_args, parser.arg_places)
|
||||
.map(|(position, parsed_arg, arg_span)| {
|
||||
Some(FormatArg {
|
||||
param: FormatParam::new(
|
||||
match parsed_arg.position {
|
||||
rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
|
||||
rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
|
||||
// NamedInline is handled by `FormatParam::new()`
|
||||
rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
|
||||
},
|
||||
FormatParamUsage::Argument,
|
||||
position.value,
|
||||
parsed_arg.position_span,
|
||||
&values,
|
||||
)?,
|
||||
format: FormatSpec::new(parsed_arg.format, position, &values)?,
|
||||
span: span_from_inner(values.format_string_span, arg_span),
|
||||
})
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
let mut explicit_values = values.value_args;
|
||||
// remove values generated for implicitly captured vars
|
||||
let len = explicit_values
|
||||
.iter()
|
||||
.take_while(|val| !format_string.span.contains(val.span))
|
||||
.count();
|
||||
explicit_values.truncate(len);
|
||||
|
||||
let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
|
||||
|
||||
Some(Self {
|
||||
format_string,
|
||||
args,
|
||||
newline,
|
||||
comma_spans,
|
||||
explicit_values,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
|
||||
for_each_expr(expr, |e| {
|
||||
let e_ctxt = e.span.ctxt();
|
||||
if e_ctxt == expr.span.ctxt() {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
} else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
|
||||
if let Some(args) = FormatArgsExpn::parse(cx, e) {
|
||||
ControlFlow::Break(args)
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Source callsite span of all inputs
|
||||
pub fn inputs_span(&self) -> Span {
|
||||
match *self.explicit_values {
|
||||
[] => self.format_string.span,
|
||||
[.., last] => self
|
||||
.format_string
|
||||
.span
|
||||
.to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the span of a value expanded to the previous comma, e.g. for the value `10`
|
||||
///
|
||||
/// ```ignore
|
||||
/// format("{}.{}", 10, 11)
|
||||
/// // ^^^^
|
||||
/// ```
|
||||
pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
|
||||
for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
|
||||
if value.hir_id == value_id {
|
||||
return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Iterator of all format params, both values and those referenced by `width`/`precision`s.
|
||||
pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
|
||||
self.args
|
||||
.iter()
|
||||
.flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// A node with a `HirId` and a `Span`
|
||||
pub trait HirNode {
|
||||
fn hir_id(&self) -> HirId;
|
||||
|
|
|
@ -23,6 +23,7 @@ pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
|
|||
pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
|
||||
pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
|
||||
pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
|
||||
pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"];
|
||||
pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
|
||||
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
|
||||
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
|
||||
|
@ -113,6 +114,7 @@ pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
|
|||
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
|
||||
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
|
||||
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
|
||||
pub const STD_IO_LINES: [&str; 3] = ["std", "io", "Lines"];
|
||||
pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
|
||||
pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"];
|
||||
pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"];
|
||||
|
|
|
@ -541,9 +541,25 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
|
|||
pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
cx.tcx
|
||||
.check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty)))
|
||||
// For types containing generic parameters we cannot get a layout to check.
|
||||
// Therefore, we are conservative and assume that they don't allow uninit.
|
||||
.unwrap_or(false)
|
||||
.unwrap_or_else(|_| is_uninit_value_valid_for_ty_fallback(cx, ty))
|
||||
}
|
||||
|
||||
/// A fallback for polymorphic types, which are not supported by `check_validity_requirement`.
|
||||
fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match *ty.kind() {
|
||||
// The array length may be polymorphic, let's try the inner type.
|
||||
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
|
||||
// Peek through tuples and try their fallbacks.
|
||||
ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
|
||||
// Unions are always fine right now.
|
||||
// This includes MaybeUninit, the main way people use uninitialized memory.
|
||||
// For ADTs, we could look at all fields just like for tuples, but that's potentially
|
||||
// exponential, so let's avoid doing that for now. Code doing that is sketchy enough to
|
||||
// just use an `#[allow()]`.
|
||||
ty::Adt(adt, _) => adt.is_union(),
|
||||
// For the rest, conservatively assume that they cannot be uninit.
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets an iterator over all predicates which apply to the given item.
|
||||
|
|
|
@ -35,7 +35,7 @@ relicensing are archived on GitHub. We also have saved Wayback Machine copies of
|
|||
|
||||
The usernames of commenters on these issues can be found in relicense_comments.txt
|
||||
|
||||
There are a couple people in relicense_comments.txt who are not found in contributors.txt:
|
||||
There are a few people in relicense_comments.txt who are not found in contributors.txt:
|
||||
|
||||
- @EpocSquadron has [made minor text contributions to the
|
||||
README](https://github.com/rust-lang/rust-clippy/commits?author=EpocSquadron) which have since been overwritten, and
|
||||
|
@ -55,7 +55,7 @@ There are a couple people in relicense_comments.txt who are not found in contrib
|
|||
we rewrote (see below)
|
||||
|
||||
|
||||
Two of these contributors had nonminor contributions (#2184, #427) requiring a rewrite, carried out in #3251
|
||||
Two of these contributors had non-minor contributions (#2184, #427) requiring a rewrite, carried out in #3251
|
||||
([archive](http://web.archive.org/web/20181005192411/https://github.com/rust-lang-nursery/rust-clippy/pull/3251),
|
||||
[screenshot](https://user-images.githubusercontent.com/1617736/46573515-5cb69580-c94b-11e8-86e5-b456452121b2.png))
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ or
|
|||
cargo lintcheck
|
||||
```
|
||||
|
||||
By default the logs will be saved into
|
||||
By default, the logs will be saved into
|
||||
`lintcheck-logs/lintcheck_crates_logs.txt`.
|
||||
|
||||
You can set a custom sources.toml by adding `--crates-toml custom.toml` or using
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2023-03-24"
|
||||
channel = "nightly-2023-04-06"
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
|
|
|
@ -130,7 +130,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
|
|||
#[allow(rustc::bad_opt_access)]
|
||||
fn config(&mut self, config: &mut interface::Config) {
|
||||
let conf_path = clippy_lints::lookup_conf_file();
|
||||
let conf_path_string = if let Ok(Some(path)) = &conf_path {
|
||||
let conf_path_string = if let Ok((Some(path), _)) = &conf_path {
|
||||
path.to_str().map(String::from)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
Using config file `$SRC_DIR/.clippy.toml`
|
||||
Warning: `$SRC_DIR/clippy.toml` will be ignored.
|
||||
warning: using config file `$SRC_DIR/.clippy.toml`, `$SRC_DIR/clippy.toml` will be ignored
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
||||
|
|
|
@ -11,6 +11,18 @@ LL - println!("val='{}'", local_i32);
|
|||
LL + println!("val='{local_i32}'");
|
||||
|
|
||||
|
||||
error: variables can be used directly in the `format!` string
|
||||
--> $DIR/uninlined_format_args.rs:10:5
|
||||
|
|
||||
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: change this to
|
||||
|
|
||||
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
|
||||
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> $DIR/uninlined_format_args.rs:10:35
|
||||
|
|
||||
|
@ -24,18 +36,6 @@ LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
|
|||
LL + println!("Hello x is {:.*}", local_i32, local_f64);
|
||||
|
|
||||
|
||||
error: variables can be used directly in the `format!` string
|
||||
--> $DIR/uninlined_format_args.rs:10:5
|
||||
|
|
||||
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: change this to
|
||||
|
|
||||
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
|
||||
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
|
||||
|
|
||||
|
||||
error: variables can be used directly in the `format!` string
|
||||
--> $DIR/uninlined_format_args.rs:11:5
|
||||
|
|
||||
|
|
1
tests/ui-toml/extra_unused_type_parameters/clippy.toml
Normal file
1
tests/ui-toml/extra_unused_type_parameters/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
avoid-breaking-exported-api = true
|
|
@ -0,0 +1,9 @@
|
|||
pub struct S;
|
||||
|
||||
impl S {
|
||||
pub fn exported_fn<T>() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
1
tests/ui-toml/large_futures/clippy.toml
Normal file
1
tests/ui-toml/large_futures/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
future-size-threshold = 1024
|
27
tests/ui-toml/large_futures/large_futures.rs
Normal file
27
tests/ui-toml/large_futures/large_futures.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
#![warn(clippy::large_futures)]
|
||||
|
||||
fn main() {}
|
||||
|
||||
pub async fn should_warn() {
|
||||
let x = [0u8; 1024];
|
||||
async {}.await;
|
||||
dbg!(x);
|
||||
}
|
||||
|
||||
pub async fn should_not_warn() {
|
||||
let x = [0u8; 1020];
|
||||
async {}.await;
|
||||
dbg!(x);
|
||||
}
|
||||
|
||||
pub async fn bar() {
|
||||
should_warn().await;
|
||||
|
||||
async {
|
||||
let x = [0u8; 1024];
|
||||
dbg!(x);
|
||||
}
|
||||
.await;
|
||||
|
||||
should_not_warn().await;
|
||||
}
|
10
tests/ui-toml/large_futures/large_futures.stderr
Normal file
10
tests/ui-toml/large_futures/large_futures.stderr
Normal file
|
@ -0,0 +1,10 @@
|
|||
error: large future with a size of 1026 bytes
|
||||
--> $DIR/large_futures.rs:18:5
|
||||
|
|
||||
LL | should_warn().await;
|
||||
| ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())`
|
||||
|
|
||||
= note: `-D clippy::large-futures` implied by `-D warnings`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
|
@ -24,6 +24,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
|
|||
enforced-import-renames
|
||||
enum-variant-name-threshold
|
||||
enum-variant-size-threshold
|
||||
future-size-threshold
|
||||
ignore-interior-mutability
|
||||
large-error-threshold
|
||||
literal-representation-threshold
|
||||
|
|
|
@ -425,4 +425,8 @@ pub fn integer_arithmetic() {
|
|||
i ^= i;
|
||||
}
|
||||
|
||||
pub fn issue_10583(a: u16) -> u16 {
|
||||
10 / a
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -576,6 +576,12 @@ error: arithmetic operation that can potentially result in unexpected side-effec
|
|||
LL | i * 2;
|
||||
| ^^^^^
|
||||
|
||||
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||
--> $DIR/arithmetic_side_effects.rs:394:5
|
||||
|
|
||||
LL | 1 % i / 2;
|
||||
| ^^^^^
|
||||
|
||||
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||
--> $DIR/arithmetic_side_effects.rs:395:5
|
||||
|
|
||||
|
@ -642,5 +648,11 @@ error: arithmetic operation that can potentially result in unexpected side-effec
|
|||
LL | i %= var2;
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: aborting due to 107 previous errors
|
||||
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||
--> $DIR/arithmetic_side_effects.rs:429:5
|
||||
|
|
||||
LL | 10 / a
|
||||
| ^^^^^^
|
||||
|
||||
error: aborting due to 109 previous errors
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Gro
|
|||
/// Token used to escape the following token from the macro's span rules.
|
||||
const ESCAPE_CHAR: char = '$';
|
||||
|
||||
/// Takes a single token followed by a sequence tokens. Returns the sequence of tokens with their
|
||||
/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their
|
||||
/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`.
|
||||
#[proc_macro]
|
||||
pub fn with_span(input: TokenStream) -> TokenStream {
|
||||
|
|
|
@ -29,6 +29,12 @@ fn main() {
|
|||
1f64 as isize;
|
||||
1f64 as usize;
|
||||
1f32 as u32 as u16;
|
||||
{
|
||||
let _x: i8 = 1i32 as _;
|
||||
1f32 as i32;
|
||||
1f64 as i32;
|
||||
1f32 as u8;
|
||||
}
|
||||
// Test clippy::cast_possible_wrap
|
||||
1u8 as i8;
|
||||
1u16 as i16;
|
||||
|
|
|
@ -44,10 +44,6 @@ LL | 1f32 as i32;
|
|||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
= note: `-D clippy::cast-possible-truncation` implied by `-D warnings`
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | i32::try_from(1f32);
|
||||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f32` to `u32` may truncate the value
|
||||
--> $DIR/cast.rs:25:5
|
||||
|
@ -56,10 +52,6 @@ LL | 1f32 as u32;
|
|||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | u32::try_from(1f32);
|
||||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:25:5
|
||||
|
@ -76,10 +68,6 @@ LL | 1f64 as f32;
|
|||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | f32::try_from(1f64);
|
||||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `i32` to `i8` may truncate the value
|
||||
--> $DIR/cast.rs:27:5
|
||||
|
@ -112,10 +100,6 @@ LL | 1f64 as isize;
|
|||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | isize::try_from(1f64);
|
||||
| ~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f64` to `usize` may truncate the value
|
||||
--> $DIR/cast.rs:30:5
|
||||
|
@ -124,10 +108,6 @@ LL | 1f64 as usize;
|
|||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | usize::try_from(1f64);
|
||||
| ~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f64` to `usize` may lose the sign of the value
|
||||
--> $DIR/cast.rs:30:5
|
||||
|
@ -154,10 +134,6 @@ LL | 1f32 as u32 as u16;
|
|||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | u32::try_from(1f32) as u16;
|
||||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:31:5
|
||||
|
@ -165,8 +141,50 @@ error: casting `f32` to `u32` may lose the sign of the value
|
|||
LL | 1f32 as u32 as u16;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `i8` may truncate the value
|
||||
--> $DIR/cast.rs:33:22
|
||||
|
|
||||
LL | let _x: i8 = 1i32 as _;
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | let _x: i8 = 1i32.try_into();
|
||||
| ~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `f32` to `i32` may truncate the value
|
||||
--> $DIR/cast.rs:34:9
|
||||
|
|
||||
LL | 1f32 as i32;
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
|
||||
error: casting `f64` to `i32` may truncate the value
|
||||
--> $DIR/cast.rs:35:9
|
||||
|
|
||||
LL | 1f64 as i32;
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
|
||||
error: casting `f32` to `u8` may truncate the value
|
||||
--> $DIR/cast.rs:36:9
|
||||
|
|
||||
LL | 1f32 as u8;
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
|
||||
error: casting `f32` to `u8` may lose the sign of the value
|
||||
--> $DIR/cast.rs:36:9
|
||||
|
|
||||
LL | 1f32 as u8;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: casting `u8` to `i8` may wrap around the value
|
||||
--> $DIR/cast.rs:33:5
|
||||
--> $DIR/cast.rs:39:5
|
||||
|
|
||||
LL | 1u8 as i8;
|
||||
| ^^^^^^^^^
|
||||
|
@ -174,43 +192,43 @@ LL | 1u8 as i8;
|
|||
= note: `-D clippy::cast-possible-wrap` implied by `-D warnings`
|
||||
|
||||
error: casting `u16` to `i16` may wrap around the value
|
||||
--> $DIR/cast.rs:34:5
|
||||
--> $DIR/cast.rs:40:5
|
||||
|
|
||||
LL | 1u16 as i16;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: casting `u32` to `i32` may wrap around the value
|
||||
--> $DIR/cast.rs:35:5
|
||||
--> $DIR/cast.rs:41:5
|
||||
|
|
||||
LL | 1u32 as i32;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: casting `u64` to `i64` may wrap around the value
|
||||
--> $DIR/cast.rs:36:5
|
||||
--> $DIR/cast.rs:42:5
|
||||
|
|
||||
LL | 1u64 as i64;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: casting `usize` to `isize` may wrap around the value
|
||||
--> $DIR/cast.rs:37:5
|
||||
--> $DIR/cast.rs:43:5
|
||||
|
|
||||
LL | 1usize as isize;
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:40:5
|
||||
--> $DIR/cast.rs:46:5
|
||||
|
|
||||
LL | -1i32 as u32;
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: casting `isize` to `usize` may lose the sign of the value
|
||||
--> $DIR/cast.rs:42:5
|
||||
--> $DIR/cast.rs:48:5
|
||||
|
|
||||
LL | -1isize as usize;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i64` to `i8` may truncate the value
|
||||
--> $DIR/cast.rs:109:5
|
||||
--> $DIR/cast.rs:115:5
|
||||
|
|
||||
LL | (-99999999999i64).min(1) as i8; // should be linted because signed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -222,7 +240,7 @@ LL | i8::try_from((-99999999999i64).min(1)); // should be linted because sig
|
|||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `u64` to `u8` may truncate the value
|
||||
--> $DIR/cast.rs:121:5
|
||||
--> $DIR/cast.rs:127:5
|
||||
|
|
||||
LL | 999999u64.clamp(0, 256) as u8; // should still be linted
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -234,7 +252,7 @@ LL | u8::try_from(999999u64.clamp(0, 256)); // should still be linted
|
|||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `main::E2` to `u8` may truncate the value
|
||||
--> $DIR/cast.rs:142:21
|
||||
--> $DIR/cast.rs:148:21
|
||||
|
|
||||
LL | let _ = self as u8;
|
||||
| ^^^^^^^^^^
|
||||
|
@ -246,7 +264,7 @@ LL | let _ = u8::try_from(self);
|
|||
| ~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `main::E2::B` to `u8` will truncate the value
|
||||
--> $DIR/cast.rs:143:21
|
||||
--> $DIR/cast.rs:149:21
|
||||
|
|
||||
LL | let _ = Self::B as u8;
|
||||
| ^^^^^^^^^^^^^
|
||||
|
@ -254,7 +272,7 @@ LL | let _ = Self::B as u8;
|
|||
= note: `-D clippy::cast-enum-truncation` implied by `-D warnings`
|
||||
|
||||
error: casting `main::E5` to `i8` may truncate the value
|
||||
--> $DIR/cast.rs:179:21
|
||||
--> $DIR/cast.rs:185:21
|
||||
|
|
||||
LL | let _ = self as i8;
|
||||
| ^^^^^^^^^^
|
||||
|
@ -266,13 +284,13 @@ LL | let _ = i8::try_from(self);
|
|||
| ~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `main::E5::A` to `i8` will truncate the value
|
||||
--> $DIR/cast.rs:180:21
|
||||
--> $DIR/cast.rs:186:21
|
||||
|
|
||||
LL | let _ = Self::A as i8;
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: casting `main::E6` to `i16` may truncate the value
|
||||
--> $DIR/cast.rs:194:21
|
||||
--> $DIR/cast.rs:200:21
|
||||
|
|
||||
LL | let _ = self as i16;
|
||||
| ^^^^^^^^^^^
|
||||
|
@ -284,7 +302,7 @@ LL | let _ = i16::try_from(self);
|
|||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers
|
||||
--> $DIR/cast.rs:209:21
|
||||
--> $DIR/cast.rs:215:21
|
||||
|
|
||||
LL | let _ = self as usize;
|
||||
| ^^^^^^^^^^^^^
|
||||
|
@ -296,7 +314,7 @@ LL | let _ = usize::try_from(self);
|
|||
| ~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `main::E10` to `u16` may truncate the value
|
||||
--> $DIR/cast.rs:250:21
|
||||
--> $DIR/cast.rs:256:21
|
||||
|
|
||||
LL | let _ = self as u16;
|
||||
| ^^^^^^^^^^^
|
||||
|
@ -308,7 +326,7 @@ LL | let _ = u16::try_from(self);
|
|||
| ~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `u32` to `u8` may truncate the value
|
||||
--> $DIR/cast.rs:258:13
|
||||
--> $DIR/cast.rs:264:13
|
||||
|
|
||||
LL | let c = (q >> 16) as u8;
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
@ -316,11 +334,11 @@ LL | let c = (q >> 16) as u8;
|
|||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | let c = u8::try_from((q >> 16));
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~
|
||||
LL | let c = u8::try_from(q >> 16);
|
||||
| ~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: casting `u32` to `u8` may truncate the value
|
||||
--> $DIR/cast.rs:261:13
|
||||
--> $DIR/cast.rs:267:13
|
||||
|
|
||||
LL | let c = (q / 1000) as u8;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
@ -328,8 +346,8 @@ LL | let c = (q / 1000) as u8;
|
|||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
help: ... or use `try_from` and handle the error accordingly
|
||||
|
|
||||
LL | let c = u8::try_from((q / 1000));
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
LL | let c = u8::try_from(q / 1000);
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: aborting due to 36 previous errors
|
||||
error: aborting due to 41 previous errors
|
||||
|
||||
|
|
358
tests/ui/clear_with_drain.fixed
Normal file
358
tests/ui/clear_with_drain.fixed
Normal file
|
@ -0,0 +1,358 @@
|
|||
// run-rustfix
|
||||
#![allow(unused)]
|
||||
#![warn(clippy::clear_with_drain)]
|
||||
|
||||
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
||||
|
||||
fn vec_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(0..v.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let n = v.drain(0..v.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(usize::MIN..v.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
}
|
||||
|
||||
fn vec_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let mut iter = v.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let next = v.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
}
|
||||
|
||||
fn vec_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
for x in v.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
}
|
||||
|
||||
fn vec_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..v.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..v.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.clear();
|
||||
}
|
||||
|
||||
fn vec_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..);
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..).max();
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..v.len() - 1);
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..v.len() - 1).min();
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..v.len() - 1);
|
||||
let mut v = vec![1, 2, 3];
|
||||
let w: Vec<i8> = v.drain(1..v.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn vec_deque_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(0..deque.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let n = deque.drain(0..deque.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(usize::MIN..deque.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
}
|
||||
|
||||
fn vec_deque_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let mut iter = deque.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let next = deque.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
}
|
||||
|
||||
fn vec_deque_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
for x in deque.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
}
|
||||
|
||||
fn vec_deque_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..deque.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..deque.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.clear();
|
||||
}
|
||||
|
||||
fn vec_deque_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..).max();
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..deque.len() - 1);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..deque.len() - 1).min();
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..deque.len() - 1);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let w: Vec<i8> = deque.drain(1..deque.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn string_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(0..s.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let n = s.drain(0..s.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(usize::MIN..s.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
fn string_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let mut iter = s.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let next = s.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
fn string_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
for x in s.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
fn string_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..s.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..s.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
fn string_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..);
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..).max();
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..s.len() - 1);
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..s.len() - 1).min();
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..s.len() - 1);
|
||||
let mut s = String::from("Hello, world!");
|
||||
let w: String = s.drain(1..s.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn hash_set() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let iter = set.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let mut iter = set.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let next = set.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
set.clear();
|
||||
}
|
||||
|
||||
fn hash_map() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let iter = map.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let mut iter = map.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let next = map.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
map.clear();
|
||||
}
|
||||
|
||||
fn binary_heap() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let iter = heap.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let mut iter = heap.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let next = heap.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
heap.clear();
|
||||
}
|
||||
|
||||
fn main() {}
|
358
tests/ui/clear_with_drain.rs
Normal file
358
tests/ui/clear_with_drain.rs
Normal file
|
@ -0,0 +1,358 @@
|
|||
// run-rustfix
|
||||
#![allow(unused)]
|
||||
#![warn(clippy::clear_with_drain)]
|
||||
|
||||
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
||||
|
||||
fn vec_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(0..v.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let n = v.drain(0..v.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(usize::MIN..v.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(0..v.len());
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(usize::MIN..v.len());
|
||||
}
|
||||
|
||||
fn vec_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let mut iter = v.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let next = v.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(0..);
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(usize::MIN..);
|
||||
}
|
||||
|
||||
fn vec_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut v = vec![1, 2, 3];
|
||||
for x in v.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..);
|
||||
}
|
||||
|
||||
fn vec_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..v.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut v = vec![1, 2, 3];
|
||||
let iter = v.drain(..v.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..v.len());
|
||||
}
|
||||
|
||||
fn vec_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..);
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..).max();
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..v.len() - 1);
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(..v.len() - 1).min();
|
||||
|
||||
let mut v = vec![1, 2, 3];
|
||||
v.drain(1..v.len() - 1);
|
||||
let mut v = vec![1, 2, 3];
|
||||
let w: Vec<i8> = v.drain(1..v.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn vec_deque_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(0..deque.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let n = deque.drain(0..deque.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(usize::MIN..deque.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(0..deque.len());
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(usize::MIN..deque.len());
|
||||
}
|
||||
|
||||
fn vec_deque_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let mut iter = deque.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let next = deque.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(0..);
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(usize::MIN..);
|
||||
}
|
||||
|
||||
fn vec_deque_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
for x in deque.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..);
|
||||
}
|
||||
|
||||
fn vec_deque_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..deque.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let iter = deque.drain(..deque.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..deque.len());
|
||||
}
|
||||
|
||||
fn vec_deque_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..).max();
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..deque.len() - 1);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(..deque.len() - 1).min();
|
||||
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
deque.drain(1..deque.len() - 1);
|
||||
let mut deque = VecDeque::from([1, 2, 3]);
|
||||
let w: Vec<i8> = deque.drain(1..deque.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn string_range() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(0..s.len());
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let n = s.drain(0..s.len()).count();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(usize::MIN..s.len());
|
||||
let n = iter.count();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(0..s.len());
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(usize::MIN..s.len());
|
||||
}
|
||||
|
||||
fn string_range_from() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(0..);
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let mut iter = s.drain(0..);
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let next = s.drain(usize::MIN..).next();
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(0..);
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(usize::MIN..);
|
||||
}
|
||||
|
||||
fn string_range_full() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..);
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut s = String::from("Hello, world!");
|
||||
for x in s.drain(..) {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..);
|
||||
}
|
||||
|
||||
fn string_range_to() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..s.len());
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut s = String::from("Hello, world!");
|
||||
let iter = s.drain(..s.len());
|
||||
for x in iter {
|
||||
let y = format!("x = {x}");
|
||||
}
|
||||
|
||||
// Do lint
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..s.len());
|
||||
}
|
||||
|
||||
fn string_partial_drains() {
|
||||
// Do not lint any of these because the ranges are not full
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..);
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..).max();
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..s.len() - 1);
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(..s.len() - 1).min();
|
||||
|
||||
let mut s = String::from("Hello, world!");
|
||||
s.drain(1..s.len() - 1);
|
||||
let mut s = String::from("Hello, world!");
|
||||
let w: String = s.drain(1..s.len() - 1).collect();
|
||||
}
|
||||
|
||||
fn hash_set() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let iter = set.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let mut iter = set.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
let next = set.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut set = HashSet::from([1, 2, 3]);
|
||||
set.drain();
|
||||
}
|
||||
|
||||
fn hash_map() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let iter = map.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let mut iter = map.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
let next = map.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut map = HashMap::from([(1, "a"), (2, "b")]);
|
||||
map.drain();
|
||||
}
|
||||
|
||||
fn binary_heap() {
|
||||
// Do not lint because iterator is assigned
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let iter = heap.drain();
|
||||
|
||||
// Do not lint because iterator is assigned and used
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let mut iter = heap.drain();
|
||||
let next = iter.next();
|
||||
|
||||
// Do not lint because iterator is used
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
let next = heap.drain().next();
|
||||
|
||||
// Do lint
|
||||
let mut heap = BinaryHeap::from([1, 2]);
|
||||
heap.drain();
|
||||
}
|
||||
|
||||
fn main() {}
|
130
tests/ui/clear_with_drain.stderr
Normal file
130
tests/ui/clear_with_drain.stderr
Normal file
|
@ -0,0 +1,130 @@
|
|||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:23:7
|
||||
|
|
||||
LL | v.drain(0..v.len());
|
||||
| ^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
|
||||
= note: `-D clippy::clear-with-drain` implied by `-D warnings`
|
||||
|
||||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:27:7
|
||||
|
|
||||
LL | v.drain(usize::MIN..v.len());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:46:7
|
||||
|
|
||||
LL | v.drain(0..);
|
||||
| ^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:50:7
|
||||
|
|
||||
LL | v.drain(usize::MIN..);
|
||||
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:66:7
|
||||
|
|
||||
LL | v.drain(..);
|
||||
| ^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `Vec`
|
||||
--> $DIR/clear_with_drain.rs:83:7
|
||||
|
|
||||
LL | v.drain(..v.len());
|
||||
| ^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:121:11
|
||||
|
|
||||
LL | deque.drain(0..deque.len());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:125:11
|
||||
|
|
||||
LL | deque.drain(usize::MIN..deque.len());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:144:11
|
||||
|
|
||||
LL | deque.drain(0..);
|
||||
| ^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:148:11
|
||||
|
|
||||
LL | deque.drain(usize::MIN..);
|
||||
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:164:11
|
||||
|
|
||||
LL | deque.drain(..);
|
||||
| ^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `VecDeque`
|
||||
--> $DIR/clear_with_drain.rs:181:11
|
||||
|
|
||||
LL | deque.drain(..deque.len());
|
||||
| ^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:219:7
|
||||
|
|
||||
LL | s.drain(0..s.len());
|
||||
| ^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:223:7
|
||||
|
|
||||
LL | s.drain(usize::MIN..s.len());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:242:7
|
||||
|
|
||||
LL | s.drain(0..);
|
||||
| ^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:246:7
|
||||
|
|
||||
LL | s.drain(usize::MIN..);
|
||||
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:262:7
|
||||
|
|
||||
LL | s.drain(..);
|
||||
| ^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `String`
|
||||
--> $DIR/clear_with_drain.rs:279:7
|
||||
|
|
||||
LL | s.drain(..s.len());
|
||||
| ^^^^^^^^^^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `HashSet`
|
||||
--> $DIR/clear_with_drain.rs:317:9
|
||||
|
|
||||
LL | set.drain();
|
||||
| ^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `HashMap`
|
||||
--> $DIR/clear_with_drain.rs:336:9
|
||||
|
|
||||
LL | map.drain();
|
||||
| ^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: `drain` used to clear a `BinaryHeap`
|
||||
--> $DIR/clear_with_drain.rs:355:10
|
||||
|
|
||||
LL | heap.drain();
|
||||
| ^^^^^^^ help: try: `clear()`
|
||||
|
||||
error: aborting due to 21 previous errors
|
||||
|
|
@ -84,13 +84,18 @@ fn shadowing_2() {
|
|||
}
|
||||
|
||||
#[allow(clippy::let_unit_value)]
|
||||
fn fake_read() {
|
||||
let mut x = vec![1, 2, 3]; // Ok
|
||||
fn fake_read_1() {
|
||||
let mut x = vec![1, 2, 3]; // WARNING
|
||||
x.reverse();
|
||||
// `collection_is_never_read` gets fooled, but other lints should catch this.
|
||||
let _: () = x.clear();
|
||||
}
|
||||
|
||||
fn fake_read_2() {
|
||||
let mut x = vec![1, 2, 3]; // WARNING
|
||||
x.reverse();
|
||||
println!("{:?}", x.push(5));
|
||||
}
|
||||
|
||||
fn assignment() {
|
||||
let mut x = vec![1, 2, 3]; // WARNING
|
||||
let y = vec![4, 5, 6]; // Ok
|
||||
|
@ -163,3 +168,23 @@ fn function_argument() {
|
|||
let x = vec![1, 2, 3]; // Ok
|
||||
foo(&x);
|
||||
}
|
||||
|
||||
fn string() {
|
||||
// Do lint (write without read)
|
||||
let mut s = String::new();
|
||||
s.push_str("Hello, World!");
|
||||
|
||||
// Do not lint (read without write)
|
||||
let mut s = String::from("Hello, World!");
|
||||
let _ = s.len();
|
||||
|
||||
// Do not lint (write and read)
|
||||
let mut s = String::from("Hello, World!");
|
||||
s.push_str("foo, bar");
|
||||
let _ = s.len();
|
||||
|
||||
// Do lint the first line, but not the second
|
||||
let mut s = String::from("Hello, World!");
|
||||
let t = String::from("foo, bar");
|
||||
s = t;
|
||||
}
|
||||
|
|
|
@ -25,28 +25,52 @@ LL | let mut x = HashMap::new(); // WARNING
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:95:5
|
||||
--> $DIR/collection_is_never_read.rs:88:5
|
||||
|
|
||||
LL | let mut x = vec![1, 2, 3]; // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:102:5
|
||||
--> $DIR/collection_is_never_read.rs:94:5
|
||||
|
|
||||
LL | let mut x = vec![1, 2, 3]; // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:119:5
|
||||
--> $DIR/collection_is_never_read.rs:100:5
|
||||
|
|
||||
LL | let mut x = vec![1, 2, 3]; // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:107:5
|
||||
|
|
||||
LL | let mut x = vec![1, 2, 3]; // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:124:5
|
||||
|
|
||||
LL | let mut x = HashSet::new(); // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:133:5
|
||||
--> $DIR/collection_is_never_read.rs:138:5
|
||||
|
|
||||
LL | let x = vec![1, 2, 3]; // WARNING
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 8 previous errors
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:174:5
|
||||
|
|
||||
LL | let mut s = String::new();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: collection is never read
|
||||
--> $DIR/collection_is_never_read.rs:187:5
|
||||
|
|
||||
LL | let mut s = String::from("Hello, World!");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 12 previous errors
|
||||
|
||||
|
|
|
@ -21,6 +21,17 @@ pub fn must_use_with_note() -> Result<(), ()> {
|
|||
unimplemented!();
|
||||
}
|
||||
|
||||
// vvvv Should not lint (#10486)
|
||||
#[must_use]
|
||||
async fn async_must_use() -> usize {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
async fn async_must_use_result() -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
must_use_result();
|
||||
must_use_tuple();
|
||||
|
|
|
@ -23,5 +23,13 @@ LL | pub fn must_use_array() -> [Result<(), ()>; 1] {
|
|||
|
|
||||
= help: either add some descriptive text or remove the attribute
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`
|
||||
--> $DIR/double_must_use.rs:31:1
|
||||
|
|
||||
LL | async fn async_must_use_result() -> Result<(), ()> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: either add some descriptive text or remove the attribute
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
|
105
tests/ui/extra_unused_type_parameters.fixed
Normal file
105
tests/ui/extra_unused_type_parameters.fixed
Normal file
|
@ -0,0 +1,105 @@
|
|||
// run-rustfix
|
||||
|
||||
#![allow(unused, clippy::needless_lifetimes)]
|
||||
#![warn(clippy::extra_unused_type_parameters)]
|
||||
|
||||
fn unused_ty(x: u8) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unused_multi(x: u8) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unused_with_lt<'a>(x: &'a u8) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn used_ty<T>(x: T, y: u8) {}
|
||||
|
||||
fn used_ref<'a, T>(x: &'a T) {}
|
||||
|
||||
fn used_ret<T: Default>(x: u8) -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
fn unused_bounded<U>(x: U) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn some_unused<B, C>(b: B, c: C) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn used_opaque<A>(iter: impl Iterator<Item = A>) -> usize {
|
||||
iter.count()
|
||||
}
|
||||
|
||||
fn used_ret_opaque<A>() -> impl Iterator<Item = A> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
fn used_vec_box<T>(x: Vec<Box<T>>) {}
|
||||
|
||||
fn used_body<T: Default + ToString>() -> String {
|
||||
T::default().to_string()
|
||||
}
|
||||
|
||||
fn used_closure<T: Default + ToString>() -> impl Fn() {
|
||||
|| println!("{}", T::default().to_string())
|
||||
}
|
||||
|
||||
struct S;
|
||||
|
||||
impl S {
|
||||
fn unused_ty_impl(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
// Don't lint on trait methods
|
||||
trait Foo {
|
||||
fn bar<T>(&self);
|
||||
}
|
||||
|
||||
impl Foo for S {
|
||||
fn bar<T>(&self) {}
|
||||
}
|
||||
|
||||
fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
|
||||
where
|
||||
Iter: Iterator<Item = A>,
|
||||
{
|
||||
iter.enumerate()
|
||||
.filter_map(move |(i, a)| if i == index { None } else { Some(a) })
|
||||
}
|
||||
|
||||
fn unused_opaque(dummy: impl Default) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
mod unexported_trait_bounds {
|
||||
mod private {
|
||||
pub trait Private {}
|
||||
}
|
||||
|
||||
fn priv_trait_bound<T: private::Private>() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn unused_with_priv_trait_bound<T: private::Private>() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
mod issue10319 {
|
||||
fn assert_send<T: Send>() {}
|
||||
|
||||
fn assert_send_where<T>()
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -1,3 +1,5 @@
|
|||
// run-rustfix
|
||||
|
||||
#![allow(unused, clippy::needless_lifetimes)]
|
||||
#![warn(clippy::extra_unused_type_parameters)]
|
||||
|
||||
|
@ -21,14 +23,7 @@ fn used_ret<T: Default>(x: u8) -> T {
|
|||
T::default()
|
||||
}
|
||||
|
||||
fn unused_bounded<T: Default, U>(x: U) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn unused_where_clause<T, U>(x: U)
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn unused_bounded<T: Default, U, V: Default>(x: U) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,75 +1,64 @@
|
|||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:4:13
|
||||
error: type parameter `T` goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:6:13
|
||||
|
|
||||
LL | fn unused_ty<T>(x: u8) {
|
||||
| ^^^
|
||||
| ^^^ help: consider removing the parameter
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
= note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings`
|
||||
|
||||
error: type parameters go unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:8:16
|
||||
error: type parameters go unused in function definition: T, U
|
||||
--> $DIR/extra_unused_type_parameters.rs:10:16
|
||||
|
|
||||
LL | fn unused_multi<T, U>(x: u8) {
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the parameters
|
||||
| ^^^^^^ help: consider removing the parameters
|
||||
|
||||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:12:23
|
||||
error: type parameter `T` goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:14:21
|
||||
|
|
||||
LL | fn unused_with_lt<'a, T>(x: &'a u8) {
|
||||
| ^
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
| ^^^ help: consider removing the parameter
|
||||
|
||||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:24:19
|
||||
error: type parameters go unused in function definition: T, V
|
||||
--> $DIR/extra_unused_type_parameters.rs:26:19
|
||||
|
|
||||
LL | fn unused_bounded<T: Default, U>(x: U) {
|
||||
| ^^^^^^^^^^^
|
||||
LL | fn unused_bounded<T: Default, U, V: Default>(x: U) {
|
||||
| ^^^^^^^^^^^^ ^^^^^^^^^^^^
|
||||
|
|
||||
help: consider removing the parameters
|
||||
|
|
||||
LL - fn unused_bounded<T: Default, U, V: Default>(x: U) {
|
||||
LL + fn unused_bounded<U>(x: U) {
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
|
||||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:28:24
|
||||
|
|
||||
LL | fn unused_where_clause<T, U>(x: U)
|
||||
| ^^
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
|
||||
error: type parameters go unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:35:16
|
||||
error: type parameters go unused in function definition: A, D, E
|
||||
--> $DIR/extra_unused_type_parameters.rs:30:16
|
||||
|
|
||||
LL | fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) {
|
||||
| ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^
|
||||
| ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider removing the parameters
|
||||
|
|
||||
LL - fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) {
|
||||
LL + fn some_unused<B, C>(b: B, c: C) {
|
||||
|
|
||||
= help: consider removing the parameters
|
||||
|
||||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:60:22
|
||||
error: type parameter `T` goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:55:22
|
||||
|
|
||||
LL | fn unused_ty_impl<T>(&self) {
|
||||
| ^^^
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
| ^^^ help: consider removing the parameter
|
||||
|
||||
error: type parameters go unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:82:17
|
||||
error: type parameters go unused in function definition: A, B
|
||||
--> $DIR/extra_unused_type_parameters.rs:77:17
|
||||
|
|
||||
LL | fn unused_opaque<A, B>(dummy: impl Default) {
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the parameters
|
||||
| ^^^^^^ help: consider removing the parameters
|
||||
|
||||
error: type parameter goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:95:58
|
||||
error: type parameter `U` goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters.rs:90:56
|
||||
|
|
||||
LL | fn unused_with_priv_trait_bound<T: private::Private, U>() {
|
||||
| ^
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
| ^^^ help: consider removing the parameter
|
||||
|
||||
error: aborting due to 9 previous errors
|
||||
error: aborting due to 8 previous errors
|
||||
|
||||
|
|
24
tests/ui/extra_unused_type_parameters_unfixable.rs
Normal file
24
tests/ui/extra_unused_type_parameters_unfixable.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
#![warn(clippy::extra_unused_type_parameters)]
|
||||
|
||||
fn unused_where_clause<T, U>(x: U)
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn unused_multi_where_clause<T, U, V: Default>(x: U)
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn unused_all_where_clause<T, U: Default, V: Default>()
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn main() {}
|
27
tests/ui/extra_unused_type_parameters_unfixable.stderr
Normal file
27
tests/ui/extra_unused_type_parameters_unfixable.stderr
Normal file
|
@ -0,0 +1,27 @@
|
|||
error: type parameter `T` goes unused in function definition
|
||||
--> $DIR/extra_unused_type_parameters_unfixable.rs:3:24
|
||||
|
|
||||
LL | fn unused_where_clause<T, U>(x: U)
|
||||
| ^
|
||||
|
|
||||
= help: consider removing the parameter
|
||||
= note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings`
|
||||
|
||||
error: type parameters go unused in function definition: T, V
|
||||
--> $DIR/extra_unused_type_parameters_unfixable.rs:10:30
|
||||
|
|
||||
LL | fn unused_multi_where_clause<T, U, V: Default>(x: U)
|
||||
| ^ ^^^^^^^^^^
|
||||
|
|
||||
= help: consider removing the parameters
|
||||
|
||||
error: type parameters go unused in function definition: T, U, V
|
||||
--> $DIR/extra_unused_type_parameters_unfixable.rs:17:28
|
||||
|
|
||||
LL | fn unused_all_where_clause<T, U: Default, V: Default>()
|
||||
| ^ ^^^^^^^^^^ ^^^^^^^^^^
|
||||
|
|
||||
= help: consider removing the parameters
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#![warn(clippy::format_in_format_args, clippy::to_string_in_format_args)]
|
||||
#![allow(unused)]
|
||||
#![allow(clippy::assertions_on_constants, clippy::eq_op, clippy::uninlined_format_args)]
|
||||
|
||||
use std::io::{stdout, Error, ErrorKind, Write};
|
||||
|
@ -57,3 +58,46 @@ fn main() {
|
|||
my_macro!();
|
||||
println!("error: {}", my_other_macro!());
|
||||
}
|
||||
|
||||
macro_rules! _internal {
|
||||
($($args:tt)*) => {
|
||||
println!("{}", format_args!($($args)*))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! my_println2 {
|
||||
($target:expr, $($args:tt)+) => {{
|
||||
if $target {
|
||||
_internal!($($args)+)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! my_println2_args {
|
||||
($target:expr, $($args:tt)+) => {{
|
||||
if $target {
|
||||
_internal!("foo: {}", format_args!($($args)+))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn test2() {
|
||||
let error = Error::new(ErrorKind::Other, "bad thing");
|
||||
|
||||
// None of these should be linted without the config change
|
||||
my_println2!(true, "error: {}", format!("something failed at {}", Location::caller()));
|
||||
my_println2!(
|
||||
true,
|
||||
"{}: {}",
|
||||
error,
|
||||
format!("something failed at {}", Location::caller())
|
||||
);
|
||||
|
||||
my_println2_args!(true, "error: {}", format!("something failed at {}", Location::caller()));
|
||||
my_println2_args!(
|
||||
true,
|
||||
"{}: {}",
|
||||
error,
|
||||
format!("something failed at {}", Location::caller())
|
||||
);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue