Merge commit '27afd6ade4bb1123a8bf82001629b69d23d62aff' into clippyup

This commit is contained in:
flip1995 2021-09-08 16:31:47 +02:00
parent f7aaa2a200
commit 091ed44b50
183 changed files with 4171 additions and 1313 deletions

View File

@ -5,4 +5,5 @@ lintcheck = "run --target-dir lintcheck/target --package lintcheck --bin lintche
collect-metadata = "test --test dogfood --features metadata-collector-lint -- run_metadata_collection_lint --ignored" collect-metadata = "test --test dogfood --features metadata-collector-lint -- run_metadata_collection_lint --ignored"
[build] [build]
rustflags = ["-Zunstable-options"] # -Zbinary-dep-depinfo allows us to track which rlib files to use for compiling UI tests
rustflags = ["-Zunstable-options", "-Zbinary-dep-depinfo"]

View File

@ -2,3 +2,17 @@
name: Blank Issue name: Blank Issue
about: Create a blank issue. about: Create a blank issue.
--- ---
<!--
Additional labels can be added to this issue by including the following command
(without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* C-an-interesting-project
* C-enhancement
* C-question
* C-tracking-issue
-->

View File

@ -20,28 +20,24 @@ Instead, this happened: *explanation*
### Meta ### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) **Rust version (`rustc -Vv`):**
- `rustc -Vv`:
``` ```
rustc 1.46.0-nightly (f455e46ea 2020-06-20) rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565 commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20 commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly release: 1.46.0-nightly
LLVM version: 10.0 LLVM version: 10.0
``` ```
<!-- <!--
Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in your Additional labels can be added to this issue by including the following command
environment. E.g. `RUST_BACKTRACE=1 cargo clippy`. (without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* `I-suggestion-causes-error`
--> -->
<details><summary>Backtrace</summary>
<p>
```
<backtrace>
```
</p>
</details>

View File

@ -22,14 +22,14 @@ Instead, this happened: *explanation*
### Meta ### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) **Rust version (`rustc -Vv`):**
- `rustc -Vv`:
``` ```
rustc 1.46.0-nightly (f455e46ea 2020-06-20) rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565 commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20 commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly release: 1.46.0-nightly
LLVM version: 10.0 LLVM version: 10.0
``` ```

View File

@ -22,14 +22,23 @@ Instead, this happened: *explanation*
### Meta ### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) **Rust version (`rustc -Vv`):**
- `rustc -Vv`: ```
``` rustc 1.46.0-nightly (f455e46ea 2020-06-20)
rustc 1.46.0-nightly (f455e46ea 2020-06-20) binary: rustc
binary: rustc commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-hash: f455e46eae1a227d735091091144601b467e1565 commit-date: 2020-06-20
commit-date: 2020-06-20 host: x86_64-unknown-linux-gnu
host: x86_64-unknown-linux-gnu release: 1.46.0-nightly
release: 1.46.0-nightly LLVM version: 10.0
LLVM version: 10.0 ```
```
<!--
Additional labels can be added to this issue by including the following command
(without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* I-suggestion-causes-error
-->

View File

@ -20,17 +20,16 @@ http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
### Meta ### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20) **Rust version (`rustc -Vv`):**
- `rustc -Vv`: ```
``` rustc 1.46.0-nightly (f455e46ea 2020-06-20)
rustc 1.46.0-nightly (f455e46ea 2020-06-20) binary: rustc
binary: rustc commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-hash: f455e46eae1a227d735091091144601b467e1565 commit-date: 2020-06-20
commit-date: 2020-06-20 host: x86_64-unknown-linux-gnu
host: x86_64-unknown-linux-gnu release: 1.46.0-nightly
release: 1.46.0-nightly LLVM version: 10.0
LLVM version: 10.0 ```
```
### Error output ### Error output

View File

@ -42,9 +42,6 @@ jobs:
run: cargo build --features deny-warnings run: cargo build --features deny-warnings
working-directory: clippy_dev working-directory: clippy_dev
- name: Test limit_stderr_length
run: cargo dev limit_stderr_length
- name: Test update_lints - name: Test update_lints
run: cargo dev update_lints --check run: cargo dev update_lints --check

View File

@ -964,7 +964,7 @@ Released 2020-11-19
[#5907](https://github.com/rust-lang/rust-clippy/pull/5907) [#5907](https://github.com/rust-lang/rust-clippy/pull/5907)
* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr` * [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr`
[#5884](https://github.com/rust-lang/rust-clippy/pull/5884) [#5884](https://github.com/rust-lang/rust-clippy/pull/5884)
* [`invalid_atomic_ordering`]: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update` * `invalid_atomic_ordering`: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
[#6025](https://github.com/rust-lang/rust-clippy/pull/6025) [#6025](https://github.com/rust-lang/rust-clippy/pull/6025)
* Avoid [`redundant_pattern_matching`] triggering in macros * Avoid [`redundant_pattern_matching`] triggering in macros
[#6069](https://github.com/rust-lang/rust-clippy/pull/6069) [#6069](https://github.com/rust-lang/rust-clippy/pull/6069)
@ -1451,7 +1451,7 @@ Released 2020-03-12
* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945) * [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945)
* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960) * [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960)
* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966) * [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966)
* [`invalid_atomic_ordering`] [#4999](https://github.com/rust-lang/rust-clippy/pull/4999) * `invalid_atomic_ordering` [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067) * [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067)
### Moves and Deprecations ### Moves and Deprecations
@ -2613,6 +2613,7 @@ Released 2018-09-13
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof [`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method [`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
@ -2712,7 +2713,6 @@ Released 2018-09-13
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic [`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division [`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref [`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage [`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex [`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons [`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
@ -2754,6 +2754,7 @@ Released 2018-09-13
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
[`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_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
@ -2795,6 +2796,7 @@ Released 2018-09-13
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes [`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals [`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception [`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions [`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
@ -2821,6 +2823,7 @@ Released 2018-09-13
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main [`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop [`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
@ -2828,6 +2831,7 @@ Released 2018-09-13
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update [`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord [`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply [`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop [`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self [`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default [`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
@ -2881,6 +2885,7 @@ Released 2018-09-13
[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call [`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls [`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else [`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
@ -2903,6 +2908,7 @@ Released 2018-09-13
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some [`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment [`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors [`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned [`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse [`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse

View File

@ -1,6 +1,6 @@
[package] [package]
name = "clippy" name = "clippy"
version = "0.1.56" version = "0.1.57"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"
@ -32,11 +32,7 @@ tempfile = { version = "3.1.0", optional = true }
cargo_metadata = "0.12" cargo_metadata = "0.12"
compiletest_rs = { version = "0.6.0", features = ["tmp"] } compiletest_rs = { version = "0.6.0", features = ["tmp"] }
tester = "0.9" tester = "0.9"
serde = { version = "1.0", features = ["derive"] }
derive-new = "0.5"
regex = "1.4" regex = "1.4"
quote = "1"
syn = { version = "1", features = ["full"] }
# This is used by the `collect-metadata` alias. # This is used by the `collect-metadata` alias.
filetime = "0.2" filetime = "0.2"
@ -45,6 +41,15 @@ filetime = "0.2"
# for more information. # for more information.
rustc-workspace-hack = "1.0.0" rustc-workspace-hack = "1.0.0"
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"
itertools = "0.10.1"
quote = "1"
serde = { version = "1.0", features = ["derive"] }
syn = { version = "1", features = ["full"] }
[build-dependencies] [build-dependencies]
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" } rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }

View File

@ -45,13 +45,13 @@ or in Travis CI.
One way to use Clippy is by installing Clippy through rustup as a cargo One way to use Clippy is by installing Clippy through rustup as a cargo
subcommand. subcommand.
#### Step 1: Install rustup #### Step 1: Install Rustup
You can install [rustup](https://rustup.rs/) on supported platforms. This will help You can install [Rustup](https://rustup.rs/) on supported platforms. This will help
us install Clippy and its dependencies. us install Clippy and its dependencies.
If you already have rustup installed, update to ensure you have the latest If you already have Rustup installed, update to ensure you have the latest
rustup and compiler: Rustup and compiler:
```terminal ```terminal
rustup update rustup update

View File

@ -17,7 +17,6 @@ pub mod fmt;
pub mod new_lint; pub mod new_lint;
pub mod serve; pub mod serve;
pub mod setup; pub mod setup;
pub mod stderr_length_check;
pub mod update_lints; pub mod update_lints;
static DEC_CLIPPY_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| { static DEC_CLIPPY_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {

View File

@ -3,7 +3,7 @@
#![warn(rust_2018_idioms, unused_lifetimes)] #![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use clippy_dev::{bless, fmt, new_lint, serve, setup, stderr_length_check, update_lints}; use clippy_dev::{bless, fmt, new_lint, serve, setup, update_lints};
fn main() { fn main() {
let matches = get_clap_config(); let matches = get_clap_config();
@ -33,9 +33,6 @@ fn main() {
Err(e) => eprintln!("Unable to create lint: {}", e), Err(e) => eprintln!("Unable to create lint: {}", e),
} }
}, },
("limit_stderr_length", _) => {
stderr_length_check::check();
},
("setup", Some(sub_command)) => match sub_command.subcommand() { ("setup", Some(sub_command)) => match sub_command.subcommand() {
("intellij", Some(matches)) => setup::intellij::setup_rustc_src( ("intellij", Some(matches)) => setup::intellij::setup_rustc_src(
matches matches
@ -152,10 +149,6 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.takes_value(true), .takes_value(true),
), ),
) )
.subcommand(
SubCommand::with_name("limit_stderr_length")
.about("Ensures that stderr files do not grow longer than a certain amount of lines."),
)
.subcommand( .subcommand(
SubCommand::with_name("setup") SubCommand::with_name("setup")
.about("Support for setting up your personal development environment") .about("Support for setting up your personal development environment")

View File

@ -1,51 +0,0 @@
use crate::clippy_project_root;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
// The maximum length allowed for stderr files.
//
// We limit this because small files are easier to deal with than bigger files.
const LENGTH_LIMIT: usize = 200;
pub fn check() {
let exceeding_files: Vec<_> = exceeding_stderr_files();
if !exceeding_files.is_empty() {
eprintln!("Error: stderr files exceeding limit of {} lines:", LENGTH_LIMIT);
for (path, count) in exceeding_files {
println!("{}: {}", path.display(), count);
}
std::process::exit(1);
}
}
fn exceeding_stderr_files() -> Vec<(PathBuf, usize)> {
// We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories.
WalkDir::new(clippy_project_root().join("tests/ui"))
.into_iter()
.filter_map(Result::ok)
.filter(|f| !f.file_type().is_dir())
.filter_map(|e| {
let p = e.into_path();
let count = count_linenumbers(&p);
if p.extension() == Some(OsStr::new("stderr")) && count > LENGTH_LIMIT {
Some((p, count))
} else {
None
}
})
.collect()
}
#[must_use]
fn count_linenumbers(filepath: &Path) -> usize {
match fs::read(filepath) {
Ok(content) => bytecount::count(&content, b'\n'),
Err(e) => {
eprintln!("Failed to read file: {}", e);
0
},
}
}

View File

@ -1,8 +1,6 @@
[package] [package]
name = "clippy_lints" name = "clippy_lints"
# begin automatic update version = "0.1.57"
version = "0.1.56"
# end automatic update
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"

View File

@ -1,8 +1,10 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{meets_msrv, msrvs};
use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol; use rustc_span::symbol;
use std::f64::consts as f64; use std::f64::consts as f64;
@ -36,68 +38,82 @@ declare_clippy_lint! {
"the approximate of a known float constant (in `std::fXX::consts`)" "the approximate of a known float constant (in `std::fXX::consts`)"
} }
// Tuples are of the form (constant, name, min_digits) // Tuples are of the form (constant, name, min_digits, msrv)
const KNOWN_CONSTS: [(f64, &str, usize); 18] = [ const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
(f64::E, "E", 4), (f64::E, "E", 4, None),
(f64::FRAC_1_PI, "FRAC_1_PI", 4), (f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
(f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5), (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
(f64::FRAC_2_PI, "FRAC_2_PI", 5), (f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
(f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5), (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
(f64::FRAC_PI_2, "FRAC_PI_2", 5), (f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
(f64::FRAC_PI_3, "FRAC_PI_3", 5), (f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
(f64::FRAC_PI_4, "FRAC_PI_4", 5), (f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
(f64::FRAC_PI_6, "FRAC_PI_6", 5), (f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
(f64::FRAC_PI_8, "FRAC_PI_8", 5), (f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
(f64::LN_10, "LN_10", 5), (f64::LN_2, "LN_2", 5, None),
(f64::LN_2, "LN_2", 5), (f64::LN_10, "LN_10", 5, None),
(f64::LOG10_E, "LOG10_E", 5), (f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
(f64::LOG2_E, "LOG2_E", 5), (f64::LOG2_E, "LOG2_E", 5, None),
(f64::LOG2_10, "LOG2_10", 5), (f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
(f64::LOG10_2, "LOG10_2", 5), (f64::LOG10_E, "LOG10_E", 5, None),
(f64::PI, "PI", 3), (f64::PI, "PI", 3, None),
(f64::SQRT_2, "SQRT_2", 5), (f64::SQRT_2, "SQRT_2", 5, None),
(f64::TAU, "TAU", 3, Some(msrvs::TAU)),
]; ];
declare_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); pub struct ApproxConstant {
msrv: Option<RustcVersion>,
}
impl ApproxConstant {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
match *lit {
LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"),
FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"),
},
LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"),
_ => (),
}
}
fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
let s = s.as_str();
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
if is_approx_const(constant, &s, min_digits)
&& msrv.as_ref().map_or(true, |msrv| meets_msrv(self.msrv.as_ref(), msrv))
{
span_lint_and_help(
cx,
APPROX_CONSTANT,
e.span,
&format!("approximate value of `{}::consts::{}` found", module, &name),
None,
"consider using the constant directly",
);
return;
}
}
}
}
}
impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
impl<'tcx> LateLintPass<'tcx> for ApproxConstant { impl<'tcx> LateLintPass<'tcx> for ApproxConstant {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Lit(lit) = &e.kind { if let ExprKind::Lit(lit) = &e.kind {
check_lit(cx, &lit.node, e); self.check_lit(cx, &lit.node, e);
} }
} }
}
fn check_lit(cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) { extract_msrv_attr!(LateContext);
match *lit {
LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
FloatTy::F32 => check_known_consts(cx, e, s, "f32"),
FloatTy::F64 => check_known_consts(cx, e, s, "f64"),
},
LitKind::Float(s, LitFloatType::Unsuffixed) => check_known_consts(cx, e, s, "f{32, 64}"),
_ => (),
}
}
fn check_known_consts(cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
let s = s.as_str();
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits) in &KNOWN_CONSTS {
if is_approx_const(constant, &s, min_digits) {
span_lint(
cx,
APPROX_CONSTANT,
e.span,
&format!(
"approximate value of `{}::consts::{}` found. \
Consider using it directly",
module, &name
),
);
return;
}
}
}
} }
/// Returns `false` if the number of significant figures in `value` are /// Returns `false` if the number of significant figures in `value` are

View File

@ -118,7 +118,7 @@ enum AssertKind {
fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> { fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
if_chain! { if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr); if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind; if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
// bind the first argument of the `assert!` macro // bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
// block // block

View File

@ -61,8 +61,8 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
// do not lint if the closure is called using an iterator (see #1141) // do not lint if the closure is called using an iterator (see #1141)
if_chain! { if_chain! {
if let Some(parent) = get_parent_expr(self.cx, expr); if let Some(parent) = get_parent_expr(self.cx, expr);
if let ExprKind::MethodCall(_, _, args, _) = parent.kind; if let ExprKind::MethodCall(_, _, [self_arg, ..], _) = &parent.kind;
let caller = self.cx.typeck_results().expr_ty(&args[0]); let caller = self.cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator); if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(self.cx, caller, iter_id, &[]); if implements_trait(self.cx, caller, iter_id, &[]);
then { then {

View File

@ -1,9 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of, ty::implements_trait};
use clippy_utils::{ast_utils, is_direct_expn_of}; use rustc_ast::ast::LitKind;
use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_hir::{Expr, ExprKind, Lit};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Ident;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -28,45 +30,77 @@ declare_clippy_lint! {
declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]); declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
fn is_bool_lit(e: &Expr) -> bool { fn is_bool_lit(e: &Expr<'_>) -> bool {
matches!( matches!(
e.kind, e.kind,
ExprKind::Lit(Lit { ExprKind::Lit(Lit {
kind: LitKind::Bool(_), node: LitKind::Bool(_),
.. ..
}) })
) && !e.span.from_expansion() ) && !e.span.from_expansion()
} }
impl EarlyLintPass for BoolAssertComparison { fn is_impl_not_trait_with_bool_out(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { let ty = cx.typeck_results().expr_ty(e);
cx.tcx
.lang_items()
.not_trait()
.filter(|trait_id| implements_trait(cx, ty, *trait_id, &[]))
.and_then(|trait_id| {
cx.tcx.associated_items(trait_id).find_by_name_and_kind(
cx.tcx,
Ident::from_str("Output"),
ty::AssocKind::Type,
trait_id,
)
})
.map_or(false, |assoc_item| {
let proj = cx.tcx.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(ty, &[]));
let nty = cx.tcx.normalize_erasing_regions(cx.param_env, proj);
nty.is_bool()
})
}
impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let macros = ["assert_eq", "debug_assert_eq"]; let macros = ["assert_eq", "debug_assert_eq"];
let inverted_macros = ["assert_ne", "debug_assert_ne"]; let inverted_macros = ["assert_ne", "debug_assert_ne"];
for mac in macros.iter().chain(inverted_macros.iter()) { for mac in macros.iter().chain(inverted_macros.iter()) {
if let Some(span) = is_direct_expn_of(e.span, mac) { if let Some(span) = is_direct_expn_of(expr.span, mac) {
if let Some([a, b]) = ast_utils::extract_assert_macro_args(e) { if let Some(args) = higher::extract_assert_macro_args(expr) {
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize; if let [a, b, ..] = args[..] {
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
if nb_bool_args != 1 { if nb_bool_args != 1 {
// If there are two boolean arguments, we definitely don't understand // If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is... // what's going on, so better leave things as is...
// //
// Or there is simply no boolean and then we can leave things as is! // Or there is simply no boolean and then we can leave things as is!
return;
}
if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
// At this point the expression which is not a boolean
// literal does not implement Not trait with a bool output,
// so we cannot suggest to rewrite our code
return;
}
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return; return;
} }
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return;
} }
} }
} }

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::match_type; use clippy_utils::ty::match_type;
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::visitors::is_local_used;
use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs}; use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
return; return;
}; };
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle); if !is_local_used(cx, needle, arg_id);
then { then {
let haystack = if let ExprKind::MethodCall(path, _, args, _) = let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_recv.kind { filter_recv.kind {

View File

@ -19,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(expr),
); );
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, _, args, _) = expr.kind { } else if let ExprKind::MethodCall(method_path, _, [self_arg, ..], _) = &expr.kind {
if_chain! { if_chain! {
if method_path.ident.name == sym!(cast); if method_path.ident.name == sym!(cast);
if let Some(generic_args) = method_path.args; if let Some(generic_args) = method_path.args;
@ -28,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !is_hir_ty_cfg_dependant(cx, cast_to); if !is_hir_ty_cfg_dependant(cx, cast_to);
then { then {
let (cast_from, cast_to) = let (cast_from, cast_to) =
(cx.typeck_results().expr_ty(&args[0]), cx.typeck_results().expr_ty(expr)); (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} }
} }

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::{higher, is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq}; use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, MatchSource, Pat, PatKind, StmtKind}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span}; use rustc_span::{MultiSpan, Span};
@ -56,11 +57,11 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body)); check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
} }
} }
} },
Some(IfLetOrMatch::IfLet(_, pat, body, els)) => { Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
check_arm(cx, false, pat, body, None, els); check_arm(cx, false, pat, body, None, els);
} },
None => {} None => {},
} }
} }
} }
@ -71,7 +72,7 @@ fn check_arm<'tcx>(
outer_pat: &'tcx Pat<'tcx>, outer_pat: &'tcx Pat<'tcx>,
outer_then_body: &'tcx Expr<'tcx>, outer_then_body: &'tcx Expr<'tcx>,
outer_guard: Option<&'tcx Guard<'tcx>>, outer_guard: Option<&'tcx Guard<'tcx>>,
outer_else_body: Option<&'tcx Expr<'tcx>> outer_else_body: Option<&'tcx Expr<'tcx>>,
) { ) {
let inner_expr = strip_singleton_blocks(outer_then_body); let inner_expr = strip_singleton_blocks(outer_then_body);
if_chain! { if_chain! {
@ -106,14 +107,13 @@ fn check_arm<'tcx>(
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b), (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
}; };
// the binding must not be used in the if guard // the binding must not be used in the if guard
let mut used_visitor = LocalUsedVisitor::new(cx, binding_id); if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !is_local_used(cx, *e, binding_id));
if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !used_visitor.check_expr(e));
// ...or anywhere in the inner expression // ...or anywhere in the inner expression
if match inner { if match inner {
IfLetOrMatch::IfLet(_, _, body, els) => { IfLetOrMatch::IfLet(_, _, body, els) => {
!used_visitor.check_expr(body) && els.map_or(true, |e| !used_visitor.check_expr(e)) !is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
}, },
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| used_visitor.check_arm(arm)), IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
}; };
then { then {
let msg = format!( let msg = format!(
@ -151,23 +151,6 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
expr expr
} }
enum IfLetOrMatch<'hir> {
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
/// scrutinee, pattern, then block, else block
IfLet(&'hir Expr<'hir>, &'hir Pat<'hir>, &'hir Expr<'hir>, Option<&'hir Expr<'hir>>),
}
impl<'hir> IfLetOrMatch<'hir> {
fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
match expr.kind {
ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
_ => higher::IfLet::hir(cx, expr).map(|higher::IfLet { let_expr, let_pat, if_then, if_else }| {
Self::IfLet(let_expr, let_pat, if_then, if_else)
})
}
}
}
/// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed" /// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
/// into a single wild arm without any significant loss in semantics or readability. /// into a single wild arm without any significant loss in semantics or readability.
fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {

View File

@ -148,7 +148,7 @@ declare_clippy_lint! {
/// }; /// };
/// ``` /// ```
pub BRANCHES_SHARING_CODE, pub BRANCHES_SHARING_CODE,
complexity, nursery,
"`if` statement with shared code in all blocks" "`if` statement with shared code in all blocks"
} }

View File

@ -0,0 +1,108 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{in_macro, is_automatically_derived, is_default_equivalent, remove_blocks};
use rustc_hir::{
def::{DefKind, Res},
Body, Expr, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node, QPath,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeFoldable;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects manual `std::default::Default` implementations that are identical to a derived implementation.
///
/// ### Why is this bad?
/// It is less concise.
///
/// ### Example
/// ```rust
/// struct Foo {
/// bar: bool
/// }
///
/// impl std::default::Default for Foo {
/// fn default() -> Self {
/// Self {
/// bar: false
/// }
/// }
/// }
/// ```
///
/// Could be written as:
///
/// ```rust
/// #[derive(Default)]
/// struct Foo {
/// bar: bool
/// }
/// ```
///
/// ### Known problems
/// Derive macros [sometimes use incorrect bounds](https://github.com/rust-lang/rust/issues/26925)
/// in generic types and the user defined `impl` maybe is more generalized or
/// specialized than what derive will produce. This lint can't detect the manual `impl`
/// has exactly equal bounds, and therefore this lint is disabled for types with
/// generic parameters.
///
pub DERIVABLE_IMPLS,
complexity,
"manual implementation of the `Default` trait which is equal to a derive"
}
declare_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]);
fn is_path_self(e: &Expr<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind {
matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _))
} else {
false
}
}
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind;
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !is_automatically_derived(attrs);
if !in_macro(item.span);
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def();
then {
if cx.tcx.type_of(item.def_id).definitely_has_param_types_or_consts(cx.tcx) {
return;
}
let should_emit = match remove_blocks(func_expr).kind {
ExprKind::Tup(fields) => fields.iter().all(|e| is_default_equivalent(cx, e)),
ExprKind::Call(callee, args)
if is_path_self(callee) => args.iter().all(|e| is_default_equivalent(cx, e)),
ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_equivalent(cx, ef.expr)),
_ => false,
};
if should_emit {
let path_string = cx.tcx.def_path_str(adt_def.did);
span_lint_and_help(
cx,
DERIVABLE_IMPLS,
item.span,
"this `impl` can be derived",
None,
&format!("try annotating `{}` with `#[derive(Default)]`", path_string),
);
}
}
}
}
}

View File

@ -105,9 +105,6 @@ declare_clippy_lint! {
/// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
/// gets you. /// gets you.
/// ///
/// ### Known problems
/// Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925
///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore
/// #[derive(Copy)] /// #[derive(Copy)]

View File

@ -9,8 +9,9 @@ use clippy_utils::{
use core::fmt::Write; use core::fmt::Write;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{ use rustc_hir::{
hir_id::HirIdSet,
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}, intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
Block, Expr, ExprKind, Guard, HirId, Local, Stmt, StmtKind, UnOp, Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp,
}; };
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -338,6 +339,8 @@ struct InsertSearcher<'cx, 'tcx> {
edits: Vec<Edit<'tcx>>, edits: Vec<Edit<'tcx>>,
/// A stack of loops the visitor is currently in. /// A stack of loops the visitor is currently in.
loops: Vec<HirId>, loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
} }
impl<'tcx> InsertSearcher<'_, 'tcx> { impl<'tcx> InsertSearcher<'_, 'tcx> {
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@ -385,13 +388,16 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
} }
}, },
StmtKind::Expr(e) => self.visit_expr(e), StmtKind::Expr(e) => self.visit_expr(e),
StmtKind::Local(Local { init: Some(e), .. }) => { StmtKind::Local(l) => {
self.allow_insert_closure &= !self.in_tail_pos; self.visit_pat(l.pat);
self.in_tail_pos = false; if let Some(e) = l.init {
self.is_single_insert = false; self.allow_insert_closure &= !self.in_tail_pos;
self.visit_expr(e); self.in_tail_pos = false;
self.is_single_insert = false;
self.visit_expr(e);
}
}, },
_ => { StmtKind::Item(_) => {
self.allow_insert_closure &= !self.in_tail_pos; self.allow_insert_closure &= !self.in_tail_pos;
self.is_single_insert = false; self.is_single_insert = false;
}, },
@ -473,6 +479,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
// Each branch may contain it's own insert expression. // Each branch may contain it's own insert expression.
let mut is_map_used = self.is_map_used; let mut is_map_used = self.is_map_used;
for arm in arms { for arm in arms {
self.visit_pat(arm.pat);
if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard { if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
self.visit_non_tail_expr(guard); self.visit_non_tail_expr(guard);
} }
@ -498,7 +505,8 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
}, },
_ => { _ => {
self.allow_insert_closure &= !self.in_tail_pos; self.allow_insert_closure &= !self.in_tail_pos;
self.allow_insert_closure &= can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops); self.allow_insert_closure &=
can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
// Sub expressions are no longer in the tail position. // Sub expressions are no longer in the tail position.
self.is_single_insert = false; self.is_single_insert = false;
self.in_tail_pos = false; self.in_tail_pos = false;
@ -507,6 +515,12 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
}, },
} }
} }
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
p.each_binding_or_first(&mut |_, id, _, _| {
self.locals.insert(id);
});
}
} }
struct InsertSearchResults<'tcx> { struct InsertSearchResults<'tcx> {
@ -632,6 +646,7 @@ fn find_insert_calls(
in_tail_pos: true, in_tail_pos: true,
is_single_insert: true, is_single_insert: true,
loops: Vec::new(), loops: Vec::new(),
locals: HirIdSet::default(),
}; };
s.visit_expr(expr); s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure; let allow_insert_closure = s.allow_insert_closure;

View File

@ -15,8 +15,8 @@ declare_clippy_lint! {
/// order of sub-expressions. /// order of sub-expressions.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// It is often confusing to read. In addition, the /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands),
/// sub-expression evaluation order for Rust is not well documented. /// the operands of these expressions are evaluated before applying the effects of the expression.
/// ///
/// ### Known problems /// ### Known problems
/// Code which intentionally depends on the evaluation /// Code which intentionally depends on the evaluation

View File

@ -0,0 +1,164 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
use rustc_hir::{Crate, CRATE_HIR_ID};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::DUMMY_SP;
declare_clippy_lint! {
/// ### What it does
/// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
///
/// ### Why is this bad?
/// These prefixes and suffixes have no significant meaning.
///
/// ### Example
/// ```toml
/// # The `Cargo.toml` with feature name redundancy
/// [features]
/// default = ["use-abc", "with-def", "ghi-support"]
/// use-abc = [] // redundant
/// with-def = [] // redundant
/// ghi-support = [] // redundant
/// ```
///
/// Use instead:
/// ```toml
/// [features]
/// default = ["abc", "def", "ghi"]
/// abc = []
/// def = []
/// ghi = []
/// ```
///
pub REDUNDANT_FEATURE_NAMES,
cargo,
"usage of a redundant feature name"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for negative feature names with prefix `no-` or `not-`
///
/// ### Why is this bad?
/// Features are supposed to be additive, and negatively-named features violate it.
///
/// ### Example
/// ```toml
/// # The `Cargo.toml` with negative feature names
/// [features]
/// default = []
/// no-abc = []
/// not-def = []
///
/// ```
/// Use instead:
/// ```toml
/// [features]
/// default = ["abc", "def"]
/// abc = []
/// def = []
///
/// ```
pub NEGATIVE_FEATURE_NAMES,
cargo,
"usage of a negative feature name"
}
declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]);
static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
static SUFFIXES: [&str; 2] = ["-support", "_support"];
fn is_negative_prefix(s: &str) -> bool {
s.starts_with("no")
}
fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
let is_negative = is_prefix && is_negative_prefix(substring);
span_lint_and_help(
cx,
if is_negative {
NEGATIVE_FEATURE_NAMES
} else {
REDUNDANT_FEATURE_NAMES
},
DUMMY_SP,
&format!(
"the \"{}\" {} in the feature name \"{}\" is {}",
substring,
if is_prefix { "prefix" } else { "suffix" },
feature,
if is_negative { "negative" } else { "redundant" }
),
None,
&format!(
"consider renaming the feature to \"{}\"{}",
if is_prefix {
feature.strip_prefix(substring)
} else {
feature.strip_suffix(substring)
}
.unwrap(),
if is_negative {
", but make sure the feature adds functionality"
} else {
""
}
),
);
}
impl LateLintPass<'_> for FeatureName {
fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) {
if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID)
&& is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID)
{
return;
}
let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false);
for package in metadata.packages {
let mut features: Vec<&String> = package.features.keys().collect();
features.sort();
for feature in features {
let prefix_opt = {
let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
Some(PREFIXES[i - 1])
} else {
None
}
};
if let Some(prefix) = prefix_opt {
lint(cx, feature, prefix, true);
}
let suffix_opt: Option<&str> = {
let i = SUFFIXES.partition_point(|suffix| {
suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
});
if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
Some(SUFFIXES[i - 1])
} else {
None
}
};
if let Some(suffix) = suffix_opt {
lint(cx, feature, suffix, false);
}
}
}
}
}
#[test]
fn test_prefixes_sorted() {
let mut sorted_prefixes = PREFIXES;
sorted_prefixes.sort_unstable();
assert_eq!(PREFIXES, sorted_prefixes);
let mut sorted_suffixes = SUFFIXES;
sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
assert_eq!(SUFFIXES, sorted_suffixes);
}

View File

@ -332,8 +332,6 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
), ),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
return;
} }
} }
} }
@ -364,22 +362,22 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
if_chain! { if_chain! {
if let ExprKind::MethodCall( if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. }, PathSegment { ident: lmethod_name, .. },
ref _lspan, _lspan,
largs, [largs_0, largs_1, ..],
_ _
) = add_lhs.kind; ) = &add_lhs.kind;
if let ExprKind::MethodCall( if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. }, PathSegment { ident: rmethod_name, .. },
ref _rspan, _rspan,
rargs, [rargs_0, rargs_1, ..],
_ _
) = add_rhs.kind; ) = &add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), &largs[1]); if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), &rargs[1]); if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
if Int(2) == lvalue && Int(2) == rvalue; if Int(2) == lvalue && Int(2) == rvalue;
then { then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."))); return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, ".."), Sugg::hir(cx, rargs_0, "..")));
} }
} }
} }
@ -409,8 +407,8 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if cx.typeck_results().expr_ty(lhs).is_floating_point(); if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs); if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value; if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(path, _, method_args, _) = lhs.kind; if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &lhs.kind;
if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point(); if cx.typeck_results().expr_ty(self_arg).is_floating_point();
if path.ident.name.as_str() == "exp"; if path.ident.name.as_str() == "exp";
then { then {
span_lint_and_sugg( span_lint_and_sugg(
@ -421,7 +419,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
"consider using", "consider using",
format!( format!(
"{}.exp_m1()", "{}.exp_m1()",
Sugg::hir(cx, &method_args[0], "..") Sugg::hir(cx, self_arg, "..")
), ),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
@ -619,8 +617,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs, rhs,
) = &expr.kind; ) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs); if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, _, largs, _) = lhs.kind; if let ExprKind::MethodCall(_, _, [largs_self, ..], _) = &lhs.kind;
if let ExprKind::MethodCall(_, _, rargs, _) = rhs.kind; if let ExprKind::MethodCall(_, _, [rargs_self, ..], _) = &rhs.kind;
then { then {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -628,7 +626,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span, expr.span,
"log base can be expressed more clearly", "log base can be expressed more clearly",
"consider using", "consider using",
format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),), format!("{}.log({})", Sugg::hir(cx, largs_self, ".."), Sugg::hir(cx, rargs_self, ".."),),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }

View File

@ -26,7 +26,6 @@ pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr { if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
return;
} else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
check_must_use_candidate( check_must_use_candidate(
cx, cx,

View File

@ -138,12 +138,12 @@ impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, _span, args, _) = &expr.kind; if let ExprKind::MethodCall(path, _span, [self_arg, ..], _) = &expr.kind;
if path.ident.as_str() == "lock"; if path.ident.as_str() == "lock";
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(self_arg);
if is_type_diagnostic_item(cx, ty, sym!(mutex_type)); if is_type_diagnostic_item(cx, ty, sym!(mutex_type));
then { then {
Some(&args[0]) Some(self_arg)
} else { } else {
None None
} }

View File

@ -46,10 +46,10 @@ impl<'tcx> LateLintPass<'tcx> for OkIfLet {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { //begin checking variables if_chain! { //begin checking variables
if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr); if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr);
if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _) if let ExprKind::MethodCall(_, ok_span, [ref result_types_0, ..], _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = let_pat.kind; //get operation if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized; if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&result_types[0]), sym::result_type); if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::result_type);
if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some"; if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
then { then {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor}; use clippy_utils::{path_to_local_id, visitors::is_local_used};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
@ -65,11 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(if_) = expr.kind; if let hir::StmtKind::Expr(if_) = expr.kind;
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind; if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id); if !is_local_used(cx, *cond, canonical_id);
if !used_visitor.check_expr(cond);
if let hir::ExprKind::Block(then, _) = then.kind; if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, &*then); if let Some(value) = check_assign(cx, canonical_id, &*then);
if !used_visitor.check_expr(value); if !is_local_used(cx, value, canonical_id);
then { then {
let span = stmt.span.to(if_.span); let span = stmt.span.to(if_.span);
@ -148,15 +147,13 @@ fn check_assign<'tcx>(
if let hir::ExprKind::Assign(var, value, _) = expr.kind; if let hir::ExprKind::Assign(var, value, _) = expr.kind;
if path_to_local_id(var, decl); if path_to_local_id(var, decl);
then { then {
let mut v = LocalUsedVisitor::new(cx, decl); if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
None
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) { } else {
return None; Some(value)
} }
} else {
return Some(value); None
} }
} }
None
} }

View File

@ -187,6 +187,7 @@ mod dbg_macro;
mod default; mod default;
mod default_numeric_fallback; mod default_numeric_fallback;
mod dereference; mod dereference;
mod derivable_impls;
mod derive; mod derive;
mod disallowed_method; mod disallowed_method;
mod disallowed_script_idents; mod disallowed_script_idents;
@ -211,6 +212,7 @@ mod exhaustive_items;
mod exit; mod exit;
mod explicit_write; mod explicit_write;
mod fallible_impl_from; mod fallible_impl_from;
mod feature_name;
mod float_equality_without_abs; mod float_equality_without_abs;
mod float_literal; mod float_literal;
mod floating_point_arithmetic; mod floating_point_arithmetic;
@ -272,6 +274,7 @@ mod missing_const_for_fn;
mod missing_doc; mod missing_doc;
mod missing_enforced_import_rename; mod missing_enforced_import_rename;
mod missing_inline; mod missing_inline;
mod module_style;
mod modulo_arithmetic; mod modulo_arithmetic;
mod multiple_crate_versions; mod multiple_crate_versions;
mod mut_key; mod mut_key;
@ -287,6 +290,7 @@ mod needless_borrow;
mod needless_borrowed_ref; mod needless_borrowed_ref;
mod needless_continue; mod needless_continue;
mod needless_for_each; mod needless_for_each;
mod needless_option_as_deref;
mod needless_pass_by_value; mod needless_pass_by_value;
mod needless_question_mark; mod needless_question_mark;
mod needless_update; mod needless_update;
@ -584,6 +588,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
default::FIELD_REASSIGN_WITH_DEFAULT, default::FIELD_REASSIGN_WITH_DEFAULT,
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK, default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
dereference::EXPLICIT_DEREF_METHODS, dereference::EXPLICIT_DEREF_METHODS,
derivable_impls::DERIVABLE_IMPLS,
derive::DERIVE_HASH_XOR_EQ, derive::DERIVE_HASH_XOR_EQ,
derive::DERIVE_ORD_XOR_PARTIAL_ORD, derive::DERIVE_ORD_XOR_PARTIAL_ORD,
derive::EXPL_IMPL_CLONE_ON_COPY, derive::EXPL_IMPL_CLONE_ON_COPY,
@ -625,6 +630,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
exit::EXIT, exit::EXIT,
explicit_write::EXPLICIT_WRITE, explicit_write::EXPLICIT_WRITE,
fallible_impl_from::FALLIBLE_IMPL_FROM, fallible_impl_from::FALLIBLE_IMPL_FROM,
feature_name::NEGATIVE_FEATURE_NAMES,
feature_name::REDUNDANT_FEATURE_NAMES,
float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
float_literal::EXCESSIVE_PRECISION, float_literal::EXCESSIVE_PRECISION,
float_literal::LOSSY_FLOAT_LITERAL, float_literal::LOSSY_FLOAT_LITERAL,
@ -770,6 +777,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
methods::MANUAL_FILTER_MAP, methods::MANUAL_FILTER_MAP,
methods::MANUAL_FIND_MAP, methods::MANUAL_FIND_MAP,
methods::MANUAL_SATURATING_ARITHMETIC, methods::MANUAL_SATURATING_ARITHMETIC,
methods::MANUAL_SPLIT_ONCE,
methods::MANUAL_STR_REPEAT, methods::MANUAL_STR_REPEAT,
methods::MAP_COLLECT_RESULT_UNIT, methods::MAP_COLLECT_RESULT_UNIT,
methods::MAP_FLATTEN, methods::MAP_FLATTEN,
@ -822,6 +830,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS, missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES, missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS, missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
module_style::MOD_MODULE_FILES,
module_style::SELF_NAMED_MODULE_FILES,
modulo_arithmetic::MODULO_ARITHMETIC, modulo_arithmetic::MODULO_ARITHMETIC,
multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
mut_key::MUTABLE_KEY_TYPE, mut_key::MUTABLE_KEY_TYPE,
@ -840,6 +850,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
needless_continue::NEEDLESS_CONTINUE, needless_continue::NEEDLESS_CONTINUE,
needless_for_each::NEEDLESS_FOR_EACH, needless_for_each::NEEDLESS_FOR_EACH,
needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF,
needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
needless_question_mark::NEEDLESS_QUESTION_MARK, needless_question_mark::NEEDLESS_QUESTION_MARK,
needless_update::NEEDLESS_UPDATE, needless_update::NEEDLESS_UPDATE,
@ -1031,6 +1042,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS), LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES), LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES),
LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS), LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC), LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN), LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
LintId::of(panic_unimplemented::PANIC), LintId::of(panic_unimplemented::PANIC),
@ -1122,7 +1135,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_for_each::NEEDLESS_FOR_EACH), LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
LintId::of(non_expressive_names::SIMILAR_NAMES), LintId::of(non_expressive_names::SIMILAR_NAMES),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
LintId::of(ranges::RANGE_MINUS_ONE), LintId::of(ranges::RANGE_MINUS_ONE),
@ -1193,10 +1205,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(collapsible_if::COLLAPSIBLE_IF), LintId::of(collapsible_if::COLLAPSIBLE_IF),
LintId::of(collapsible_match::COLLAPSIBLE_MATCH), LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(comparison_chain::COMPARISON_CHAIN), LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(copies::IFS_SAME_COND), LintId::of(copies::IFS_SAME_COND),
LintId::of(copies::IF_SAME_THEN_ELSE), LintId::of(copies::IF_SAME_THEN_ELSE),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ), LintId::of(derive::DERIVE_HASH_XOR_EQ),
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD), LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(doc::MISSING_SAFETY_DOC), LintId::of(doc::MISSING_SAFETY_DOC),
@ -1316,6 +1328,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
LintId::of(methods::MANUAL_SPLIT_ONCE),
LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::MANUAL_STR_REPEAT),
LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
LintId::of(methods::MAP_IDENTITY), LintId::of(methods::MAP_IDENTITY),
@ -1366,6 +1379,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrow::NEEDLESS_BORROW), LintId::of(needless_borrow::NEEDLESS_BORROW),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
@ -1581,7 +1595,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST), LintId::of(casts::UNNECESSARY_CAST),
LintId::of(copies::BRANCHES_SHARING_CODE), LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(double_comparison::DOUBLE_COMPARISONS), LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS), LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(duration_subsec::DURATION_SUBSEC), LintId::of(duration_subsec::DURATION_SUBSEC),
@ -1614,6 +1628,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(methods::ITER_COUNT), LintId::of(methods::ITER_COUNT),
LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SPLIT_ONCE),
LintId::of(methods::MAP_IDENTITY), LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OPTION_FILTER_MAP),
@ -1628,6 +1643,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_bool::BOOL_COMPARISON), LintId::of(needless_bool::BOOL_COMPARISON),
LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
@ -1779,6 +1795,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA), LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA),
LintId::of(feature_name::NEGATIVE_FEATURE_NAMES),
LintId::of(feature_name::REDUNDANT_FEATURE_NAMES),
LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES), LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES),
]); ]);
@ -1786,6 +1804,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR), LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY), LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(disallowed_method::DISALLOWED_METHOD), LintId::of(disallowed_method::DISALLOWED_METHOD),
LintId::of(disallowed_type::DISALLOWED_TYPE), LintId::of(disallowed_type::DISALLOWED_TYPE),
LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM), LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM),
@ -1797,6 +1816,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
LintId::of(mutex_atomic::MUTEX_INTEGER), LintId::of(mutex_atomic::MUTEX_INTEGER),
LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES), LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE), LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
LintId::of(regex::TRIVIAL_REGEX), LintId::of(regex::TRIVIAL_REGEX),
@ -1835,7 +1855,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(serde_api::SerdeApi)); store.register_late_pass(|| Box::new(serde_api::SerdeApi));
let vec_box_size_threshold = conf.vec_box_size_threshold; let vec_box_size_threshold = conf.vec_box_size_threshold;
let type_complexity_threshold = conf.type_complexity_threshold; let type_complexity_threshold = conf.type_complexity_threshold;
store.register_late_pass(move || Box::new(types::Types::new(vec_box_size_threshold, type_complexity_threshold))); let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
store.register_late_pass(move || Box::new(types::Types::new(
vec_box_size_threshold,
type_complexity_threshold,
avoid_breaking_exported_api,
)));
store.register_late_pass(|| Box::new(booleans::NonminimalBool)); store.register_late_pass(|| Box::new(booleans::NonminimalBool));
store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool)); store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
store.register_late_pass(|| Box::new(eq_op::EqOp)); store.register_late_pass(|| Box::new(eq_op::EqOp));
@ -1846,9 +1871,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(ptr::Ptr)); store.register_late_pass(|| Box::new(ptr::Ptr));
store.register_late_pass(|| Box::new(ptr_eq::PtrEq)); store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
store.register_late_pass(|| Box::new(needless_bool::NeedlessBool)); store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|| Box::new(needless_option_as_deref::OptionNeedlessDeref));
store.register_late_pass(|| Box::new(needless_bool::BoolComparison)); store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
store.register_late_pass(|| Box::new(approx_const::ApproxConstant));
store.register_late_pass(|| Box::new(misc::MiscLints)); store.register_late_pass(|| Box::new(misc::MiscLints));
store.register_late_pass(|| Box::new(eta_reduction::EtaReduction)); store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
store.register_late_pass(|| Box::new(identity_op::IdentityOp)); store.register_late_pass(|| Box::new(identity_op::IdentityOp));
@ -1877,6 +1902,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
}); });
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
store.register_late_pass(move || Box::new(approx_const::ApproxConstant::new(msrv)));
store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv))); store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv)));
store.register_late_pass(move || Box::new(matches::Matches::new(msrv))); store.register_late_pass(move || Box::new(matches::Matches::new(msrv)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv))); store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv)));
@ -1920,6 +1946,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented)); store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented));
store.register_late_pass(|| Box::new(strings::StringLitAsBytes)); store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
store.register_late_pass(|| Box::new(derive::Derive)); store.register_late_pass(|| Box::new(derive::Derive));
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen)); store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef)); store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum)); store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
@ -2092,7 +2119,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10));
store.register_late_pass(|| Box::new(manual_map::ManualMap)); store.register_late_pass(|| Box::new(manual_map::ManualMap));
store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv))); store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
store.register_early_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison)); store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|| Box::new(unused_async::UnusedAsync)); store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>(); let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(&disallowed_types))); store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(&disallowed_types)));
@ -2102,6 +2130,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts))); store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings)); store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings));
store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors)); store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors));
store.register_late_pass(move || Box::new(feature_name::FeatureName));
} }
#[rustfmt::skip] #[rustfmt::skip]

View File

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::sugg; use clippy_utils::sugg;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::visitors::is_local_used;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
@ -66,9 +66,7 @@ pub(super) fn check<'tcx>(
fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
match *pat { match *pat {
PatKind::Wild => true, PatKind::Wild => true,
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
!LocalUsedVisitor::new(cx, id).check_expr(body)
},
_ => false, _ => false,
} }
} }

View File

@ -2,6 +2,7 @@ use super::utils::make_iterator_snippet;
use super::MANUAL_FLATTEN; use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher; use clippy_utils::higher;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, path_to_local_id}; use clippy_utils::{is_lang_ctor, path_to_local_id};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -37,7 +38,8 @@ pub(super) fn check<'tcx>(
if_chain! { if_chain! {
if let Some(inner_expr) = inner_expr; if let Some(inner_expr) = inner_expr;
if let Some(higher::IfLet { let_pat, let_expr, if_else: None, .. }) = higher::IfLet::hir(cx, inner_expr); if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr);
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
if path_to_local_id(let_expr, pat_hir_id); if path_to_local_id(let_expr, pat_hir_id);
@ -46,6 +48,8 @@ pub(super) fn check<'tcx>(
let some_ctor = is_lang_ctor(cx, qpath, OptionSome); let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
let ok_ctor = is_lang_ctor(cx, qpath, ResultOk); let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
if some_ctor || ok_ctor; if some_ctor || ok_ctor;
// Ensure epxr in `if let` is not used afterwards
if !is_local_used(cx, if_then, pat_hir_id);
then { then {
let if_let_type = if some_ctor { "Some" } else { "Ok" }; let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message // Prepare the error message

View File

@ -397,6 +397,21 @@ declare_clippy_lint! {
/// ### Why is this bad? /// ### Why is this bad?
/// One might think that modifying the mutable variable changes the loop bounds /// One might think that modifying the mutable variable changes the loop bounds
/// ///
/// ### Known problems
/// False positive when mutation is followed by a `break`, but the `break` is not immediately
/// after the mutation:
///
/// ```rust
/// let mut x = 5;
/// for _ in 0..x {
/// x += 1; // x is a range bound that is mutated
/// ..; // some other expression
/// break; // leaves the loop, so mutation is not an issue
/// }
/// ```
///
/// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
///
/// ### Example /// ### Example
/// ```rust /// ```rust
/// let mut foo = 42; /// let mut foo = 42;
@ -580,8 +595,8 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
while_let_on_iterator::check(cx, expr); while_let_on_iterator::check(cx, expr);
if let Some(higher::While { if_cond, if_then, .. }) = higher::While::hir(&expr) { if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
while_immutable_condition::check(cx, if_cond, if_then); while_immutable_condition::check(cx, condition, body);
} }
needless_collect::check(expr, cx); needless_collect::check(expr, cx);

View File

@ -1,24 +1,27 @@
use super::MUT_RANGE_BOUND; use super::MUT_RANGE_BOUND;
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::{higher, path_to_local}; use clippy_utils::{get_enclosing_block, higher, path_to_local};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind}; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_middle::{mir::FakeReadCause, ty}; use rustc_middle::{mir::FakeReadCause, ty};
use rustc_span::source_map::Span; use rustc_span::source_map::Span;
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
if let Some(higher::Range { if_chain! {
start: Some(start), if let Some(higher::Range {
end: Some(end), start: Some(start),
.. end: Some(end),
}) = higher::Range::hir(arg) ..
{ }) = higher::Range::hir(arg);
let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
if mut_ids[0].is_some() || mut_ids[1].is_some() { if mut_id_start.is_some() || mut_id_end.is_some();
let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); then {
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low); mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high); mut_warn_with_span(cx, span_high);
} }
@ -27,11 +30,13 @@ pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) { fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
if let Some(sp) = span { if let Some(sp) = span {
span_lint( span_lint_and_note(
cx, cx,
MUT_RANGE_BOUND, MUT_RANGE_BOUND,
sp, sp,
"attempt to mutate range bound within loop; note that the range of the loop is unchanged", "attempt to mutate range bound within loop",
None,
"the range of the loop is unchanged",
); );
} }
} }
@ -51,12 +56,13 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
fn check_for_mutation<'tcx>( fn check_for_mutation<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
body: &Expr<'_>, body: &Expr<'_>,
bound_ids: &[Option<HirId>], bound_id_start: Option<HirId>,
bound_id_end: Option<HirId>,
) -> (Option<Span>, Option<Span>) { ) -> (Option<Span>, Option<Span>) {
let mut delegate = MutatePairDelegate { let mut delegate = MutatePairDelegate {
cx, cx,
hir_id_low: bound_ids[0], hir_id_low: bound_id_start,
hir_id_high: bound_ids[1], hir_id_high: bound_id_end,
span_low: None, span_low: None,
span_high: None, span_high: None,
}; };
@ -70,6 +76,7 @@ fn check_for_mutation<'tcx>(
) )
.walk_expr(body); .walk_expr(body);
}); });
delegate.mutation_span() delegate.mutation_span()
} }
@ -87,10 +94,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::MutBorrow = bk { if let ty::BorrowKind::MutBorrow = bk {
if let PlaceBase::Local(id) = cmt.place.base { if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low { if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)); self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
} }
if Some(id) == self.hir_id_high { if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)); self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
} }
} }
@ -99,10 +106,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
if let PlaceBase::Local(id) = cmt.place.base { if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low { if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)); self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
} }
if Some(id) == self.hir_id_high { if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)); self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
} }
} }
@ -116,3 +123,52 @@ impl MutatePairDelegate<'_, '_> {
(self.span_low, self.span_high) (self.span_low, self.span_high)
} }
} }
struct BreakAfterExprVisitor {
hir_id: HirId,
past_expr: bool,
past_candidate: bool,
break_after_expr: bool,
}
impl BreakAfterExprVisitor {
pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
let mut visitor = BreakAfterExprVisitor {
hir_id,
past_expr: false,
past_candidate: false,
break_after_expr: false,
};
get_enclosing_block(cx, hir_id).map_or(false, |block| {
visitor.visit_block(block);
visitor.break_after_expr
})
}
}
impl intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.past_candidate {
return;
}
if expr.hir_id == self.hir_id {
self.past_expr = true;
} else if self.past_expr {
if matches!(&expr.kind, ExprKind::Break(..)) {
self.break_after_expr = true;
}
self.past_candidate = true;
} else {
intravisit::walk_expr(self, expr);
}
}
}

View File

@ -26,7 +26,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
then { then {
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(&args[0]);
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MaybeIncorrect;
let is_empty_sugg = "next().is_none()".to_string(); let is_empty_sugg = "next().is_none()".to_string();
let method_name = &*method.ident.name.as_str(); let method_name = &*method.ident.name.as_str();
let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
@ -113,7 +113,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
(stmt.span, String::new()), (stmt.span, String::new()),
(iter_call.span, iter_replacement) (iter_call.span, iter_replacement)
], ],
Applicability::MachineApplicable,// MaybeIncorrect, Applicability::MaybeIncorrect,
); );
}, },
); );

View File

@ -2,10 +2,8 @@ use super::NEEDLESS_RANGE_LOOP;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method; use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::visitors::is_local_used;
use clippy_utils::{ use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
contains_name, higher, is_integer_const, match_trait_method, path_to_local_id, paths, sugg, SpanlessEq,
};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast; use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -256,43 +254,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
if let ExprKind::Path(ref seqpath) = seqexpr.kind; if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, seqvar) = *seqpath; if let QPath::Resolved(None, seqvar) = *seqpath;
if seqvar.segments.len() == 1; if seqvar.segments.len() == 1;
let index_used_directly = path_to_local_id(idx, self.var); if is_local_used(self.cx, idx, self.var);
let indexed_indirectly = {
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
walk_expr(&mut used_visitor, idx);
used_visitor.used
};
if indexed_indirectly || index_used_directly;
then { then {
if self.prefer_mutable { if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name); self.indexed_mut.insert(seqvar.segments[0].ident.name);
} }
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res { match res {
Res::Local(hir_id) => { Res::Local(hir_id) => {
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
}
if index_used_directly { if index_used_directly {
self.indexed_directly.insert( self.indexed_directly.insert(
seqvar.segments[0].ident.name, seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
); );
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
} }
return false; // no need to walk further *on the variable* return false; // no need to walk further *on the variable*
} }
Res::Def(DefKind::Static | DefKind::Const, ..) => { Res::Def(DefKind::Static | DefKind::Const, ..) => {
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
if index_used_directly { if index_used_directly {
self.indexed_directly.insert( self.indexed_directly.insert(
seqvar.segments[0].ident.name, seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)), (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
); );
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
} }
return false; // no need to walk further *on the variable* return false; // no need to walk further *on the variable*
} }
@ -310,10 +301,10 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// a range index op // a range index op
if let ExprKind::MethodCall(meth, _, args, _) = expr.kind; if let ExprKind::MethodCall(meth, _, [args_0, args_1, ..], _) = &expr.kind;
if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
|| (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
if !self.check(&args[1], &args[0], expr); if !self.check(args_1, args_0, expr);
then { return } then { return }
} }

View File

@ -87,7 +87,7 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
let stmts = block.stmts.iter().map(stmt_to_expr); let stmts = block.stmts.iter().map(stmt_to_expr);
let expr = once(block.expr.as_deref()); let expr = once(block.expr);
let mut iter = stmts.chain(expr).flatten(); let mut iter = stmts.chain(expr).flatten();
never_loop_expr_seq(&mut iter, main_loop_id) never_loop_expr_seq(&mut iter, main_loop_id)
} }
@ -100,7 +100,7 @@ fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_lo
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match stmt.kind { match stmt.kind {
StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e), StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e),
StmtKind::Local(local) => local.init.as_deref(), StmtKind::Local(local) => local.init,
StmtKind::Item(..) => None, StmtKind::Item(..) => None,
} }
} }

View File

@ -24,13 +24,13 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'
} }
} }
if let ExprKind::Match(ref matchexpr, ref arms, MatchSource::Normal) = inner.kind { if let ExprKind::Match(matchexpr, arms, MatchSource::Normal) = inner.kind {
if arms.len() == 2 if arms.len() == 2
&& arms[0].guard.is_none() && arms[0].guard.is_none()
&& arms[1].guard.is_none() && arms[1].guard.is_none()
&& is_simple_break_expr(&arms[1].body) && is_simple_break_expr(arms[1].body)
{ {
could_be_while_let(cx, expr, &arms[0].pat, matchexpr); could_be_while_let(cx, expr, arms[0].pat, matchexpr);
} }
} }
} }

View File

@ -14,12 +14,7 @@ use rustc_span::{symbol::sym, Span, Symbol};
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! { let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! {
if let Some(higher::WhileLet { if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
if_then,
let_pat,
let_expr,
..
}) = higher::WhileLet::hir(expr);
// check for `Some(..)` pattern // check for `Some(..)` pattern
if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind; if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind;
if let Res::Def(_, pat_did) = pat_path.res; if let Res::Def(_, pat_did) = pat_path.res;

View File

@ -48,8 +48,7 @@ pub struct MacroRefData {
impl MacroRefData { impl MacroRefData {
pub fn new(name: String, callee: Span, cx: &LateContext<'_>) -> Self { pub fn new(name: String, callee: Span, cx: &LateContext<'_>) -> Self {
let sm = cx.sess().source_map(); let sm = cx.sess().source_map();
let mut path = sm.filename_for_diagnostics(&sm.span_to_filename(callee)) let mut path = sm.filename_for_diagnostics(&sm.span_to_filename(callee)).to_string();
.to_string();
// std lib paths are <::std::module::file type> // std lib paths are <::std::module::file type>
// so remove brackets, space and type. // so remove brackets, space and type.

View File

@ -1,16 +1,18 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF}; use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher; use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{ use clippy_utils::{
can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id, can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
peel_hir_expr_refs, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
}; };
use rustc_ast::util::parser::PREC_POSTFIX; use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind}; use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath,
};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -44,168 +46,169 @@ declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl LateLintPass<'_> for ManualMap { impl LateLintPass<'_> for ManualMap {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(higher::IfLet { let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) {
let_pat, Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else),
let_expr, Some(IfLetOrMatch::Match(
if_then,
if_else: Some(if_else),
}) = higher::IfLet::hir(cx, expr)
{
manage_lint(cx, expr, (&let_pat.kind, if_then), (&PatKind::Wild, if_else), let_expr);
}
if let ExprKind::Match(scrutinee, [then @ Arm { guard: None, .. }, r#else @ Arm { guard: None, .. }], _) =
expr.kind
{
manage_lint(
cx,
expr,
(&then.pat.kind, then.body),
(&r#else.pat.kind, r#else.body),
scrutinee, scrutinee,
); [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
_,
)) => (scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body),
_ => return,
};
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
} }
}
}
fn manage_lint<'tcx>( let (scrutinee_ty, ty_ref_count, ty_mutability) =
cx: &LateContext<'tcx>, peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
expr: &'tcx Expr<'_>, if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
then: (&'tcx PatKind<'_>, &'tcx Expr<'_>), && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type))
r#else: (&'tcx PatKind<'_>, &'tcx Expr<'_>), {
scrut: &'tcx Expr<'_>, return;
) { }
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrut)); let expr_ctxt = expr.span.ctxt();
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)) try_parse_pattern(cx, then_pat, expr_ctxt),
{ else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
return; ) {
} (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
let (then_pat, then_expr) = then;
let (else_pat, else_expr) = r#else;
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
try_parse_pattern(cx, else_pat, expr_ctxt),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_expr) => {
(else_expr, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_expr) => {
(else_expr, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_expr) => {
(then_expr, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_expr) => {
(then_expr, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
Some(expr) => expr,
None => return,
};
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id) {
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
return;
}
if !can_move_expr_to_closure(cx, some_expr) {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrut).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
}, },
_ => { (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
if path_to_local_id(some_expr, id) (else_body, pattern, ref_count, false)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id) },
&& binding_ref.is_some() (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
{ (then_body, pattern, ref_count, true)
return; },
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
Some(expr) => expr,
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
} }
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
}, },
} None => return,
} else if !is_wild_none && explicit_ref.is_none() { };
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg( let mut app = Applicability::MachineApplicable;
cx,
MANUAL_MAP, // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
expr.span, // it's being passed by value.
"manual implementation of `Option::map`", let scrutinee = peel_hir_expr_refs(scrutinee).0;
"try this", let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
if is_else_clause(cx.tcx, expr) { let scrutinee_str =
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str) if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
},
_ => {
if path_to_local_id(some_expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
},
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
} else { } else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str) // Refutable bindings and mixed reference annotations can't be handled by `map`.
}, return;
app, };
);
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
} else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
},
app,
);
}
} }
// Checks whether the expression could be passed as a function, or whether a closure is needed. // Checks whether the expression could be passed as a function, or whether a closure is needed.
@ -213,7 +216,7 @@ fn manage_lint<'tcx>(
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind { match expr.kind {
ExprKind::Call(func, [arg]) ExprKind::Call(func, [arg])
if path_to_local_id (arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() => if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
{ {
Some(func) Some(func)
}, },
@ -235,28 +238,21 @@ enum OptionPat<'a> {
// Try to parse into a recognized `Option` pattern. // Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those. // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern( fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
cx: &LateContext<'tcx>, fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
pat_kind: &'tcx PatKind<'_>, match pat.kind {
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
fn f(
cx: &LateContext<'tcx>,
pat_kind: &'tcx PatKind<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat_kind {
PatKind::Wild => Some(OptionPat::Wild), PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(ref_pat, _) => f(cx, &ref_pat.kind, ref_count + 1, ctxt), PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None), PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
PatKind::TupleStruct(ref qpath, [pattern], _) if is_lang_ctor(cx, qpath, OptionSome) => { PatKind::TupleStruct(ref qpath, [pattern], _)
if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count }) Some(OptionPat::Some { pattern, ref_count })
}, },
_ => None, _ => None,
} }
} }
f(cx, pat_kind, 0, ctxt) f(cx, pat, 0, ctxt)
} }
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression. // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.

View File

@ -6,7 +6,7 @@ use clippy_utils::higher;
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::visitors::is_local_used;
use clippy_utils::{ use clippy_utils::{
get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild,
meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
@ -631,7 +631,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
check_match_single_binding(cx, ex, arms, expr); check_match_single_binding(cx, ex, arms, expr);
} }
} }
if let ExprKind::Match(ref ex, ref arms, _) = expr.kind { if let ExprKind::Match(ex, arms, _) = expr.kind {
check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
} }
if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) { if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
@ -959,9 +959,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
// Looking for unused bindings (i.e.: `_e`) // Looking for unused bindings (i.e.: `_e`)
for pat in inner.iter() { for pat in inner.iter() {
if let PatKind::Binding(_, id, ident, None) = pat.kind { if let PatKind::Binding(_, id, ident, None) = pat.kind {
if ident.as_str().starts_with('_') if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
&& !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
{
ident_bind_name = (&ident.name.as_str()).to_string(); ident_bind_name = (&ident.name.as_str()).to_string();
matching_wild = true; matching_wild = true;
} }
@ -1196,7 +1194,7 @@ where
let (first_sugg, msg, title); let (first_sugg, msg, title);
let span = ex.span.source_callsite(); let span = ex.span.source_callsite();
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind { if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
msg = "try"; msg = "try";
title = "you don't need to add `&` to both the expression and the patterns"; title = "you don't need to add `&` to both the expression and the patterns";
@ -1207,7 +1205,7 @@ where
} }
let remaining_suggs = pats.filter_map(|pat| { let remaining_suggs = pats.filter_map(|pat| {
if let PatKind::Ref(ref refp, _) = pat.kind { if let PatKind::Ref(refp, _) = pat.kind {
Some((pat.span, snippet(cx, refp.span, "..").to_string())) Some((pat.span, snippet(cx, refp.span, "..").to_string()))
} else { } else {
None None
@ -1367,7 +1365,7 @@ where
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
}); });
then { then {
if let Some(ref last_pat) = last_pat_opt { if let Some(last_pat) = last_pat_opt {
if !is_wild(last_pat) { if !is_wild(last_pat) {
return false; return false;
} }
@ -1829,13 +1827,13 @@ mod redundant_pattern_match {
.. ..
}) = higher::IfLet::hir(cx, expr) }) = higher::IfLet::hir(cx, expr)
{ {
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some()) find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
} }
if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind { if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind {
find_sugg_for_match(cx, expr, op, arms) find_sugg_for_match(cx, expr, op, arms);
} }
if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false) find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
} }
} }

View File

@ -28,11 +28,11 @@ declare_lint_pass!(MemForget => [MEM_FORGET]);
impl<'tcx> LateLintPass<'tcx> for MemForget { impl<'tcx> LateLintPass<'tcx> for MemForget {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Call(path_expr, args) = e.kind { if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.kind {
if let ExprKind::Path(ref qpath) = path_expr.kind { if let ExprKind::Path(ref qpath) = path_expr.kind {
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() { if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
if match_def_path(cx, def_id, &paths::MEM_FORGET) { if match_def_path(cx, def_id, &paths::MEM_FORGET) {
let forgot_ty = cx.typeck_results().expr_ty(&args[0]); let forgot_ty = cx.typeck_results().expr_ty(first_arg);
if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) { if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) {
span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type"); span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type");

View File

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::{in_macro, is_diag_trait_item, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths}; use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{in_macro, is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -194,64 +194,37 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
} }
} }
/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent" fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
/// constructor from the std library // disable lint for primitives
fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool { let expr_type = cx.typeck_results().expr_ty_adjusted(src);
let std_types_symbols = &[ if is_non_aggregate_primitive_type(expr_type) {
sym::string_type, return;
sym::vec_type, }
sym::vecdeque_type, // disable lint for Option since it is covered in another lint
sym::LinkedList, if let ExprKind::Path(q) = &src.kind {
sym::hashmap_type, if is_lang_ctor(cx, q, OptionNone) {
sym::BTreeMap, return;
sym::hashset_type,
sym::BTreeSet,
sym::BinaryHeap,
];
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
.any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
}
}
} }
} }
false if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
} span_lint_and_then(
cx,
MEM_REPLACE_WITH_DEFAULT,
expr_span,
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|diag| {
if !in_macro(expr_span) {
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { diag.span_suggestion(
if_chain! { expr_span,
if let ExprKind::Call(repl_func, _) = src.kind; "consider using",
if !in_external_macro(cx.tcx.sess, expr_span); suggestion,
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; Applicability::MachineApplicable,
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); );
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then {
span_lint_and_then(
cx,
MEM_REPLACE_WITH_DEFAULT,
expr_span,
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|diag| {
if !in_macro(expr_span) {
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
diag.span_suggestion(
expr_span,
"consider using",
suggestion,
Applicability::MachineApplicable
);
}
} }
); },
} );
} }
} }

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::is_trait_method;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -16,7 +16,10 @@ pub(super) fn check<'tcx>(
filter_arg: &'tcx hir::Expr<'_>, filter_arg: &'tcx hir::Expr<'_>,
) { ) {
// lint if caller of `.filter().next()` is an Iterator // lint if caller of `.filter().next()` is an Iterator
if is_trait_method(cx, expr, sym::Iterator) { let recv_impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])
});
if recv_impls_iterator {
let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
`.find(..)` instead"; `.find(..)` instead";
let filter_snippet = snippet(cx, filter_arg.span, ".."); let filter_snippet = snippet(cx, filter_arg.span, "..");

View File

@ -0,0 +1,213 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::{is_diag_item_method, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, adjustment::Adjust};
use rustc_span::{symbol::sym, Span, SyntaxContext};
use super::MANUAL_SPLIT_ONCE;
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
let ctxt = expr.span.ctxt();
let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id)) {
Some(x) => x,
None => return,
};
let (method_name, msg) = if method_name == "splitn" {
("split_once", "manual implementation of `split_once`")
} else {
("rsplit_once", "manual implementation of `rsplit_once`")
};
let mut app = Applicability::MachineApplicable;
let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
match usage.kind {
IterUsageKind::NextTuple => {
span_lint_and_sugg(
cx,
MANUAL_SPLIT_ONCE,
usage.span,
msg,
"try this",
format!("{}.{}({})", self_snip, method_name, pat_snip),
app,
);
},
IterUsageKind::Next => {
let self_deref = {
let adjust = cx.typeck_results().expr_adjustments(self_arg);
if adjust.is_empty() {
String::new()
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|| adjust
.iter()
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
{
format!("&{}", "*".repeat(adjust.len() - 1))
} else {
"*".repeat(adjust.len() - 2)
}
};
let sugg = if usage.unwrap_kind.is_some() {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
} else {
format!(
"Some({}.{}({}).map_or({}{}, |x| x.0))",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
};
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
},
IterUsageKind::Second => {
let access_str = match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => ".unwrap().1",
Some(UnwrapKind::QuestionMark) => "?.1",
None => ".map(|x| x.1)",
};
span_lint_and_sugg(
cx,
MANUAL_SPLIT_ONCE,
usage.span,
msg,
"try this",
format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str),
app,
);
},
}
}
enum IterUsageKind {
Next,
Second,
NextTuple,
}
enum UnwrapKind {
Unwrap,
QuestionMark,
}
struct IterUsage {
kind: IterUsageKind,
unwrap_kind: Option<UnwrapKind>,
span: Span,
}
fn parse_iter_usage(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
) -> Option<IterUsage> {
let (kind, span) = match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
(name, args)
} else {
return None;
};
let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
match (&*name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Next, e.span),
("next_tuple", []) => {
if_chain! {
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did);
if let ty::Tuple(subs) = subs.type_at(0).kind();
if subs.len() == 2;
then {
return Some(IterUsage { kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None });
} else {
return None;
}
}
},
("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
let span = if name.ident.as_str() == "nth" {
e.span
} else {
if_chain! {
if let Some((_, Node::Expr(next_expr))) = iter.next();
if let ExprKind::MethodCall(next_name, _, [_], _) = next_expr.kind;
if next_name.ident.name == sym::next;
if next_expr.span.ctxt() == ctxt;
if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
if cx.tcx.trait_of_item(next_id) == Some(iter_id);
then {
next_expr.span
} else {
return None;
}
}
};
match idx {
0 => (IterUsageKind::Next, span),
1 => (IterUsageKind::Second, span),
_ => return None,
}
} else {
return None;
}
},
_ => return None,
}
},
_ => return None,
};
let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
match e.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)),
..
},
_,
) => {
let parent_span = e.span.parent().unwrap();
if parent_span.ctxt() == ctxt {
(Some(UnwrapKind::QuestionMark), parent_span)
} else {
(None, span)
}
},
_ if e.span.ctxt() != ctxt => (None, span),
ExprKind::MethodCall(name, _, [_], _)
if name.ident.name == sym::unwrap
&& cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
.map_or(false, |id| is_diag_item_method(cx, id, sym::option_type)) =>
{
(Some(UnwrapKind::Unwrap), e.span)
},
_ => (None, span),
}
} else {
(None, span)
};
Some(IterUsage {
kind,
unwrap_kind,
span,
})
}

View File

@ -33,6 +33,7 @@ mod iter_nth_zero;
mod iter_skip_next; mod iter_skip_next;
mod iterator_step_by_zero; mod iterator_step_by_zero;
mod manual_saturating_arithmetic; mod manual_saturating_arithmetic;
mod manual_split_once;
mod manual_str_repeat; mod manual_str_repeat;
mod map_collect_result_unit; mod map_collect_result_unit;
mod map_flatten; mod map_flatten;
@ -64,6 +65,7 @@ mod wrong_self_convention;
mod zst_offset; mod zst_offset;
use bind_instead_of_map::BindInsteadOfMap; use bind_instead_of_map::BindInsteadOfMap;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item}; use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty}; use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
@ -1771,6 +1773,29 @@ declare_clippy_lint! {
"manual implementation of `str::repeat`" "manual implementation of `str::repeat`"
} }
declare_clippy_lint! {
/// **What it does:** Checks for usages of `str::splitn(2, _)`
///
/// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust,ignore
/// // Bad
/// let (key, value) = _.splitn(2, '=').next_tuple()?;
/// let value = _.splitn(2, '=').nth(1)?;
///
/// // Good
/// let (key, value) = _.split_once('=')?;
/// let value = _.split_once('=')?.1;
/// ```
pub MANUAL_SPLIT_ONCE,
complexity,
"replace `.splitn(2, pat)` with `.split_once(pat)`"
}
pub struct Methods { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>, msrv: Option<RustcVersion>,
@ -1848,7 +1873,8 @@ impl_lint_pass!(Methods => [
IMPLICIT_CLONE, IMPLICIT_CLONE,
SUSPICIOUS_SPLITN, SUSPICIOUS_SPLITN,
MANUAL_STR_REPEAT, MANUAL_STR_REPEAT,
EXTEND_WITH_DRAIN EXTEND_WITH_DRAIN,
MANUAL_SPLIT_ONCE
]); ]);
/// Extracts a method call name, args, and `Span` of the method name. /// Extracts a method call name, args, and `Span` of the method name.
@ -2176,8 +2202,18 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
} }
}, },
("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => { ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
suspicious_splitn::check(cx, name, expr, recv, count_arg); if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
manual_split_once::check(cx, name, expr, recv, pat_arg);
}
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
}
}, },
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {

View File

@ -96,9 +96,9 @@ pub(super) fn check<'tcx>(
(&paths::RESULT, true, &["or", "unwrap_or"], "else"), (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
]; ];
if let hir::ExprKind::MethodCall(path, _, args, _) = &arg.kind { if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &arg.kind {
if path.ident.name == sym::len { if path.ident.name == sym::len {
let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
match ty.kind() { match ty.kind() {
ty::Slice(_) | ty::Array(_, _) | ty::Str => return, ty::Slice(_) | ty::Array(_, _) | ty::Str => return,

View File

@ -1,4 +1,3 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_note;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::LitKind; use rustc_ast::LitKind;
@ -8,15 +7,8 @@ use rustc_span::source_map::Spanned;
use super::SUSPICIOUS_SPLITN; use super::SUSPICIOUS_SPLITN;
pub(super) fn check( pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
count_arg: &Expr<'_>,
) {
if_chain! { if_chain! {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg);
if count <= 1; if count <= 1;
if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(call_id); if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
@ -24,9 +16,9 @@ pub(super) fn check(
if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id); if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
then { then {
// Ignore empty slice and string literals when used with a literal count. // Ignore empty slice and string literals when used with a literal count.
if (matches!(self_arg.kind, ExprKind::Array([])) if matches!(self_arg.kind, ExprKind::Array([]))
|| matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty()) || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
) && matches!(count_arg.kind, ExprKind::Lit(_))
{ {
return; return;
} }

View File

@ -24,9 +24,9 @@ pub(super) fn derefs_to_slice<'tcx>(
} }
} }
if let hir::ExprKind::MethodCall(path, _, args, _) = expr.kind { if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind {
if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(&args[0])) { if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(self_arg)) {
Some(&args[0]) Some(self_arg)
} else { } else {
None None
} }

View File

@ -513,12 +513,12 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
} }
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, _, expressions, _) = expr.kind; if let ExprKind::MethodCall(method_name, _, [ref self_arg, ..], _) = expr.kind;
if sym!(signum) == method_name.ident.name; if sym!(signum) == method_name.ident.name;
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call) // the method call)
then { then {
return is_float(cx, &expressions[0]); return is_float(cx, self_arg);
} }
} }
false false

View File

@ -0,0 +1,178 @@
use std::{
ffi::OsString,
path::{Component, Path},
};
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only self named module files, bans mod.rs files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
pub MOD_MODULE_FILES,
restriction,
"checks that module layout is consistent"
}
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only mod.rs files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
pub SELF_NAMED_MODULE_FILES,
restriction,
"checks that module layout is consistent"
}
pub struct ModStyle;
impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
impl EarlyLintPass for ModStyle {
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
{
return;
}
let files = cx.sess.source_map().files();
let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess.opts.working_dir {
p.to_string_lossy()
} else {
return;
};
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
// `[path, to]` but not foo
let mut folder_segments = FxHashSet::default();
// `mod_folders` is all the unique folder names that contain a mod.rs file
let mut mod_folders = FxHashSet::default();
// `file_map` maps file names to the full path including the file name
// `{ foo => path/to/foo.rs, .. }
let mut file_map = FxHashMap::default();
for file in files.iter() {
match &file.name {
FileName::Real(RealFileName::LocalPath(lp))
if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
{
let p = lp.to_string_lossy();
let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
if let Some(stem) = path.file_stem() {
file_map.insert(stem.to_os_string(), (file, path.to_owned()));
}
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
check_self_named_mod_exists(cx, path, file);
}
_ => {},
}
}
for folder in &folder_segments {
if !mod_folders.contains(folder) {
if let Some((file, path)) = file_map.get(folder) {
let mut correct = path.clone();
correct.pop();
correct.push(folder);
correct.push("mod.rs");
cx.struct_span_lint(
SELF_NAMED_MODULE_FILES,
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
|build| {
let mut lint =
build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
lint.emit();
},
);
}
}
}
}
}
/// For each `path` we add each folder component to `folder_segments` and if the file name
/// is `mod.rs` we add it's parent folder to `mod_folders`.
fn process_paths_for_mod_files(
path: &Path,
folder_segments: &mut FxHashSet<OsString>,
mod_folders: &mut FxHashSet<OsString>,
) {
let mut comp = path.components().rev().peekable();
let _ = comp.next();
if path.ends_with("mod.rs") {
mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
}
let folders = comp
.filter_map(|c| {
if let Component::Normal(s) = c {
Some(s.to_os_string())
} else {
None
}
})
.collect::<Vec<_>>();
folder_segments.extend(folders);
}
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
if path.ends_with("mod.rs") {
let mut mod_file = path.to_path_buf();
mod_file.pop();
mod_file.set_extension("rs");
cx.struct_span_lint(
MOD_MODULE_FILES,
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
|build| {
let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
lint.emit();
},
);
}
}

View File

@ -47,9 +47,9 @@ declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]);
impl<'tcx> LateLintPass<'tcx> for MutMutexLock { impl<'tcx> LateLintPass<'tcx> for MutMutexLock {
fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, method_span, args, _) = &ex.kind; if let ExprKind::MethodCall(path, method_span, [self_arg, ..], _) = &ex.kind;
if path.ident.name == sym!(lock); if path.ident.name == sym!(lock);
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(self_arg);
if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind();
if is_type_diagnostic_item(cx, inner_ty, sym!(mutex_type)); if is_type_diagnostic_item(cx, inner_ty, sym!(mutex_type));
then { then {

View File

@ -0,0 +1,66 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_macro;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyS;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for no-op uses of Option::{as_deref,as_deref_mut},
/// for example, `Option<&T>::as_deref()` returns the same type.
///
/// ### Why is this bad?
/// Redundant code and improving readability.
///
/// ### Example
/// ```rust
/// let a = Some(&1);
/// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
/// ```
/// Could be written as:
/// ```rust
/// let a = Some(&1);
/// let b = a;
/// ```
pub NEEDLESS_OPTION_AS_DEREF,
complexity,
"no-op use of `deref` or `deref_mut` method to `Option`."
}
declare_lint_pass!(OptionNeedlessDeref=> [
NEEDLESS_OPTION_AS_DEREF,
]);
impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() || in_macro(expr.span) {
return;
}
let typeck = cx.typeck_results();
let outer_ty = typeck.expr_ty(expr);
if_chain! {
if is_type_diagnostic_item(cx,outer_ty,sym::option_type);
if let ExprKind::MethodCall(path, _, [sub_expr], _) = expr.kind;
let symbol = path.ident.as_str();
if symbol=="as_deref" || symbol=="as_deref_mut";
if TyS::same_type( outer_ty, typeck.expr_ty(sub_expr) );
then{
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_AS_DEREF,
expr.span,
"derefed type is same as origin",
"try this",
snippet_opt(cx,sub_expr.span).unwrap(),
Applicability::MachineApplicable
);
}
}
}
}

View File

@ -96,28 +96,63 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
if has_no_effect(cx, expr) { if has_no_effect(cx, expr) {
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect"); span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
} else if let Some(reduced) = reduce_expression(cx, expr) { } else if let Some(reduced) = reduce_expression(cx, expr) {
let mut snippet = String::new(); for e in &reduced {
for e in reduced {
if e.span.from_expansion() { if e.span.from_expansion() {
return; return;
} }
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
} }
span_lint_hir_and_then( if let ExprKind::Index(..) = &expr.kind {
cx, let snippet;
UNNECESSARY_OPERATION, if_chain! {
expr.hir_id, if let Some(arr) = snippet_opt(cx, reduced[0].span);
stmt.span, if let Some(func) = snippet_opt(cx, reduced[1].span);
"statement can be reduced", then {
|diag| { snippet = format!("assert!({}.len() > {});", &arr, &func);
diag.span_suggestion(stmt.span, "replace it with", snippet, Applicability::MachineApplicable); } else {
}, return;
); }
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be written as",
snippet,
Applicability::MaybeIncorrect,
);
},
);
} else {
let mut snippet = String::new();
for e in reduced {
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be reduced to",
snippet,
Applicability::MachineApplicable,
);
},
);
}
} }
} }
} }

View File

@ -31,11 +31,11 @@ declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]);
impl<'tcx> LateLintPass<'tcx> for OpenOptions { impl<'tcx> LateLintPass<'tcx> for OpenOptions {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::MethodCall(path, _, arguments, _) = e.kind { if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &e.kind {
let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs(); let obj_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) { if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) {
let mut options = Vec::new(); let mut options = Vec::new();
get_open_options(cx, &arguments[0], &mut options); get_open_options(cx, self_arg, &mut options);
check_open_options(cx, &options, e.span); check_open_options(cx, &options, e.span);
} }
} }

View File

@ -2,12 +2,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher; use clippy_utils::higher;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro; use clippy_utils::{
use clippy_utils::{eager_or_lazy, in_macro, is_else_clause, is_lang_ctor}; can_move_expr_to_closure, eager_or_lazy, in_constant, in_macro, is_else_clause, is_lang_ctor, peel_hir_expr_while,
CaptureKind,
};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionSome; use rustc_hir::LangItem::OptionSome;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, UnOp}; use rustc_hir::{def::Res, BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym; use rustc_span::sym;
@ -58,7 +60,7 @@ declare_clippy_lint! {
/// }, |foo| foo); /// }, |foo| foo);
/// ``` /// ```
pub OPTION_IF_LET_ELSE, pub OPTION_IF_LET_ELSE,
pedantic, nursery,
"reimplementation of Option::map_or" "reimplementation of Option::map_or"
} }
@ -125,20 +127,30 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurence> { fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurence> {
if_chain! { if_chain! {
if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) = higher::IfLet::hir(cx, expr); if !in_constant(cx, expr.hir_id);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if !is_else_clause(cx.tcx, expr); if !is_else_clause(cx.tcx, expr);
if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already
if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind; if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind;
if is_lang_ctor(cx, struct_qpath, OptionSome); if is_lang_ctor(cx, struct_qpath, OptionSome);
if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind; if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind;
if !contains_return_break_continue_macro(if_then); if let Some(some_captures) = can_move_expr_to_closure(cx, if_then);
if !contains_return_break_continue_macro(if_else); if let Some(none_captures) = can_move_expr_to_closure(cx, if_else);
if some_captures
.iter()
.filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2)))
.all(|(x, y)| x.is_imm_ref() && y.is_imm_ref());
then { then {
let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" };
let some_body = extract_body_from_expr(if_then)?; let some_body = extract_body_from_expr(if_then)?;
let none_body = extract_body_from_expr(if_else)?; let none_body = extract_body_from_expr(if_else)?;
let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { "map_or" } else { "map_or_else" }; let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) {
"map_or"
} else {
"map_or_else"
};
let capture_name = id.name.to_ident_string(); let capture_name = id.name.to_ident_string();
let (as_ref, as_mut) = match &let_expr.kind { let (as_ref, as_mut) = match &let_expr.kind {
ExprKind::AddrOf(_, Mutability::Not, _) => (true, false), ExprKind::AddrOf(_, Mutability::Not, _) => (true, false),
@ -150,6 +162,24 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr, ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr,
_ => let_expr, _ => let_expr,
}; };
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if as_ref || as_mut {
let e = peel_hir_expr_while(cond_expr, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
match some_captures.get(local_id)
.or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id)))
{
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
Some(OptionIfLetElseOccurence { Some(OptionIfLetElseOccurence {
option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut), option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
method_sugg: method_sugg.to_string(), method_sugg: method_sugg.to_string(),

View File

@ -118,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
} }
} }
if let ExprKind::Let(let_pat, let_expr, _) = expr.kind { if let ExprKind::Let(let_pat, let_expr, _) = expr.kind {
if let Some(ref expr_ty) = cx.typeck_results().node_type_opt(let_expr.hir_id) { if let Some(expr_ty) = cx.typeck_results().node_type_opt(let_expr.hir_id) {
if in_external_macro(cx.sess(), let_pat.span) { if in_external_macro(cx.sess(), let_pat.span) {
return; return;
} }

View File

@ -92,13 +92,13 @@ fn expr_as_ptr_offset_call<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
if let ExprKind::MethodCall(path_segment, _, args, _) = expr.kind { if let ExprKind::MethodCall(path_segment, _, [arg_0, arg_1, ..], _) = &expr.kind {
if is_expr_ty_raw_ptr(cx, &args[0]) { if is_expr_ty_raw_ptr(cx, arg_0) {
if path_segment.ident.name == sym::offset { if path_segment.ident.name == sym::offset {
return Some((&args[0], &args[1], Method::Offset)); return Some((arg_0, arg_1, Method::Offset));
} }
if path_segment.ident.name == sym!(wrapping_offset) { if path_segment.ident.name == sym!(wrapping_offset) {
return Some((&args[0], &args[1], Method::WrappingOffset)); return Some((arg_0, arg_1, Method::WrappingOffset));
} }
} }
} }

View File

@ -97,7 +97,8 @@ impl QuestionMark {
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) = higher::IfLet::hir(cx, expr); if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if Self::is_option(cx, let_expr); if Self::is_option(cx, let_expr);
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
@ -105,7 +106,7 @@ impl QuestionMark {
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind; if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Block(ref block, None) = if_then.kind; if let ExprKind::Block(block, None) = if_then.kind;
if block.stmts.is_empty(); if block.stmts.is_empty();
if let Some(trailing_expr) = &block.expr; if let Some(trailing_expr) = &block.expr;
if path_to_local_id(trailing_expr, bind_id); if path_to_local_id(trailing_expr, bind_id);

View File

@ -625,7 +625,10 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
.flat_map(HybridBitSet::iter) .flat_map(HybridBitSet::iter)
.collect(); .collect();
if ContainsRegion(self.cx.tcx).visit_ty(self.body.local_decls[*dest].ty).is_break() { if ContainsRegion(self.cx.tcx)
.visit_ty(self.body.local_decls[*dest].ty)
.is_break()
{
mutable_variables.push(*dest); mutable_variables.push(*dest);
} }

View File

@ -51,9 +51,7 @@ impl ReturnVisitor {
impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor { impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor {
fn visit_expr(&mut self, ex: &'ast ast::Expr) { fn visit_expr(&mut self, ex: &'ast ast::Expr) {
if let ast::ExprKind::Ret(_) = ex.kind { if let ast::ExprKind::Ret(_) | ast::ExprKind::Try(_) = ex.kind {
self.found_return = true;
} else if let ast::ExprKind::Try(_) = ex.kind {
self.found_return = true; self.found_return = true;
} }

View File

@ -206,13 +206,10 @@ fn check_final_expr<'tcx>(
// an if/if let expr, check both exprs // an if/if let expr, check both exprs
// note, if without else is going to be a type checking error anyways // note, if without else is going to be a type checking error anyways
// (except for unit type functions) so we don't match it // (except for unit type functions) so we don't match it
ExprKind::Match(_, arms, source) => match source { ExprKind::Match(_, arms, MatchSource::Normal) => {
MatchSource::Normal => { for arm in arms.iter() {
for arm in arms.iter() { check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Block);
check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Block); }
}
},
_ => (),
}, },
ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty), ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty),
_ => (), _ => (),

View File

@ -345,9 +345,9 @@ declare_lint_pass!(StrToString => [STR_TO_STRING]);
impl LateLintPass<'_> for StrToString { impl LateLintPass<'_> for StrToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind; if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
if path.ident.name == sym!(to_string); if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(self_arg);
if let ty::Ref(_, ty, ..) = ty.kind(); if let ty::Ref(_, ty, ..) = ty.kind();
if *ty.kind() == ty::Str; if *ty.kind() == ty::Str;
then { then {
@ -394,9 +394,9 @@ declare_lint_pass!(StringToString => [STRING_TO_STRING]);
impl LateLintPass<'_> for StringToString { impl LateLintPass<'_> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind; if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
if path.ident.name == sym!(to_string); if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(self_arg);
if is_type_diagnostic_item(cx, ty, sym::string_type); if is_type_diagnostic_item(cx, ty, sym::string_type);
then { then {
span_lint_and_help( span_lint_and_help(

View File

@ -92,11 +92,11 @@ impl LateLintPass<'_> for ToStringInDisplay {
if_chain! { if_chain! {
if self.in_display_impl; if self.in_display_impl;
if let Some(self_hir_id) = self.self_hir_id; if let Some(self_hir_id) = self.self_hir_id;
if let ExprKind::MethodCall(path, _, args, _) = expr.kind; if let ExprKind::MethodCall(path, _, [ref self_arg, ..], _) = expr.kind;
if path.ident.name == sym!(to_string); if path.ident.name == sym!(to_string);
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diag_trait_item(cx, expr_def_id, sym::ToString); if is_diag_trait_item(cx, expr_def_id, sym::ToString);
if path_to_local_id(&args[0], self_hir_id); if path_to_local_id(self_arg, self_hir_id);
then { then {
span_lint( span_lint(
cx, cx,

View File

@ -295,6 +295,7 @@ declare_clippy_lint! {
pub struct Types { pub struct Types {
vec_box_size_threshold: u64, vec_box_size_threshold: u64,
type_complexity_threshold: u64, type_complexity_threshold: u64,
avoid_breaking_exported_api: bool,
} }
impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]); impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]);
@ -308,19 +309,31 @@ impl<'tcx> LateLintPass<'tcx> for Types {
false false
}; };
let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(id));
self.check_fn_decl( self.check_fn_decl(
cx, cx,
decl, decl,
CheckTyContext { CheckTyContext {
is_in_trait_impl, is_in_trait_impl,
is_exported,
..CheckTyContext::default() ..CheckTyContext::default()
}, },
); );
} }
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let is_exported = cx.access_levels.is_exported(item.def_id);
match item.kind { match item.kind {
ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(cx, ty, CheckTyContext::default()), ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(
cx,
ty,
CheckTyContext {
is_exported,
..CheckTyContext::default()
},
),
// functions, enums, structs, impls and traits are covered // functions, enums, structs, impls and traits are covered
_ => (), _ => (),
} }
@ -342,15 +355,31 @@ impl<'tcx> LateLintPass<'tcx> for Types {
} }
fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) { fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
self.check_ty(cx, field.ty, CheckTyContext::default()); let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(field.hir_id));
self.check_ty(
cx,
field.ty,
CheckTyContext {
is_exported,
..CheckTyContext::default()
},
);
} }
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) {
let is_exported = cx.access_levels.is_exported(item.def_id);
let context = CheckTyContext {
is_exported,
..CheckTyContext::default()
};
match item.kind { match item.kind {
TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => { TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => {
self.check_ty(cx, ty, CheckTyContext::default()); self.check_ty(cx, ty, context);
}, },
TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, CheckTyContext::default()), TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, context),
TraitItemKind::Type(..) => (), TraitItemKind::Type(..) => (),
} }
} }
@ -370,10 +399,11 @@ impl<'tcx> LateLintPass<'tcx> for Types {
} }
impl Types { impl Types {
pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64) -> Self { pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64, avoid_breaking_exported_api: bool) -> Self {
Self { Self {
vec_box_size_threshold, vec_box_size_threshold,
type_complexity_threshold, type_complexity_threshold,
avoid_breaking_exported_api,
} }
} }
@ -410,17 +440,24 @@ impl Types {
let hir_id = hir_ty.hir_id; let hir_id = hir_ty.hir_id;
let res = cx.qpath_res(qpath, hir_id); let res = cx.qpath_res(qpath, hir_id);
if let Some(def_id) = res.opt_def_id() { if let Some(def_id) = res.opt_def_id() {
let mut triggered = false; if self.is_type_change_allowed(context) {
triggered |= box_vec::check(cx, hir_ty, qpath, def_id); // All lints that are being checked in this block are guarded by
triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id); // the `avoid_breaking_exported_api` configuration. When adding a
triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id); // new lint, please also add the name to the configuration documentation
triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold); // in `clippy_lints::utils::conf.rs`
triggered |= option_option::check(cx, hir_ty, qpath, def_id);
triggered |= linked_list::check(cx, hir_ty, def_id);
triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
if triggered { let mut triggered = false;
return; triggered |= box_vec::check(cx, hir_ty, qpath, def_id);
triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
triggered |= option_option::check(cx, hir_ty, qpath, def_id);
triggered |= linked_list::check(cx, hir_ty, def_id);
triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
if triggered {
return;
}
} }
} }
match *qpath { match *qpath {
@ -487,11 +524,21 @@ impl Types {
_ => {}, _ => {},
} }
} }
/// This function checks if the type is allowed to change in the current context
/// based on the `avoid_breaking_exported_api` configuration
fn is_type_change_allowed(&self, context: CheckTyContext) -> bool {
!(context.is_exported && self.avoid_breaking_exported_api)
}
} }
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
struct CheckTyContext { struct CheckTyContext {
is_in_trait_impl: bool, is_in_trait_impl: bool,
/// `true` for types on local variables.
is_local: bool, is_local: bool,
/// `true` for types that are part of the public API.
is_exported: bool,
is_nested_call: bool, is_nested_call: bool,
} }

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_ty_param_diagnostic_item; use clippy_utils::is_ty_param_diagnostic_item;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{self as hir, def_id::DefId, QPath}; use rustc_hir::{self as hir, def_id::DefId, QPath};
@ -11,13 +11,14 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
if_chain! { if_chain! {
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ; if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ;
if let Some(_) = is_ty_param_diagnostic_item(cx, qpath, sym!(mutex_type)) ; if let Some(_) = is_ty_param_diagnostic_item(cx, qpath, sym!(mutex_type)) ;
then {
then{ span_lint_and_help(
span_lint(
cx, cx,
RC_MUTEX, RC_MUTEX,
hir_ty.span, hir_ty.span,
"found `Rc<Mutex<_>>`. Consider using `Rc<RefCell<_>>` or `Arc<Mutex<_>>` instead", "usage of `Rc<Mutex<_>>`",
None,
"consider using `Rc<RefCell<_>>` or `Arc<Mutex<_>>` instead",
); );
return true; return true;
} }

View File

@ -54,7 +54,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
_ => return false, _ => return false,
}; };
let inner_span = match get_qpath_generic_tys(inner_qpath).next() { let inner_span = match get_qpath_generic_tys(inner_qpath).next() {
Some(ty) => ty.span, Some(ty) => {
// Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
if matches!(ty.kind, TyKind::TraitObject(..)) {
return false;
}
ty.span
},
None => return false, None => return false,
}; };
if inner_sym == outer_sym { if inner_sym == outer_sym {

View File

@ -37,8 +37,8 @@ declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]);
impl LateLintPass<'tcx> for UndroppedManuallyDrops { impl LateLintPass<'tcx> for UndroppedManuallyDrops {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(args) = match_function_call(cx, expr, &paths::DROP) { if let Some([arg_0, ..]) = match_function_call(cx, expr, &paths::DROP) {
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(arg_0);
if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) { if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) {
span_lint_and_help( span_lint_and_help(
cx, cx,

View File

@ -218,7 +218,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
matches!(ty.kind(), ty::Ref(..)) || ty.walk(cx.tcx).any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))) matches!(ty.kind(), ty::Ref(..))
|| ty
.walk(cx.tcx)
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
} }
impl LateLintPass<'_> for UnnecessarySortBy { impl LateLintPass<'_> for UnnecessarySortBy {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, Item, ItemKind, YieldSource}; use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, YieldSource};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -57,11 +57,6 @@ impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> {
} }
impl<'tcx> LateLintPass<'tcx> for UnusedAsync { impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Trait(..) = item.kind {
return;
}
}
fn check_fn( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,

View File

@ -45,20 +45,20 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
match expr.kind { match expr.kind {
hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => { hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
if let hir::ExprKind::Call(func, args) = res.kind { if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
if matches!( if matches!(
func.kind, func.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, _)) hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, _))
) { ) {
check_map_error(cx, &args[0], expr); check_map_error(cx, arg_0, expr);
} }
} else { } else {
check_map_error(cx, res, expr); check_map_error(cx, res, expr);
} }
}, },
hir::ExprKind::MethodCall(path, _, args, _) => match &*path.ident.as_str() { hir::ExprKind::MethodCall(path, _, [ref arg_0, ..], _) => match &*path.ident.as_str() {
"expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => { "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
check_map_error(cx, &args[0], expr); check_map_error(cx, arg_0, expr);
}, },
_ => (), _ => (),
}, },

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::visitors::LocalUsedVisitor; use clippy_utils::visitors::is_local_used;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
let body = cx.tcx.hir().body(*body_id); let body = cx.tcx.hir().body(*body_id);
if let [self_param, ..] = body.params; if let [self_param, ..] = body.params;
let self_hir_id = self_param.pat.hir_id; if !is_local_used(cx, body, self_param.pat.hir_id);
if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body);
then { then {
span_lint_and_help( span_lint_and_help(
cx, cx,

View File

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher; use clippy_utils::higher;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{differing_macro_contexts, usage::is_potentially_mutated}; use clippy_utils::{differing_macro_contexts, path_to_local, usage::is_potentially_mutated};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Path, QPath, UnOp}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -74,26 +75,61 @@ struct UnwrappableVariablesVisitor<'a, 'tcx> {
unwrappables: Vec<UnwrapInfo<'tcx>>, unwrappables: Vec<UnwrapInfo<'tcx>>,
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
} }
/// What kind of unwrappable this is.
#[derive(Copy, Clone, Debug)]
enum UnwrappableKind {
Option,
Result,
}
impl UnwrappableKind {
fn success_variant_pattern(self) -> &'static str {
match self {
UnwrappableKind::Option => "Some(..)",
UnwrappableKind::Result => "Ok(..)",
}
}
fn error_variant_pattern(self) -> &'static str {
match self {
UnwrappableKind::Option => "None",
UnwrappableKind::Result => "Err(..)",
}
}
}
/// Contains information about whether a variable can be unwrapped. /// Contains information about whether a variable can be unwrapped.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
struct UnwrapInfo<'tcx> { struct UnwrapInfo<'tcx> {
/// The variable that is checked /// The variable that is checked
ident: &'tcx Path<'tcx>, local_id: HirId,
/// The if itself
if_expr: &'tcx Expr<'tcx>,
/// The check, like `x.is_ok()` /// The check, like `x.is_ok()`
check: &'tcx Expr<'tcx>, check: &'tcx Expr<'tcx>,
/// The check's name, like `is_ok`
check_name: &'tcx PathSegment<'tcx>,
/// The branch where the check takes place, like `if x.is_ok() { .. }` /// The branch where the check takes place, like `if x.is_ok() { .. }`
branch: &'tcx Expr<'tcx>, branch: &'tcx Expr<'tcx>,
/// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`). /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
safe_to_unwrap: bool, safe_to_unwrap: bool,
/// What kind of unwrappable this is.
kind: UnwrappableKind,
/// If the check is the entire condition (`if x.is_ok()`) or only a part of it (`foo() &&
/// x.is_ok()`)
is_entire_condition: bool,
} }
/// Collects the information about unwrappable variables from an if condition /// Collects the information about unwrappable variables from an if condition
/// The `invert` argument tells us whether the condition is negated. /// The `invert` argument tells us whether the condition is negated.
fn collect_unwrap_info<'tcx>( fn collect_unwrap_info<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
if_expr: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>,
branch: &'tcx Expr<'_>, branch: &'tcx Expr<'_>,
invert: bool, invert: bool,
is_entire_condition: bool,
) -> Vec<UnwrapInfo<'tcx>> { ) -> Vec<UnwrapInfo<'tcx>> {
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool { fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::option_type) && ["is_some", "is_none"].contains(&method_name) is_type_diagnostic_item(cx, ty, sym::option_type) && ["is_some", "is_none"].contains(&method_name)
@ -106,18 +142,18 @@ fn collect_unwrap_info<'tcx>(
if let ExprKind::Binary(op, left, right) = &expr.kind { if let ExprKind::Binary(op, left, right) = &expr.kind {
match (invert, op.node) { match (invert, op.node) {
(false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => { (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
let mut unwrap_info = collect_unwrap_info(cx, left, branch, invert); let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
unwrap_info.append(&mut collect_unwrap_info(cx, right, branch, invert)); unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
return unwrap_info; return unwrap_info;
}, },
_ => (), _ => (),
} }
} else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind { } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
return collect_unwrap_info(cx, expr, branch, !invert); return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
} else { } else {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind; if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
if let ExprKind::Path(QPath::Resolved(None, path)) = &args[0].kind; if let Some(local_id) = path_to_local(&args[0]);
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(&args[0]);
let name = method_name.ident.as_str(); let name = method_name.ident.as_str();
if is_relevant_option_call(cx, ty, &name) || is_relevant_result_call(cx, ty, &name); if is_relevant_option_call(cx, ty, &name) || is_relevant_result_call(cx, ty, &name);
@ -129,7 +165,24 @@ fn collect_unwrap_info<'tcx>(
_ => unreachable!(), _ => unreachable!(),
}; };
let safe_to_unwrap = unwrappable != invert; let safe_to_unwrap = unwrappable != invert;
return vec![UnwrapInfo { ident: path, check: expr, branch, safe_to_unwrap }]; let kind = if is_type_diagnostic_item(cx, ty, sym::option_type) {
UnwrappableKind::Option
} else {
UnwrappableKind::Result
};
return vec![
UnwrapInfo {
local_id,
if_expr,
check: expr,
check_name: method_name,
branch,
safe_to_unwrap,
kind,
is_entire_condition,
}
]
} }
} }
} }
@ -137,11 +190,17 @@ fn collect_unwrap_info<'tcx>(
} }
impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> { impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
fn visit_branch(&mut self, cond: &'tcx Expr<'_>, branch: &'tcx Expr<'_>, else_branch: bool) { fn visit_branch(
&mut self,
if_expr: &'tcx Expr<'_>,
cond: &'tcx Expr<'_>,
branch: &'tcx Expr<'_>,
else_branch: bool,
) {
let prev_len = self.unwrappables.len(); let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, cond, branch, else_branch) { for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
if is_potentially_mutated(unwrap_info.ident, cond, self.cx) if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
|| is_potentially_mutated(unwrap_info.ident, branch, self.cx) || is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
{ {
// if the variable is mutated, we don't know whether it can be unwrapped: // if the variable is mutated, we don't know whether it can be unwrapped:
continue; continue;
@ -163,32 +222,62 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
} }
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) { if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
walk_expr(self, cond); walk_expr(self, cond);
self.visit_branch(cond, then, false); self.visit_branch(expr, cond, then, false);
if let Some(else_inner) = r#else { if let Some(else_inner) = r#else {
self.visit_branch(cond, else_inner, true); self.visit_branch(expr, cond, else_inner, true);
} }
} else { } else {
// find `unwrap[_err]()` calls: // find `unwrap[_err]()` calls:
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind; if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(None, path)) = args[0].kind; if let Some(id) = path_to_local(&args[0]);
if [sym::unwrap, sym!(unwrap_err)].contains(&method_name.ident.name); if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
let call_to_unwrap = method_name.ident.name == sym::unwrap; let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
if let Some(unwrappable) = self.unwrappables.iter() if let Some(unwrappable) = self.unwrappables.iter()
.find(|u| u.ident.res == path.res); .find(|u| u.local_id == id);
// Span contexts should not differ with the conditional branch // Span contexts should not differ with the conditional branch
if !differing_macro_contexts(unwrappable.branch.span, expr.span); if !differing_macro_contexts(unwrappable.branch.span, expr.span);
if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span); if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
then { then {
if call_to_unwrap == unwrappable.safe_to_unwrap { if call_to_unwrap == unwrappable.safe_to_unwrap {
let is_entire_condition = unwrappable.is_entire_condition;
let unwrappable_variable_name = self.cx.tcx.hir().name(unwrappable.local_id);
let suggested_pattern = if call_to_unwrap {
unwrappable.kind.success_variant_pattern()
} else {
unwrappable.kind.error_variant_pattern()
};
span_lint_and_then( span_lint_and_then(
self.cx, self.cx,
UNNECESSARY_UNWRAP, UNNECESSARY_UNWRAP,
expr.span, expr.span,
&format!("you checked before that `{}()` cannot fail, \ &format!(
instead of checking and unwrapping, it's better to use `if let` or `match`", "called `{}` on `{}` after checking its variant with `{}`",
method_name.ident.name), method_name.ident.name,
|diag| { diag.span_label(unwrappable.check.span, "the check is happening here"); }, unwrappable_variable_name,
unwrappable.check_name.ident.as_str(),
),
|diag| {
if is_entire_condition {
diag.span_suggestion(
unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
"try",
format!(
"if let {} = {}",
suggested_pattern,
unwrappable_variable_name,
),
// We don't track how the unwrapped value is used inside the
// block or suggest deleting the unwrap, so we can't offer a
// fixable solution.
Applicability::Unspecified,
);
} else {
diag.span_label(unwrappable.check.span, "the check is happening here");
diag.help("try using `if let` or `match`");
}
},
); );
} else { } else {
span_lint_and_then( span_lint_and_then(

View File

@ -31,9 +31,6 @@ impl TryConf {
} }
} }
/// Note that the configuration parsing currently doesn't support documentation that will
/// that spans over several lines. This will be possible with the new implementation
/// See (rust-clippy#7172)
macro_rules! define_Conf { macro_rules! define_Conf {
($( ($(
$(#[doc = $doc:literal])+ $(#[doc = $doc:literal])+
@ -130,13 +127,12 @@ macro_rules! define_Conf {
}; };
} }
// N.B., this macro is parsed by util/lintlib.py
define_Conf! { define_Conf! {
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION. /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_VEC, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
/// ///
/// Suppress lints whenever the suggested change would cause breakage for other crates. /// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true), (avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE. /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT.
/// ///
/// The minimum rust version that the project supports /// The minimum rust version that the project supports
(msrv: Option<String> = None), (msrv: Option<String> = None),

View File

@ -142,7 +142,7 @@ fn print_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, indent: usize) {
print_expr(cx, arg, indent + 1); print_expr(cx, arg, indent + 1);
} }
}, },
hir::ExprKind::Let(ref pat, ref expr, _) => { hir::ExprKind::Let(pat, expr, _) => {
print_pat(cx, pat, indent + 1); print_pat(cx, pat, indent + 1);
print_expr(cx, expr, indent + 1); print_expr(cx, expr, indent + 1);
}, },

View File

@ -1,5 +1,6 @@
use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::match_type; use clippy_utils::ty::match_type;
use clippy_utils::{ use clippy_utils::{
@ -17,8 +18,8 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{ use rustc_hir::{
BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MatchSource, MutTy, Mutability, Node, Path, Stmt, BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty,
StmtKind, Ty, TyKind, UnOp, TyKind, UnOp,
}; };
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
@ -503,10 +504,10 @@ impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
} }
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = expr.kind; if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
let fn_name = path.ident; let fn_name = path.ident;
if let Some(sugg) = self.map.get(&*fn_name.as_str()); if let Some(sugg) = self.map.get(&*fn_name.as_str());
let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if match_type(cx, ty, &paths::EARLY_CONTEXT) if match_type(cx, ty, &paths::EARLY_CONTEXT)
|| match_type(cx, ty, &paths::LATE_CONTEXT); || match_type(cx, ty, &paths::LATE_CONTEXT);
then { then {
@ -1106,16 +1107,10 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let (cond, then, els) = match expr.kind { let (cond, then, els) = if let Some(higher::IfOrIfLet { cond, r#else, then }) = higher::IfOrIfLet::hir(expr) {
ExprKind::If(cond, then, els) => (Some(cond), then, els.is_some()), (cond, then, r#else.is_some())
ExprKind::Match( } else {
_, return;
[arm, ..],
MatchSource::IfLetDesugar {
contains_else_clause: els,
},
) => (None, arm.body, els),
_ => return,
}; };
let then_block = match then.kind { let then_block = match then.kind {
ExprKind::Block(block, _) => block, ExprKind::Block(block, _) => block,
@ -1131,7 +1126,6 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
}; };
// check for `if a && b;` // check for `if a && b;`
if_chain! { if_chain! {
if let Some(cond) = cond;
if let ExprKind::Binary(op, _, _) = cond.kind; if let ExprKind::Binary(op, _, _) = cond.kind;
if op.node == BinOpKind::And; if op.node == BinOpKind::And;
if cx.sess().source_map().is_multiline(cond.span); if cx.sess().source_map().is_multiline(cond.span);
@ -1166,9 +1160,7 @@ fn check_nested_if_chains(
_ => return, _ => return,
}; };
if_chain! { if_chain! {
if matches!(tail.kind, if let Some(higher::IfOrIfLet { r#else: None, .. }) = higher::IfOrIfLet::hir(tail);
ExprKind::If(_, _, None)
| ExprKind::Match(.., MatchSource::IfLetDesugar { contains_else_clause: false }));
let sm = cx.sess().source_map(); let sm = cx.sess().source_map();
if head if head
.iter() .iter()

View File

@ -82,7 +82,7 @@ This lint has the following configuration variables:
/// `default` /// `default`
macro_rules! CONFIGURATION_VALUE_TEMPLATE { macro_rules! CONFIGURATION_VALUE_TEMPLATE {
() => { () => {
"* {name}: `{ty}`: {doc} (defaults to `{default}`)\n" "* `{name}`: `{ty}`: {doc} (defaults to `{default}`)\n"
}; };
} }
@ -786,8 +786,6 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
} }
}; };
// TODO xFrednet 2021-03-01: support function arguments?
intravisit::walk_expr(self, expr); intravisit::walk_expr(self, expr);
} }
} }

View File

@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
if_chain! { if_chain! {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind(); if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
if let ty::Slice(..) = ty.kind(); if let ty::Slice(..) = ty.kind();
if let ExprKind::AddrOf(BorrowKind::Ref, mutability, ref addressee) = expr.kind; if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
if let Some(vec_args) = higher::VecArgs::hir(cx, addressee); if let Some(vec_args) = higher::VecArgs::hir(cx, addressee);
then { then {
self.check_vec_macro(cx, &vec_args, mutability, expr.span); self.check_vec_macro(cx, &vec_args, mutability, expr.span);

View File

@ -1,15 +1,11 @@
[package] [package]
name = "clippy_utils" name = "clippy_utils"
version = "0.1.56" version = "0.1.57"
edition = "2018" edition = "2018"
publish = false publish = false
[dependencies] [dependencies]
if_chain = "1.0.0" if_chain = "1.0.0"
itertools = "0.9"
regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] }
unicode-normalization = "0.1"
rustc-semver="1.1.0" rustc-semver="1.1.0"
[features] [features]

View File

@ -21,7 +21,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}", "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| { &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
// extract just major + minor version and ignore patch versions // extract just major + minor version and ignore patch versions
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap()) format!("rust-{}", n.rsplit_once('.').unwrap().1)
}), }),
lint lint
)); ));

View File

@ -6,33 +6,38 @@ use crate::{is_expn_of, match_def_path, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind}; use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::{Block, BorrowKind, Expr, ExprKind, LoopSource, Node, Pat, StmtKind, UnOp}; use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::{sym, ExpnKind, Span, Symbol}; use rustc_span::{sym, ExpnKind, Span, Symbol};
/// The essential nodes of a desugared for loop as well as the entire span: /// The essential nodes of a desugared for loop as well as the entire span:
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`. /// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
pub struct ForLoop<'tcx> { pub struct ForLoop<'tcx> {
/// `for` loop item
pub pat: &'tcx hir::Pat<'tcx>, pub pat: &'tcx hir::Pat<'tcx>,
/// `IntoIterator` argument
pub arg: &'tcx hir::Expr<'tcx>, pub arg: &'tcx hir::Expr<'tcx>,
/// `for` loop body
pub body: &'tcx hir::Expr<'tcx>, pub body: &'tcx hir::Expr<'tcx>,
/// entire `for` loop span
pub span: Span, pub span: Span,
} }
impl<'tcx> ForLoop<'tcx> { impl<'tcx> ForLoop<'tcx> {
#[inline] #[inline]
/// Parses a desugared `for` loop
pub fn hir(expr: &Expr<'tcx>) -> Option<Self> { pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
if_chain! { if_chain! {
if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind; if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
if let Some(first_arm) = arms.get(0); if let Some(first_arm) = arms.get(0);
if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind; if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
if let Some(first_arg) = iterargs.get(0); if let Some(first_arg) = iterargs.get(0);
if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none(); if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none();
if let hir::ExprKind::Loop(ref block, ..) = first_arm.body.kind; if let hir::ExprKind::Loop(block, ..) = first_arm.body.kind;
if block.expr.is_none(); if block.expr.is_none();
if let [ _, _, ref let_stmt, ref body ] = *block.stmts; if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
if let hir::StmtKind::Local(ref local) = let_stmt.kind; if let hir::StmtKind::Local(local) = let_stmt.kind;
if let hir::StmtKind::Expr(ref body_expr) = body.kind; if let hir::StmtKind::Expr(body_expr) = body.kind;
then { then {
return Some(Self { return Some(Self {
pat: &*local.pat, pat: &*local.pat,
@ -46,14 +51,19 @@ impl<'tcx> ForLoop<'tcx> {
} }
} }
/// An `if` expression without `DropTemps`
pub struct If<'hir> { pub struct If<'hir> {
/// `if` condition
pub cond: &'hir Expr<'hir>, pub cond: &'hir Expr<'hir>,
pub r#else: Option<&'hir Expr<'hir>>, /// `if` then expression
pub then: &'hir Expr<'hir>, pub then: &'hir Expr<'hir>,
/// `else` expression
pub r#else: Option<&'hir Expr<'hir>>,
} }
impl<'hir> If<'hir> { impl<'hir> If<'hir> {
#[inline] #[inline]
/// Parses an `if` expression
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If( if let ExprKind::If(
Expr { Expr {
@ -64,21 +74,27 @@ impl<'hir> If<'hir> {
r#else, r#else,
) = expr.kind ) = expr.kind
{ {
Some(Self { cond, r#else, then }) Some(Self { cond, then, r#else })
} else { } else {
None None
} }
} }
} }
/// An `if let` expression
pub struct IfLet<'hir> { pub struct IfLet<'hir> {
/// `if let` pattern
pub let_pat: &'hir Pat<'hir>, pub let_pat: &'hir Pat<'hir>,
/// `if let` scrutinee
pub let_expr: &'hir Expr<'hir>, pub let_expr: &'hir Expr<'hir>,
/// `if let` then expression
pub if_then: &'hir Expr<'hir>, pub if_then: &'hir Expr<'hir>,
/// `if let` else expression
pub if_else: Option<&'hir Expr<'hir>>, pub if_else: Option<&'hir Expr<'hir>>,
} }
impl<'hir> IfLet<'hir> { impl<'hir> IfLet<'hir> {
/// Parses an `if let` expression
pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> { pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If( if let ExprKind::If(
Expr { Expr {
@ -92,7 +108,14 @@ impl<'hir> IfLet<'hir> {
let hir = cx.tcx.hir(); let hir = cx.tcx.hir();
let mut iter = hir.parent_iter(expr.hir_id); let mut iter = hir.parent_iter(expr.hir_id);
if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() { if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() {
if let Some((_, Node::Expr(Expr { kind: ExprKind::Loop(_, _, LoopSource::While, _), .. }))) = iter.next() { if let Some((
_,
Node::Expr(Expr {
kind: ExprKind::Loop(_, _, LoopSource::While, _),
..
}),
)) = iter.next()
{
// while loop desugar // while loop desugar
return None; return None;
} }
@ -108,14 +131,49 @@ impl<'hir> IfLet<'hir> {
} }
} }
/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
pub enum IfLetOrMatch<'hir> {
/// Any `match` expression
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
/// scrutinee, pattern, then block, else block
IfLet(
&'hir Expr<'hir>,
&'hir Pat<'hir>,
&'hir Expr<'hir>,
Option<&'hir Expr<'hir>>,
),
}
impl<'hir> IfLetOrMatch<'hir> {
/// Parses an `if let` or `match` expression
pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
match expr.kind {
ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
_ => IfLet::hir(cx, expr).map(
|IfLet {
let_expr,
let_pat,
if_then,
if_else,
}| { Self::IfLet(let_expr, let_pat, if_then, if_else) },
),
}
}
}
/// An `if` or `if let` expression
pub struct IfOrIfLet<'hir> { pub struct IfOrIfLet<'hir> {
/// `if` condition that is maybe a `let` expression
pub cond: &'hir Expr<'hir>, pub cond: &'hir Expr<'hir>,
pub r#else: Option<&'hir Expr<'hir>>, /// `if` then expression
pub then: &'hir Expr<'hir>, pub then: &'hir Expr<'hir>,
/// `else` expression
pub r#else: Option<&'hir Expr<'hir>>,
} }
impl<'hir> IfOrIfLet<'hir> { impl<'hir> IfOrIfLet<'hir> {
#[inline] #[inline]
/// Parses an `if` or `if let` expression
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If(cond, then, r#else) = expr.kind { if let ExprKind::If(cond, then, r#else) = expr.kind {
if let ExprKind::DropTemps(new_cond) = cond.kind { if let ExprKind::DropTemps(new_cond) = cond.kind {
@ -126,7 +184,7 @@ impl<'hir> IfOrIfLet<'hir> {
}); });
} }
if let ExprKind::Let(..) = cond.kind { if let ExprKind::Let(..) = cond.kind {
return Some(Self { cond, r#else, then }); return Some(Self { cond, then, r#else });
} }
} }
None None
@ -155,7 +213,7 @@ impl<'a> Range<'a> {
} }
match expr.kind { match expr.kind {
hir::ExprKind::Call(ref path, ref args) hir::ExprKind::Call(path, args)
if matches!( if matches!(
path.kind, path.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _)) hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
@ -167,7 +225,7 @@ impl<'a> Range<'a> {
limits: ast::RangeLimits::Closed, limits: ast::RangeLimits::Closed,
}) })
}, },
hir::ExprKind::Struct(ref path, ref fields, None) => match path { hir::ExprKind::Struct(path, fields, None) => match &path {
hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range { hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
start: None, start: None,
end: None, end: None,
@ -213,7 +271,7 @@ impl<'a> VecArgs<'a> {
/// from `vec!`. /// from `vec!`.
pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> { pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> {
if_chain! { if_chain! {
if let hir::ExprKind::Call(ref fun, ref args) = expr.kind; if let hir::ExprKind::Call(fun, args) = expr.kind;
if let hir::ExprKind::Path(ref qpath) = fun.kind; if let hir::ExprKind::Path(ref qpath) = fun.kind;
if is_expn_of(fun.span, "vec").is_some(); if is_expn_of(fun.span, "vec").is_some();
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
@ -225,10 +283,10 @@ impl<'a> VecArgs<'a> {
else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 { else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
// `vec![a, b, c]` case // `vec![a, b, c]` case
if_chain! { if_chain! {
if let hir::ExprKind::Box(ref boxed) = args[0].kind; if let hir::ExprKind::Box(boxed) = args[0].kind;
if let hir::ExprKind::Array(ref args) = boxed.kind; if let hir::ExprKind::Array(args) = boxed.kind;
then { then {
return Some(VecArgs::Vec(&*args)); return Some(VecArgs::Vec(args));
} }
} }
@ -247,14 +305,17 @@ impl<'a> VecArgs<'a> {
} }
} }
/// A desugared `while` loop
pub struct While<'hir> { pub struct While<'hir> {
pub if_cond: &'hir Expr<'hir>, /// `while` loop condition
pub if_then: &'hir Expr<'hir>, pub condition: &'hir Expr<'hir>,
pub if_else: Option<&'hir Expr<'hir>>, /// `while` loop body
pub body: &'hir Expr<'hir>,
} }
impl<'hir> While<'hir> { impl<'hir> While<'hir> {
#[inline] #[inline]
/// Parses a desugared `while` loop
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::Loop( if let ExprKind::Loop(
Block { Block {
@ -263,11 +324,11 @@ impl<'hir> While<'hir> {
kind: kind:
ExprKind::If( ExprKind::If(
Expr { Expr {
kind: ExprKind::DropTemps(if_cond), kind: ExprKind::DropTemps(condition),
.. ..
}, },
if_then, body,
if_else_ref, _,
), ),
.. ..
}), }),
@ -278,59 +339,53 @@ impl<'hir> While<'hir> {
_, _,
) = expr.kind ) = expr.kind
{ {
let if_else = *if_else_ref; return Some(Self { condition, body });
return Some(Self {
if_cond,
if_then,
if_else,
});
} }
None None
} }
} }
/// A desugared `while let` loop
pub struct WhileLet<'hir> { pub struct WhileLet<'hir> {
pub if_expr: &'hir Expr<'hir>, /// `while let` loop item pattern
pub let_pat: &'hir Pat<'hir>, pub let_pat: &'hir Pat<'hir>,
/// `while let` loop scrutinee
pub let_expr: &'hir Expr<'hir>, pub let_expr: &'hir Expr<'hir>,
/// `while let` loop body
pub if_then: &'hir Expr<'hir>, pub if_then: &'hir Expr<'hir>,
pub if_else: Option<&'hir Expr<'hir>>,
} }
impl<'hir> WhileLet<'hir> { impl<'hir> WhileLet<'hir> {
#[inline] #[inline]
/// Parses a desugared `while let` loop
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::Loop( if let ExprKind::Loop(
Block { Block {
expr: Some(if_expr), .. expr:
Some(Expr {
kind:
ExprKind::If(
Expr {
kind: ExprKind::Let(let_pat, let_expr, _),
..
},
if_then,
_,
),
..
}),
..
}, },
_, _,
LoopSource::While, LoopSource::While,
_, _,
) = expr.kind ) = expr.kind
{ {
if let Expr { return Some(Self {
kind: let_pat,
ExprKind::If( let_expr,
Expr { if_then,
kind: ExprKind::Let(let_pat, let_expr, _), });
..
},
if_then,
if_else_ref,
),
..
} = if_expr
{
let if_else = *if_else_ref;
return Some(Self {
if_expr,
let_pat,
let_expr,
if_then,
if_else,
});
}
} }
None None
} }
@ -532,7 +587,7 @@ pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
// } // }
// ``` // ```
if_chain! { if_chain! {
if let Some(ref expr) = local.init; if let Some(expr) = local.init;
if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind; if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
then { then {
return true; return true;

View File

@ -232,9 +232,7 @@ impl HirEqInterExpr<'_, '_, '_> {
(&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => { (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => {
self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r)) self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
}, },
(&ExprKind::Let(ref lp, ref le, _), &ExprKind::Let(ref rp, ref re, _)) => { (&ExprKind::Let(lp, le, _), &ExprKind::Let(rp, re, _)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
self.eq_pat(lp, rp) && self.eq_expr(le, re)
},
(&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node, (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
(&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => { (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => {
lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name) lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
@ -668,7 +666,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
} }
} }
}, },
ExprKind::Let(ref pat, ref expr, _) => { ExprKind::Let(pat, expr, _) => {
self.hash_expr(expr); self.hash_expr(expr);
self.hash_pat(pat); self.hash_pat(pat);
}, },

View File

@ -2,6 +2,7 @@
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(rustc_private)] #![feature(rustc_private)]
#![feature(control_flow_enum)]
#![recursion_limit = "512"] #![recursion_limit = "512"]
#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
@ -62,23 +63,27 @@ use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind}; use rustc_ast::ast::{self, Attribute, LitKind};
use rustc_data_structures::unhash::UnhashMap; use rustc_data_structures::unhash::UnhashMap;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat,
PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
}; };
use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export; use rustc_middle::hir::exports::Export;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::ty as rustc_ty; use rustc_middle::ty as rustc_ty;
use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::hygiene::{ExpnKind, MacroKind};
@ -89,7 +94,7 @@ use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::Integer; use rustc_target::abi::Integer;
use crate::consts::{constant, Constant}; use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type}; use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> { pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) { if let Ok(version) = RustcVersion::parse(msrv) {
@ -255,7 +260,17 @@ pub fn in_macro(span: Span) -> bool {
} }
pub fn is_unit_expr(expr: &Expr<'_>) -> bool { pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
matches!(expr.kind, ExprKind::Block(Block { stmts: [], expr: None, .. }, _) | ExprKind::Tup([])) matches!(
expr.kind,
ExprKind::Block(
Block {
stmts: [],
expr: None,
..
},
_
) | ExprKind::Tup([])
)
} }
/// Checks if given pattern is a wildcard (`_`) /// Checks if given pattern is a wildcard (`_`)
@ -629,12 +644,106 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
false false
} }
/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
/// constructor from the std library
fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
let std_types_symbols = &[
sym::string_type,
sym::vec_type,
sym::vecdeque_type,
sym::LinkedList,
sym::hashmap_type,
sym::BTreeMap,
sym::hashset_type,
sym::BTreeSet,
sym::BinaryHeap,
];
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
.any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
}
}
}
}
false
}
/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
/// It doesn't cover all cases, for example indirect function calls (some of std
/// functions are supported) but it is the best we have.
pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match &e.kind {
ExprKind::Lit(lit) => match lit.node {
LitKind::Bool(false) | LitKind::Int(0, _) => true,
LitKind::Str(s, _) => s.is_empty(),
_ => false,
},
ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
ExprKind::Repeat(x, _) => is_default_equivalent(cx, x),
ExprKind::Call(repl_func, _) => if_chain! {
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then {
true
}
else {
false
}
},
ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
_ => false,
}
}
/// Checks if the top level expression can be moved into a closure as is. /// Checks if the top level expression can be moved into a closure as is.
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool { /// Currently checks for:
/// * Break/Continue outside the given loop HIR ids.
/// * Yield/Return statments.
/// * Inline assembly.
/// * Usages of a field of a local where the type of the local can be partially moved.
///
/// For example, given the following function:
///
/// ```
/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) {
/// for item in iter {
/// let s = item.1;
/// if item.0 > 10 {
/// continue;
/// } else {
/// s.clear();
/// }
/// }
/// }
/// ```
///
/// When called on the expression `item.0` this will return false unless the local `item` is in the
/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it
/// isn't always safe to move into a closure when only a single field is needed.
///
/// When called on the `continue` expression this will return false unless the outer loop expression
/// is in the `loop_ids` set.
///
/// Note that this check is not recursive, so passing the `if` expression will always return true
/// even though sub-expressions might return false.
pub fn can_move_expr_to_closure_no_visit(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
loop_ids: &[HirId],
ignore_locals: &HirIdSet,
) -> bool {
match expr.kind { match expr.kind {
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
| ExprKind::Continue(Destination { target_id: Ok(id), .. }) | ExprKind::Continue(Destination { target_id: Ok(id), .. })
if jump_targets.contains(&id) => if loop_ids.contains(&id) =>
{ {
true true
}, },
@ -646,25 +755,170 @@ pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Exp
| ExprKind::LlvmInlineAsm(_) => false, | ExprKind::LlvmInlineAsm(_) => false,
// Accessing a field of a local value can only be done if the type isn't // Accessing a field of a local value can only be done if the type isn't
// partially moved. // partially moved.
ExprKind::Field(base_expr, _) ExprKind::Field(
if matches!( &Expr {
base_expr.kind, hir_id,
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. })) kind:
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) => ExprKind::Path(QPath::Resolved(
{ _,
Path {
res: Res::Local(local_id),
..
},
)),
..
},
_,
) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
// TODO: check if the local has been partially moved. Assume it has for now. // TODO: check if the local has been partially moved. Assume it has for now.
false false
} },
_ => true, _ => true,
} }
} }
/// Checks if the expression can be moved into a closure as is. /// How a local is captured by a closure
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptureKind {
Value,
Ref(Mutability),
}
impl CaptureKind {
pub fn is_imm_ref(self) -> bool {
self == Self::Ref(Mutability::Not)
}
}
impl std::ops::BitOr for CaptureKind {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
(CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
| (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
(CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
}
}
}
impl std::ops::BitOrAssign for CaptureKind {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
/// Given an expression referencing a local, determines how it would be captured in a closure.
/// Note as this will walk up to parent expressions until the capture can be determined it should
/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
/// function argument (other than a receiver).
pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
let mut capture = CaptureKind::Ref(Mutability::Not);
pat.each_binding_or_first(&mut |_, id, span, _| match cx
.typeck_results()
.extract_binding_mode(cx.sess(), id, span)
.unwrap()
{
BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
capture = CaptureKind::Value;
},
BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
capture = CaptureKind::Ref(Mutability::Mut);
},
_ => (),
});
capture
}
debug_assert!(matches!(
e.kind,
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
));
let map = cx.tcx.hir();
let mut child_id = e.hir_id;
let mut capture = CaptureKind::Value;
let mut capture_expr_ty = e;
for (parent_id, parent) in map.parent_iter(e.hir_id) {
if let [Adjustment {
kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
target,
}, ref adjust @ ..] = *cx
.typeck_results()
.adjustments()
.get(child_id)
.map_or(&[][..], |x| &**x)
{
if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
*adjust.last().map_or(target, |a| a.target).kind()
{
return CaptureKind::Ref(mutability);
}
}
match parent {
Node::Expr(e) => match e.kind {
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
return CaptureKind::Ref(Mutability::Mut);
},
ExprKind::Field(..) => {
if capture == CaptureKind::Value {
capture_expr_ty = e;
}
},
ExprKind::Let(pat, ..) => {
let mutability = match pat_capture_kind(cx, pat) {
CaptureKind::Value => Mutability::Not,
CaptureKind::Ref(m) => m,
};
return CaptureKind::Ref(mutability);
},
ExprKind::Match(_, arms, _) => {
let mut mutability = Mutability::Not;
for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
match capture {
CaptureKind::Value => break,
CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
CaptureKind::Ref(Mutability::Not) => (),
}
}
return CaptureKind::Ref(mutability);
},
_ => break,
},
Node::Local(l) => match pat_capture_kind(cx, l.pat) {
CaptureKind::Value => break,
capture @ CaptureKind::Ref(_) => return capture,
},
_ => break,
}
child_id = parent_id;
}
if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
// Copy types are never automatically captured by value.
CaptureKind::Ref(Mutability::Not)
} else {
capture
}
}
/// Checks if the expression can be moved into a closure as is. This will return a list of captures
/// if so, otherwise, `None`.
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
struct V<'cx, 'tcx> { struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>, cx: &'cx LateContext<'tcx>,
// Stack of potential break targets contained in the expression.
loops: Vec<HirId>, loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
/// Whether this expression can be turned into a closure.
allow_closure: bool, allow_closure: bool,
/// Locals which need to be captured, and whether they need to be by value, reference, or
/// mutable reference.
captures: HirIdMap<CaptureKind>,
} }
impl Visitor<'tcx> for V<'_, 'tcx> { impl Visitor<'tcx> for V<'_, 'tcx> {
type Map = ErasedMap<'tcx>; type Map = ErasedMap<'tcx>;
@ -676,24 +930,67 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
if !self.allow_closure { if !self.allow_closure {
return; return;
} }
if let ExprKind::Loop(b, ..) = e.kind {
self.loops.push(e.hir_id); match e.kind {
self.visit_block(b); ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
self.loops.pop(); if !self.locals.contains(&l) {
} else { let cap = capture_local_usage(self.cx, e);
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops); self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
walk_expr(self, e); }
},
ExprKind::Closure(..) => {
let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id();
for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
let local_id = match capture.place.base {
PlaceBase::Local(id) => id,
PlaceBase::Upvar(var) => var.var_path.hir_id,
_ => continue,
};
if !self.locals.contains(&local_id) {
let capture = match capture.info.capture_kind {
UpvarCapture::ByValue(_) => CaptureKind::Value,
UpvarCapture::ByRef(borrow) => match borrow.kind {
BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
CaptureKind::Ref(Mutability::Mut)
},
},
};
self.captures
.entry(local_id)
.and_modify(|e| *e |= capture)
.or_insert(capture);
}
}
},
ExprKind::Loop(b, ..) => {
self.loops.push(e.hir_id);
self.visit_block(b);
self.loops.pop();
},
_ => {
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
walk_expr(self, e);
},
} }
} }
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
p.each_binding_or_first(&mut |_, id, _, _| {
self.locals.insert(id);
});
}
} }
let mut v = V { let mut v = V {
cx, cx,
allow_closure: true, allow_closure: true,
loops: Vec::new(), loops: Vec::new(),
locals: HirIdSet::default(),
captures: HirIdMap::default(),
}; };
v.visit_expr(expr); v.visit_expr(expr);
v.allow_closure v.allow_closure.then(|| v.captures)
} }
/// Returns the method names and argument list of nested method call expressions that make up /// Returns the method names and argument list of nested method call expressions that make up
@ -1365,13 +1662,13 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) { while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
conds.push(&*cond); conds.push(&*cond);
if let ExprKind::Block(ref block, _) = then.kind { if let ExprKind::Block(block, _) = then.kind {
blocks.push(block); blocks.push(block);
} else { } else {
panic!("ExprKind::If node is not an ExprKind::Block"); panic!("ExprKind::If node is not an ExprKind::Block");
} }
if let Some(ref else_expr) = r#else { if let Some(else_expr) = r#else {
expr = else_expr; expr = else_expr;
} else { } else {
break; break;
@ -1708,7 +2005,7 @@ pub fn peel_hir_expr_while<'tcx>(
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
let mut remaining = count; let mut remaining = count;
let e = peel_hir_expr_while(expr, |e| match e.kind { let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => { ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
remaining -= 1; remaining -= 1;
Some(e) Some(e)
}, },
@ -1722,7 +2019,7 @@ pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>,
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
let mut count = 0; let mut count = 0;
let e = peel_hir_expr_while(expr, |e| match e.kind { let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) => { ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
count += 1; count += 1;
Some(e) Some(e)
}, },

View File

@ -13,9 +13,12 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items // names may refer to stabilized feature flags or library items
msrv_aliases! { msrv_aliases! {
1,53,0 { OR_PATTERNS } 1,53,0 { OR_PATTERNS }
1,52,0 { STR_SPLIT_ONCE }
1,50,0 { BOOL_THEN } 1,50,0 { BOOL_THEN }
1,47,0 { TAU }
1,46,0 { CONST_IF_MATCH } 1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX } 1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2 }
1,42,0 { MATCHES_MACRO } 1,42,0 { MATCHES_MACRO }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }

View File

@ -68,6 +68,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"]; pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]

View File

@ -36,9 +36,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&Ru
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate), ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate), ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate), ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
ty::PredicateKind::Coerce(_) => { ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate),
panic!("coerce predicate on function: {:#?}", predicate)
},
ty::PredicateKind::Trait(pred) => { ty::PredicateKind::Trait(pred) => {
if Some(pred.def_id()) == tcx.lang_items().sized_trait() { if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
continue; continue;

View File

@ -329,7 +329,7 @@ fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
} }
} }
// Copied from the rust standart library, and then edited /// Copied from the rust standard library, and then edited
macro_rules! forward_binop_impls_to_ref { macro_rules! forward_binop_impls_to_ref {
(impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => { (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
impl $imp<$t> for &$t { impl $imp<$t> for &$t {

View File

@ -10,7 +10,7 @@ use rustc_hir::{TyKind, Unsafety};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
use rustc_middle::ty::{self, TyCtxt, AdtDef, IntTy, Ty, TypeFoldable, UintTy}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
use rustc_span::sym; use rustc_span::sym;
use rustc_span::symbol::{Ident, Symbol}; use rustc_span::symbol::{Ident, Symbol};
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
@ -224,6 +224,13 @@ fn is_normalizable_helper<'tcx>(
result result
} }
/// Returns true iff the given type is a non aggregate primitive (a bool or char, any integer or
/// floating-point number type). For checking aggregation of primitive types (e.g. tuples and slices
/// of primitive type) see `is_recursively_primitive_type`
pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool {
matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_))
}
/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point /// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
/// number type, a str, or an array, slice, or tuple of those types). /// number type, a str, or an array, slice, or tuple of those types).
pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {

View File

@ -1,10 +1,9 @@
use crate as utils; use crate as utils;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit; use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::HirIdSet; use rustc_hir::HirIdSet;
use rustc_hir::{Expr, ExprKind, HirId, Path}; use rustc_hir::{Expr, ExprKind, HirId};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
@ -35,12 +34,8 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) ->
Some(delegate.used_mutably) Some(delegate.used_mutably)
} }
pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
if let Res::Local(id) = variable.res { mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
} else {
true
}
} }
struct MutVarsDelegate { struct MutVarsDelegate {

View File

@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use std::ops::ControlFlow;
/// returns `true` if expr contains match expr desugared from try /// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool { fn contains_try(expr: &hir::Expr<'_>) -> bool {
@ -133,62 +134,6 @@ where
} }
} }
pub struct LocalUsedVisitor<'hir> {
hir: Map<'hir>,
pub local_hir_id: HirId,
pub used: bool,
}
impl<'hir> LocalUsedVisitor<'hir> {
pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
Self {
hir: cx.tcx.hir(),
local_hir_id,
used: false,
}
}
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
visit(self, t);
std::mem::replace(&mut self.used, false)
}
pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
self.check(arm, Self::visit_arm)
}
pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
self.check(body, Self::visit_body)
}
pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
self.check(expr, Self::visit_expr)
}
pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
self.check(stmt, Self::visit_stmt)
}
}
impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
type Map = Map<'v>;
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
if self.used {
return;
}
if path_to_local_id(expr, self.local_hir_id) {
self.used = true;
} else {
walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.hir)
}
}
/// A type which can be visited. /// A type which can be visited.
pub trait Visitable<'tcx> { pub trait Visitable<'tcx> {
/// Calls the corresponding `visit_*` function on the visitor. /// Calls the corresponding `visit_*` function on the visitor.
@ -203,7 +148,22 @@ macro_rules! visitable_ref {
} }
}; };
} }
visitable_ref!(Arm, visit_arm);
visitable_ref!(Block, visit_block); visitable_ref!(Block, visit_block);
visitable_ref!(Body, visit_body);
visitable_ref!(Expr, visit_expr);
visitable_ref!(Stmt, visit_stmt);
// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
// where
// I::Item: Visitable<'tcx>,
// {
// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
// for x in self {
// x.visit(visitor);
// }
// }
// }
/// Calls the given function for each break expression. /// Calls the given function for each break expression.
pub fn visit_break_exprs<'tcx>( pub fn visit_break_exprs<'tcx>(
@ -260,3 +220,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
v.visit_expr(&cx.tcx.hir().body(body).value); v.visit_expr(&cx.tcx.hir().body(body).value);
v.found v.found
} }
/// Calls the given function for each usage of the given local.
pub fn for_each_local_usage<'tcx, B>(
cx: &LateContext<'tcx>,
visitable: impl Visitable<'tcx>,
id: HirId,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
) -> ControlFlow<B> {
struct V<'tcx, B, F> {
map: Map<'tcx>,
id: HirId,
f: F,
res: ControlFlow<B>,
}
impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.map)
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.res.is_continue() {
if path_to_local_id(e, self.id) {
self.res = (self.f)(e);
} else {
walk_expr(self, e);
}
}
}
}
let mut v = V {
map: cx.tcx.hir(),
id,
f,
res: ControlFlow::CONTINUE,
};
visitable.visit(&mut v);
v.res
}
/// Checks if the given local is used.
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
}

View File

@ -556,14 +556,15 @@ directory. Adding a configuration to a lint can be useful for thresholds or to c
behavior that can be seen as a false positive for some users. Adding a configuration is done behavior that can be seen as a false positive for some users. Adding a configuration is done
in the following steps: in the following steps:
1. Adding a new configuration entry to [clippy_utils::conf](/clippy_utils/src/conf.rs) 1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs)
like this: like this:
```rust ```rust
/// Lint: LINT_NAME. <The configuration field doc comment> /// Lint: LINT_NAME.
///
/// <The configuration field doc comment>
(configuration_ident: Type = DefaultValue), (configuration_ident: Type = DefaultValue),
``` ```
The configuration value and identifier should usually be the same. The doc comment will be The doc comment will be automatically added to the lint documentation.
automatically added to the lint documentation.
2. Adding the configuration value to the lint impl struct: 2. Adding the configuration value to the lint impl struct:
1. This first requires the definition of a lint impl struct. Lint impl structs are usually 1. This first requires the definition of a lint impl struct. Lint impl structs are usually
generated with the `declare_lint_pass!` macro. This struct needs to be defined manually generated with the `declare_lint_pass!` macro. This struct needs to be defined manually

View File

@ -11,6 +11,7 @@ You may need following tooltips to catch up with common operations.
Useful Rustc dev guide links: Useful Rustc dev guide links:
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) - [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) - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
@ -75,20 +76,21 @@ impl LateLintPass<'_> for MyStructLint {
# Checking if a type implements a specific trait # Checking if a type implements a specific trait
There are two ways to do this, depending if the target trait is part of lang items. There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
```rust ```rust
use clippy_utils::{implements_trait, match_trait_method, paths}; use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
use rustc_span::symbol::sym;
impl LateLintPass<'_> for MyStructLint { impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// 1. Using expression and Clippy's convenient method // 1. Using diagnostic items with the expression
// we use `match_trait_method` function from Clippy's toolbox // we use `is_trait_method` function from Clippy's utils
if match_trait_method(cx, expr, &paths::INTO) { if is_trait_method(cx, expr, sym::Iterator) {
// `expr` implements `Into` trait // method call in `expr` belongs to `Iterator` trait
} }
// 2. Using type context `TyCtxt` // 2. Using lang items with the expression type
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if cx.tcx.lang_items() if cx.tcx.lang_items()
// we are looking for the `DefId` of `Drop` trait in lang items // we are looking for the `DefId` of `Drop` trait in lang items
@ -97,15 +99,20 @@ impl LateLintPass<'_> for MyStructLint {
.map_or(false, |id| implements_trait(cx, ty, id, &[])) { .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
// `expr` implements `Drop` trait // `expr` implements `Drop` trait
} }
// 3. Using the type path with the expression
// we use `match_trait_method` function from Clippy's utils
if match_trait_method(cx, expr, &paths::INTO) {
// `expr` implements `Into` trait
}
} }
} }
``` ```
> Prefer using lang items, if the target trait is available there. > Prefer using diagnostic and lang items, if the target trait has one.
A list of defined paths for Clippy can be found in [paths.rs][paths]
We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
A list of defined paths for Clippy can be found in [paths.rs][paths]
# Checking if a type defines a specific method # Checking if a type defines a specific method

View File

@ -19,6 +19,7 @@ serde_json = {version = "1.0"}
tar = {version = "0.4.30"} tar = {version = "0.4.30"}
toml = {version = "0.5"} toml = {version = "0.5"}
ureq = {version = "2.0.0-rc3"} ureq = {version = "2.0.0-rc3"}
walkdir = {version = "2.3.2"}
[features] [features]
deny-warnings = [] deny-warnings = []

View File

@ -21,6 +21,7 @@ use clap::{App, Arg, ArgMatches};
use rayon::prelude::*; use rayon::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use walkdir::{DirEntry, WalkDir};
#[cfg(not(windows))] #[cfg(not(windows))]
const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver"; const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
@ -193,32 +194,41 @@ impl CrateSource {
} }
}, },
CrateSource::Path { name, path, options } => { CrateSource::Path { name, path, options } => {
use fs_extra::dir; // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
// The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
// as a result of this filter.
let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
if dest_crate_root.exists() {
println!("Deleting existing directory at {:?}", dest_crate_root);
std::fs::remove_dir_all(&dest_crate_root).unwrap();
}
// simply copy the entire directory into our target dir println!("Copying {:?} to {:?}", path, dest_crate_root);
let copy_dest = PathBuf::from(format!("{}/", LINTCHECK_SOURCES));
// the source path of the crate we copied, ${copy_dest}/crate_name fn is_cache_dir(entry: &DirEntry) -> bool {
let crate_root = copy_dest.join(name); // .../crates/local_crate std::fs::read(entry.path().join("CACHEDIR.TAG"))
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
.unwrap_or(false)
}
if crate_root.exists() { for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
println!( let entry = entry.unwrap();
"Not copying {} to {}, destination already exists", let entry_path = entry.path();
path.display(), let relative_entry_path = entry_path.strip_prefix(path).unwrap();
crate_root.display() let dest_path = dest_crate_root.join(relative_entry_path);
); let metadata = entry_path.symlink_metadata().unwrap();
} else {
println!("Copying {} to {}", path.display(), copy_dest.display());
dir::copy(path, &copy_dest, &dir::CopyOptions::new()).unwrap_or_else(|_| { if metadata.is_dir() {
panic!("Failed to copy from {}, to {}", path.display(), crate_root.display()) std::fs::create_dir(dest_path).unwrap();
}); } else if metadata.is_file() {
std::fs::copy(entry_path, dest_path).unwrap();
}
} }
Crate { Crate {
version: String::from("local"), version: String::from("local"),
name: name.clone(), name: name.clone(),
path: crate_root, path: dest_crate_root,
options: options.clone(), options: options.clone(),
} }
}, },

View File

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2021-08-12" channel = "nightly-2021-09-08"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"] components = ["llvm-tools-preview", "rustc-dev", "rust-src"]

View File

@ -1,10 +1,12 @@
#![feature(test)] // compiletest_rs requires this attribute #![feature(test)] // compiletest_rs requires this attribute
#![feature(once_cell)] #![feature(once_cell)]
#![feature(try_blocks)] #![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn(rust_2018_idioms, unused_lifetimes)]
use compiletest_rs as compiletest; use compiletest_rs as compiletest;
use compiletest_rs::common::Mode as TestMode; use compiletest_rs::common::Mode as TestMode;
use std::collections::HashMap;
use std::env::{self, remove_var, set_var, var_os}; use std::env::{self, remove_var, set_var, var_os};
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs; use std::fs;
@ -16,6 +18,34 @@ mod cargo;
// whether to run internal tests or not // whether to run internal tests or not
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints"); const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
/// All crates used in UI tests are listed here
static TEST_DEPENDENCIES: &[&str] = &[
"clippy_utils",
"derive_new",
"if_chain",
"itertools",
"quote",
"regex",
"serde",
"serde_derive",
"syn",
];
// Test dependencies may need an `extern crate` here to ensure that they show up
// in the depinfo file (otherwise cargo thinks they are unused)
#[allow(unused_extern_crates)]
extern crate clippy_utils;
#[allow(unused_extern_crates)]
extern crate derive_new;
#[allow(unused_extern_crates)]
extern crate if_chain;
#[allow(unused_extern_crates)]
extern crate itertools;
#[allow(unused_extern_crates)]
extern crate quote;
#[allow(unused_extern_crates)]
extern crate syn;
fn host_lib() -> PathBuf { fn host_lib() -> PathBuf {
option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from) option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
} }
@ -24,71 +54,58 @@ fn clippy_driver_path() -> PathBuf {
option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from) option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
} }
// When we'll want to use `extern crate ..` for a dependency that is used /// Produces a string with an `--extern` flag for all UI test crate
// both by the crate and the compiler itself, we can't simply pass -L flags /// dependencies.
// as we'll get a duplicate matching versions. Instead, disambiguate with ///
// `--extern dep=path`. /// The dependency files are located by parsing the depinfo file for this test
// See https://github.com/rust-lang/rust-clippy/issues/4015. /// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
// /// dependencies must be added to Cargo.toml at the project root. Test
// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files. /// dependencies that are not *directly* used by this test module require an
// Because it would force-rebuild if the options passed to `build` command is not the same /// `extern crate` declaration.
// as what we manually pass to `cargo` invocation fn extern_flags() -> String {
fn third_party_crates() -> String { let current_exe_depinfo = {
use std::collections::HashMap; let mut path = env::current_exe().unwrap();
static CRATES: &[&str] = &[ path.set_extension("d");
"clippy_lints", std::fs::read_to_string(path).unwrap()
"clippy_utils", };
"if_chain", let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
"quote", for line in current_exe_depinfo.lines() {
"regex", // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
"serde", let parse_name_path = || {
"serde_derive", if line.starts_with(char::is_whitespace) {
"syn", return None;
]; }
let dep_dir = cargo::TARGET_LIB.join("deps"); let path_str = line.strip_suffix(':')?;
let mut crates: HashMap<&str, Vec<PathBuf>> = HashMap::with_capacity(CRATES.len()); let path = Path::new(path_str);
let mut flags = String::new(); if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
for entry in fs::read_dir(dep_dir).unwrap().flatten() { return None;
let path = entry.path(); }
if let Some(name) = try { let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
let name = path.file_name()?.to_str()?; // the "lib" prefix is not present for dll files
let (name, _) = name.strip_suffix(".rlib")?.strip_prefix("lib")?.split_once('-')?; let name = name.strip_prefix("lib").unwrap_or(name);
CRATES.iter().copied().find(|&c| c == name)? Some((name, path_str))
} { };
flags += &format!(" --extern {}={}", name, path.display()); if let Some((name, path)) = parse_name_path() {
crates.entry(name).or_default().push(path.clone()); if TEST_DEPENDENCIES.contains(&name) {
// A dependency may be listed twice if it is available in sysroot,
// and the sysroot dependencies are listed first. As of the writing,
// this only seems to apply to if_chain.
crates.insert(name, path);
}
} }
} }
crates.retain(|_, paths| paths.len() > 1); let not_found: Vec<&str> = TEST_DEPENDENCIES
if !crates.is_empty() { .iter()
let crate_names = crates.keys().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", "); .copied()
// add backslashes for an easy copy-paste `rm` command .filter(|n| !crates.contains_key(n))
let paths = crates .collect();
.into_values() if !not_found.is_empty() {
.flatten() panic!("dependencies not found in depinfo: {:?}", not_found);
.map(|p| strip_current_dir(&p).display().to_string())
.collect::<Vec<_>>()
.join(" \\\n");
// Check which action should be done in order to remove compiled deps.
// If pre-installed version of compiler is used, `cargo clean` will do.
// Otherwise (for bootstrapped compiler), the dependencies directory
// must be removed manually.
let suggested_action = if std::env::var_os("RUSTC_BOOTSTRAP").is_some() {
"removing the stageN-tools directory"
} else {
"running `cargo clean`"
};
panic!(
"\n----------------------------------------------------------------------\n\
ERROR: Found multiple rlibs for crates: {}\n\
Try {} or remove the following files:\n\n{}\n\n\
For details on this error see https://github.com/rust-lang/rust-clippy/issues/7343\n\
----------------------------------------------------------------------\n",
crate_names, suggested_action, paths
);
} }
flags crates
.into_iter()
.map(|(name, path)| format!("--extern {}={} ", name, path))
.collect()
} }
fn default_config() -> compiletest::Config { fn default_config() -> compiletest::Config {
@ -104,11 +121,14 @@ fn default_config() -> compiletest::Config {
config.compile_lib_path = path; config.compile_lib_path = path;
} }
// Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
// This is valuable because a) it allows us to monitor what external dependencies are used
// and b) it ensures that conflicting rlibs are resolved properly.
config.target_rustcflags = Some(format!( config.target_rustcflags = Some(format!(
"--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}", "--emit=metadata -L dependency={} -L dependency={} -Dwarnings -Zui-testing {}",
host_lib().join("deps").display(), host_lib().join("deps").display(),
cargo::TARGET_LIB.join("deps").display(), cargo::TARGET_LIB.join("deps").display(),
third_party_crates(), extern_flags(),
)); ));
config.build_base = host_lib().join("test_build_base"); config.build_base = host_lib().join("test_build_base");
@ -315,12 +335,3 @@ impl Drop for VarGuard {
} }
} }
} }
fn strip_current_dir(path: &Path) -> &Path {
if let Ok(curr) = env::current_dir() {
if let Ok(stripped) = path.strip_prefix(curr) {
return stripped;
}
}
path
}

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