mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-07 12:33:14 +00:00
Merge commit '27afd6ade4bb1123a8bf82001629b69d23d62aff' into clippyup
This commit is contained in:
parent
f7aaa2a200
commit
091ed44b50
@ -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"]
|
||||||
|
14
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
14
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
@ -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
|
||||||
|
-->
|
||||||
|
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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>
|
|
||||||
|
22
.github/ISSUE_TEMPLATE/false_negative.md
vendored
22
.github/ISSUE_TEMPLATE/false_negative.md
vendored
@ -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
|
||||||
```
|
```
|
||||||
|
31
.github/ISSUE_TEMPLATE/false_positive.md
vendored
31
.github/ISSUE_TEMPLATE/false_positive.md
vendored
@ -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
|
||||||
|
-->
|
||||||
|
21
.github/ISSUE_TEMPLATE/ice.md
vendored
21
.github/ISSUE_TEMPLATE/ice.md
vendored
@ -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
|
||||||
|
|
||||||
|
3
.github/workflows/clippy_dev.yml
vendored
3
.github/workflows/clippy_dev.yml
vendored
@ -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
|
||||||
|
|
||||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -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
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@ -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" }
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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(|| {
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
clippy_lints/src/derivable_impls.rs
Normal file
108
clippy_lints/src/derivable_impls.rs
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)]
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
164
clippy_lints/src/feature_name.rs
Normal file
164
clippy_lints/src/feature_name.rs
Normal 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);
|
||||||
|
}
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, "..");
|
||||||
|
213
clippy_lints/src/methods/manual_split_once.rs
Normal file
213
clippy_lints/src/methods/manual_split_once.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
@ -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", []) => {
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
178
clippy_lints/src/module_style.rs
Normal file
178
clippy_lints/src/module_style.rs
Normal 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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
66
clippy_lints/src/needless_option_as_deref.rs
Normal file
66
clippy_lints/src/needless_option_as_deref.rs
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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>,
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
));
|
));
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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 }
|
||||||
|
@ -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")]
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 = []
|
||||||
|
@ -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, ©_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(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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"]
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user