Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2023-06-29 19:48:06 +02:00
commit 8010c3462d
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
901 changed files with 20724 additions and 5605 deletions

View file

@ -1,5 +1,7 @@
[alias]
uitest = "test --test compile-test"
uibless = "test --test compile-test -- -- --bless"
bless = "test -- -- --bless"
dev = "run --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --"
lintcheck = "run --package lintcheck --bin lintcheck --manifest-path lintcheck/Cargo.toml -- "
collect-metadata = "test --test dogfood --features internal -- run_metadata_collection_lint --ignored"

View file

@ -25,7 +25,6 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
jobs:
base:

View file

@ -11,7 +11,6 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
defaults:
run:

View file

@ -16,7 +16,6 @@ on:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
jobs:
clippy_dev:

View file

@ -10,9 +10,9 @@ document.
## Rust 1.70
Current beta, released 2023-06-01
Current stable, released 2023-06-01
[149392b0...83e42a23](https://github.com/rust-lang/rust-clippy/compare/149392b0...83e42a23)
[**View 85 PRs merged since 1.69**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2023-04-20..2023-06-01+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -95,7 +95,7 @@ Current beta, released 2023-06-01
[#10375](https://github.com/rust-lang/rust-clippy/pull/10375)
* [`mem_replace_option_with_none`]: No longer lints on field expressions
[#10594](https://github.com/rust-lang/rust-clippy/pull/10594)
* [`items_after_statements`]: No longer lints on times from macros
* [`items_after_statements`]: No longer lints on items from macros
[#10542](https://github.com/rust-lang/rust-clippy/pull/10542)
* [`print_literal`], [`write_literal`]: No longer lint strings coming from the `file!()` macro
[#10573](https://github.com/rust-lang/rust-clippy/pull/10573)
@ -135,9 +135,9 @@ Current beta, released 2023-06-01
## Rust 1.69
Current stable, released 2023-04-20
Released 2023-04-20
[7f27e2e7...149392b0](https://github.com/rust-lang/rust-clippy/compare/7f27e2e7...149392b0)
[**View 86 PRs merged since 1.68**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2023-03-09..2023-04-20+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -252,7 +252,7 @@ Current stable, released 2023-04-20
Released 2023-03-09
[d822110d...7f27e2e7](https://github.com/rust-lang/rust-clippy/compare/d822110d...7f27e2e7)
[**View 85 PRs merged since 1.67**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2023-01-26..2023-03-09+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -399,7 +399,7 @@ Released 2023-03-09
Released 2023-01-26
[4f142aa1...d822110d](https://github.com/rust-lang/rust-clippy/compare/4f142aa1...d822110d)
[**View 68 PRs merged since 1.66**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-12-15..2023-01-26+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -590,7 +590,8 @@ Released 2023-01-26
Released 2022-12-15
[b52fb523...4f142aa1](https://github.com/rust-lang/rust-clippy/compare/b52fb523...4f142aa1)
[**View 93 PRs merged since 1.65**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-11-03..2022-12-15+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -761,7 +762,8 @@ Released 2022-12-15
Released 2022-11-03
[3c7e7dbc...b52fb523](https://github.com/rust-lang/rust-clippy/compare/3c7e7dbc...b52fb523)
[**View 129 PRs merged since 1.64**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-09-22..2022-11-03+base%3Amaster+sort%3Amerged-desc+)
### Important Changes
@ -905,7 +907,8 @@ Released 2022-11-03
Released 2022-09-22
[d7b5cbf0...3c7e7dbc](https://github.com/rust-lang/rust-clippy/compare/d7b5cbf0...3c7e7dbc)
[**View 92 PRs merged since 1.63**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-08-11..2022-09-22+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1055,7 +1058,8 @@ Released 2022-09-22
Released 2022-08-11
[7c21f91b...d7b5cbf0](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...d7b5cbf0)
[**View 100 PRs merged since 1.62**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-06-30..2022-08-11+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1201,7 +1205,8 @@ Released 2022-08-11
Released 2022-06-30
[d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b)
[**View 104 PRs merged since 1.61**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-05-19..2022-06-30+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1358,7 +1363,8 @@ Released 2022-06-30
Released 2022-05-19
[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
[**View 93 PRs merged since 1.60**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-04-07..2022-05-19+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1459,7 +1465,8 @@ Released 2022-05-19
Released 2022-04-07
[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
[**View 75 PRs merged since 1.59**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-02-24..2022-04-07+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1591,7 +1598,8 @@ Released 2022-04-07
Released 2022-02-24
[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
[**View 63 PRs merged since 1.58**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-01-13..2022-02-24+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -1755,7 +1763,8 @@ Released 2022-02-24
Released 2022-01-13
[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
[**View 73 PRs merged since 1.57**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-12-02..2022-01-13+base%3Amaster+sort%3Amerged-desc+)
### Rust 1.58.1
@ -1876,7 +1885,8 @@ Released 2022-01-13
Released 2021-12-02
[7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa)
[**View 92 PRs merged since 1.56**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-10-21..2021-12-02+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2027,7 +2037,7 @@ Released 2021-12-02
Released 2021-10-21
[74d1561...7bfc26e](https://github.com/rust-lang/rust-clippy/compare/74d1561...7bfc26e)
[**View 92 PRs merged since 1.55**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-09-09..2021-10-21+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2093,7 +2103,7 @@ Released 2021-10-21
Released 2021-09-09
[3ae8faf...74d1561](https://github.com/rust-lang/rust-clippy/compare/3ae8faf...74d1561)
[**View 61 PRs merged since 1.54**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-07-29..2021-09-09+base%3Amaster+sort%3Amerged-desc+)
### Important Changes
@ -2211,7 +2221,8 @@ Released 2021-09-09
Released 2021-07-29
[7c7683c...3ae8faf](https://github.com/rust-lang/rust-clippy/compare/7c7683c...3ae8faf)
[**View 68 PRs merged since 1.53**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-06-17..2021-07-29+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2339,7 +2350,7 @@ Released 2021-07-29
Released 2021-06-17
[6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c)
[**View 80 PRs merged since 1.52**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-05-06..2021-06-17+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2523,7 +2534,8 @@ Released 2021-06-17
Released 2021-05-06
[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
[**View 113 PRs merged since 1.51**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-03-25..2021-05-06+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2658,7 +2670,8 @@ Released 2021-05-06
Released 2021-03-25
[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
[**View 117 PRs merged since 1.50**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2021-02-11..2021-03-25+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2773,7 +2786,8 @@ Released 2021-03-25
Released 2021-02-11
[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
[**View 90 PRs merged since 1.49**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-12-31..2021-02-11+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -2902,7 +2916,8 @@ Released 2021-02-11
Released 2020-12-31
[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1)
[**View 85 PRs merged since 1.48**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-11-19..2020-12-31+base%3Amaster+sort%3Amerged-desc+)
### New Lints
@ -3008,7 +3023,7 @@ Released 2020-12-31
Released 2020-11-19
[09bd400...e636b88](https://github.com/rust-lang/rust-clippy/compare/09bd400...e636b88)
[**View 112 PRs merged since 1.47**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-10-08..2020-11-19+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3126,7 +3141,8 @@ Released 2020-11-19
Released 2020-10-08
[c2c07fa...09bd400](https://github.com/rust-lang/rust-clippy/compare/c2c07fa...09bd400)
[**View 80 PRs merged since 1.46**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-08-27..2020-10-08+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3228,7 +3244,8 @@ Released 2020-10-08
Released 2020-08-27
[7ea7cd1...c2c07fa](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...c2c07fa)
[**View 93 PRs merged since 1.45**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-07-16..2020-08-27+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3290,7 +3307,8 @@ Released 2020-08-27
Released 2020-07-16
[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1)
[**View 65 PRs merged since 1.44**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-06-04..2020-07-16+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3367,7 +3385,8 @@ and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/565
Released 2020-06-04
[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8)
[**View 88 PRs merged since 1.43**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-04-23..2020-06-04+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3450,7 +3469,8 @@ Released 2020-06-04
Released 2020-04-23
[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b)
[**View 121 PRs merged since 1.42**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-03-12..2020-04-23+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3508,7 +3528,7 @@ Released 2020-04-23
Released 2020-03-12
[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206)
[**View 106 PRs merged since 1.41**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2020-01-30..2020-03-12+base%3Amaster+sort%3Amerged-desc+)
### New lints
@ -3575,7 +3595,7 @@ Released 2020-03-12
Released 2020-01-30
[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7)
[**View 107 PRs merged since 1.40**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-12-19..2020-01-30+base%3Amaster+sort%3Amerged-desc+)
* New Lints:
* [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697)
@ -3620,7 +3640,8 @@ Released 2020-01-30
Released 2019-12-19
[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb)
[**View 69 😺 PRs merged since 1.39**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-11-07..2019-12-19+base%3Amaster+sort%3Amerged-desc+)
* New Lints:
* [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537)
@ -3662,7 +3683,7 @@ Released 2019-12-19
Released 2019-11-07
[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b)
[**View 84 PRs merged since 1.38**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-09-26..2019-11-07+base%3Amaster+sort%3Amerged-desc+)
* New Lints:
* [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479)
@ -3706,7 +3727,7 @@ Released 2019-11-07
Released 2019-09-26
[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860)
[**View 102 PRs merged since 1.37**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-08-15..2019-09-26+base%3Amaster+sort%3Amerged-desc+)
* New Lints:
* [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203)
@ -3736,7 +3757,7 @@ Released 2019-09-26
Released 2019-08-15
[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e)
[**View 83 PRs merged since 1.36**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-07-04..2019-08-15+base%3Amaster+sort%3Amerged-desc+)
* New Lints:
* [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088)
@ -3760,7 +3781,8 @@ Released 2019-08-15
Released 2019-07-04
[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7)
[**View 75 PRs merged since 1.35**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-05-20..2019-07-04+base%3Amaster+sort%3Amerged-desc+)
* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039)
* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954)
@ -3791,7 +3813,8 @@ Released 2019-07-04
Released 2019-05-20
[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e)
[**View 90 PRs merged since 1.34**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-04-10..2019-05-20+base%3Amaster+sort%3Amerged-desc+)
* New lint: `drop_bounds` to detect `T: Drop` bounds
* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
@ -3819,7 +3842,8 @@ Released 2019-05-20
Released 2019-04-10
[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380)
[**View 66 PRs merged since 1.33**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-02-26..2019-04-10+base%3Amaster+sort%3Amerged-desc+)
* New lint: [`assertions_on_constants`] to detect for example `assert!(true)`
* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro
@ -3849,7 +3873,7 @@ Released 2019-04-10
Released 2019-02-26
[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724)
[**View 83 PRs merged since 1.32**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2019-01-17..2019-02-26+base%3Amaster+sort%3Amerged-desc+)
* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`]
* The `rust-clippy` repository is now part of the `rust-lang` org.
@ -3882,7 +3906,7 @@ Released 2019-02-26
Released 2019-01-17
[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be)
[**View 106 PRs merged since 1.31**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2018-12-06..2019-01-17+base%3Amaster+sort%3Amerged-desc+)
* New lints: [`slow_vector_initialization`], `mem_discriminant_non_enum`,
[`redundant_clone`], [`wildcard_dependencies`],
@ -3912,7 +3936,8 @@ Released 2019-01-17
Released 2018-12-06
[125907ad..2e26fdc2](https://github.com/rust-lang/rust-clippy/compare/125907ad..2e26fdc2)
[**View 85 PRs merged since 1.30**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2018-10-25..2018-12-06+base%3Amaster+sort%3Amerged-desc+)
* Clippy has been relicensed under a dual MIT / Apache license.
See [#3093](https://github.com/rust-lang/rust-clippy/issues/3093) for more
@ -3952,7 +3977,8 @@ Released 2018-12-06
Released 2018-10-25
[14207503...125907ad](https://github.com/rust-lang/rust-clippy/compare/14207503...125907ad)
[**View 106 PRs merged since 1.29**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2018-09-13..2018-10-25+base%3Amaster+sort%3Amerged-desc+)
* Deprecate `assign_ops` lint
* New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`],
@ -4628,6 +4654,7 @@ Released 2018-09-13
[`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
[`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
[`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut
@ -4641,6 +4668,7 @@ Released 2018-09-13
[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
[`big_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#big_endian_bytes
[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
@ -4735,6 +4763,7 @@ Released 2018-09-13
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
[`drain_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#drain_collect
[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
@ -4757,6 +4786,7 @@ Released 2018-09-13
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
[`excessive_nesting`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
@ -4811,6 +4841,7 @@ Released 2018-09-13
[`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
[`host_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#host_endian_bytes
[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
@ -4829,6 +4860,7 @@ Released 2018-09-13
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type
[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
@ -4879,6 +4911,7 @@ Released 2018-09-13
[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
[`large_stack_frames`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_frames
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
@ -4892,6 +4925,7 @@ Released 2018-09-13
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
@ -4915,6 +4949,7 @@ Released 2018-09-13
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`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_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
@ -4947,11 +4982,13 @@ Released 2018-09-13
[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
[`maybe_misused_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_misused_cfg
[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default
[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit
[`min_ident_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars
[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
@ -5002,6 +5039,7 @@ Released 2018-09-13
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
[`needless_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_else
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_if
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
@ -5009,8 +5047,11 @@ Released 2018-09-13
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_pub_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pub_self
[`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_raw_string_hashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string_hashes
[`needless_raw_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_strings
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
@ -5081,6 +5122,8 @@ Released 2018-09-13
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
[`pub_with_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_with_shorthand
[`pub_without_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_without_shorthand
[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
[`question_mark_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark_used
[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
@ -5106,6 +5149,7 @@ Released 2018-09-13
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
@ -5146,6 +5190,7 @@ Released 2018-09-13
[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
[`significant_drop_tightening`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_tightening
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
[`single_call_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn
[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
[`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
@ -5154,6 +5199,7 @@ Released 2018-09-13
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`single_range_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_range_in_vec_init
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
@ -5238,6 +5284,7 @@ Released 2018-09-13
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
@ -5311,3 +5358,65 @@ Released 2018-09-13
[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
<!-- end autogenerated links to lint list -->
<!-- begin autogenerated links to configuration documentation -->
[`arithmetic-side-effects-allowed`]: https://doc.rust-lang.org/clippy/lint_configuration.html#arithmetic-side-effects-allowed
[`arithmetic-side-effects-allowed-binary`]: https://doc.rust-lang.org/clippy/lint_configuration.html#arithmetic-side-effects-allowed-binary
[`arithmetic-side-effects-allowed-unary`]: https://doc.rust-lang.org/clippy/lint_configuration.html#arithmetic-side-effects-allowed-unary
[`avoid-breaking-exported-api`]: https://doc.rust-lang.org/clippy/lint_configuration.html#avoid-breaking-exported-api
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
[`excessive-nesting-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#excessive-nesting-threshold
[`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names
[`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline
[`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline
[`doc-valid-idents`]: https://doc.rust-lang.org/clippy/lint_configuration.html#doc-valid-idents
[`too-many-arguments-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-arguments-threshold
[`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold
[`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold
[`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack
[`enum-variant-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-name-threshold
[`enum-variant-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-size-threshold
[`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
[`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
[`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold
[`array-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#array-size-threshold
[`stack-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#stack-size-threshold
[`vec-box-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#vec-box-size-threshold
[`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds
[`max-struct-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-struct-bools
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools
[`warn-on-all-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-on-all-wildcard-imports
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
[`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods
[`disallowed-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types
[`unreadable-literal-lint-fractions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unreadable-literal-lint-fractions
[`upper-case-acronyms-aggressive`]: https://doc.rust-lang.org/clippy/lint_configuration.html#upper-case-acronyms-aggressive
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
[`cargo-ignore-publish`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cargo-ignore-publish
[`standard-macro-braces`]: https://doc.rust-lang.org/clippy/lint_configuration.html#standard-macro-braces
[`enforced-import-renames`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforced-import-renames
[`allowed-scripts`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-scripts
[`enable-raw-pointer-heuristic-for-send`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enable-raw-pointer-heuristic-for-send
[`max-suggested-slice-pattern-length`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-suggested-slice-pattern-length
[`await-holding-invalid-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#await-holding-invalid-types
[`max-include-file-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-include-file-size
[`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests
[`allow-unwrap-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-unwrap-in-tests
[`allow-dbg-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-dbg-in-tests
[`allow-print-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-print-in-tests
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
[`allow-mixed-uninlined-format-args`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-mixed-uninlined-format-args
[`suppress-restriction-lint-in-const`]: https://doc.rust-lang.org/clippy/lint_configuration.html#suppress-restriction-lint-in-const
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
[`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size
[`allow-private-module-inception`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-private-module-inception
[`allowed-idents-below-min-chars`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-idents-below-min-chars
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
[`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement
[`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
<!-- end autogenerated links to configuration documentation -->

View file

@ -27,27 +27,14 @@ tempfile = { version = "3.2", optional = true }
termize = "0.1"
[dev-dependencies]
compiletest_rs = { version = "0.10", features = ["tmp"] }
ui_test = "0.11.5"
tester = "0.9"
regex = "1.5"
toml = "0.7.3"
walkdir = "2.3"
# This is used by the `collect-metadata` alias.
filetime = "0.2"
# UI test dependencies
clap = { version = "4.1.4", features = ["derive"] }
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"
itertools = "0.10.1"
quote = "1.0"
serde = { version = "1.0.125", features = ["derive"] }
syn = { version = "2.0", features = ["full"] }
futures = "0.3"
parking_lot = "0.12"
tokio = { version = "1", features = ["io-util"] }
rustc-semver = "1.1"
[build-dependencies]
rustc_tools_util = "0.3.0"
@ -60,3 +47,7 @@ internal = ["clippy_lints/internal", "tempfile"]
[package.metadata.rust-analyzer]
# This package uses #[feature(rustc_private)]
rustc_private = true
[[test]]
name = "compile-test"
harness = false

View file

@ -122,20 +122,17 @@ fn main() {
}
```
Now we can run the test with `TESTNAME=foo_functions cargo uitest`, currently
Now we can run the test with `TESTNAME=foo_functions cargo uibless`, currently
this test is meaningless though.
While we are working on implementing our lint, we can keep running the UI test.
That allows us to check if the output is turning into what we want.
That allows us to check if the output is turning into what we want by checking the
`.stderr` file that gets updated on every test run.
Once we are satisfied with the output, we need to run `cargo dev bless` to
update the `.stderr` file for our lint. Please note that, we should run
`TESTNAME=foo_functions cargo uitest` every time before running `cargo dev
bless`. Running `TESTNAME=foo_functions cargo uitest` should pass then. When we
Running `TESTNAME=foo_functions cargo uitest` should pass on its own. When we
commit our lint, we need to commit the generated `.stderr` files, too. In
general, you should only commit files changed by `cargo dev bless` for the
specific lint you are creating/editing. Note that if the generated files are
empty, they should be removed.
general, you should only commit files changed by `cargo bless` for the
specific lint you are creating/editing.
> _Note:_ you can run multiple test files by specifying a comma separated list:
> `TESTNAME=foo_functions,test2,test3`.
@ -169,7 +166,7 @@ additionally run [rustfix] for that test. Rustfix will apply the suggestions
from the lint to the code of the test file and compare that to the contents of a
`.fixed` file.
Use `cargo dev bless` to automatically generate the `.fixed` file after running
Use `cargo bless` to automatically generate the `.fixed` file while running
the tests.
[rustfix]: https://github.com/rust-lang/rustfix
@ -417,7 +414,7 @@ fn is_foo_fn(fn_kind: FnKind<'_>) -> bool {
Now we should also run the full test suite with `cargo test`. At this point
running `cargo test` should produce the expected output. Remember to run `cargo
dev bless` to update the `.stderr` file.
bless` to update the `.stderr` file.
`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint
implementation is not violating any Clippy lints itself.

View file

@ -66,7 +66,7 @@ If the output of a [UI test] differs from the expected output, you can update
the reference file with:
```bash
cargo dev bless
cargo bless
```
For example, this is necessary if you fix a typo in an error message of a lint,

View file

@ -56,6 +56,28 @@ and open that file in your editor of choice.
When updating the changelog it's also a good idea to make sure that `commit1` is
already correct in the current changelog.
#### PR ranges
We developed the concept of PR ranges to help the user understand the size of a new update. To create a PR range,
get the current release date and the date that the last version was released (YYYY-MM-DD) and use the following link:
```
[**View <NUMBER OF PRs> PRs merged since 1.<LAST VERSION NUM>**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A<LAST VERSION DATE>..<CURRENT VERSION DATE>+base%3Amaster+sort%3Amerged-desc+)
```
> Note: Be sure to check click the link and check how many PRs got merged between
Example:
```
[**View 85 PRs merged since 1.69**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2023-04-20..2023-06-01+base%3Amaster+sort%3Amerged-desc+)
```
Which renders to:
[**View 85 PRs merged since 1.69**](https://github.com/rust-lang/rust-clippy/pulls?q=is%3Apr+is%3Aclosed+merged%3A2023-04-20..2023-06-01+base%3Amaster+sort%3Amerged-desc+)
Note that **commit ranges should not be included**, only PR ranges.
### 3. Authoring the final changelog
The above script should have dumped all the relevant PRs to the file you

View file

@ -3,63 +3,14 @@ This file is generated by `cargo collect-metadata`.
Please use that command to update the file and do not edit it by hand.
-->
## Lint Configuration Options
| <div style="width:290px">Option</div> | Default Value |
|--|--|
| [arithmetic-side-effects-allowed](#arithmetic-side-effects-allowed) | `{}` |
| [arithmetic-side-effects-allowed-binary](#arithmetic-side-effects-allowed-binary) | `[]` |
| [arithmetic-side-effects-allowed-unary](#arithmetic-side-effects-allowed-unary) | `{}` |
| [avoid-breaking-exported-api](#avoid-breaking-exported-api) | `true` |
| [msrv](#msrv) | `None` |
| [cognitive-complexity-threshold](#cognitive-complexity-threshold) | `25` |
| [disallowed-names](#disallowed-names) | `["foo", "baz", "quux"]` |
| [semicolon-inside-block-ignore-singleline](#semicolon-inside-block-ignore-singleline) | `false` |
| [semicolon-outside-block-ignore-multiline](#semicolon-outside-block-ignore-multiline) | `false` |
| [doc-valid-idents](#doc-valid-idents) | `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` |
| [too-many-arguments-threshold](#too-many-arguments-threshold) | `7` |
| [type-complexity-threshold](#type-complexity-threshold) | `250` |
| [single-char-binding-names-threshold](#single-char-binding-names-threshold) | `4` |
| [too-large-for-stack](#too-large-for-stack) | `200` |
| [enum-variant-name-threshold](#enum-variant-name-threshold) | `3` |
| [enum-variant-size-threshold](#enum-variant-size-threshold) | `200` |
| [verbose-bit-mask-threshold](#verbose-bit-mask-threshold) | `1` |
| [literal-representation-threshold](#literal-representation-threshold) | `16384` |
| [trivial-copy-size-limit](#trivial-copy-size-limit) | `None` |
| [pass-by-value-size-limit](#pass-by-value-size-limit) | `256` |
| [too-many-lines-threshold](#too-many-lines-threshold) | `100` |
| [array-size-threshold](#array-size-threshold) | `512000` |
| [vec-box-size-threshold](#vec-box-size-threshold) | `4096` |
| [max-trait-bounds](#max-trait-bounds) | `3` |
| [max-struct-bools](#max-struct-bools) | `3` |
| [max-fn-params-bools](#max-fn-params-bools) | `3` |
| [warn-on-all-wildcard-imports](#warn-on-all-wildcard-imports) | `false` |
| [disallowed-macros](#disallowed-macros) | `[]` |
| [disallowed-methods](#disallowed-methods) | `[]` |
| [disallowed-types](#disallowed-types) | `[]` |
| [unreadable-literal-lint-fractions](#unreadable-literal-lint-fractions) | `true` |
| [upper-case-acronyms-aggressive](#upper-case-acronyms-aggressive) | `false` |
| [matches-for-let-else](#matches-for-let-else) | `WellKnownTypes` |
| [cargo-ignore-publish](#cargo-ignore-publish) | `false` |
| [standard-macro-braces](#standard-macro-braces) | `[]` |
| [enforced-import-renames](#enforced-import-renames) | `[]` |
| [allowed-scripts](#allowed-scripts) | `["Latin"]` |
| [enable-raw-pointer-heuristic-for-send](#enable-raw-pointer-heuristic-for-send) | `true` |
| [max-suggested-slice-pattern-length](#max-suggested-slice-pattern-length) | `3` |
| [await-holding-invalid-types](#await-holding-invalid-types) | `[]` |
| [max-include-file-size](#max-include-file-size) | `1000000` |
| [allow-expect-in-tests](#allow-expect-in-tests) | `false` |
| [allow-unwrap-in-tests](#allow-unwrap-in-tests) | `false` |
| [allow-dbg-in-tests](#allow-dbg-in-tests) | `false` |
| [allow-print-in-tests](#allow-print-in-tests) | `false` |
| [large-error-threshold](#large-error-threshold) | `128` |
| [ignore-interior-mutability](#ignore-interior-mutability) | `["bytes::Bytes"]` |
| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` |
| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` |
| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` |
| [future-size-threshold](#future-size-threshold) | `16384` |
| [unnecessary-box-size](#unnecessary-box-size) | `128` |
# Lint Configuration Options
### arithmetic-side-effects-allowed
The following list shows each configuration option, along with a description, its default value, an example
and lints affected.
---
## `arithmetic-side-effects-allowed`
Suppress checking of the passed type names in all types of operations.
If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead.
@ -77,10 +28,12 @@ A type, say `SomeType`, listed in this configuration has the same behavior of
**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`)
* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
---
**Affected lints:**
* [`arithmetic_side_effects`](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
### arithmetic-side-effects-allowed-binary
## `arithmetic-side-effects-allowed-binary`
Suppress checking of the passed type pair names in binary operations like addition or
multiplication.
@ -98,10 +51,12 @@ arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType",
**Default Value:** `[]` (`Vec<[String; 2]>`)
* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
---
**Affected lints:**
* [`arithmetic_side_effects`](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
### arithmetic-side-effects-allowed-unary
## `arithmetic-side-effects-allowed-unary`
Suppress checking of the passed type names in unary operations like "negation" (`-`).
#### Example
@ -112,116 +67,143 @@ arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`)
* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
---
**Affected lints:**
* [`arithmetic_side_effects`](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects)
### avoid-breaking-exported-api
## `avoid-breaking-exported-api`
Suppress lints whenever the suggested change would cause breakage for other crates.
**Default Value:** `true` (`bool`)
* [enum_variant_names](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
* [large_types_passed_by_value](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
* [trivially_copy_pass_by_ref](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)
* [unnecessary_wraps](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps)
* [unused_self](https://rust-lang.github.io/rust-clippy/master/index.html#unused_self)
* [upper_case_acronyms](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms)
* [wrong_self_convention](https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention)
* [box_collection](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection)
* [redundant_allocation](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation)
* [rc_buffer](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer)
* [vec_box](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box)
* [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
* [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
* [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
---
**Affected lints:**
* [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
* [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
* [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)
* [`unnecessary_wraps`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps)
* [`unused_self`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_self)
* [`upper_case_acronyms`](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms)
* [`wrong_self_convention`](https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention)
* [`box_collection`](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection)
* [`redundant_allocation`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation)
* [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer)
* [`vec_box`](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box)
* [`option_option`](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
* [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
* [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
* [`unnecessary_box_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
* [`single_call_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn)
### msrv
## `msrv`
The minimum rust version that the project supports
**Default Value:** `None` (`Option<String>`)
* [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)
* [cloned_instead_of_copied](https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied)
* [redundant_field_names](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
* [redundant_static_lifetimes](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes)
* [filter_map_next](https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next)
* [checked_conversions](https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions)
* [manual_range_contains](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains)
* [use_self](https://rust-lang.github.io/rust-clippy/master/index.html#use_self)
* [mem_replace_with_default](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default)
* [manual_non_exhaustive](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive)
* [option_as_ref_deref](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
* [map_unwrap_or](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or)
* [match_like_matches_macro](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro)
* [manual_strip](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip)
* [missing_const_for_fn](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
* [unnested_or_patterns](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns)
* [from_over_into](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into)
* [ptr_as_ptr](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
* [if_then_some_else_none](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none)
* [approx_constant](https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant)
* [deprecated_cfg_attr](https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr)
* [index_refutable_slice](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
* [map_clone](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone)
* [borrow_as_ptr](https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr)
* [manual_bits](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
* [err_expect](https://rust-lang.github.io/rust-clippy/master/index.html#err_expect)
* [cast_abs_to_unsigned](https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned)
* [uninlined_format_args](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
* [manual_clamp](https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp)
* [manual_let_else](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
* [unchecked_duration_subtraction](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction)
* [collapsible_str_replace](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace)
* [seek_from_current](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current)
* [seek_rewind](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind)
* [unnecessary_lazy_evaluations](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations)
* [transmute_ptr_to_ref](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref)
* [almost_complete_range](https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range)
* [needless_borrow](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
* [derivable_impls](https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls)
* [manual_is_ascii_check](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check)
* [manual_rem_euclid](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid)
* [manual_retain](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain)
---
**Affected lints:**
* [`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)
* [`cloned_instead_of_copied`](https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied)
* [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or)
* [`redundant_static_lifetimes`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes)
* [`filter_map_next`](https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next)
* [`checked_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions)
* [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains)
* [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self)
* [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default)
* [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive)
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
* [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or)
* [`match_like_matches_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro)
* [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip)
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
* [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns)
* [`from_over_into`](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into)
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
* [`if_then_some_else_none`](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none)
* [`approx_constant`](https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant)
* [`deprecated_cfg_attr`](https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr)
* [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
* [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone)
* [`borrow_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr)
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
* [`err_expect`](https://rust-lang.github.io/rust-clippy/master/index.html#err_expect)
* [`cast_abs_to_unsigned`](https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned)
* [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
* [`manual_clamp`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp)
* [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
* [`unchecked_duration_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction)
* [`collapsible_str_replace`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace)
* [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current)
* [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind)
* [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations)
* [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref)
* [`almost_complete_range`](https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range)
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
* [`derivable_impls`](https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls)
* [`manual_is_ascii_check`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check)
* [`manual_rem_euclid`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid)
* [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain)
* [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
### cognitive-complexity-threshold
## `cognitive-complexity-threshold`
The maximum cognitive complexity a function can have
**Default Value:** `25` (`u64`)
* [cognitive_complexity](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity)
---
**Affected lints:**
* [`cognitive_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity)
### disallowed-names
## `excessive-nesting-threshold`
The maximum amount of nesting a block can reside in
**Default Value:** `0` (`u64`)
---
**Affected lints:**
* [`excessive_nesting`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting)
## `disallowed-names`
The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
`".."` can be used as part of the list to indicate, that the configured values should be appended to the
default configuration of Clippy. By default, any configuration will replace the default value.
**Default Value:** `["foo", "baz", "quux"]` (`Vec<String>`)
* [disallowed_names](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names)
---
**Affected lints:**
* [`disallowed_names`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names)
### semicolon-inside-block-ignore-singleline
## `semicolon-inside-block-ignore-singleline`
Whether to lint only if it's multiline.
**Default Value:** `false` (`bool`)
* [semicolon_inside_block](https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block)
---
**Affected lints:**
* [`semicolon_inside_block`](https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block)
### semicolon-outside-block-ignore-multiline
## `semicolon-outside-block-ignore-multiline`
Whether to lint only if it's singleline.
**Default Value:** `false` (`bool`)
* [semicolon_outside_block](https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block)
---
**Affected lints:**
* [`semicolon_outside_block`](https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block)
### doc-valid-idents
## `doc-valid-idents`
The list of words this lint should not consider as identifiers needing ticks. The value
`".."` can be used as part of the list to indicate, that the configured values should be appended to the
default configuration of Clippy. By default, any configuration will replace the default value. For example:
@ -230,207 +212,267 @@ default configuration of Clippy. By default, any configuration will replace the
Default list:
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` (`Vec<String>`)
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` (`Vec<String>`)
* [doc_markdown](https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown)
---
**Affected lints:**
* [`doc_markdown`](https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown)
### too-many-arguments-threshold
## `too-many-arguments-threshold`
The maximum number of argument a function or method can have
**Default Value:** `7` (`u64`)
* [too_many_arguments](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments)
---
**Affected lints:**
* [`too_many_arguments`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments)
### type-complexity-threshold
## `type-complexity-threshold`
The maximum complexity a type can have
**Default Value:** `250` (`u64`)
* [type_complexity](https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity)
---
**Affected lints:**
* [`type_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity)
### single-char-binding-names-threshold
## `single-char-binding-names-threshold`
The maximum number of single char bindings a scope may have
**Default Value:** `4` (`u64`)
* [many_single_char_names](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names)
---
**Affected lints:**
* [`many_single_char_names`](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names)
### too-large-for-stack
## `too-large-for-stack`
The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
**Default Value:** `200` (`u64`)
* [boxed_local](https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local)
* [useless_vec](https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec)
---
**Affected lints:**
* [`boxed_local`](https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local)
* [`useless_vec`](https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec)
### enum-variant-name-threshold
## `enum-variant-name-threshold`
The minimum number of enum variants for the lints about variant names to trigger
**Default Value:** `3` (`u64`)
* [enum_variant_names](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
---
**Affected lints:**
* [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
### enum-variant-size-threshold
## `enum-variant-size-threshold`
The maximum size of an enum's variant to avoid box suggestion
**Default Value:** `200` (`u64`)
* [large_enum_variant](https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant)
---
**Affected lints:**
* [`large_enum_variant`](https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant)
### verbose-bit-mask-threshold
## `verbose-bit-mask-threshold`
The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
**Default Value:** `1` (`u64`)
* [verbose_bit_mask](https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask)
---
**Affected lints:**
* [`verbose_bit_mask`](https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask)
### literal-representation-threshold
## `literal-representation-threshold`
The lower bound for linting decimal literals
**Default Value:** `16384` (`u64`)
* [decimal_literal_representation](https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation)
---
**Affected lints:**
* [`decimal_literal_representation`](https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation)
### trivial-copy-size-limit
## `trivial-copy-size-limit`
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
**Default Value:** `None` (`Option<u64>`)
* [trivially_copy_pass_by_ref](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)
---
**Affected lints:**
* [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)
### pass-by-value-size-limit
## `pass-by-value-size-limit`
The minimum size (in bytes) to consider a type for passing by reference instead of by value.
**Default Value:** `256` (`u64`)
* [large_types_passed_by_value](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
---
**Affected lints:**
* [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
### too-many-lines-threshold
## `too-many-lines-threshold`
The maximum number of lines a function or method can have
**Default Value:** `100` (`u64`)
* [too_many_lines](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
---
**Affected lints:**
* [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
### array-size-threshold
## `array-size-threshold`
The maximum allowed size for arrays on the stack
**Default Value:** `512000` (`u64`)
* [large_stack_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays)
* [large_const_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays)
---
**Affected lints:**
* [`large_stack_arrays`](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays)
* [`large_const_arrays`](https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays)
### vec-box-size-threshold
## `stack-size-threshold`
The maximum allowed stack size for functions in bytes
**Default Value:** `512000` (`u64`)
---
**Affected lints:**
* [`large_stack_frames`](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_frames)
## `vec-box-size-threshold`
The size of the boxed type in bytes, where boxing in a `Vec` is allowed
**Default Value:** `4096` (`u64`)
* [vec_box](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box)
---
**Affected lints:**
* [`vec_box`](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box)
### max-trait-bounds
## `max-trait-bounds`
The maximum number of bounds a trait can have to be linted
**Default Value:** `3` (`u64`)
* [type_repetition_in_bounds](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
---
**Affected lints:**
* [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
### max-struct-bools
## `max-struct-bools`
The maximum number of bool fields a struct can have
**Default Value:** `3` (`u64`)
* [struct_excessive_bools](https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools)
---
**Affected lints:**
* [`struct_excessive_bools`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools)
### max-fn-params-bools
## `max-fn-params-bools`
The maximum number of bool parameters a function can have
**Default Value:** `3` (`u64`)
* [fn_params_excessive_bools](https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools)
---
**Affected lints:**
* [`fn_params_excessive_bools`](https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools)
### warn-on-all-wildcard-imports
## `warn-on-all-wildcard-imports`
Whether to allow certain wildcard imports (prelude, super in tests).
**Default Value:** `false` (`bool`)
* [wildcard_imports](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
---
**Affected lints:**
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
### disallowed-macros
## `disallowed-macros`
The list of disallowed macros, written as fully qualified paths.
**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`)
* [disallowed_macros](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros)
---
**Affected lints:**
* [`disallowed_macros`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros)
### disallowed-methods
## `disallowed-methods`
The list of disallowed methods, written as fully qualified paths.
**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`)
* [disallowed_methods](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods)
---
**Affected lints:**
* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods)
### disallowed-types
## `disallowed-types`
The list of disallowed types, written as fully qualified paths.
**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`)
* [disallowed_types](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types)
---
**Affected lints:**
* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types)
### unreadable-literal-lint-fractions
## `unreadable-literal-lint-fractions`
Should the fraction of a decimal be linted to include separators.
**Default Value:** `true` (`bool`)
* [unreadable_literal](https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal)
---
**Affected lints:**
* [`unreadable_literal`](https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal)
### upper-case-acronyms-aggressive
## `upper-case-acronyms-aggressive`
Enables verbose mode. Triggers if there is more than one uppercase char next to each other
**Default Value:** `false` (`bool`)
* [upper_case_acronyms](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms)
---
**Affected lints:**
* [`upper_case_acronyms`](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms)
### matches-for-let-else
## `matches-for-let-else`
Whether the matches should be considered by the lint, and whether there should
be filtering for common types.
**Default Value:** `WellKnownTypes` (`crate::manual_let_else::MatchLintBehaviour`)
* [manual_let_else](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
---
**Affected lints:**
* [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
### cargo-ignore-publish
## `cargo-ignore-publish`
For internal testing only, ignores the current `publish` settings in the Cargo manifest.
**Default Value:** `false` (`bool`)
* [_cargo_common_metadata](https://rust-lang.github.io/rust-clippy/master/index.html#_cargo_common_metadata)
---
**Affected lints:**
* [`_cargo_common_metadata`](https://rust-lang.github.io/rust-clippy/master/index.html#_cargo_common_metadata)
### standard-macro-braces
## `standard-macro-braces`
Enforce the named macros always use the braces specified.
A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
@ -439,119 +481,147 @@ could be used with a full path two `MacroMatcher`s have to be added one with the
**Default Value:** `[]` (`Vec<crate::nonstandard_macro_braces::MacroMatcher>`)
* [nonstandard_macro_braces](https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces)
---
**Affected lints:**
* [`nonstandard_macro_braces`](https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces)
### enforced-import-renames
## `enforced-import-renames`
The list of imports to always rename, a fully qualified path followed by the rename.
**Default Value:** `[]` (`Vec<crate::utils::conf::Rename>`)
* [missing_enforced_import_renames](https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames)
---
**Affected lints:**
* [`missing_enforced_import_renames`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames)
### allowed-scripts
## `allowed-scripts`
The list of unicode scripts allowed to be used in the scope.
**Default Value:** `["Latin"]` (`Vec<String>`)
* [disallowed_script_idents](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents)
---
**Affected lints:**
* [`disallowed_script_idents`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents)
### enable-raw-pointer-heuristic-for-send
## `enable-raw-pointer-heuristic-for-send`
Whether to apply the raw pointer heuristic to determine if a type is `Send`.
**Default Value:** `true` (`bool`)
* [non_send_fields_in_send_ty](https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty)
---
**Affected lints:**
* [`non_send_fields_in_send_ty`](https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty)
### max-suggested-slice-pattern-length
## `max-suggested-slice-pattern-length`
When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
**Default Value:** `3` (`u64`)
* [index_refutable_slice](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
---
**Affected lints:**
* [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
### await-holding-invalid-types
## `await-holding-invalid-types`
**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`)
* [await_holding_invalid_type](https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type)
---
**Affected lints:**
* [`await_holding_invalid_type`](https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type)
### max-include-file-size
## `max-include-file-size`
The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
**Default Value:** `1000000` (`u64`)
* [large_include_file](https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file)
---
**Affected lints:**
* [`large_include_file`](https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file)
### allow-expect-in-tests
## `allow-expect-in-tests`
Whether `expect` should be allowed in test functions or `#[cfg(test)]`
**Default Value:** `false` (`bool`)
* [expect_used](https://rust-lang.github.io/rust-clippy/master/index.html#expect_used)
---
**Affected lints:**
* [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#expect_used)
### allow-unwrap-in-tests
## `allow-unwrap-in-tests`
Whether `unwrap` should be allowed in test functions or `#[cfg(test)]`
**Default Value:** `false` (`bool`)
* [unwrap_used](https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used)
---
**Affected lints:**
* [`unwrap_used`](https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used)
### allow-dbg-in-tests
## `allow-dbg-in-tests`
Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
**Default Value:** `false` (`bool`)
* [dbg_macro](https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro)
---
**Affected lints:**
* [`dbg_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro)
### allow-print-in-tests
## `allow-print-in-tests`
Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]`
**Default Value:** `false` (`bool`)
* [print_stdout](https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout)
* [print_stderr](https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr)
---
**Affected lints:**
* [`print_stdout`](https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout)
* [`print_stderr`](https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr)
### large-error-threshold
## `large-error-threshold`
The maximum size of the `Err`-variant in a `Result` returned from a function
**Default Value:** `128` (`u64`)
* [result_large_err](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err)
---
**Affected lints:**
* [`result_large_err`](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err)
### ignore-interior-mutability
## `ignore-interior-mutability`
A list of paths to types that should be treated like `Arc`, i.e. ignored but
for the generic parameters for determining interior mutability
**Default Value:** `["bytes::Bytes"]` (`Vec<String>`)
* [mutable_key_type](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type)
* [ifs_same_cond](https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond)
---
**Affected lints:**
* [`mutable_key_type`](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type)
* [`ifs_same_cond`](https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond)
### allow-mixed-uninlined-format-args
## `allow-mixed-uninlined-format-args`
Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
**Default Value:** `true` (`bool`)
* [uninlined_format_args](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
---
**Affected lints:**
* [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
### suppress-restriction-lint-in-const
## `suppress-restriction-lint-in-const`
Whether to suppress a restriction lint in constant code. In same
cases the restructured operation might not be unavoidable, as the
suggested counterparts are unavailable in constant code. This
@ -560,32 +630,101 @@ if no suggestion can be made.
**Default Value:** `false` (`bool`)
* [indexing_slicing](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing)
---
**Affected lints:**
* [`indexing_slicing`](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing)
### missing-docs-in-crate-items
## `missing-docs-in-crate-items`
Whether to **only** check for missing documentation in items visible within the current
crate. For example, `pub(crate)` items.
**Default Value:** `false` (`bool`)
* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
---
**Affected lints:**
* [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
### future-size-threshold
## `future-size-threshold`
The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
**Default Value:** `16384` (`u64`)
* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)
---
**Affected lints:**
* [`large_futures`](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)
### unnecessary-box-size
## `unnecessary-box-size`
The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
**Default Value:** `128` (`u64`)
* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
---
**Affected lints:**
* [`unnecessary_box_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
## `allow-private-module-inception`
Whether to allow module inception if it's not public.
**Default Value:** `false` (`bool`)
---
**Affected lints:**
* [`module_inception`](https://rust-lang.github.io/rust-clippy/master/index.html#module_inception)
## `allowed-idents-below-min-chars`
Allowed names below the minimum allowed characters. The value `".."` can be used as part of
the list to indicate, that the configured values should be appended to the default
configuration of Clippy. By default, any configuration will replace the default value.
**Default Value:** `{"j", "z", "i", "y", "n", "x", "w"}` (`rustc_data_structures::fx::FxHashSet<String>`)
---
**Affected lints:**
* [`min_ident_chars`](https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars)
## `min-ident-chars-threshold`
Minimum chars an ident can have, anything below or equal to this will be linted.
**Default Value:** `1` (`u64`)
---
**Affected lints:**
* [`min_ident_chars`](https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars)
## `accept-comment-above-statement`
Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
**Default Value:** `false` (`bool`)
---
**Affected lints:**
* [`undocumented_unsafe_blocks`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks)
## `accept-comment-above-attributes`
Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
**Default Value:** `false` (`bool`)
---
**Affected lints:**
* [`undocumented_unsafe_blocks`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks)
## `allow-one-hash-in-raw-strings`
Whether to allow `r#""#` when `r""` can be used
**Default Value:** `false` (`bool`)
---
**Affected lints:**
* [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes)

View file

@ -1,60 +0,0 @@
//! `bless` updates the reference files in the repo with changed output files
//! from the last test run.
use crate::cargo_clippy_path;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use walkdir::{DirEntry, WalkDir};
static CLIPPY_BUILD_TIME: LazyLock<Option<std::time::SystemTime>> =
LazyLock::new(|| cargo_clippy_path().metadata().ok()?.modified().ok());
/// # Panics
///
/// Panics if the path to a test file is broken
pub fn bless(ignore_timestamp: bool) {
let extensions = ["stdout", "stderr", "fixed"].map(OsStr::new);
WalkDir::new(build_dir())
.into_iter()
.map(Result::unwrap)
.filter(|entry| entry.path().extension().map_or(false, |ext| extensions.contains(&ext)))
.for_each(|entry| update_reference_file(&entry, ignore_timestamp));
}
fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) {
let test_output_path = test_output_entry.path();
let reference_file_name = test_output_entry.file_name().to_str().unwrap().replace(".stage-id", "");
let reference_file_path = Path::new("tests")
.join(test_output_path.strip_prefix(build_dir()).unwrap())
.with_file_name(reference_file_name);
// If the test output was not updated since the last clippy build, it may be outdated
if !ignore_timestamp && !updated_since_clippy_build(test_output_entry).unwrap_or(true) {
return;
}
let test_output_file = fs::read(test_output_path).expect("Unable to read test output file");
let reference_file = fs::read(&reference_file_path).unwrap_or_default();
if test_output_file != reference_file {
// If a test run caused an output file to change, update the reference file
println!("updating {}", reference_file_path.display());
fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file");
}
}
fn updated_since_clippy_build(entry: &DirEntry) -> Option<bool> {
let clippy_build_time = (*CLIPPY_BUILD_TIME)?;
let modified = entry.metadata().ok()?.modified().ok()?;
Some(modified >= clippy_build_time)
}
fn build_dir() -> PathBuf {
let mut path = std::env::current_exe().unwrap();
path.set_file_name("test");
path
}

View file

@ -35,6 +35,7 @@ struct FmtContext {
}
// the "main" function of cargo dev fmt
#[allow(clippy::missing_panics_doc)]
pub fn run(check: bool, verbose: bool) {
fn try_run(context: &FmtContext) -> Result<bool, CliError> {
let mut success = true;

View file

@ -14,7 +14,6 @@ use std::io;
use std::path::PathBuf;
use std::process::{self, ExitStatus};
pub mod bless;
pub mod dogfood;
pub mod fmt;
pub mod lint;
@ -29,6 +28,10 @@ static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
/// Returns the path to the `cargo-clippy` binary
///
/// # Panics
///
/// Panics if the path of current executable could not be retrieved.
#[must_use]
pub fn cargo_clippy_path() -> PathBuf {
let mut path = std::env::current_exe().expect("failed to get current executable name");
@ -61,6 +64,8 @@ pub fn clippy_project_root() -> PathBuf {
panic!("error: Can't determine root of project. Please run inside a Clippy working dir.");
}
/// # Panics
/// Panics if given command result was failed.
pub fn exit_if_err(status: io::Result<ExitStatus>) {
match status.expect("failed to run command").code() {
Some(0) => {},

View file

@ -3,7 +3,7 @@
#![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{Arg, ArgAction, ArgMatches, Command};
use clippy_dev::{bless, dogfood, fmt, lint, new_lint, serve, setup, update_lints};
use clippy_dev::{dogfood, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc;
use std::convert::Infallible;
@ -11,8 +11,8 @@ fn main() {
let matches = get_clap_config();
match matches.subcommand() {
Some(("bless", matches)) => {
bless::bless(matches.get_flag("ignore-timestamp"));
Some(("bless", _)) => {
eprintln!("use `cargo bless` to automatically replace `.stderr` and `.fixed` files as tests are being run");
},
Some(("dogfood", matches)) => {
dogfood::dogfood(
@ -35,7 +35,7 @@ fn main() {
},
Some(("new_lint", matches)) => {
match new_lint::create(
matches.get_one::<String>("pass"),
matches.get_one::<String>("pass").unwrap(),
matches.get_one::<String>("name"),
matches.get_one::<String>("category").map(String::as_str),
matches.get_one::<String>("type").map(String::as_str),
@ -176,7 +176,7 @@ fn get_clap_config() -> ArgMatches {
.help("Specify whether the lint runs during the early or late pass")
.value_parser(["early", "late"])
.conflicts_with("type")
.required_unless_present("type"),
.default_value("late"),
Arg::new("name")
.short('n')
.long("name")

View file

@ -36,8 +36,9 @@ impl<T> Context for io::Result<T> {
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
#[allow(clippy::missing_panics_doc)]
pub fn create(
pass: Option<&String>,
pass: &String,
lint_name: Option<&String>,
category: Option<&str>,
mut ty: Option<&str>,
@ -49,7 +50,7 @@ pub fn create(
}
let lint = LintData {
pass: pass.map_or("", String::as_str),
pass,
name: lint_name.expect("`name` argument is validated by clap"),
category: category.expect("`category` argument is validated by clap"),
ty,
@ -63,6 +64,14 @@ pub fn create(
add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?;
}
if pass == "early" {
println!(
"\n\
NOTE: Use a late pass unless you need something specific from\
an early pass, as they lack many features and utilities"
);
}
Ok(())
}
@ -87,7 +96,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> {
path.push("src");
fs::create_dir(&path)?;
let header = format!("// compile-flags: --crate-name={lint_name}");
let header = format!("//@compile-flags: --crate-name={lint_name}");
write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
Ok(())

View file

@ -22,6 +22,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
tempfile = { version = "3.3.0", optional = true }
toml = "0.7.3"
regex = { version = "1.5", optional = true }
unicode-normalization = "0.1"
unicode-script = { version = "0.5", default-features = false }
semver = "1.0"
@ -31,7 +32,7 @@ url = "2.2"
[features]
deny-warnings = ["clippy_utils/deny-warnings"]
# build clippy with internal lints enabled, off by default
internal = ["clippy_utils/internal", "serde_json", "tempfile"]
internal = ["clippy_utils/internal", "serde_json", "tempfile", "regex"]
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]

View file

@ -1,5 +1,5 @@
use ast::AttrStyle;
use clippy_utils::diagnostics::span_lint_and_sugg;
use ast::{AttrStyle, Attribute};
use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro};
use rustc_ast as ast;
use rustc_errors::Applicability;
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -50,13 +50,14 @@ declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
impl LateLintPass<'_> for AllowAttribute {
// Separate each crate's features.
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) {
if_chain! {
if !in_external_macro(cx.sess(), attr.span);
if cx.tcx.features().lint_reasons;
if let AttrStyle::Outer = attr.style;
if let Some(ident) = attr.ident();
if ident.name == rustc_span::symbol::sym::allow;
if !is_from_proc_macro(cx, &attr);
then {
span_lint_and_sugg(
cx,

View file

@ -0,0 +1,72 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::last_path_segment;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_lint::LateLintPass;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
declare_clippy_lint! {
/// ### What it does.
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
///
/// ### Why is this bad?
/// Wrapping a type in Arc doesn't add thread safety to the underlying data, so data races
/// could occur when touching the underlying data.
///
/// ### Example
/// ```rust
/// # use std::cell::RefCell;
/// # use std::sync::Arc;
///
/// fn main() {
/// // This is safe, as `i32` implements `Send` and `Sync`.
/// let a = Arc::new(42);
///
/// // This is not safe, as `RefCell` does not implement `Sync`.
/// let b = Arc::new(RefCell::new(42));
/// }
/// ```
#[clippy::version = "1.72.0"]
pub ARC_WITH_NON_SEND_SYNC,
correctness,
"using `Arc` with a type that does not implement `Send` or `Sync`"
}
declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]);
impl LateLintPass<'_> for ArcWithNonSendSync {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if is_type_diagnostic_item(cx, ty, sym::Arc);
if let ExprKind::Call(func, [arg]) = expr.kind;
if let ExprKind::Path(func_path) = func.kind;
if last_path_segment(&func_path).ident.name == sym::new;
if let arg_ty = cx.typeck_results().expr_ty(arg);
if !matches!(arg_ty.kind(), ty::Param(_));
if !cx.tcx
.lang_items()
.sync_trait()
.map_or(false, |id| implements_trait(cx, arg_ty, id, &[])) ||
!cx.tcx
.get_diagnostic_item(sym::Send)
.map_or(false, |id| implements_trait(cx, arg_ty, id, &[]));
then {
span_lint_and_help(
cx,
ARC_WITH_NON_SEND_SYNC,
expr.span,
"usage of `Arc<T>` where `T` is not `Send` or `Sync`",
None,
"consider using `Rc<T>` instead or wrapping `T` in a std::sync type like \
`Mutex<T>`",
);
}
}
}
}

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Expr, ExprKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use clippy_utils::is_from_proc_macro;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -45,9 +46,9 @@ declare_clippy_lint! {
declare_lint_pass!(AsConversions => [AS_CONVERSIONS]);
impl EarlyLintPass for AsConversions {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) {
impl<'tcx> LateLintPass<'tcx> for AsConversions {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if in_external_macro(cx.sess(), expr.span) || is_from_proc_macro(cx, expr) {
return;
}

View file

@ -1,9 +1,12 @@
//! checks for attributes
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use clippy_utils::{
diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then},
is_from_proc_macro,
};
use if_chain::if_chain;
use rustc_ast::{AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem};
use rustc_errors::Applicability;
@ -362,6 +365,32 @@ declare_clippy_lint! {
"ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `#[cfg(features = "...")]` and suggests to replace it with
/// `#[cfg(feature = "...")]`.
///
/// ### Why is this bad?
/// Misspelling `feature` as `features` can be sometimes hard to spot. It
/// may cause conditional compilation not work quitely.
///
/// ### Example
/// ```rust
/// #[cfg(features = "some-feature")]
/// fn conditional() { }
/// ```
///
/// Use instead:
/// ```rust
/// #[cfg(feature = "some-feature")]
/// fn conditional() { }
/// ```
#[clippy::version = "1.69.0"]
pub MAYBE_MISUSED_CFG,
suspicious,
"prevent from misusing the wrong attr name"
}
declare_lint_pass!(Attributes => [
ALLOW_ATTRIBUTES_WITHOUT_REASON,
INLINE_ALWAYS,
@ -540,7 +569,7 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
}
}
fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
fn check_lint_reason<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute) {
// Check for the feature
if !cx.tcx.features().lint_reasons {
return;
@ -555,7 +584,7 @@ fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem
}
// Check if the attribute is in an external macro and therefore out of the developer's control
if in_external_macro(cx.sess(), attr.span) {
if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, &attr) {
return;
}
@ -676,6 +705,7 @@ impl_lint_pass!(EarlyAttributes => [
EMPTY_LINE_AFTER_OUTER_ATTR,
EMPTY_LINE_AFTER_DOC_COMMENTS,
NON_MINIMAL_CFG,
MAYBE_MISUSED_CFG,
]);
impl EarlyLintPass for EarlyAttributes {
@ -687,6 +717,7 @@ impl EarlyLintPass for EarlyAttributes {
check_deprecated_cfg_attr(cx, attr, &self.msrv);
check_mismatched_target_os(cx, attr);
check_minimal_cfg_condition(cx, attr);
check_misused_cfg(cx, attr);
}
extract_msrv_attr!(EarlyContext);
@ -777,7 +808,7 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
}
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
for item in items.iter() {
for item in items {
if let NestedMetaItem::MetaItem(meta) = item {
if !meta.has_name(sym::any) && !meta.has_name(sym::all) {
continue;
@ -810,6 +841,27 @@ fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
}
}
fn check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
for item in items {
if let NestedMetaItem::MetaItem(meta) = item {
if meta.has_name(sym!(features)) && let Some(val) = meta.value_str() {
span_lint_and_sugg(
cx,
MAYBE_MISUSED_CFG,
meta.span,
"feature may misspelled as features",
"use",
format!("feature = \"{val}\""),
Applicability::MaybeIncorrect,
);
}
if let MetaItemKind::List(list) = &meta.kind {
check_nested_misused_cfg(cx, list);
}
}
}
}
fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
if attr.has_name(sym::cfg) &&
let Some(items) = attr.meta_item_list()
@ -818,6 +870,14 @@ fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
}
}
fn check_misused_cfg(cx: &EarlyContext<'_>, attr: &Attribute) {
if attr.has_name(sym::cfg) &&
let Some(items) = attr.meta_item_list()
{
check_nested_misused_cfg(cx, &items);
}
}
fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
fn find_os(name: &str) -> Option<&'static str> {
UNIX_SYSTEMS

View file

@ -85,8 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
);
}
} else {
let span =
block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
if span.from_expansion() || expr.span.from_expansion() {
return;
}

View file

@ -440,7 +440,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
diag.span_suggestions(
e.span,
"try",
suggestions.into_iter(),
suggestions,
// nonminimal_bool can produce minimal but
// not human readable expressions (#3141)
Applicability::Unspecified,

View file

@ -4,6 +4,7 @@ use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use super::BORROW_AS_PTR;
@ -23,6 +24,15 @@ pub(super) fn check<'tcx>(
};
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
// Fix #9884
if !e.is_place_expr(|base| {
cx.typeck_results()
.adjustments()
.get(base.hir_id)
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
}) {
return;
}
span_lint_and_sugg(
cx,

View file

@ -1,41 +1,90 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_isize_or_usize;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::Ty;
use super::{utils, CAST_POSSIBLE_WRAP};
// this should be kept in sync with the allowed bit widths of `usize` and `isize`
const ALLOWED_POINTER_SIZES: [u64; 3] = [16, 32, 64];
// whether the lint should be emitted, and the required pointer size, if it matters
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum EmitState {
NoLint,
LintAlways,
LintOnPtrSize(u64),
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
if !(cast_from.is_integral() && cast_to.is_integral()) {
return;
}
let arch_64_suffix = " on targets with 64-bit wide pointers";
let arch_32_suffix = " on targets with 32-bit wide pointers";
let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed();
// emit a lint if a cast is:
// 1. unsigned to signed
// and
// 2. either:
//
// 2a. between two types of constant size that are always the same size
// 2b. between one target-dependent size and one constant size integer,
// and the constant integer is in the allowed set of target dependent sizes
// (the ptr size could be chosen to be the same as the constant size)
if cast_from.is_signed() || !cast_to.is_signed() {
return;
}
let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
(true, true) | (false, false) => (to_nbits == from_nbits && cast_unsigned_to_signed, ""),
(true, false) => (to_nbits <= 32 && cast_unsigned_to_signed, arch_32_suffix),
(false, true) => (
cast_unsigned_to_signed,
if from_nbits == 64 {
arch_64_suffix
let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) {
(true, true) => {
// casts between two ptr sized integers are trivially always the same size
// so do not depend on any specific pointer size to be the same
EmitState::LintAlways
},
(true, false) => {
// the first type is `usize` and the second is a constant sized signed integer
if ALLOWED_POINTER_SIZES.contains(&to_nbits) {
EmitState::LintOnPtrSize(to_nbits)
} else {
arch_32_suffix
},
EmitState::NoLint
}
},
(false, true) => {
// the first type is a constant sized unsigned integer, and the second is `isize`
if ALLOWED_POINTER_SIZES.contains(&from_nbits) {
EmitState::LintOnPtrSize(from_nbits)
} else {
EmitState::NoLint
}
},
(false, false) => {
// the types are both a constant known size
// and do not depend on any specific pointer size to be the same
if from_nbits == to_nbits {
EmitState::LintAlways
} else {
EmitState::NoLint
}
},
};
let message = match should_lint {
EmitState::NoLint => return,
EmitState::LintAlways => format!("casting `{cast_from}` to `{cast_to}` may wrap around the value"),
EmitState::LintOnPtrSize(ptr_size) => format!(
"casting `{cast_from}` to `{cast_to}` may wrap around the value on targets with {ptr_size}-bit wide pointers",
),
};
if should_lint {
span_lint(
cx,
CAST_POSSIBLE_WRAP,
expr.span,
&format!("casting `{cast_from}` to `{cast_to}` may wrap around the value{suffix}",),
);
}
cx.struct_span_lint(CAST_POSSIBLE_WRAP, expr.span, message, |diag| {
if let EmitState::LintOnPtrSize(16) = should_lint {
diag
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types")
} else {
diag
}
});
}

View file

@ -118,9 +118,10 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts from an unsigned type to a signed type of
/// the same size. Performing such a cast is a 'no-op' for the compiler,
/// i.e., nothing is changed at the bit level, and the binary representation of
/// the value is reinterpreted. This can cause wrapping if the value is too big
/// the same size, or possibly smaller due to target dependent integers.
/// Performing such a cast is a 'no-op' for the compiler, i.e., nothing is
/// changed at the bit level, and the binary representation of the value is
/// reinterpreted. This can cause wrapping if the value is too big
/// for the target signed type. However, the cast works as defined, so this lint
/// is `Allow` by default.
///
@ -174,8 +175,8 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts to the same type, casts of int literals to integer types
/// and casts of float literals to float types.
/// Checks for casts to the same type, casts of int literals to integer types, casts of float
/// literals to float types and casts between raw pointers without changing type or constness.
///
/// ### Why is this bad?
/// It's just unnecessary.
@ -522,7 +523,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Check for the usage of `as _` conversion using inferred type.
/// Checks for the usage of `as _` conversion using inferred type.
///
/// ### Why is this bad?
/// The conversion might include lossy conversion and dangerous cast that might go

View file

@ -9,20 +9,21 @@ use rustc_middle::ty::{self, Ty, TypeAndMut};
use super::PTR_CAST_CONSTNESS;
pub(super) fn check(
pub(super) fn check<'tcx>(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
cast_from: Ty<'tcx>,
cast_to: Ty<'tcx>,
msrv: &Msrv,
) {
if_chain! {
if msrv.meets(POINTER_CAST_CONSTNESS);
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind();
if let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind();
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, ty: from_ty }) = cast_from.kind();
if let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, ty: to_ty }) = cast_to.kind();
if matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not));
if from_ty == to_ty;
then {
let sugg = Sugg::hir(cx, cast_expr, "_");
let constness = match *to_mutbl {
@ -34,7 +35,7 @@ pub(super) fn check(
cx,
PTR_CAST_CONSTNESS,
expr.span,
"`as` casting between raw pointers while changing its constness",
"`as` casting between raw pointers while changing only its constness",
&format!("try `pointer::cast_{constness}`, a safer alternative"),
format!("{}.cast_{constness}()", sugg.maybe_par()),
Applicability::MachineApplicable,

View file

@ -1,18 +1,21 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_expr, path_to_local};
use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
use if_chain::if_chain;
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, Lit, QPath, TyKind, UnOp};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
use std::ops::ControlFlow;
use super::UNNECESSARY_CAST;
#[expect(clippy::too_many_lines)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
@ -20,19 +23,84 @@ pub(super) fn check<'tcx>(
cast_from: Ty<'tcx>,
cast_to: Ty<'tcx>,
) -> bool {
// skip non-primitive type cast
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
if_chain! {
if let ty::RawPtr(..) = cast_from.kind();
// check both mutability and type are the same
if cast_from.kind() == cast_to.kind();
if let ExprKind::Cast(_, cast_to_hir) = expr.kind;
// Ignore casts to e.g. type aliases and infer types
// - p as pointer_alias
// - p as _
if let TyKind::Ptr(to_pointee) = cast_to_hir.kind;
then {
match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
},
// Ignore `p as *const _`
TyKind::Infer => return false,
_ => {},
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!("casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"),
"try",
cast_str.clone(),
Applicability::MachineApplicable,
);
}
}
// skip cast of local that is a type alias
if let ExprKind::Cast(inner, ..) = expr.kind
&& let ExprKind::Path(qpath) = inner.kind
&& let QPath::Resolved(None, Path { res, .. }) = qpath
&& let Res::Local(hir_id) = res
&& let parent = cx.tcx.hir().get_parent(*hir_id)
&& let Node::Local(local) = parent
{
if let Some(ty) = local.ty
&& let TyKind::Path(qpath) = ty.kind
&& is_ty_alias(&qpath)
{
return false;
}
if let Some(expr) = local.init
&& let ExprKind::Cast(.., cast_to) = expr.kind
&& let TyKind::Path(qpath) = cast_to.kind
&& is_ty_alias(&qpath)
{
return false;
}
}
// skip cast of fn call that returns type alias
if let ExprKind::Cast(inner, ..) = expr.kind && is_cast_from_ty_alias(cx, inner, cast_from) {
return false;
}
// skip cast to non-primitive type
if_chain! {
if let ExprKind::Cast(_, cast_to) = expr.kind;
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
if let Res::PrimTy(_) = path.res;
then {}
else {
return false
return false;
}
}
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
if let Some(lit) = get_numeric_literal(cast_expr) {
let literal_str = &cast_str;
@ -162,3 +230,61 @@ fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {
_ => 0,
}
}
/// Finds whether an `Expr` returns a type alias.
///
/// TODO: Maybe we should move this to `clippy_utils` so others won't need to go down this dark,
/// dark path reimplementing this (or something similar).
fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx>, cast_from: Ty<'tcx>) -> bool {
for_each_expr(expr, |expr| {
// Calls are a `Path`, and usage of locals are a `Path`. So, this checks
// - call() as i32
// - local as i32
if let ExprKind::Path(qpath) = expr.kind {
let res = cx.qpath_res(&qpath, expr.hir_id);
// Function call
if let Res::Def(DefKind::Fn, def_id) = res {
let Some(snippet) = snippet_opt(cx, cx.tcx.def_span(def_id)) else {
return ControlFlow::Continue(());
};
// This is the worst part of this entire function. This is the only way I know of to
// check whether a function returns a type alias. Sure, you can get the return type
// from a function in the current crate as an hir ty, but how do you get it for
// external functions?? Simple: It's impossible. So, we check whether a part of the
// function's declaration snippet is exactly equal to the `Ty`. That way, we can
// see whether it's a type alias.
//
// Will this work for more complex types? Probably not!
if !snippet
.split("->")
.skip(0)
.map(|s| {
s.trim() == cast_from.to_string()
|| s.split("where").any(|ty| ty.trim() == cast_from.to_string())
})
.any(|a| a)
{
return ControlFlow::Break(());
}
// Local usage
} else if let Res::Local(hir_id) = res
&& let Some(parent) = get_parent_node(cx.tcx, hir_id)
&& let Node::Local(l) = parent
{
if let Some(e) = l.init && is_cast_from_ty_alias(cx, e, cast_from) {
return ControlFlow::Break::<()>(());
}
if let Some(ty) = l.ty
&& let TyKind::Path(qpath) = ty.kind
&& is_ty_alias(&qpath)
{
return ControlFlow::Break::<()>(());
}
}
}
ControlFlow::Continue(())
})
.is_some()
}

View file

@ -3,6 +3,8 @@
// Manual edits will be overwritten.
pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")]
crate::utils::internal_lints::almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::clippy_lints_internal::CLIPPY_LINTS_INTERNAL_INFO,
#[cfg(feature = "internal")]
@ -38,6 +40,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::allow_attributes::ALLOW_ATTRIBUTES_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_INFO,
crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
crate::as_conversions::AS_CONVERSIONS_INFO,
crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO,
crate::asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX_INFO,
@ -51,6 +54,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
crate::attrs::MAYBE_MISUSED_CFG_INFO,
crate::attrs::MISMATCHED_TARGET_OS_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO,
crate::attrs::USELESS_ATTRIBUTE_INFO,
@ -136,11 +140,15 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::double_parens::DOUBLE_PARENS_INFO,
crate::drop_forget_ref::DROP_NON_DROP_INFO,
crate::drop_forget_ref::FORGET_NON_DROP_INFO,
crate::drop_forget_ref::MEM_FORGET_INFO,
crate::duplicate_mod::DUPLICATE_MOD_INFO,
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
crate::empty_drop::EMPTY_DROP_INFO,
crate::empty_enum::EMPTY_ENUM_INFO,
crate::empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO,
crate::endian_bytes::BIG_ENDIAN_BYTES_INFO,
crate::endian_bytes::HOST_ENDIAN_BYTES_INFO,
crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO,
crate::entry::MAP_ENTRY_INFO,
crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO,
crate::enum_variants::ENUM_VARIANT_NAMES_INFO,
@ -152,6 +160,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO,
crate::excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS_INFO,
crate::excessive_bools::STRUCT_EXCESSIVE_BOOLS_INFO,
crate::excessive_nesting::EXCESSIVE_NESTING_INFO,
crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO,
crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO,
crate::exit::EXIT_INFO,
@ -197,6 +206,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
crate::incorrect_impls::INCORRECT_CLONE_IMPL_ON_COPY_TYPE_INFO,
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
crate::indexing_slicing::INDEXING_SLICING_INFO,
crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO,
@ -219,6 +229,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::large_futures::LARGE_FUTURES_INFO,
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::large_stack_frames::LARGE_STACK_FRAMES_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::LEN_ZERO_INFO,
@ -266,6 +277,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO,
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
crate::manual_retain::MANUAL_RETAIN_INFO,
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
@ -299,7 +311,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::matches::TRY_ERR_INFO,
crate::matches::WILDCARD_ENUM_MATCH_ARM_INFO,
crate::matches::WILDCARD_IN_OR_PATTERNS_INFO,
crate::mem_forget::MEM_FORGET_INFO,
crate::mem_replace::MEM_REPLACE_OPTION_WITH_NONE_INFO,
crate::mem_replace::MEM_REPLACE_WITH_DEFAULT_INFO,
crate::mem_replace::MEM_REPLACE_WITH_UNINIT_INFO,
@ -314,6 +325,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::CLONE_ON_COPY_INFO,
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO,
crate::methods::EXPECT_USED_INFO,
@ -398,6 +410,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::UNNECESSARY_FOLD_INFO,
crate::methods::UNNECESSARY_JOIN_INFO,
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
crate::methods::UNNECESSARY_SORT_BY_INFO,
crate::methods::UNNECESSARY_TO_OWNED_INFO,
crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,
@ -407,6 +420,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::VERBOSE_FILE_READS_INFO,
crate::methods::WRONG_SELF_CONVENTION_INFO,
crate::methods::ZST_OFFSET_INFO,
crate::min_ident_chars::MIN_IDENT_CHARS_INFO,
crate::minmax::MIN_MAX_INFO,
crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
crate::misc::TOPLEVEL_REF_ARG_INFO,
@ -450,6 +464,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
crate::needless_else::NEEDLESS_ELSE_INFO,
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
crate::needless_if::NEEDLESS_IF_INFO,
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,
@ -524,6 +539,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::ranges::RANGE_MINUS_ONE_INFO,
crate::ranges::RANGE_PLUS_ONE_INFO,
crate::ranges::REVERSED_EMPTY_RANGES_INFO,
crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO,
crate::raw_strings::NEEDLESS_RAW_STRING_HASHES_INFO,
crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO,
crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO,
crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO,
@ -535,6 +552,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
crate::ref_option_ref::REF_OPTION_REF_INFO,
crate::ref_patterns::REF_PATTERNS_INFO,
crate::reference::DEREF_ADDROF_INFO,
@ -553,8 +571,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::shadow::SHADOW_SAME_INFO,
crate::shadow::SHADOW_UNRELATED_INFO,
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
crate::single_call_fn::SINGLE_CALL_FN_INFO,
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
crate::single_range_in_vec_init::SINGLE_RANGE_IN_VEC_INIT_INFO,
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
crate::size_of_ref::SIZE_OF_REF_INFO,
crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,
@ -644,6 +664,9 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::useless_conversion::USELESS_CONVERSION_INFO,
crate::vec::USELESS_VEC_INFO,
crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,
crate::visibility::NEEDLESS_PUB_SELF_INFO,
crate::visibility::PUB_WITHOUT_SHORTHAND_INFO,
crate::visibility::PUB_WITH_SHORTHAND_INFO,
crate::wildcard_imports::ENUM_GLOB_USE_INFO,
crate::wildcard_imports::WILDCARD_IMPORTS_INFO,
crate::write::PRINTLN_EMPTY_STRING_INFO,

View file

@ -8,7 +8,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Check for construction on unit struct using `default`.
/// Checks for construction on unit struct using `default`.
///
/// ### Why is this bad?
/// This adds code complexity and an unnecessary function call.

View file

@ -161,7 +161,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
let fields_def = &variant.fields;
// Push field type then visit each field expr.
for field in fields.iter() {
for field in *fields {
let bound =
fields_def
.iter()

View file

@ -56,9 +56,11 @@ declare_clippy_lint! {
/// let b = &*a;
/// ```
///
/// This lint excludes:
/// This lint excludes all of:
/// ```rust,ignore
/// let _ = d.unwrap().deref();
/// let _ = Foo::deref(&foo);
/// let _ = <Foo as Deref>::deref(&foo);
/// ```
#[clippy::version = "1.44.0"]
pub EXPLICIT_DEREF_METHODS,
@ -355,15 +357,17 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
// start auto-deref.
// 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow
// adjustments will not be inserted automatically, then leave one further reference to avoid
// moving a mutable borrow.
// e.g.
// fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
// let x = match x {
// // Removing the borrow will cause `x` to be moved
// Some(x) => &mut *x,
// None => y
// };
// }
// moving a mutable borrow. e.g.
//
// ```rust
// fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
// let x = match x {
// // Removing the borrow will cause `x` to be moved
// Some(x) => &mut *x,
// None => y
// };
// }
// ```
let deref_msg =
"this expression creates a reference which is immediately dereferenced by the compiler";
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
@ -1481,7 +1485,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
target_mut,
} => {
let mut app = Applicability::MachineApplicable;
let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
let ty = cx.typeck_results().expr_ty(expr);
let (_, ref_count) = peel_mid_ty_refs(ty);
let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
@ -1504,11 +1508,20 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
"&"
};
let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
format!("({expr_str})")
// expr_str (the suggestion) is never shown if is_final_ufcs is true, since it's
// `expr.kind == ExprKind::Call`. Therefore, this is, afaik, always unnecessary.
/*
expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
Cow::Owned(format!("({expr_str})"))
} else {
expr_str.into_owned()
expr_str
};
*/
// Fix #10850, do not lint if it's `Foo::deref` instead of `foo.deref()`.
if is_final_ufcs {
return;
}
span_lint_and_sugg(
cx,

View file

@ -4,11 +4,13 @@ use clippy_utils::source::indent_of;
use clippy_utils::{is_default_equivalent, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::{
self as hir,
def::{CtorKind, CtorOf, DefKind, Res},
Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, Ty, TyKind,
Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Adt, AdtDef, SubstsRef};
use rustc_middle::ty::adjustment::{Adjust, PointerCast};
use rustc_middle::ty::{self, Adt, AdtDef, SubstsRef, Ty, TypeckResults};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
@ -75,13 +77,23 @@ fn is_path_self(e: &Expr<'_>) -> bool {
}
}
fn contains_trait_object(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Ref(_, ty, _) => contains_trait_object(*ty),
ty::Adt(def, substs) => def.is_box() && substs[0].as_type().map_or(false, contains_trait_object),
ty::Dynamic(..) => true,
_ => false,
}
}
fn check_struct<'tcx>(
cx: &LateContext<'tcx>,
item: &'tcx Item<'_>,
self_ty: &Ty<'_>,
self_ty: &hir::Ty<'_>,
func_expr: &Expr<'_>,
adt_def: AdtDef<'_>,
substs: SubstsRef<'_>,
typeck_results: &'tcx TypeckResults<'tcx>,
) {
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
if let Some(PathSegment { args, .. }) = p.segments.last() {
@ -96,10 +108,23 @@ fn check_struct<'tcx>(
}
}
}
// the default() call might unsize coerce to a trait object (e.g. Box<T> to Box<dyn Trait>),
// which would not be the same if derived (see #10158).
// this closure checks both if the expr is equivalent to a `default()` call and does not
// have such coercions.
let is_default_without_adjusts = |expr| {
is_default_equivalent(cx, expr)
&& typeck_results.expr_adjustments(expr).iter().all(|adj| {
!matches!(adj.kind, Adjust::Pointer(PointerCast::Unsize)
if contains_trait_object(adj.target))
})
};
let should_emit = match peel_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)),
ExprKind::Tup(fields) => fields.iter().all(is_default_without_adjusts),
ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(is_default_without_adjusts),
ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_without_adjusts(ef.expr)),
_ => false,
};
@ -197,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
then {
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, substs);
check_struct(cx, item, self_ty, func_expr, adt_def, substs, cx.tcx.typeck_body(*b));
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}

View file

@ -14,8 +14,8 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::traits::Reveal;
use rustc_middle::ty::{
self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv,
ToPredicate, TraitPredicate, Ty, TyCtxt,
self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, ToPredicate,
TraitPredicate, Ty, TyCtxt,
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
@ -520,7 +520,8 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) ->
trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]),
constness: BoundConstness::NotConst,
polarity: ImplPolarity::Positive,
}).to_predicate(tcx)
})
.to_predicate(tcx)
}),
)),
Reveal::UserFacing,

View file

@ -571,6 +571,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut in_link = None;
let mut in_heading = false;
let mut is_rust = false;
let mut no_test = false;
let mut edition = None;
let mut ticks_unbalanced = false;
let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
@ -584,6 +585,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
if item == "ignore" {
is_rust = false;
break;
} else if item == "no_test" {
no_test = true;
}
if let Some(stripped) = item.strip_prefix("edition") {
is_rust = true;
@ -648,7 +651,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
headers.errors |= in_heading && trimmed_text == "Errors";
headers.panics |= in_heading && trimmed_text == "Panics";
if in_code {
if is_rust {
if is_rust && !no_test {
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
check_code(cx, &text, edition, span);
}
@ -906,15 +909,15 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
if is_panic(self.cx, macro_call.def_id)
|| matches!(
self.cx.tcx.item_name(macro_call.def_id).as_str(),
"assert" | "assert_eq" | "assert_ne" | "todo"
"assert" | "assert_eq" | "assert_ne"
)
{
self.panic_span = Some(macro_call.span);
}
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
// check for `unwrap` and `expect` for both `Option` and `Result`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)

View file

@ -6,6 +6,7 @@ use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
@ -47,6 +48,27 @@ declare_clippy_lint! {
"call to `std::mem::forget` with a value which does not implement `Drop`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `std::mem::forget(t)` where `t` is
/// `Drop` or has a field that implements `Drop`.
///
/// ### Why is this bad?
/// `std::mem::forget(t)` prevents `t` from running its
/// destructor, possibly causing leaks.
///
/// ### Example
/// ```rust
/// # use std::mem;
/// # use std::rc::Rc;
/// mem::forget(Rc::new(55))
/// ```
#[clippy::version = "pre 1.29.0"]
pub MEM_FORGET,
restriction,
"`mem::forget` usage on `Drop` types, likely to cause memory leaks"
}
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
Dropping such a type only extends its contained lifetimes";
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
@ -55,6 +77,7 @@ const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value t
declare_lint_pass!(DropForgetRef => [
DROP_NON_DROP,
FORGET_NON_DROP,
MEM_FORGET,
]);
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
@ -67,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
let arg_ty = cx.typeck_results().expr_ty(arg);
let is_copy = is_copy(cx, arg_ty);
let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr);
let (lint, msg) = match fn_name {
let (lint, msg, note_span) = match fn_name {
// early return for uplifted lints: dropping_references, dropping_copy_types, forgetting_references, forgetting_copy_types
sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => return,
sym::mem_forget if arg_ty.is_ref() => return,
@ -81,19 +104,34 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|| drop_is_single_call_in_arm
) =>
{
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY)
},
sym::mem_forget if !arg_ty.needs_drop(cx.tcx, cx.param_env) => {
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY)
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY.into(), Some(arg.span))
},
sym::mem_forget => {
if arg_ty.needs_drop(cx.tcx, cx.param_env) {
(
MEM_FORGET,
Cow::Owned(format!(
"usage of `mem::forget` on {}",
if arg_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) {
"`Drop` type"
} else {
"type with `Drop` fields"
}
)),
None,
)
} else {
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY.into(), Some(arg.span))
}
}
_ => return,
};
span_lint_and_note(
cx,
lint,
expr.span,
msg,
Some(arg.span),
&msg,
note_span,
&format!("argument has type `{arg_ty}`"),
);
}

View file

@ -0,0 +1,216 @@
use crate::Lint;
use clippy_utils::{diagnostics::span_lint_and_then, is_lint_allowed};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::{lint::in_external_macro, ty::Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Symbol;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of the `to_ne_bytes` method and/or the function `from_ne_bytes`.
///
/// ### Why is this bad?
/// It's not, but some may prefer to specify the target endianness explicitly.
///
/// ### Example
/// ```rust,ignore
/// let _x = 2i32.to_ne_bytes();
/// let _y = 2i64.to_ne_bytes();
/// ```
#[clippy::version = "1.71.0"]
pub HOST_ENDIAN_BYTES,
restriction,
"disallows usage of the `to_ne_bytes` method"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`.
///
/// ### Why is this bad?
/// It's not, but some may wish to lint usage of this method, either to suggest using the host
/// endianness or big endian.
///
/// ### Example
/// ```rust,ignore
/// let _x = 2i32.to_le_bytes();
/// let _y = 2i64.to_le_bytes();
/// ```
#[clippy::version = "1.71.0"]
pub LITTLE_ENDIAN_BYTES,
restriction,
"disallows usage of the `to_le_bytes` method"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`.
///
/// ### Why is this bad?
/// It's not, but some may wish to lint usage of this method, either to suggest using the host
/// endianness or little endian.
///
/// ### Example
/// ```rust,ignore
/// let _x = 2i32.to_be_bytes();
/// let _y = 2i64.to_be_bytes();
/// ```
#[clippy::version = "1.71.0"]
pub BIG_ENDIAN_BYTES,
restriction,
"disallows usage of the `to_be_bytes` method"
}
declare_lint_pass!(EndianBytes => [HOST_ENDIAN_BYTES, LITTLE_ENDIAN_BYTES, BIG_ENDIAN_BYTES]);
const HOST_NAMES: [&str; 2] = ["from_ne_bytes", "to_ne_bytes"];
const LITTLE_NAMES: [&str; 2] = ["from_le_bytes", "to_le_bytes"];
const BIG_NAMES: [&str; 2] = ["from_be_bytes", "to_be_bytes"];
#[derive(Clone, Debug)]
enum LintKind {
Host,
Little,
Big,
}
#[derive(Clone, Copy, PartialEq)]
enum Prefix {
From,
To,
}
impl LintKind {
fn allowed(&self, cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
is_lint_allowed(cx, self.as_lint(), expr.hir_id)
}
fn as_lint(&self) -> &'static Lint {
match self {
LintKind::Host => HOST_ENDIAN_BYTES,
LintKind::Little => LITTLE_ENDIAN_BYTES,
LintKind::Big => BIG_ENDIAN_BYTES,
}
}
fn as_name(&self, prefix: Prefix) -> &str {
let index = usize::from(prefix == Prefix::To);
match self {
LintKind::Host => HOST_NAMES[index],
LintKind::Little => LITTLE_NAMES[index],
LintKind::Big => BIG_NAMES[index],
}
}
}
impl LateLintPass<'_> for EndianBytes {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if_chain! {
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind;
if args.is_empty();
let ty = cx.typeck_results().expr_ty(receiver);
if ty.is_primitive_ty();
if maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty);
then {
return;
}
}
if_chain! {
if let ExprKind::Call(function, ..) = expr.kind;
if let ExprKind::Path(qpath) = function.kind;
if let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id();
if let Some(function_name) = cx.get_def_path(def_id).last();
let ty = cx.typeck_results().expr_ty(expr);
if ty.is_primitive_ty();
then {
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
}
}
}
}
fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix, name: Symbol, ty: Ty<'_>) -> bool {
let ne = LintKind::Host.as_name(prefix);
let le = LintKind::Little.as_name(prefix);
let be = LintKind::Big.as_name(prefix);
let (lint, other_lints) = match name.as_str() {
name if name == ne => ((&LintKind::Host), [(&LintKind::Little), (&LintKind::Big)]),
name if name == le => ((&LintKind::Little), [(&LintKind::Host), (&LintKind::Big)]),
name if name == be => ((&LintKind::Big), [(&LintKind::Host), (&LintKind::Little)]),
_ => return false,
};
let mut help = None;
'build_help: {
// all lints disallowed, don't give help here
if [&[lint], other_lints.as_slice()]
.concat()
.iter()
.all(|lint| !lint.allowed(cx, expr))
{
break 'build_help;
}
// ne_bytes and all other lints allowed
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
help = Some(Cow::Borrowed("specify the desired endianness explicitly"));
break 'build_help;
}
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
// le_bytes is not
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
help = Some(Cow::Borrowed("use the native endianness instead"));
break 'build_help;
}
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
let len = allowed_lints.clone().count();
let mut help_str = "use ".to_owned();
for (i, lint) in allowed_lints.enumerate() {
let only_one = len == 1;
if !only_one {
help_str.push_str("either of ");
}
help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix)));
if i != len && !only_one {
help_str.push_str("or ");
}
}
help = Some(Cow::Owned(help_str + "instead"));
}
span_lint_and_then(
cx,
lint.as_lint(),
expr.span,
&format!(
"usage of the {}`{ty}::{}`{}",
if prefix == Prefix::From { "function " } else { "" },
lint.as_name(prefix),
if prefix == Prefix::To { " method" } else { "" },
),
move |diag| {
if let Some(help) = help {
diag.help(help);
}
},
);
true
}

View file

@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
.const_eval_poly(def_id.to_def_id())
.ok()
.map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty));
if let Some(Constant::Int(val)) = constant.and_then(|c| miri_to_const(cx.tcx, c)) {
if let Some(Constant::Int(val)) = constant.and_then(|c| miri_to_const(cx, c)) {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() {
ty = adt.repr().discr_type().to_ty(cx.tcx);

View file

@ -3,7 +3,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
use clippy_utils::source::is_present_in_source;
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start};
use rustc_hir::{EnumDef, Item, ItemKind, Variant};
use rustc_hir::{EnumDef, Item, ItemKind, OwnerId, Variant};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
@ -105,18 +105,20 @@ declare_clippy_lint! {
}
pub struct EnumVariantNames {
modules: Vec<(Symbol, String)>,
modules: Vec<(Symbol, String, OwnerId)>,
threshold: u64,
avoid_breaking_exported_api: bool,
allow_private_module_inception: bool,
}
impl EnumVariantNames {
#[must_use]
pub fn new(threshold: u64, avoid_breaking_exported_api: bool) -> Self {
pub fn new(threshold: u64, avoid_breaking_exported_api: bool, allow_private_module_inception: bool) -> Self {
Self {
modules: Vec::new(),
threshold,
avoid_breaking_exported_api,
allow_private_module_inception,
}
}
}
@ -252,18 +254,19 @@ impl LateLintPass<'_> for EnumVariantNames {
let item_name = item.ident.name.as_str();
let item_camel = to_camel_case(item_name);
if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
if let Some((mod_name, mod_camel)) = self.modules.last() {
if let [.., (mod_name, mod_camel, owner_id)] = &*self.modules {
// constants don't have surrounding modules
if !mod_camel.is_empty() {
if mod_name == &item.ident.name {
if let ItemKind::Mod(..) = item.kind {
span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
if mod_name == &item.ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(owner_id.def_id).is_public())
{
span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is accepted.
@ -302,6 +305,6 @@ impl LateLintPass<'_> for EnumVariantNames {
check_variant(cx, self.threshold, def, item_name, item.span);
}
}
self.modules.push((item.ident.name, item_camel));
self.modules.push((item.ident.name, item_camel, item.owner_id));
}
}

View file

@ -120,6 +120,13 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc);
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc);
if let ty::Closure(_, substs) = *closure_ty.kind();
// Don't lint if this is an inclusive range expression.
// They desugar to a call to `RangeInclusiveNew` which would have odd suggestions. (#10684)
if !matches!(higher::Range::hir(body.value), Some(higher::Range {
start: Some(_),
end: Some(_),
limits: rustc_ast::RangeLimits::Closed
}));
then {
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
@ -136,6 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
// Mutable closure is used after current expr; we cannot consume it.
snippet = format!("&mut {snippet}");
}
diag.span_suggestion(
expr.span,
"replace the closure with the function itself",

View file

@ -0,0 +1,181 @@
use clippy_utils::{diagnostics::span_lint_and_help, source::snippet};
use rustc_ast::{
node_id::NodeSet,
visit::{walk_block, walk_item, Visitor},
Block, Crate, Inline, Item, ItemKind, ModKind, NodeId,
};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for blocks which are nested beyond a certain threshold.
///
/// Note: Even though this lint is warn-by-default, it will only trigger if a maximum nesting level is defined in the clippy.toml file.
///
/// ### Why is this bad?
/// It can severely hinder readability.
///
/// ### Example
/// An example clippy.toml configuration:
/// ```toml
/// # clippy.toml
/// excessive-nesting-threshold = 3
/// ```
/// ```rust,ignore
/// // lib.rs
/// pub mod a {
/// pub struct X;
/// impl X {
/// pub fn run(&self) {
/// if true {
/// // etc...
/// }
/// }
/// }
/// }
/// ```
/// Use instead:
/// ```rust,ignore
/// // a.rs
/// fn private_run(x: &X) {
/// if true {
/// // etc...
/// }
/// }
///
/// pub struct X;
/// impl X {
/// pub fn run(&self) {
/// private_run(self);
/// }
/// }
/// ```
/// ```rust,ignore
/// // lib.rs
/// pub mod a;
/// ```
#[clippy::version = "1.70.0"]
pub EXCESSIVE_NESTING,
complexity,
"checks for blocks nested beyond a certain threshold"
}
impl_lint_pass!(ExcessiveNesting => [EXCESSIVE_NESTING]);
#[derive(Clone)]
pub struct ExcessiveNesting {
pub excessive_nesting_threshold: u64,
pub nodes: NodeSet,
}
impl ExcessiveNesting {
pub fn check_node_id(&self, cx: &EarlyContext<'_>, span: Span, node_id: NodeId) {
if self.nodes.contains(&node_id) {
span_lint_and_help(
cx,
EXCESSIVE_NESTING,
span,
"this block is too nested",
None,
"try refactoring your code to minimize nesting",
);
}
}
}
impl EarlyLintPass for ExcessiveNesting {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
if self.excessive_nesting_threshold == 0 {
return;
}
let mut visitor = NestingVisitor {
conf: self,
cx,
nest_level: 0,
};
for item in &krate.items {
visitor.visit_item(item);
}
}
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
self.check_node_id(cx, block.span, block.id);
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
self.check_node_id(cx, item.span, item.id);
}
}
struct NestingVisitor<'conf, 'cx> {
conf: &'conf mut ExcessiveNesting,
cx: &'cx EarlyContext<'cx>,
nest_level: u64,
}
impl NestingVisitor<'_, '_> {
fn check_indent(&mut self, span: Span, id: NodeId) -> bool {
if self.nest_level > self.conf.excessive_nesting_threshold && !in_external_macro(self.cx.sess(), span) {
self.conf.nodes.insert(id);
return true;
}
false
}
}
impl<'conf, 'cx> Visitor<'_> for NestingVisitor<'conf, 'cx> {
fn visit_block(&mut self, block: &Block) {
if block.span.from_expansion() {
return;
}
// TODO: This should be rewritten using `LateLintPass` so we can use `is_from_proc_macro` instead,
// but for now, this is fine.
let snippet = snippet(self.cx, block.span, "{}").trim().to_owned();
if !snippet.starts_with('{') || !snippet.ends_with('}') {
return;
}
self.nest_level += 1;
if !self.check_indent(block.span, block.id) {
walk_block(self, block);
}
self.nest_level -= 1;
}
fn visit_item(&mut self, item: &Item) {
if item.span.from_expansion() {
return;
}
match &item.kind {
ItemKind::Trait(_) | ItemKind::Impl(_) | ItemKind::Mod(.., ModKind::Loaded(_, Inline::Yes, _)) => {
self.nest_level += 1;
if !self.check_indent(item.span, item.id) {
walk_item(self, item);
}
self.nest_level -= 1;
},
// Reset nesting level for non-inline modules (since these are in another file)
ItemKind::Mod(..) => walk_item(
&mut NestingVisitor {
conf: self.conf,
cx: self.cx,
nest_level: 0,
},
item,
),
_ => walk_item(self, item),
}
}
}

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::is_from_proc_macro;
use clippy_utils::trait_ref_of_method;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
@ -265,6 +266,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn(_, generics, body_id) = item.kind
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
&& !is_from_proc_macro(cx, item)
{
let mut walker = TypeWalker::new(cx, generics);
walk_item(&mut walker, item);

View file

@ -82,19 +82,24 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Unsuffixed => None
};
let (is_whole, mut float_str) = match fty {
let (is_whole, is_inf, mut float_str) = match fty {
FloatTy::F32 => {
let value = sym_str.parse::<f32>().unwrap();
(value.fract() == 0.0, formatter.format(value))
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap();
(value.fract() == 0.0, formatter.format(value))
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
};
if is_inf {
return;
}
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {

View file

@ -215,7 +215,7 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
// ranges [-16777215, 16777216) for type f32 as whole number floats outside
// this range are lossy and ambiguous.
#[expect(clippy::cast_possible_truncation)]
fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
match value {
F32(num) if num.fract() == 0.0 => {
if (-16_777_215.0..16_777_216.0).contains(num) {

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{match_def_path, paths, peel_hir_expr_refs};
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
use clippy_utils::{higher, match_def_path, paths};
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@ -44,10 +44,24 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
is_type_lang_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), LangItem::String)
}
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id {
let e = e.peel_blocks().peel_borrows();
if e.span.from_expansion()
&& let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id
{
cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro)
} else if let Some(higher::If { then, r#else, .. }) = higher::If::hir(e) {
is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e))
} else {
false
match higher::IfLetOrMatch::parse(cx, e) {
Some(higher::IfLetOrMatch::Match(_, arms, MatchSource::Normal)) => {
arms.iter().any(|arm| is_format(cx, arm.body))
},
Some(higher::IfLetOrMatch::IfLet(_, _, then, r#else)) => {
is_format(cx, then) ||r#else.is_some_and(|e| is_format(cx, e))
},
_ => false,
}
}
}
@ -68,7 +82,6 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString {
},
_ => return,
};
let (arg, _) = peel_hir_expr_refs(arg);
if is_format(cx, arg) {
span_lint_and_help(
cx,

View file

@ -236,6 +236,12 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
}
}
// Don't warn if the only thing inside post_else_post_eol is a comment block.
let trimmed_post_else_post_eol = post_else_post_eol.trim();
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
return
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,

View file

@ -134,9 +134,10 @@ impl<'a, 'tcx> Visitor<'tcx> for SelfFinder<'a, 'tcx> {
kw::SelfUpper => self.upper.push(segment.ident.span),
_ => continue,
}
self.invalid |= segment.ident.span.from_expansion();
}
self.invalid |= path.span.from_expansion();
if !self.invalid {
walk_path(self, path);
}
@ -156,6 +157,11 @@ fn convert_to_from(
self_ty: &Ty<'_>,
impl_item_ref: &ImplItemRef,
) -> Option<Vec<(Span, String)>> {
if !target_ty.find_self_aliases().is_empty() {
// It's tricky to expand self-aliases correctly, we'll ignore it to not cause a
// bad suggestion/fix.
return None;
}
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
let ImplItemKind::Fn(ref sig, body_id) = impl_item.kind else { return None };
let body = cx.tcx.hir().body(body_id);

View file

@ -0,0 +1,124 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_node, last_path_segment, ty::implements_trait};
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, ImplItem, ImplItemKind, ItemKind, Node, UnOp};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::EarlyBinder;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, symbol};
declare_clippy_lint! {
/// ### What it does
/// Checks for manual implementations of `Clone` when `Copy` is already implemented.
///
/// ### Why is this bad?
/// If both `Clone` and `Copy` are implemented, they must agree. This is done by dereferencing
/// `self` in `Clone`'s implementation. Anything else is incorrect.
///
/// ### Example
/// ```rust,ignore
/// #[derive(Eq, PartialEq)]
/// struct A(u32);
///
/// impl Clone for A {
/// fn clone(&self) -> Self {
/// Self(self.0)
/// }
/// }
///
/// impl Copy for A {}
/// ```
/// Use instead:
/// ```rust,ignore
/// #[derive(Eq, PartialEq)]
/// struct A(u32);
///
/// impl Clone for A {
/// fn clone(&self) -> Self {
/// *self
/// }
/// }
///
/// impl Copy for A {}
/// ```
#[clippy::version = "1.72.0"]
pub INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
correctness,
"manual implementation of `Clone` on a `Copy` type"
}
declare_lint_pass!(IncorrectImpls => [INCORRECT_CLONE_IMPL_ON_COPY_TYPE]);
impl LateLintPass<'_> for IncorrectImpls {
#[expect(clippy::needless_return)]
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
let node = get_parent_node(cx.tcx, impl_item.hir_id());
let Some(Node::Item(item)) = node else {
return;
};
let ItemKind::Impl(imp) = item.kind else {
return;
};
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
return;
};
let trait_impl_def_id = trait_impl.def_id;
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
return;
}
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
return;
};
let body = cx.tcx.hir().body(impl_item_id);
let ExprKind::Block(block, ..) = body.value.kind else {
return;
};
// Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
// Remove it while solving conflicts once that PR is merged.
// Actual implementation; remove this comment once aforementioned PR is merged
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl_def_id)
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
&& implements_trait(
cx,
hir_ty_to_ty(cx.tcx, imp.self_ty),
copy_def_id,
trait_impl.substs,
)
{
if impl_item.ident.name == sym::clone {
if block.stmts.is_empty()
&& let Some(expr) = block.expr
&& let ExprKind::Unary(UnOp::Deref, inner) = expr.kind
&& let ExprKind::Path(qpath) = inner.kind
&& last_path_segment(&qpath).ident.name == symbol::kw::SelfLower
{} else {
span_lint_and_sugg(
cx,
INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
block.span,
"incorrect implementation of `clone` on a `Copy` type",
"change this to",
"{ *self }".to_owned(),
Applicability::MaybeIncorrect,
);
return;
}
}
if impl_item.ident.name == sym::clone_from {
span_lint_and_sugg(
cx,
INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
impl_item.span,
"incorrect implementation of `clone_from` on a `Copy` type",
"remove this",
String::new(),
Applicability::MaybeIncorrect,
);
return;
}
}
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::{diagnostics::span_lint_and_help, is_in_cfg_test};
use clippy_utils::{diagnostics::span_lint_and_help, is_from_proc_macro, is_in_cfg_test};
use rustc_hir::{HirId, ItemId, ItemKind, Mod};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -59,6 +59,7 @@ impl LateLintPass<'_> for ItemsAfterTestModule {
if !matches!(item.kind, ItemKind::Mod(_));
if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself
if !in_external_macro(cx.sess(), item.span);
if !is_from_proc_macro(cx, item);
then {
span_lint_and_help(cx, ITEMS_AFTER_TEST_MODULE, test_mod_span.unwrap().with_hi(item.span.hi()), "items were found after the testing module", None, "move the items to before the testing module was defined");

View file

@ -0,0 +1,162 @@
use std::ops::AddAssign;
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::fn_has_unsatisfiable_preds;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::Body;
use rustc_hir::FnDecl;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_tool_lint;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for functions that use a lot of stack space.
///
/// This often happens when constructing a large type, such as an array with a lot of elements,
/// or constructing *many* smaller-but-still-large structs, or copying around a lot of large types.
///
/// This lint is a more general version of [`large_stack_arrays`](https://rust-lang.github.io/rust-clippy/master/#large_stack_arrays)
/// that is intended to look at functions as a whole instead of only individual array expressions inside of a function.
///
/// ### Why is this bad?
/// The stack region of memory is very limited in size (usually *much* smaller than the heap) and attempting to
/// use too much will result in a stack overflow and crash the program.
/// To avoid this, you should consider allocating large types on the heap instead (e.g. by boxing them).
///
/// Keep in mind that the code path to construction of large types does not even need to be reachable;
/// it purely needs to *exist* inside of the function to contribute to the stack size.
/// For example, this causes a stack overflow even though the branch is unreachable:
/// ```rust,ignore
/// fn main() {
/// if false {
/// let x = [0u8; 10000000]; // 10 MB stack array
/// black_box(&x);
/// }
/// }
/// ```
///
/// ### Known issues
/// False positives. The stack size that clippy sees is an estimated value and can be vastly different
/// from the actual stack usage after optimizations passes have run (especially true in release mode).
/// Modern compilers are very smart and are able to optimize away a lot of unnecessary stack allocations.
/// In debug mode however, it is usually more accurate.
///
/// This lint works by summing up the size of all variables that the user typed, variables that were
/// implicitly introduced by the compiler for temporaries, function arguments and the return value,
/// and comparing them against a (configurable, but high-by-default).
///
/// ### Example
/// This function creates four 500 KB arrays on the stack. Quite big but just small enough to not trigger `large_stack_arrays`.
/// However, looking at the function as a whole, it's clear that this uses a lot of stack space.
/// ```rust
/// struct QuiteLargeType([u8; 500_000]);
/// fn foo() {
/// // ... some function that uses a lot of stack space ...
/// let _x1 = QuiteLargeType([0; 500_000]);
/// let _x2 = QuiteLargeType([0; 500_000]);
/// let _x3 = QuiteLargeType([0; 500_000]);
/// let _x4 = QuiteLargeType([0; 500_000]);
/// }
/// ```
///
/// Instead of doing this, allocate the arrays on the heap.
/// This currently requires going through a `Vec` first and then converting it to a `Box`:
/// ```rust
/// struct NotSoLargeType(Box<[u8]>);
///
/// fn foo() {
/// let _x1 = NotSoLargeType(vec![0; 500_000].into_boxed_slice());
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now heap allocated.
/// // The size of `NotSoLargeType` is 16 bytes.
/// // ...
/// }
/// ```
#[clippy::version = "1.71.0"]
pub LARGE_STACK_FRAMES,
nursery,
"checks for functions that allocate a lot of stack space"
}
pub struct LargeStackFrames {
maximum_allowed_size: u64,
}
impl LargeStackFrames {
#[must_use]
pub fn new(size: u64) -> Self {
Self {
maximum_allowed_size: size,
}
}
}
impl_lint_pass!(LargeStackFrames => [LARGE_STACK_FRAMES]);
#[derive(Copy, Clone)]
enum Space {
Used(u64),
Overflow,
}
impl Space {
pub fn exceeds_limit(self, limit: u64) -> bool {
match self {
Self::Used(used) => used > limit,
Self::Overflow => true,
}
}
}
impl AddAssign<u64> for Space {
fn add_assign(&mut self, rhs: u64) {
if let Self::Used(lhs) = self {
match lhs.checked_add(rhs) {
Some(sum) => *self = Self::Used(sum),
None => *self = Self::Overflow,
}
}
}
}
impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
_: FnKind<'tcx>,
_: &'tcx FnDecl<'tcx>,
_: &'tcx Body<'tcx>,
span: Span,
local_def_id: LocalDefId,
) {
let def_id = local_def_id.to_def_id();
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
if fn_has_unsatisfiable_preds(cx, def_id) {
return;
}
let mir = cx.tcx.optimized_mir(def_id);
let param_env = cx.tcx.param_env(def_id);
let mut frame_size = Space::Used(0);
for local in &mir.local_decls {
if let Ok(layout) = cx.tcx.layout_of(param_env.and(local.ty)) {
frame_size += layout.size.bytes();
}
}
if frame_size.exceeds_limit(self.maximum_allowed_size) {
span_lint_and_note(
cx,
LARGE_STACK_FRAMES,
span,
"this function allocates a large amount of stack space",
None,
"allocating large amounts of stack space can overflow the stack",
);
}
}
}

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet;
use rustc_hir::{Local, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
@ -25,14 +26,21 @@ declare_clippy_lint! {
declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped {
fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
if_chain! {
if !in_external_macro(cx.tcx.sess, local.span);
if let Some(ty) = local.ty; // Ensure that it has a type defined
if let TyKind::Infer = &ty.kind; // that type is '_'
if local.span.ctxt() == ty.span.ctxt();
then {
span_lint_and_help(cx,
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",

View file

@ -68,6 +68,7 @@ mod renamed_lints;
mod allow_attributes;
mod almost_complete_range;
mod approx_const;
mod arc_with_non_send_sync;
mod as_conversions;
mod asm_syntax;
mod assertions_on_constants;
@ -114,6 +115,7 @@ mod else_if_without_else;
mod empty_drop;
mod empty_enum;
mod empty_structs_with_brackets;
mod endian_bytes;
mod entry;
mod enum_clike;
mod enum_variants;
@ -121,6 +123,7 @@ mod equatable_if_let;
mod escape;
mod eta_reduction;
mod excessive_bools;
mod excessive_nesting;
mod exhaustive_items;
mod exit;
mod explicit_write;
@ -147,6 +150,7 @@ mod implicit_return;
mod implicit_saturating_add;
mod implicit_saturating_sub;
mod inconsistent_struct_constructor;
mod incorrect_impls;
mod index_refutable_slice;
mod indexing_slicing;
mod infinite_iter;
@ -165,6 +169,7 @@ mod large_enum_variant;
mod large_futures;
mod large_include_file;
mod large_stack_arrays;
mod large_stack_frames;
mod len_zero;
mod let_if_seq;
mod let_underscore;
@ -183,6 +188,7 @@ mod manual_is_ascii_check;
mod manual_let_else;
mod manual_main_separator_str;
mod manual_non_exhaustive;
mod manual_range_patterns;
mod manual_rem_euclid;
mod manual_retain;
mod manual_slice_size_calculation;
@ -191,9 +197,9 @@ mod manual_strip;
mod map_unit_fn;
mod match_result_ok;
mod matches;
mod mem_forget;
mod mem_replace;
mod methods;
mod min_ident_chars;
mod minmax;
mod misc;
mod misc_early;
@ -220,6 +226,7 @@ mod needless_borrowed_ref;
mod needless_continue;
mod needless_else;
mod needless_for_each;
mod needless_if;
mod needless_late_init;
mod needless_parens_on_range_literals;
mod needless_pass_by_value;
@ -256,6 +263,7 @@ mod pub_use;
mod question_mark;
mod question_mark_used;
mod ranges;
mod raw_strings;
mod rc_clone_in_vec_init;
mod read_zero_byte_vec;
mod redundant_async_block;
@ -266,6 +274,7 @@ mod redundant_field_names;
mod redundant_pub_crate;
mod redundant_slicing;
mod redundant_static_lifetimes;
mod redundant_type_annotations;
mod ref_option_ref;
mod ref_patterns;
mod reference;
@ -279,8 +288,10 @@ mod semicolon_if_nothing_returned;
mod serde_api;
mod shadow;
mod significant_drop_tightening;
mod single_call_fn;
mod single_char_lifetime_names;
mod single_component_path_imports;
mod single_range_in_vec_init;
mod size_of_in_element_count;
mod size_of_ref;
mod slow_vector_initialization;
@ -327,6 +338,7 @@ mod use_self;
mod useless_conversion;
mod vec;
mod vec_init_then_push;
mod visibility;
mod wildcard_imports;
mod write;
mod zero_div_zero;
@ -486,26 +498,27 @@ pub(crate) struct LintInfo {
explanation: &'static str,
}
pub fn explain(name: &str) {
pub fn explain(name: &str) -> i32 {
let target = format!("clippy::{}", name.to_ascii_uppercase());
match declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
Some(info) => {
println!("{}", info.explanation);
// Check if the lint has configuration
let mdconf = get_configuration_metadata();
if let Some(config_vec_positions) = mdconf
.iter()
.find_all(|cconf| cconf.lints.contains(&info.lint.name_lower()[8..].to_owned()))
{
// If it has, print it
println!("### Configuration for {}:\n", info.lint.name_lower());
for position in config_vec_positions {
let conf = &mdconf[position];
println!(" - {}: {} (default: {})", conf.name, conf.doc, conf.default);
}
if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
println!("{}", info.explanation);
// Check if the lint has configuration
let mdconf = get_configuration_metadata();
if let Some(config_vec_positions) = mdconf
.iter()
.find_all(|cconf| cconf.lints.contains(&info.lint.name_lower()[8..].to_owned()))
{
// If it has, print it
println!("### Configuration for {}:\n", info.lint.name_lower());
for position in config_vec_positions {
let conf = &mdconf[position];
println!(" - {}: {} (default: {})", conf.name, conf.doc, conf.default);
}
},
None => println!("unknown lint: {name}"),
}
0
} else {
println!("unknown lint: {name}");
1
}
}
@ -564,6 +577,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::<utils::internal_lints::unnecessary_def_path::UnnecessaryDefPath>::default());
store.register_late_pass(|_| Box::new(utils::internal_lints::outer_expn_data_pass::OuterExpnDataPass));
store.register_late_pass(|_| Box::new(utils::internal_lints::msrv_attr_impl::MsrvAttrImpl));
store.register_late_pass(|_| {
Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new())
});
}
let arithmetic_side_effects_allowed = conf.arithmetic_side_effects_allowed.clone();
@ -672,7 +688,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
store.register_late_pass(|_| Box::new(loops::Loops));
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv())));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
store.register_late_pass(|_| Box::new(entry::HashMapPass));
@ -693,7 +709,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
let too_large_for_stack = conf.too_large_for_stack;
store.register_late_pass(move |_| Box::new(escape::BoxedLocal { too_large_for_stack }));
store.register_late_pass(move |_| Box::new(vec::UselessVec { too_large_for_stack }));
store.register_late_pass(move |_| {
Box::new(vec::UselessVec {
too_large_for_stack,
msrv: msrv(),
})
});
store.register_late_pass(|_| Box::new(panic_unimplemented::PanicUnimplemented));
store.register_late_pass(|_| Box::new(strings::StringLitAsBytes));
store.register_late_pass(|_| Box::new(derive::Derive));
@ -725,7 +746,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
let missing_docs_in_crate_items = conf.missing_docs_in_crate_items;
store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply));
store.register_late_pass(|_| Box::new(mem_forget::MemForget));
store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq));
store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(missing_docs_in_crate_items)));
@ -751,7 +771,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::<useless_conversion::UselessConversion>::default());
store.register_late_pass(|_| Box::new(implicit_hasher::ImplicitHasher));
store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom));
store.register_late_pass(|_| Box::new(question_mark::QuestionMark));
store.register_late_pass(|_| Box::<question_mark::QuestionMark>::default());
store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed));
store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl));
@ -773,7 +793,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString));
let max_trait_bounds = conf.max_trait_bounds;
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds, msrv())));
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
@ -785,7 +805,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
store.register_early_pass(|| Box::new(formatting::Formatting));
store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
store.register_early_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
@ -810,10 +829,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
let allow_private_module_inception = conf.allow_private_module_inception;
store.register_late_pass(move |_| {
Box::new(enum_variants::EnumVariantNames::new(
enum_variant_name_threshold,
avoid_breaking_exported_api,
allow_private_module_inception,
))
});
store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
@ -833,7 +854,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
store.register_early_pass(|| Box::new(as_conversions::AsConversions));
store.register_late_pass(|_| Box::new(as_conversions::AsConversions));
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
let max_fn_params_bools = conf.max_fn_params_bools;
@ -909,7 +930,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
enable_raw_pointer_heuristic_for_send,
))
});
store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
let accept_comment_above_statement = conf.accept_comment_above_statement;
let accept_comment_above_attributes = conf.accept_comment_above_attributes;
store.register_late_pass(move |_| {
Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(
accept_comment_above_statement,
accept_comment_above_attributes,
))
});
let allow_mixed_uninlined = conf.allow_mixed_uninlined_format_args;
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined)));
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
@ -1002,11 +1030,48 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
let excessive_nesting_threshold = conf.excessive_nesting_threshold;
store.register_early_pass(move || {
Box::new(excessive_nesting::ExcessiveNesting {
excessive_nesting_threshold,
nodes: rustc_ast::node_id::NodeSet::new(),
})
});
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
store.register_early_pass(|| Box::new(ref_patterns::RefPatterns));
store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs));
store.register_early_pass(|| Box::new(needless_else::NeedlessElse));
store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug));
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf));
let allowed_idents_below_min_chars = conf.allowed_idents_below_min_chars.clone();
let min_ident_chars_threshold = conf.min_ident_chars_threshold;
store.register_late_pass(move |_| {
Box::new(min_ident_chars::MinIdentChars {
allowed_idents_below_min_chars: allowed_idents_below_min_chars.clone(),
min_ident_chars_threshold,
})
});
let stack_size_threshold = conf.stack_size_threshold;
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
store.register_late_pass(move |_| {
Box::new(single_call_fn::SingleCallFn {
avoid_breaking_exported_api,
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
})
});
let needless_raw_string_hashes_allow_one = conf.allow_one_hash_in_raw_strings;
store.register_early_pass(move || {
Box::new(raw_strings::RawStrings {
needless_raw_string_hashes_allow_one,
})
});
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
store.register_early_pass(|| Box::new(visibility::Visibility));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
use itertools::Itertools;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
@ -201,7 +202,19 @@ fn check_fn_inner<'tcx>(
span_lint_and_then(
cx,
NEEDLESS_LIFETIMES,
span.with_hi(sig.decl.output.span().hi()),
elidable_lts
.iter()
.map(|&lt| cx.tcx.def_span(lt))
.chain(usages.iter().filter_map(|usage| {
if let LifetimeName::Param(def_id) = usage.res
&& elidable_lts.contains(&def_id)
{
return Some(usage.ident.span);
}
None
}))
.collect_vec(),
&format!("the following explicit lifetimes could be elided: {lts}"),
|diag| {
if sig.header.is_async() {
@ -562,7 +575,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
// if the bounds define new lifetimes, they are fine to occur
let allowed_lts = allowed_lts_from(pred.bound_generic_params);
// now walk the bounds
for bound in pred.bounds.iter() {
for bound in pred.bounds {
walk_param_bound(&mut visitor, bound);
}
// and check that all lifetimes are allowed

View file

@ -5,15 +5,76 @@ use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_span::symbol::sym;
#[derive(Clone, Copy)]
enum AdjustKind {
None,
Borrow,
BorrowMut,
Reborrow,
ReborrowMut,
}
impl AdjustKind {
fn borrow(mutbl: AutoBorrowMutability) -> Self {
match mutbl {
AutoBorrowMutability::Not => Self::Borrow,
AutoBorrowMutability::Mut { .. } => Self::BorrowMut,
}
}
fn reborrow(mutbl: AutoBorrowMutability) -> Self {
match mutbl {
AutoBorrowMutability::Not => Self::Reborrow,
AutoBorrowMutability::Mut { .. } => Self::ReborrowMut,
}
}
fn display(self) -> &'static str {
match self {
Self::None => "",
Self::Borrow => "&",
Self::BorrowMut => "&mut ",
Self::Reborrow => "&*",
Self::ReborrowMut => "&mut *",
}
}
}
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) {
let self_ty = cx.typeck_results().expr_ty(self_arg);
let self_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
if !(self_ty == self_ty_adjusted && is_trait_method(cx, call_expr, sym::IntoIterator)) {
if !is_trait_method(cx, call_expr, sym::IntoIterator) {
return;
}
let typeck = cx.typeck_results();
let self_ty = typeck.expr_ty(self_arg);
let adjust = match typeck.expr_adjustments(self_arg) {
[] => AdjustKind::None,
&[
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
..
},
] => AdjustKind::borrow(mutbl),
&[
Adjustment {
kind: Adjust::Deref(_), ..
},
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
target,
},
] => {
if self_ty == target && matches!(mutbl, AutoBorrowMutability::Not) {
AdjustKind::None
} else {
AdjustKind::reborrow(mutbl)
}
},
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
span_lint_and_sugg(
@ -23,7 +84,7 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<
"it is more concise to loop over containers instead of using explicit \
iteration methods",
"to write this more concisely, try",
object.to_string(),
format!("{}{object}", adjust.display()),
applicability,
);
}

View file

@ -1,75 +1,235 @@
use super::EXPLICIT_ITER_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::{
implements_trait, implements_trait_with_env, is_copy, make_normalized_projection,
make_normalized_projection_with_regions, normalize_with_regions,
};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, EarlyBinder, Ty, TypeAndMut};
use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) {
let should_lint = match method_name {
"iter" | "iter_mut" => is_ref_iterable_type(cx, self_arg),
"into_iter" if is_trait_method(cx, arg, sym::IntoIterator) => {
let receiver_ty = cx.typeck_results().expr_ty(self_arg);
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
let ref_receiver_ty = cx.tcx.mk_ref(
cx.tcx.lifetimes.re_erased,
ty::TypeAndMut {
ty: receiver_ty,
mutbl: Mutability::Not,
},
);
receiver_ty_adjusted == ref_receiver_ty
},
_ => false,
};
if !should_lint {
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, msrv: &Msrv) {
let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr) else {
return;
};
if let ty::Array(_, count) = *ty.peel_refs().kind() {
if !ty.is_ref() {
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
return;
}
} else if count
.try_eval_target_usize(cx.tcx, cx.param_env)
.map_or(true, |x| x > 32)
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
{
return;
}
}
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
let muta = if method_name == "iter_mut" { "mut " } else { "" };
span_lint_and_sugg(
cx,
EXPLICIT_ITER_LOOP,
arg.span,
call_expr.span,
"it is more concise to loop over references to containers instead of using explicit \
iteration methods",
"to write this more concisely, try",
format!("&{muta}{object}"),
format!("{}{object}", adjust.display()),
applicability,
);
}
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
/// for `&T` and `&mut T`, such as `Vec`.
#[rustfmt::skip]
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
// will allow further borrows afterwards
let ty = cx.typeck_results().expr_ty(e);
is_iterable_array(ty, cx) ||
is_type_diagnostic_item(cx, ty, sym::Vec) ||
is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
is_type_diagnostic_item(cx, ty, sym::HashMap) ||
is_type_diagnostic_item(cx, ty, sym::HashSet) ||
is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
is_type_diagnostic_item(cx, ty, sym::BTreeSet)
#[derive(Clone, Copy)]
enum AdjustKind {
None,
Borrow,
BorrowMut,
Deref,
Reborrow,
ReborrowMut,
}
impl AdjustKind {
fn borrow(mutbl: Mutability) -> Self {
match mutbl {
Mutability::Not => Self::Borrow,
Mutability::Mut => Self::BorrowMut,
}
}
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
match ty.kind() {
ty::Array(_, n) => n
.try_eval_target_usize(cx.tcx, cx.param_env)
.map_or(false, |val| (0..=32).contains(&val)),
_ => false,
fn auto_borrow(mutbl: AutoBorrowMutability) -> Self {
match mutbl {
AutoBorrowMutability::Not => Self::Borrow,
AutoBorrowMutability::Mut { .. } => Self::BorrowMut,
}
}
fn reborrow(mutbl: Mutability) -> Self {
match mutbl {
Mutability::Not => Self::Reborrow,
Mutability::Mut => Self::ReborrowMut,
}
}
fn auto_reborrow(mutbl: AutoBorrowMutability) -> Self {
match mutbl {
AutoBorrowMutability::Not => Self::Reborrow,
AutoBorrowMutability::Mut { .. } => Self::ReborrowMut,
}
}
fn display(self) -> &'static str {
match self {
Self::None => "",
Self::Borrow => "&",
Self::BorrowMut => "&mut ",
Self::Deref => "*",
Self::Reborrow => "&*",
Self::ReborrowMut => "&mut *",
}
}
}
/// Checks if an `iter` or `iter_mut` call returns `IntoIterator::IntoIter`. Returns how the
/// argument needs to be adjusted.
#[expect(clippy::too_many_lines)]
fn is_ref_iterable<'tcx>(
cx: &LateContext<'tcx>,
self_arg: &Expr<'_>,
call_expr: &Expr<'_>,
) -> Option<(AdjustKind, Ty<'tcx>)> {
let typeck = cx.typeck_results();
if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
&& let Some(fn_id) = typeck.type_dependent_def_id(call_expr.hir_id)
&& let sig = cx.tcx.liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder())
&& let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output
&& let param_env = cx.tcx.param_env(fn_id)
&& implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, [])
&& let Some(into_iter_ty) =
make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty])
&& let req_res_ty = normalize_with_regions(cx.tcx, param_env, req_res_ty)
&& into_iter_ty == req_res_ty
{
let adjustments = typeck.expr_adjustments(self_arg);
let self_ty = typeck.expr_ty(self_arg);
let self_is_copy = is_copy(cx, self_ty);
if adjustments.is_empty() && self_is_copy {
// Exact type match, already checked earlier
return Some((AdjustKind::None, self_ty));
}
let res_ty = cx.tcx.erase_regions(EarlyBinder::bind(req_res_ty)
.subst(cx.tcx, typeck.node_substs(call_expr.hir_id)));
let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() {
Some(mutbl)
} else {
None
};
if !adjustments.is_empty() {
if self_is_copy {
// Using by value won't consume anything
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::None, self_ty));
}
} else if let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind()
&& let Some(mutbl) = mutbl
{
// Attempt to reborrow the mutable reference
let self_ty = if mutbl.is_mut() {
self_ty
} else {
cx.tcx.mk_ref(region, TypeAndMut { ty, mutbl })
};
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::reborrow(mutbl), self_ty));
}
}
}
if let Some(mutbl) = mutbl
&& !self_ty.is_ref()
{
// Attempt to borrow
let self_ty = cx.tcx.mk_ref(cx.tcx.lifetimes.re_erased, TypeAndMut {
ty: self_ty,
mutbl,
});
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::borrow(mutbl), self_ty));
}
}
match adjustments {
[] => Some((AdjustKind::None, self_ty)),
&[
Adjustment { kind: Adjust::Deref(_), ..},
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
target,
},
..
] => {
if target != self_ty
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
&& ty == res_ty
{
Some((AdjustKind::auto_reborrow(mutbl), target))
} else {
None
}
}
&[Adjustment { kind: Adjust::Deref(_), target }, ..] => {
if is_copy(cx, target)
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
&& ty == res_ty
{
Some((AdjustKind::Deref, target))
} else {
None
}
}
&[
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
target,
},
..
] => {
if self_ty.is_ref()
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
&& ty == res_ty
{
Some((AdjustKind::auto_borrow(mutbl), target))
} else {
None
}
}
_ => None,
}
} else {
None
}
}

View file

@ -51,7 +51,7 @@ pub(super) fn check<'tcx>(
iter_b = Some(get_assignment(body));
}
let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter());
let assignments = iter_a.into_iter().flatten().chain(iter_b);
let big_sugg = assignments
// The only statements in the for loops can be indexed assignments from
@ -402,7 +402,7 @@ fn get_assignments<'a, 'tcx>(
StmtKind::Local(..) | StmtKind::Item(..) => None,
StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
})
.chain((*expr).into_iter())
.chain(*expr)
.filter(move |e| {
if let ExprKind::AssignOp(_, place, _) = e.kind {
path_to_local(place).map_or(false, |id| {

View file

@ -20,9 +20,10 @@ mod while_let_loop;
mod while_let_on_iterator;
use clippy_utils::higher;
use clippy_utils::msrvs::Msrv;
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
@ -479,7 +480,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Check for unnecessary `if let` usage in a for loop
/// Checks for unnecessary `if let` usage in a for loop
/// where only the `Some` or `Ok` variant of the iterator element is used.
///
/// ### Why is this bad?
@ -511,7 +512,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Check for empty spin loops
/// Checks for empty spin loops
///
/// ### Why is this bad?
/// The loop body should have something like `thread::park()` or at least
@ -547,7 +548,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Check for manual implementations of Iterator::find
/// Checks for manual implementations of Iterator::find
///
/// ### Why is this bad?
/// It doesn't affect performance, but using `find` is shorter and easier to read.
@ -606,7 +607,15 @@ declare_clippy_lint! {
"checking for emptiness of a `Vec` in the loop condition and popping an element in the body"
}
declare_lint_pass!(Loops => [
pub struct Loops {
msrv: Msrv,
}
impl Loops {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(Loops => [
MANUAL_MEMCPY,
MANUAL_FLATTEN,
NEEDLESS_RANGE_LOOP,
@ -645,7 +654,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
if body.span.from_expansion() {
return;
}
check_for_loop(cx, pat, arg, body, expr, span);
self.check_for_loop(cx, pat, arg, body, expr, span);
if let ExprKind::Block(block, _) = body.kind {
never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
}
@ -678,46 +687,48 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
manual_while_let_some::check(cx, condition, body, span);
}
}
extract_msrv_attr!(LateContext);
}
fn check_for_loop<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
arg: &'tcx Expr<'_>,
body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
span: Span,
) {
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
if !is_manual_memcpy_triggered {
needless_range_loop::check(cx, pat, arg, body, expr);
explicit_counter_loop::check(cx, pat, arg, body, expr);
impl Loops {
fn check_for_loop<'tcx>(
&self,
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
arg: &'tcx Expr<'_>,
body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
span: Span,
) {
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
if !is_manual_memcpy_triggered {
needless_range_loop::check(cx, pat, arg, body, expr);
explicit_counter_loop::check(cx, pat, arg, body, expr);
}
self.check_for_loop_arg(cx, pat, arg);
for_kv_map::check(cx, pat, arg, body);
mut_range_bound::check(cx, arg, body);
single_element_loop::check(cx, pat, arg, body, expr);
same_item_push::check(cx, pat, arg, body, expr);
manual_flatten::check(cx, pat, arg, body, span);
manual_find::check(cx, pat, arg, body, span, expr);
}
check_for_loop_arg(cx, pat, arg);
for_kv_map::check(cx, pat, arg, body);
mut_range_bound::check(cx, arg, body);
single_element_loop::check(cx, pat, arg, body, expr);
same_item_push::check(cx, pat, arg, body, expr);
manual_flatten::check(cx, pat, arg, body, span);
manual_find::check(cx, pat, arg, body, span, expr);
}
fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
let method_name = method.ident.as_str();
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
match method_name {
"iter" | "iter_mut" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name);
},
"into_iter" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name);
explicit_into_iter_loop::check(cx, self_arg, arg);
},
"next" => {
iter_next_loop::check(cx, arg);
},
_ => {},
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
match method.ident.as_str() {
"iter" | "iter_mut" => {
explicit_iter_loop::check(cx, self_arg, arg, &self.msrv);
},
"into_iter" => {
explicit_into_iter_loop::check(cx, self_arg, arg);
},
"next" => {
iter_next_loop::check(cx, arg);
},
_ => {},
}
}
}
}

View file

@ -1,22 +1,23 @@
use super::utils::make_iterator_snippet;
use super::NEVER_LOOP;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::consts::constant;
use clippy_utils::higher::ForLoop;
use clippy_utils::source::snippet;
use clippy_utils::{consts::Constant, diagnostics::span_lint_and_then};
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use std::iter::{once, Iterator};
pub(super) fn check(
cx: &LateContext<'_>,
block: &Block<'_>,
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
block: &Block<'tcx>,
loop_id: HirId,
span: Span,
for_loop: Option<&ForLoop<'_>>,
) {
match never_loop_block(block, &mut Vec::new(), loop_id) {
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
NeverLoopResult::AlwaysBreak => {
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
if let Some(ForLoop {
@ -95,7 +96,12 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirI
}
}
fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
fn never_loop_block<'tcx>(
cx: &LateContext<'tcx>,
block: &Block<'tcx>,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
let iter = block
.stmts
.iter()
@ -103,10 +109,10 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id
.chain(block.expr.map(|expr| (expr, None)));
iter.map(|(e, els)| {
let e = never_loop_expr(e, ignore_ids, main_loop_id);
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
// els is an else block in a let...else binding
els.map_or(e, |els| {
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids)
combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
})
})
.fold(NeverLoopResult::Otherwise, combine_seq)
@ -122,7 +128,12 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
}
#[allow(clippy::too_many_lines)]
fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
fn never_loop_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
match expr.kind {
ExprKind::Unary(_, e)
| ExprKind::Cast(e, _)
@ -130,45 +141,51 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
| ExprKind::Field(e, _)
| ExprKind::AddrOf(_, _, e)
| ExprKind::Repeat(e, _)
| ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, main_loop_id),
ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, ignore_ids, main_loop_id),
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), ignore_ids, main_loop_id),
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
cx,
&mut std::iter::once(receiver).chain(es.iter()),
ignore_ids,
main_loop_id,
),
ExprKind::Struct(_, fields, base) => {
let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
if let Some(base) = base {
combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id))
combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
} else {
fields
}
},
ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, e1, e2)
| ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
| ExprKind::Index(e1, e2) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
ExprKind::Loop(b, _, _, _) => {
// Break can come from the inner loop so remove them.
absorb_break(never_loop_block(b, ignore_ids, main_loop_id))
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
},
ExprKind::If(e, e2, e3) => {
let e1 = never_loop_expr(e, ignore_ids, main_loop_id);
let e2 = never_loop_expr(e2, ignore_ids, main_loop_id);
let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
// If we know the `if` condition evaluates to `true`, don't check everything past it; it
// should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
return combine_seq(e1, e2);
}
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
never_loop_expr(e, ignore_ids, main_loop_id)
never_loop_expr(cx, e, ignore_ids, main_loop_id)
});
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
},
ExprKind::Match(e, arms, _) => {
let e = never_loop_expr(e, ignore_ids, main_loop_id);
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
if arms.is_empty() {
e
} else {
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
combine_seq(e, arms)
}
},
@ -176,7 +193,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
if l.is_some() {
ignore_ids.push(b.hir_id);
}
let ret = never_loop_block(b, ignore_ids, main_loop_id);
let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
if l.is_some() {
ignore_ids.pop();
}
@ -198,31 +215,30 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
// checks if break targets a block instead of a loop
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
never_loop_expr(e, ignore_ids, main_loop_id)
never_loop_expr(cx, e, ignore_ids, main_loop_id)
}),
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
combine_seq(
never_loop_expr(e, ignore_ids, main_loop_id),
never_loop_expr(cx, e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
)
}),
ExprKind::Become(e) => {
combine_seq(
never_loop_expr(e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
)
}
ExprKind::Become(e) => combine_seq(
never_loop_expr(cx, e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
),
ExprKind::InlineAsm(asm) => asm
.operands
.iter()
.map(|(o, _)| match o {
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
never_loop_expr(expr, ignore_ids, main_loop_id)
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
},
InlineAsmOperand::Out { expr, .. } => {
never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id)
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
},
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
cx,
&mut once(*in_expr).chain(out_expr.iter().copied()),
ignore_ids,
main_loop_id,
@ -242,22 +258,24 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
}
}
fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
es: &mut T,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
.fold(NeverLoopResult::Otherwise, combine_seq)
}
fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
e: &mut T,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids)
combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
})
}

View file

@ -148,7 +148,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
}
fn visit_block(&mut self, b: &'tcx Block<'_>) {
for stmt in b.stmts.iter() {
for stmt in b.stmts {
self.visit_stmt(stmt);
}
}

View file

@ -35,7 +35,8 @@ struct PathAndSpan {
span: Span,
}
/// `MacroRefData` includes the name of the macro.
/// `MacroRefData` includes the name of the macro
/// and the path from `SourceMap::span_to_filename`.
#[derive(Debug, Clone)]
pub struct MacroRefData {
name: String,
@ -100,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
});
if !id.is_local();
then {
for kid in cx.tcx.module_children(id).iter() {
for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);

View file

@ -6,17 +6,18 @@ use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, Visitable};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use serde::Deserialize;
use std::ops::ControlFlow;
use std::slice;
declare_clippy_lint! {
/// ### What it does
@ -81,11 +82,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
{
match if_let_or_match {
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => if_chain! {
if expr_is_simple_identity(let_pat, if_then);
if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then);
if let Some(if_else) = if_else;
if expr_diverges(cx, if_else);
then {
emit_manual_let_else(cx, stmt.span, if_let_expr, local.pat, let_pat, if_else);
emit_manual_let_else(cx, stmt.span, if_let_expr, &ident_map, let_pat, if_else);
}
},
IfLetOrMatch::Match(match_expr, arms, source) => {
@ -118,11 +119,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
return;
}
let pat_arm = &arms[1 - idx];
if !expr_is_simple_identity(pat_arm.pat, pat_arm.body) {
return;
}
let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else {
return
};
emit_manual_let_else(cx, stmt.span, match_expr, local.pat, pat_arm.pat, diverging_arm.body);
emit_manual_let_else(cx, stmt.span, match_expr, &ident_map, pat_arm.pat, diverging_arm.body);
},
}
};
@ -135,7 +136,7 @@ fn emit_manual_let_else(
cx: &LateContext<'_>,
span: Span,
expr: &Expr<'_>,
local: &Pat<'_>,
ident_map: &FxHashMap<Symbol, &Pat<'_>>,
pat: &Pat<'_>,
else_body: &Expr<'_>,
) {
@ -146,8 +147,8 @@ fn emit_manual_let_else(
"this could be rewritten as `let...else`",
|diag| {
// This is far from perfect, for example there needs to be:
// * tracking for multi-binding cases: let (foo, bar) = if let (Some(foo), Ok(bar)) = ...
// * renamings of the bindings for many `PatKind`s like structs, slices, etc.
// * renamings of the bindings for many `PatKind`s like slices, etc.
// * limitations in the existing replacement algorithms
// * unused binding collision detection with existing ones
// for this to be machine applicable.
let mut app = Applicability::HasPlaceholders;
@ -159,57 +160,126 @@ fn emit_manual_let_else(
} else {
format!("{{ {sn_else} }}")
};
let sn_bl = replace_in_pattern(cx, span, local, pat, &mut app);
let sn_bl = replace_in_pattern(cx, span, ident_map, pat, &mut app, true);
let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};");
diag.span_suggestion(span, "consider writing", sugg, app);
},
);
}
// replaces the locals in the pattern
/// Replaces the locals in the pattern
///
/// For this example:
///
/// ```ignore
/// let (a, FooBar { b, c }) = if let Bar { Some(a_i), b_i } = ex { (a_i, b_i) } else { return };
/// ```
///
/// We have:
///
/// ```ignore
/// pat: Bar { Some(a_i), b_i }
/// ident_map: (a_i) -> (a), (b_i) -> (FooBar { b, c })
/// ```
///
/// We return:
///
/// ```ignore
/// Bar { Some(a), b_i: FooBar { b, c } }
/// ```
fn replace_in_pattern(
cx: &LateContext<'_>,
span: Span,
local: &Pat<'_>,
ident_map: &FxHashMap<Symbol, &Pat<'_>>,
pat: &Pat<'_>,
app: &mut Applicability,
top_level: bool,
) -> String {
let mut bindings_count = 0;
pat.each_binding_or_first(&mut |_, _, _, _| bindings_count += 1);
// If the pattern creates multiple bindings, exit early,
// as otherwise we might paste the pattern to the positions of multiple bindings.
if bindings_count > 1 {
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", app);
return sn_pat.into_owned();
}
// We put a labeled block here so that we can implement the fallback in this function.
// As the function has multiple call sites, implementing the fallback via an Option<T>
// return type and unwrap_or_else would cause repetition. Similarly, the function also
// invokes the fall back multiple times.
'a: {
// If the ident map is empty, there is no replacement to do.
// The code following this if assumes a non-empty ident_map.
if ident_map.is_empty() {
break 'a;
}
match pat.kind {
PatKind::Binding(..) => {
let (sn_bdg, _) = snippet_with_context(cx, local.span, span.ctxt(), "", app);
return sn_bdg.to_string();
},
PatKind::Or(pats) => {
let patterns = pats
.iter()
.map(|pat| replace_in_pattern(cx, span, local, pat, app))
.collect::<Vec<_>>();
let or_pat = patterns.join(" | ");
return format!("({or_pat})");
},
// Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`.
PatKind::TupleStruct(ref w, args, dot_dot_pos) => {
let mut args = args
.iter()
.map(|pat| replace_in_pattern(cx, span, local, pat, app))
.collect::<Vec<_>>();
if let Some(pos) = dot_dot_pos.as_opt_usize() {
args.insert(pos, "..".to_owned());
}
let args = args.join(", ");
let sn_wrapper = cx.sess().source_map().span_to_snippet(w.span()).unwrap_or_default();
return format!("{sn_wrapper}({args})");
},
_ => {},
match pat.kind {
PatKind::Binding(_ann, _id, binding_name, opt_subpt) => {
let Some(pat_to_put) = ident_map.get(&binding_name.name) else { break 'a };
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
if let Some(subpt) = opt_subpt {
let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false);
return format!("{sn_ptp} @ {subpt}");
}
return sn_ptp.to_string();
},
PatKind::Or(pats) => {
let patterns = pats
.iter()
.map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false))
.collect::<Vec<_>>();
let or_pat = patterns.join(" | ");
if top_level {
return format!("({or_pat})");
}
return or_pat;
},
PatKind::Struct(path, fields, has_dot_dot) => {
let fields = fields
.iter()
.map(|fld| {
if let PatKind::Binding(_, _, name, None) = fld.pat.kind &&
let Some(pat_to_put) = ident_map.get(&name.name)
{
let (sn_fld_name, _) = snippet_with_context(cx, fld.ident.span, span.ctxt(), "", app);
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
// TODO: this is a bit of a hack, but it does its job. Ideally, we'd check if pat_to_put is
// a PatKind::Binding but that is also hard to get right.
if sn_fld_name == sn_ptp {
// Field init shorthand
return format!("{sn_fld_name}");
}
return format!("{sn_fld_name}: {sn_ptp}");
}
let (sn_fld, _) = snippet_with_context(cx, fld.span, span.ctxt(), "", app);
sn_fld.into_owned()
})
.collect::<Vec<_>>();
let fields_string = fields.join(", ");
let dot_dot_str = if has_dot_dot { " .." } else { "" };
let (sn_pth, _) = snippet_with_context(cx, path.span(), span.ctxt(), "", app);
return format!("{sn_pth} {{ {fields_string}{dot_dot_str} }}");
},
// Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`.
PatKind::TupleStruct(ref w, args, dot_dot_pos) => {
let mut args = args
.iter()
.map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false))
.collect::<Vec<_>>();
if let Some(pos) = dot_dot_pos.as_opt_usize() {
args.insert(pos, "..".to_owned());
}
let args = args.join(", ");
let sn_wrapper = cx.sess().source_map().span_to_snippet(w.span()).unwrap_or_default();
return format!("{sn_wrapper}({args})");
},
PatKind::Tuple(args, dot_dot_pos) => {
let mut args = args
.iter()
.map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false))
.collect::<Vec<_>>();
if let Some(pos) = dot_dot_pos.as_opt_usize() {
args.insert(pos, "..".to_owned());
}
let args = args.join(", ");
return format!("({args})");
},
_ => {},
}
}
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", app);
sn_pat.into_owned()
@ -353,37 +423,74 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo
!has_disallowed
}
/// Checks if the passed block is a simple identity referring to bindings created by the pattern
fn expr_is_simple_identity(pat: &'_ Pat<'_>, expr: &'_ Expr<'_>) -> bool {
// We support patterns with multiple bindings and tuples, like:
// let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
/// Checks if the passed block is a simple identity referring to bindings created by the pattern,
/// and if yes, returns a mapping between the relevant sub-pattern and the identifier it corresponds
/// to.
///
/// We support patterns with multiple bindings and tuples, e.g.:
///
/// ```ignore
/// let (foo_o, bar_o) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
/// ```
///
/// The expected params would be:
///
/// ```ignore
/// local_pat: (foo_o, bar_o)
/// let_pat: (Some(foo), bar)
/// expr: (foo, bar)
/// ```
///
/// We build internal `sub_pats` so that it looks like `[foo_o, bar_o]` and `paths` so that it looks
/// like `[foo, bar]`. Then we turn that into `FxHashMap [(foo) -> (foo_o), (bar) -> (bar_o)]` which
/// we return.
fn expr_simple_identity_map<'a, 'hir>(
local_pat: &'a Pat<'hir>,
let_pat: &'_ Pat<'hir>,
expr: &'_ Expr<'hir>,
) -> Option<FxHashMap<Symbol, &'a Pat<'hir>>> {
let peeled = peel_blocks(expr);
let paths = match peeled.kind {
ExprKind::Tup(exprs) | ExprKind::Array(exprs) => exprs,
ExprKind::Path(_) => std::slice::from_ref(peeled),
_ => return false,
let (sub_pats, paths) = match (local_pat.kind, peeled.kind) {
(PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) | (PatKind::Slice(pats, ..), ExprKind::Array(exprs)) => {
(pats, exprs)
},
(_, ExprKind::Path(_)) => (slice::from_ref(local_pat), slice::from_ref(peeled)),
_ => return None,
};
// There is some length mismatch, which indicates usage of .. in the patterns above e.g.:
// let (a, ..) = if let [a, b, _c] = ex { (a, b) } else { ... };
// We bail in these cases as they should be rare.
if paths.len() != sub_pats.len() {
return None;
}
let mut pat_bindings = FxHashSet::default();
pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| {
let_pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| {
pat_bindings.insert(ident);
});
if pat_bindings.len() < paths.len() {
return false;
// This rebinds some bindings from the outer scope, or it repeats some copy-able bindings multiple
// times. We don't support these cases so we bail here. E.g.:
// let foo = 0;
// let (new_foo, bar, bar_copied) = if let Some(bar) = Some(0) { (foo, bar, bar) } else { .. };
return None;
}
for path in paths {
if_chain! {
if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind;
if let [path_seg] = path.segments;
then {
if !pat_bindings.remove(&path_seg.ident) {
return false;
}
} else {
return false;
let mut ident_map = FxHashMap::default();
for (sub_pat, path) in sub_pats.iter().zip(paths.iter()) {
if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind &&
let [path_seg] = path.segments
{
let ident = path_seg.ident;
if !pat_bindings.remove(&ident) {
return None;
}
ident_map.insert(ident.name, sub_pat);
} else {
return None;
}
}
true
Some(ident_map)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]

View file

@ -0,0 +1,123 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::ExprKind;
use rustc_hir::PatKind;
use rustc_hir::RangeEnd;
use rustc_lint::LintContext;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Looks for combined OR patterns that are all contained in a specific range,
/// e.g. `6 | 4 | 5 | 9 | 7 | 8` can be rewritten as `4..=9`.
///
/// ### Why is this bad?
/// Using an explicit range is more concise and easier to read.
///
/// ### Example
/// ```rust
/// let x = 6;
/// let foo = matches!(x, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
/// ```
/// Use instead:
/// ```rust
/// let x = 6;
/// let foo = matches!(x, 1..=10);
/// ```
#[clippy::version = "1.72.0"]
pub MANUAL_RANGE_PATTERNS,
complexity,
"manually writing range patterns using a combined OR pattern (`|`)"
}
declare_lint_pass!(ManualRangePatterns => [MANUAL_RANGE_PATTERNS]);
fn expr_as_u128(expr: &Expr<'_>) -> Option<u128> {
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Int(num, _) = lit.node
{
Some(num)
} else {
None
}
}
impl LateLintPass<'_> for ManualRangePatterns {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
if in_external_macro(cx.sess(), pat.span) {
return;
}
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
if let PatKind::Or(pats) = pat.kind
&& pats.len() >= 3
{
let mut min = u128::MAX;
let mut max = 0;
let mut numbers_found = FxHashSet::default();
let mut ranges_found = Vec::new();
for pat in pats {
if let PatKind::Lit(lit) = pat.kind
&& let Some(num) = expr_as_u128(lit)
{
numbers_found.insert(num);
min = min.min(num);
max = max.max(num);
} else if let PatKind::Range(Some(left), Some(right), end) = pat.kind
&& let Some(left) = expr_as_u128(left)
&& let Some(right) = expr_as_u128(right)
&& right >= left
{
min = min.min(left);
max = max.max(right);
ranges_found.push(left..=match end {
RangeEnd::Included => right,
RangeEnd::Excluded => right - 1,
});
} else {
return;
}
}
let contains_whole_range = 'contains: {
let mut num = min;
while num <= max {
if numbers_found.contains(&num) {
num += 1;
}
// Given a list of (potentially overlapping) ranges like:
// 1..=5, 3..=7, 6..=10
// We want to find the range with the highest end that still contains the current number
else if let Some(range) = ranges_found
.iter()
.filter(|range| range.contains(&num))
.max_by_key(|range| range.end())
{
num = range.end() + 1;
} else {
break 'contains false;
}
}
break 'contains true;
};
if contains_whole_range {
span_lint_and_sugg(
cx,
MANUAL_RANGE_PATTERNS,
pat.span,
"this OR pattern can be rewritten using a range",
"try",
format!("{min}..={max}"),
Applicability::MachineApplicable,
);
}
}
}
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
use clippy_utils::{is_lint_allowed, path_to_local, search_same, SpanlessEq, SpanlessHash};
use core::cmp::Ordering;
use core::iter;
use core::slice;
@ -9,6 +9,7 @@ use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatKind, RangeEnd};
use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Symbol;
@ -103,17 +104,21 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
if matches!(arm2.pat.kind, PatKind::Wild) {
span_lint_and_then(
cx,
MATCH_SAME_ARMS,
arm1.span,
"this match arm has an identical body to the `_` wildcard arm",
|diag| {
diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect)
.help("or try changing either arm body")
.span_note(arm2.span, "`_` wildcard arm here");
},
);
if !cx.tcx.features().non_exhaustive_omitted_patterns_lint
|| is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
{
span_lint_and_then(
cx,
MATCH_SAME_ARMS,
arm1.span,
"this match arm has an identical body to the `_` wildcard arm",
|diag| {
diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect)
.help("or try changing either arm body")
.span_note(arm2.span, "`_` wildcard arm here");
},
);
}
} else {
let back_block = backwards_blocking_idxs[j];
let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {

View file

@ -25,7 +25,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'
let mut ident_bind_name = kw::Underscore;
if !matching_wild {
// Looking for unused bindings (i.e.: `_e`)
for pat in inner.iter() {
for pat in inner {
if let PatKind::Binding(_, id, ident, None) = pat.kind {
if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
ident_bind_name = ident.name;

View file

@ -38,6 +38,11 @@ declare_clippy_lint! {
/// Checks for matches with a single arm where an `if let`
/// will usually suffice.
///
/// This intentionally does not lint if there are comments
/// inside of the other arm, so as to allow the user to document
/// why having another explicit pattern with an empty body is necessary,
/// or because the comments need to be preserved for other reasons.
///
/// ### Why is this bad?
/// Just readability `if let` nests less than a `match`.
///
@ -559,6 +564,9 @@ declare_clippy_lint! {
/// ### What it does
/// Checks for `match` with identical arm bodies.
///
/// Note: Does not lint on wildcards if the `non_exhaustive_omitted_patterns_lint` feature is
/// enabled and disallowed.
///
/// ### Why is this bad?
/// This is probably a copy & paste error. If arm bodies
/// are the same on purpose, you can factor them
@ -777,7 +785,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Check for temporaries returned from function calls in a match scrutinee that have the
/// Checks for temporaries returned from function calls in a match scrutinee that have the
/// `clippy::has_significant_drop` attribute.
///
/// ### Why is this bad?

View file

@ -41,7 +41,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
cx.tcx.valtree_to_const_val((ty, min_val_const.to_valtree())),
ty,
);
miri_to_const(cx.tcx, min_constant)?
miri_to_const(cx, min_constant)?
},
};
let rhs_const = match rhs {
@ -52,7 +52,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
cx.tcx.valtree_to_const_val((ty, max_val_const.to_valtree())),
ty,
);
miri_to_const(cx.tcx, max_constant)?
miri_to_const(cx, max_constant)?
},
};
let lhs_val = lhs_const.int_value(cx, ty)?;

View file

@ -140,7 +140,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
}
}
for generic_arg in b.iter() {
for generic_arg in *b {
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
if self.has_sig_drop_attr(cx, ty) {
return true;

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{expr_block, snippet};
use clippy_utils::source::{expr_block, get_source_text, snippet};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs};
use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs};
use core::cmp::max;
@ -7,10 +7,26 @@ use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::sym;
use rustc_span::{sym, Span};
use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
/// Checks if there are comments contained within a span.
/// This is a very "naive" check, as it just looks for the literal characters // and /* in the
/// source text. This won't be accurate if there are potentially expressions contained within the
/// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty
/// match arms.
fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
if let Some(ff) = get_source_text(cx, span)
&& let Some(text) = ff.as_str()
{
text.as_bytes().windows(2)
.any(|w| w == b"//" || w == b"/*")
} else {
false
}
}
#[rustfmt::skip]
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
@ -25,7 +41,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
return;
}
let els = arms[1].body;
let els = if is_unit_expr(peel_blocks(els)) {
let els = if is_unit_expr(peel_blocks(els)) && !empty_arm_has_comment(cx, els.span) {
None
} else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind {
if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
@ -35,7 +51,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
// block with 2+ statements or 1 expr and 1+ statement
Some(els)
} else {
// not a block, don't lint
// not a block or an emtpy block w/ comments, don't lint
return;
};

View file

@ -81,7 +81,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
/// Finds function return type by examining return expressions in match arms.
fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr {
for arm in arms.iter() {
for arm in *arms {
if let ExprKind::Ret(Some(ret)) = arm.body.kind {
return Some(cx.typeck_results().expr_ty(ret));
}

View file

@ -1,46 +0,0 @@
use clippy_utils::diagnostics::span_lint;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `std::mem::forget(t)` where `t` is
/// `Drop`.
///
/// ### Why is this bad?
/// `std::mem::forget(t)` prevents `t` from running its
/// destructor, possibly causing leaks.
///
/// ### Example
/// ```rust
/// # use std::mem;
/// # use std::rc::Rc;
/// mem::forget(Rc::new(55))
/// ```
#[clippy::version = "pre 1.29.0"]
pub MEM_FORGET,
restriction,
"`mem::forget` usage on `Drop` types, likely to cause memory leaks"
}
declare_lint_pass!(MemForget => [MEM_FORGET]);
impl<'tcx> LateLintPass<'tcx> for MemForget {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.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 cx.tcx.is_diagnostic_item(sym::mem_forget, def_id) {
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)) {
span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type");
}
}
}
}
}
}
}

View file

@ -0,0 +1,85 @@
use crate::methods::DRAIN_COLLECT;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_range_full;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::ExprKind;
use rustc_hir::LangItem;
use rustc_hir::Path;
use rustc_hir::QPath;
use rustc_lint::LateContext;
use rustc_middle::query::Key;
use rustc_middle::ty;
use rustc_middle::ty::Ty;
use rustc_span::sym;
use rustc_span::Symbol;
/// Checks if both types match the given diagnostic item, e.g.:
///
/// `vec![1,2].drain(..).collect::<Vec<_>>()`
/// ^^^^^^^^^ ^^^^^^ true
/// `vec![1,2].drain(..).collect::<HashSet<_>>()`
/// ^^^^^^^^^ ^^^^^^^^^^ false
fn types_match_diagnostic_item(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>, sym: Symbol) -> bool {
if let Some(expr_adt_did) = expr.ty_adt_id()
&& let Some(recv_adt_did) = recv.ty_adt_id()
{
cx.tcx.is_diagnostic_item(sym, expr_adt_did) && cx.tcx.is_diagnostic_item(sym, recv_adt_did)
} else {
false
}
}
/// Checks `std::{vec::Vec, collections::VecDeque}`.
fn check_vec(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
(types_match_diagnostic_item(cx, expr, recv, sym::Vec)
|| types_match_diagnostic_item(cx, expr, recv, sym::VecDeque))
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
}
/// Checks `std::string::String`
fn check_string(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
is_type_lang_item(cx, expr, LangItem::String)
&& is_type_lang_item(cx, recv, LangItem::String)
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
}
/// Checks `std::collections::{HashSet, HashMap, BinaryHeap}`.
fn check_collections(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>) -> Option<&'static str> {
types_match_diagnostic_item(cx, expr, recv, sym::HashSet)
.then_some("HashSet")
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::HashMap).then_some("HashMap"))
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::BinaryHeap).then_some("BinaryHeap"))
}
pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], expr: &Expr<'_>, recv: &Expr<'_>) {
let expr_ty = cx.typeck_results().expr_ty(expr);
let recv_ty = cx.typeck_results().expr_ty(recv);
let recv_ty_no_refs = recv_ty.peel_refs();
if let ExprKind::Path(QPath::Resolved(_, recv_path)) = recv.kind
&& let Some(typename) = check_vec(cx, args, expr_ty, recv_ty_no_refs, recv_path)
.then_some("Vec")
.or_else(|| check_string(cx, args, expr_ty, recv_ty_no_refs, recv_path).then_some("String"))
.or_else(|| check_collections(cx, expr_ty, recv_ty_no_refs))
{
let recv = snippet(cx, recv.span, "<expr>");
let sugg = if let ty::Ref(..) = recv_ty.kind() {
format!("std::mem::take({recv})")
} else {
format!("std::mem::take(&mut {recv})")
};
span_lint_and_sugg(
cx,
DRAIN_COLLECT,
expr.span,
&format!("you seem to be trying to move all elements into a new `{typename}`"),
"consider using `mem::take`",
sugg,
Applicability::MachineApplicable,
);
}
}

View file

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -23,21 +22,15 @@ pub(super) fn check<'tcx>(
let mut applicability = Applicability::MachineApplicable;
let expr_ty = cx.typeck_results().expr_ty(recv);
let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability);
let mut needs_ref;
let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() {
needs_ref = get_args_str.parse::<usize>().is_ok();
"slice"
} else if is_type_diagnostic_item(cx, expr_ty, sym::Vec) {
needs_ref = get_args_str.parse::<usize>().is_ok();
"Vec"
} else if is_type_diagnostic_item(cx, expr_ty, sym::VecDeque) {
needs_ref = get_args_str.parse::<usize>().is_ok();
"VecDeque"
} else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::HashMap) {
needs_ref = true;
"HashMap"
} else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::BTreeMap) {
needs_ref = true;
"BTreeMap"
} else {
return; // caller is not a type that we want to lint
@ -45,18 +38,24 @@ pub(super) fn check<'tcx>(
let mut span = expr.span;
// Handle the case where the result is immediately dereferenced
// by not requiring ref and pulling the dereference into the
// suggestion.
if_chain! {
if needs_ref;
if let Some(parent) = get_parent_expr(cx, expr);
if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind;
then {
needs_ref = false;
// Handle the case where the result is immediately dereferenced,
// either directly be the user, or as a result of a method call or the like
// by not requiring an explicit reference
let needs_ref = if let Some(parent) = get_parent_expr(cx, expr)
&& let hir::ExprKind::Unary(hir::UnOp::Deref, _)
| hir::ExprKind::MethodCall(..)
| hir::ExprKind::Field(..)
| hir::ExprKind::Index(..) = parent.kind
{
if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind {
// if the user explicitly dereferences the result, we can adjust
// the span to also include the deref part
span = parent.span;
}
}
false
} else {
true
};
let mut_str = if is_mut { "_mut" } else { "" };
let borrow_str = if !needs_ref {

View file

@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::symbol::{Symbol, sym};
use rustc_span::symbol::{sym, Symbol};
use super::INEFFICIENT_TO_STRING;

View file

@ -20,9 +20,9 @@ pub(super) fn check<'tcx>(
let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() {
"slice"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::Vec) {
"Vec"
"`Vec`"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::VecDeque) {
"VecDeque"
"`VecDeque`"
} else {
iter_nth_zero::check(cx, expr, nth_recv, nth_arg);
return; // caller is not a type that we want to lint

View file

@ -1,8 +1,8 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use clippy_utils::{is_lang_item_or_ctor, is_trait_method};
use hir::{LangItem, OwnerNode};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -11,20 +11,21 @@ use rustc_span::sym;
use super::ITER_NTH_ZERO;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
if_chain! {
if is_trait_method(cx, expr, sym::Iterator);
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
ITER_NTH_ZERO,
expr.span,
"called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent",
"try calling `.next()` instead of `.nth(0)`",
format!("{}.next()", snippet_with_applicability(cx, recv.span, "..", &mut applicability)),
applicability,
);
}
if let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
&& let def_id = item.owner_id.to_def_id()
&& is_trait_method(cx, expr, sym::Iterator)
&& let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg)
&& !is_lang_item_or_ctor(cx, def_id, LangItem::IteratorNext)
{
let mut app = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
ITER_NTH_ZERO,
expr.span,
"called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent",
"try calling `.next()` instead of `.nth(0)`",
format!("{}.next()", snippet_with_applicability(cx, recv.span, "..", &mut app)),
app,
);
}
}

View file

@ -14,6 +14,7 @@ mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod collapsible_str_replace;
mod drain_collect;
mod err_expect;
mod expect_fun_call;
mod expect_used;
@ -93,6 +94,7 @@ mod unnecessary_fold;
mod unnecessary_iter_cloned;
mod unnecessary_join;
mod unnecessary_lazy_eval;
mod unnecessary_literal_unwrap;
mod unnecessary_sort_by;
mod unnecessary_to_owned;
mod unwrap_or_else_default;
@ -273,6 +275,32 @@ declare_clippy_lint! {
"using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed.
///
/// ### Why is this bad?
/// It is better to write the value directly without the indirection.
///
/// ### Examples
/// ```rust
/// let val1 = Some(1).unwrap();
/// let val2 = Ok::<_, ()>(1).unwrap();
/// let val3 = Err::<(), _>(1).unwrap_err();
/// ```
///
/// Use instead:
/// ```rust
/// let val1 = 1;
/// let val2 = 1;
/// let val3 = 1;
/// ```
#[clippy::version = "1.69.0"]
pub UNNECESSARY_LITERAL_UNWRAP,
complexity,
"using `unwrap()` related calls on `Result` and `Option` constructors"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s.
@ -485,6 +513,7 @@ declare_clippy_lint! {
/// # let result: Result<usize, ()> = Ok(1);
/// # fn some_function(foo: ()) -> usize { 1 }
/// option.map(|a| a + 1).unwrap_or(0);
/// option.map(|a| a > 10).unwrap_or(false);
/// result.map(|a| a + 1).unwrap_or_else(some_function);
/// ```
///
@ -494,6 +523,7 @@ declare_clippy_lint! {
/// # let result: Result<usize, ()> = Ok(1);
/// # fn some_function(foo: ()) -> usize { 1 }
/// option.map_or(0, |a| a + 1);
/// option.is_some_and(|a| a > 10);
/// result.map_or_else(some_function, |a| a + 1);
/// ```
#[clippy::version = "1.45.0"]
@ -3220,6 +3250,42 @@ declare_clippy_lint! {
"manual reverse iteration of `DoubleEndedIterator`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`.
///
/// > "Collection" in this context refers to any type with a `drain` method:
/// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String`
///
/// ### Why is this bad?
/// Using `mem::take` is faster as it avoids the allocation.
/// When using `mem::take`, the old collection is replaced with an empty one and ownership of
/// the old collection is returned.
///
/// ### Known issues
/// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that
/// it also moves the **capacity**. The user might have explicitly written it this way
/// to keep the capacity on the original `Vec`.
///
/// ### Example
/// ```rust
/// fn remove_all(v: &mut Vec<i32>) -> Vec<i32> {
/// v.drain(..).collect()
/// }
/// ```
/// Use instead:
/// ```rust
/// use std::mem;
/// fn remove_all(v: &mut Vec<i32>) -> Vec<i32> {
/// mem::take(v)
/// }
/// ```
#[clippy::version = "1.71.0"]
pub DRAIN_COLLECT,
perf,
"calling `.drain(..).collect()` to move all elements into a new collection"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -3349,6 +3415,8 @@ impl_lint_pass!(Methods => [
SUSPICIOUS_COMMAND_ARG_SPACE,
CLEAR_WITH_DRAIN,
MANUAL_NEXT_BACK,
UNNECESSARY_LITERAL_UNWRAP,
DRAIN_COLLECT
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -3578,6 +3646,9 @@ impl Methods {
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
}
},
Some(("drain", recv, args, ..)) => {
drain_collect::check(cx, args, expr, recv);
}
_ => {},
}
},
@ -3606,12 +3677,18 @@ impl Methods {
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
}
},
("expect", [_]) => match method_call(recv) {
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
("expect", [_]) => {
match method_call(recv) {
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("expect_err", [_]) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests);
},
("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests),
("extend", [arg]) => {
string_extend_chars::check(cx, expr, recv, arg);
extend_with_drain::check(cx, expr, recv, arg);
@ -3816,28 +3893,41 @@ impl Methods {
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests);
},
("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests),
("unwrap_or", [u_arg]) => match method_call(recv) {
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
},
Some(("map", m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
},
Some(("then_some", t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
},
_ => {},
("unwrap_err", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests);
},
("unwrap_or_else", [u_arg]) => match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
_ => {
unwrap_or_else_default::check(cx, expr, recv, u_arg);
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
},
("unwrap_or", [u_arg]) => {
match method_call(recv) {
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
},
Some(("map", m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, &self.msrv);
},
Some(("then_some", t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or_default", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
}
("unwrap_or_else", [u_arg]) => {
match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
_ => {
unwrap_or_else_default::check(cx, expr, recv, u_arg);
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("zip", [arg]) => {
if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind

View file

@ -1,19 +1,26 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_copy;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_path, Visitor};
use rustc_hir::ExprKind;
use rustc_hir::Node;
use rustc_hir::PatKind;
use rustc_hir::QPath;
use rustc_hir::{self, HirId, Path};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_span::source_map::Span;
use rustc_span::{sym, Symbol};
use rustc_span::sym;
use super::MAP_UNWRAP_OR;
/// lint use of `map().unwrap_or()` for `Option`s
#[expect(clippy::too_many_arguments)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &rustc_hir::Expr<'_>,
@ -22,12 +29,27 @@ pub(super) fn check<'tcx>(
unwrap_recv: &rustc_hir::Expr<'_>,
unwrap_arg: &'tcx rustc_hir::Expr<'_>,
map_span: Span,
msrv: &Msrv,
) {
// lint if the caller of `map()` is an `Option`
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
// Do not lint if the `map` argument uses identifiers in the `map`
// argument that are also used in the `unwrap_or` argument
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
// borrowck errors, see #10579 for one such instance.
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
// ```
// let x = vec![1, 2];
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
// ```
// This compiles, but changing it to `map_or` will produce a compile error:
// ```
// let x = vec![1, 2];
// x.get(0..1).map_or(x, |s| s.to_vec())
// ^ moving `x` here
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
// ```
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
// before the call to `unwrap_or`.
let mut unwrap_visitor = UnwrapVisitor {
cx,
@ -35,14 +57,18 @@ pub(super) fn check<'tcx>(
};
unwrap_visitor.visit_expr(unwrap_arg);
let mut map_expr_visitor = MapExprVisitor {
let mut reference_visitor = ReferenceVisitor {
cx,
identifiers: unwrap_visitor.identifiers,
found_identifier: false,
found_reference: false,
unwrap_or_span: unwrap_arg.span,
};
map_expr_visitor.visit_expr(map_arg);
if map_expr_visitor.found_identifier {
let map = cx.tcx.hir();
let body = map.body(map.body_owned_by(map.enclosing_body_owner(expr.hir_id)));
reference_visitor.visit_body(body);
if reference_visitor.found_reference {
return;
}
}
@ -51,16 +77,29 @@ pub(super) fn check<'tcx>(
return;
}
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
&& matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
let mut applicability = Applicability::MachineApplicable;
// get snippet for unwrap_or()
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
// lint message
// comparing the snippet from source to raw text ("None") below is safe
// because we already have checked the type.
let arg = if unwrap_snippet == "None" { "None" } else { "<a>" };
let arg = if unwrap_snippet == "None" {
"None"
} else if suggest_is_some_and {
"false"
} else {
"<a>"
};
let unwrap_snippet_none = unwrap_snippet == "None";
let suggest = if unwrap_snippet_none {
"and_then(<f>)"
} else if suggest_is_some_and {
"is_some_and(<f>)"
} else {
"map_or(<a>, <f>)"
};
@ -75,12 +114,18 @@ pub(super) fn check<'tcx>(
let mut suggestion = vec![
(
map_span,
String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
String::from(if unwrap_snippet_none {
"and_then"
} else if suggest_is_some_and {
"is_some_and"
} else {
"map_or"
}),
),
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
];
if !unwrap_snippet_none {
if !unwrap_snippet_none && !suggest_is_some_and {
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
}
@ -91,35 +136,18 @@ pub(super) fn check<'tcx>(
struct UnwrapVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<Symbol>,
identifiers: FxHashSet<HirId>,
}
impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
self.identifiers.insert(ident(path));
walk_path(self, path);
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}
struct MapExprVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<Symbol>,
found_identifier: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
if self.identifiers.contains(&ident(path)) {
self.found_identifier = true;
return;
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
if let Res::Local(local_id) = path.res
&& let Some(Node::Pat(pat)) = self.cx.tcx.hir().find(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
{
self.identifiers.insert(local_id);
}
walk_path(self, path);
}
@ -129,10 +157,35 @@ impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
}
}
fn ident(path: &Path<'_>) -> Symbol {
path.segments
.last()
.expect("segments should be composed of at least 1 element")
.ident
.name
struct ReferenceVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
found_reference: bool,
unwrap_or_span: Span,
}
impl<'a, 'tcx> Visitor<'tcx> for ReferenceVisitor<'a, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) {
// If we haven't found a reference yet, check if this references
// one of the locals that was moved in the `unwrap_or` argument.
// We are only interested in exprs that appear before the `unwrap_or` call.
if !self.found_reference {
if expr.span < self.unwrap_or_span
&& let ExprKind::Path(ref path) = expr.kind
&& let QPath::Resolved(_, path) = path
&& let Res::Local(local_id) = path.res
&& let Some(Node::Pat(pat)) = self.cx.tcx.hir().find(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
&& self.identifiers.contains(&local_id)
{
self.found_reference = true;
}
rustc_hir::intravisit::walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}

View file

@ -7,10 +7,113 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::PatKind;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{source_map::Span, sym};
use super::UNNECESSARY_FOLD;
/// Do we need to suggest turbofish when suggesting a replacement method?
/// Changing `fold` to `sum` needs it sometimes when the return type can't be
/// inferred. This checks for some common cases where it can be safely omitted
fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
let parent = cx.tcx.hir().get_parent(expr.hir_id);
// some common cases where turbofish isn't needed:
// - assigned to a local variable with a type annotation
if let hir::Node::Local(local) = parent
&& local.ty.is_some()
{
return false;
}
// - part of a function call argument, can be inferred from the function signature (provided that
// the parameter is not a generic type parameter)
if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::Call(recv, args) = parent_expr.kind
&& let hir::ExprKind::Path(ref qpath) = recv.kind
&& let Some(fn_def_id) = cx.qpath_res(qpath, recv.hir_id).opt_def_id()
&& let fn_sig = cx.tcx.fn_sig(fn_def_id).skip_binder().skip_binder()
&& let Some(arg_pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
&& let Some(ty) = fn_sig.inputs().get(arg_pos)
&& !matches!(ty.kind(), ty::Param(_))
{
return false;
}
// if it's neither of those, stay on the safe side and suggest turbofish,
// even if it could work!
true
}
#[derive(Copy, Clone)]
struct Replacement {
method_name: &'static str,
has_args: bool,
has_generic_return: bool,
}
fn check_fold_with_op(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
acc: &hir::Expr<'_>,
fold_span: Span,
op: hir::BinOpKind,
replacement: Replacement,
) {
if_chain! {
// Extract the body of the closure passed to fold
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
let closure_body = cx.tcx.hir().body(body);
let closure_expr = peel_blocks(closure_body.value);
// Check if the closure body is of the form `acc <op> some_expr(x)`
if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
if bin_op.node == op;
// Extract the names of the two arguments to the closure
if let [param_a, param_b] = closure_body.params;
if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
if path_to_local_id(left_expr, first_arg_id);
if replacement.has_args || path_to_local_id(right_expr, second_arg_id);
then {
let mut applicability = Applicability::MachineApplicable;
let turbofish = if replacement.has_generic_return {
format!("::<{}>", cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs())
} else {
String::new()
};
let sugg = if replacement.has_args {
format!(
"{method}{turbofish}(|{second_arg_ident}| {r})",
method = replacement.method_name,
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
)
} else {
format!(
"{method}{turbofish}()",
method = replacement.method_name,
)
};
span_lint_and_sugg(
cx,
UNNECESSARY_FOLD,
fold_span.with_hi(expr.span.hi()),
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
"this `.fold` can be written more succinctly using another method",
"try",
sugg,
applicability,
);
}
}
}
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
@ -18,60 +121,6 @@ pub(super) fn check(
acc: &hir::Expr<'_>,
fold_span: Span,
) {
fn check_fold_with_op(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
acc: &hir::Expr<'_>,
fold_span: Span,
op: hir::BinOpKind,
replacement_method_name: &str,
replacement_has_args: bool,
) {
if_chain! {
// Extract the body of the closure passed to fold
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
let closure_body = cx.tcx.hir().body(body);
let closure_expr = peel_blocks(closure_body.value);
// Check if the closure body is of the form `acc <op> some_expr(x)`
if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
if bin_op.node == op;
// Extract the names of the two arguments to the closure
if let [param_a, param_b] = closure_body.params;
if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
if path_to_local_id(left_expr, first_arg_id);
if replacement_has_args || path_to_local_id(right_expr, second_arg_id);
then {
let mut applicability = Applicability::MachineApplicable;
let sugg = if replacement_has_args {
format!(
"{replacement_method_name}(|{second_arg_ident}| {r})",
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
)
} else {
format!(
"{replacement_method_name}()",
)
};
span_lint_and_sugg(
cx,
UNNECESSARY_FOLD,
fold_span.with_hi(expr.span.hi()),
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
"this `.fold` can be written more succinctly using another method",
"try",
sugg,
applicability,
);
}
}
}
// Check that this is a call to Iterator::fold rather than just some function called fold
if !is_trait_method(cx, expr, sym::Iterator) {
return;
@ -80,11 +129,59 @@ pub(super) fn check(
// Check if the first argument to .fold is a suitable literal
if let hir::ExprKind::Lit(lit) = init.kind {
match lit.node {
ast::LitKind::Bool(false) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, "any", true),
ast::LitKind::Bool(true) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, "all", true),
ast::LitKind::Int(0, _) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, "sum", false),
ast::LitKind::Bool(false) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Or,
Replacement {
has_args: true,
has_generic_return: false,
method_name: "any",
},
);
},
ast::LitKind::Bool(true) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::And,
Replacement {
has_args: true,
has_generic_return: false,
method_name: "all",
},
);
},
ast::LitKind::Int(0, _) => check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Add,
Replacement {
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "sum",
},
),
ast::LitKind::Int(1, _) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, "product", false);
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Mul,
Replacement {
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "product",
},
);
},
_ => (),
}

View file

@ -0,0 +1,95 @@
use clippy_utils::{diagnostics::span_lint_and_then, is_res_lang_ctor, last_path_segment, path_res, MaybePath};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use super::UNNECESSARY_LITERAL_UNWRAP;
fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) -> Option<&'a hir::Ty<'a>> {
let args = args?;
if args.len() <= index {
return None;
}
match args[index] {
hir::GenericArg::Type(ty) => match ty.kind {
hir::TyKind::Infer => None,
_ => Some(ty),
},
_ => None,
}
}
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
recv: &hir::Expr<'_>,
method: &str,
args: &[hir::Expr<'_>],
) {
let init = clippy_utils::expr_or_init(cx, recv);
let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind {
let Some(qpath) = call.qpath_opt() else { return };
let args = last_path_segment(qpath).args.map(|args| args.args);
let res = cx.qpath_res(qpath, call.hir_id());
if is_res_lang_ctor(cx, res, hir::LangItem::OptionSome) {
("Some", call_args, get_ty_from_args(args, 0))
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultOk) {
("Ok", call_args, get_ty_from_args(args, 0))
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultErr) {
("Err", call_args, get_ty_from_args(args, 1))
} else {
return;
}
} else if is_res_lang_ctor(cx, path_res(cx, init), hir::LangItem::OptionNone) {
let call_args: &[hir::Expr<'_>] = &[];
("None", call_args, None)
} else {
return;
};
let help_message = format!("used `{method}()` on `{constructor}` value");
let suggestion_message = format!("remove the `{constructor}` and `{method}()`");
span_lint_and_then(cx, UNNECESSARY_LITERAL_UNWRAP, expr.span, &help_message, |diag| {
let suggestions = match (constructor, method, ty) {
("None", "unwrap", _) => Some(vec![(expr.span, "panic!()".to_string())]),
("None", "expect", _) => Some(vec![
(expr.span.with_hi(args[0].span.lo()), "panic!(".to_string()),
(expr.span.with_lo(args[0].span.hi()), ")".to_string()),
]),
(_, _, Some(_)) => None,
("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![
(
recv.span.with_hi(call_args[0].span.lo()),
"panic!(\"{:?}\", ".to_string(),
),
(expr.span.with_lo(call_args[0].span.hi()), ")".to_string()),
]),
("Ok", "expect_err", None) | ("Err", "expect", None) => Some(vec![
(
recv.span.with_hi(call_args[0].span.lo()),
"panic!(\"{1}: {:?}\", ".to_string(),
),
(call_args[0].span.with_lo(args[0].span.lo()), ", ".to_string()),
]),
(_, _, None) => Some(vec![
(recv.span.with_hi(call_args[0].span.lo()), String::new()),
(expr.span.with_lo(call_args[0].span.hi()), String::new()),
]),
};
match (init.span == recv.span, suggestions) {
(true, Some(suggestions)) => {
diag.multipart_suggestion(suggestion_message, suggestions, Applicability::MachineApplicable);
},
_ => {
diag.span_help(init.span, suggestion_message);
},
}
});
}

View file

@ -144,6 +144,11 @@ fn check_addr_of_expr(
if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
if cx.get_associated_type(receiver_ty, deref_trait_id, "Target") == Some(target_ty);
// Make sure that it's actually calling the right `.to_string()`, (#10033)
// *or* this is a `Cow::into_owned()` call (which would be the wrong into_owned receiver (str != Cow)
// but that's ok for Cow::into_owned specifically)
if cx.typeck_results().expr_ty_adjusted(receiver).peel_refs() == target_ty
|| is_cow_into_owned(cx, method_name, method_def_id);
then {
if n_receiver_refs > 0 {
span_lint_and_sugg(

View file

@ -0,0 +1,153 @@
use clippy_utils::{diagnostics::span_lint, is_from_proc_macro};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{
def::{DefKind, Res},
intravisit::{walk_item, Visitor},
GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
/// Checks for idents which comprise of a single letter.
///
/// Note: This lint can be very noisy when enabled; it may be desirable to only enable it
/// temporarily.
///
/// ### Why is this bad?
/// In many cases it's not, but at times it can severely hinder readability. Some codebases may
/// wish to disallow this to improve readability.
///
/// ### Example
/// ```rust,ignore
/// for m in movies {
/// let title = m.t;
/// }
/// ```
/// Use instead:
/// ```rust,ignore
/// for movie in movies {
/// let title = movie.title;
/// }
/// ```
#[clippy::version = "1.72.0"]
pub MIN_IDENT_CHARS,
restriction,
"disallows idents that are too short"
}
impl_lint_pass!(MinIdentChars => [MIN_IDENT_CHARS]);
#[derive(Clone)]
pub struct MinIdentChars {
pub allowed_idents_below_min_chars: FxHashSet<String>,
pub min_ident_chars_threshold: u64,
}
impl MinIdentChars {
#[expect(clippy::cast_possible_truncation)]
fn is_ident_too_short(&self, cx: &LateContext<'_>, str: &str, span: Span) -> bool {
!in_external_macro(cx.sess(), span)
&& str.len() <= self.min_ident_chars_threshold as usize
&& !str.starts_with('_')
&& !str.is_empty()
&& self.allowed_idents_below_min_chars.get(&str.to_owned()).is_none()
}
}
impl LateLintPass<'_> for MinIdentChars {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if self.min_ident_chars_threshold == 0 {
return;
}
walk_item(&mut IdentVisitor { conf: self, cx }, item);
}
// This is necessary as `Node::Pat`s are not visited in `visit_id`. :/
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
if let PatKind::Binding(_, _, ident, ..) = pat.kind
&& let str = ident.as_str()
&& self.is_ident_too_short(cx, str, ident.span)
{
emit_min_ident_chars(self, cx, str, ident.span);
}
}
}
struct IdentVisitor<'cx, 'tcx> {
conf: &'cx MinIdentChars,
cx: &'cx LateContext<'tcx>,
}
impl Visitor<'_> for IdentVisitor<'_, '_> {
fn visit_id(&mut self, hir_id: HirId) {
let Self { conf, cx } = *self;
// FIXME(#112534) Reimplementation of `find`, as it uses indexing, which can (and will in
// async functions, or really anything async) panic. This should probably be fixed on the
// rustc side, this is just a temporary workaround.
let node = if hir_id.local_id == ItemLocalId::from_u32(0) {
// In this case, we can just use `find`, `Owner`'s `node` field is private anyway so we can't
// reimplement it even if we wanted to
cx.tcx.hir().find(hir_id)
} else {
let Some(owner) = cx.tcx.hir_owner_nodes(hir_id.owner).as_owner() else {
return;
};
owner.nodes.get(hir_id.local_id).copied().flatten().map(|p| p.node)
};
let Some(node) = node else {
return;
};
let Some(ident) = node.ident() else {
return;
};
let str = ident.as_str();
if conf.is_ident_too_short(cx, str, ident.span) {
if let Node::Item(item) = node && let ItemKind::Use(..) = item.kind {
return;
}
// `struct Awa<T>(T)`
// ^
if let Node::PathSegment(path) = node {
if let Res::Def(def_kind, ..) = path.res && let DefKind::TyParam = def_kind {
return;
}
if matches!(path.res, Res::PrimTy(..)) || path.res.opt_def_id().is_some_and(|def_id| !def_id.is_local())
{
return;
}
}
// `struct Awa<T>(T)`
// ^
if let Node::GenericParam(generic_param) = node
&& let GenericParamKind::Type { .. } = generic_param.kind
{
return;
}
if is_from_proc_macro(cx, &ident) {
return;
}
emit_min_ident_chars(conf, cx, str, ident.span);
}
}
}
fn emit_min_ident_chars(conf: &MinIdentChars, cx: &impl LintContext, ident: &str, span: Span) {
let help = if conf.min_ident_chars_threshold == 1 {
Cow::Borrowed("this ident consists of a single char")
} else {
Cow::Owned(format!(
"this ident is too short ({} <= {})",
ident.len(),
conf.min_ident_chars_threshold,
))
};
span_lint(cx, MIN_IDENT_CHARS, span, &help);
}

View file

@ -66,7 +66,7 @@ enum MinMax {
Max,
}
fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> {
match expr.kind {
ExprKind::Call(path, args) => {
if let ExprKind::Path(ref qpath) = path.kind {
@ -99,12 +99,12 @@ fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Cons
}
}
fn fetch_const<'a>(
cx: &LateContext<'_>,
fn fetch_const<'a, 'tcx>(
cx: &LateContext<'tcx>,
receiver: Option<&'a Expr<'a>>,
args: &'a [Expr<'a>],
m: MinMax,
) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> {
let mut args = receiver.into_iter().chain(args);
let first_arg = args.next()?;
let second_arg = args.next()?;

View file

@ -13,7 +13,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, suffix: &str, lit_sni
return;
}
let mut seen = (false, false);
for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
for ch in &lit_snip.as_bytes()[2..=maybe_last_sep_idx] {
match ch {
b'a'..=b'f' => seen.0 = true,
b'A'..=b'F' => seen.1 = true,

View file

@ -339,6 +339,10 @@ impl EarlyLintPass for MiscEarlyLints {
}
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
if in_external_macro(cx.sess(), pat.span) {
return;
}
unneeded_field_pattern::check(cx, pat);
redundant_pattern::check(cx, pat);
unneeded_wildcard_pattern::check(cx, pat);

View file

@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
then {
// get the name and span of the generic parameters in the Impl
let mut impl_params = Vec::new();
for p in generic_args.args.iter() {
for p in generic_args.args {
match p {
GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) =>
impl_params.push((path.segments[0].ident.to_string(), path.span)),

View file

@ -207,6 +207,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingFieldsInDebug {
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), self_ty, items, .. }) = item.kind
&& let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res
&& let TyKind::Path(QPath::Resolved(_, self_path)) = &self_ty.kind
// don't trigger if self is a generic parameter, e.g. `impl<T> Debug for T`
// this can only happen in core itself, where the trait is defined,
// but it caused ICEs in the past:
// https://github.com/rust-lang/rust-clippy/issues/10887
&& !matches!(self_path.res, Res::Def(DefKind::TyParam, _))
&& cx.match_def_path(trait_def_id, &[sym::core, sym::fmt, sym::Debug])
// don't trigger if this impl was derived
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)

View file

@ -114,7 +114,7 @@ struct DivergenceVisitor<'a, 'tcx> {
impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind {
ExprKind::Closure { .. } => {},
ExprKind::Closure(..) | ExprKind::If(..) | ExprKind::Loop(..) => {},
ExprKind::Match(e, arms, _) => {
self.visit_expr(e);
for arm in arms {
@ -128,6 +128,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
_ => walk_expr(self, e),
}
}
fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
}
@ -136,6 +137,15 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind {
// fix #10776
ExprKind::Block(block, ..) => match (block.stmts, block.expr) {
([], Some(e)) => self.visit_expr(e),
([stmt], None) => match stmt.kind {
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
_ => {},
},
_ => {},
},
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
ExprKind::Call(func, _) => {
let typ = self.cx.typeck_results().expr_ty(func);

View file

@ -2,6 +2,7 @@ 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::def_id::LOCAL_CRATE;
use rustc_span::{FileName, SourceFile, Span, SyntaxContext};
use std::ffi::OsStr;
use std::path::{Component, Path};
@ -90,7 +91,14 @@ impl EarlyLintPass for ModStyle {
// `{ foo => path/to/foo.rs, .. }
let mut file_map = FxHashMap::default();
for file in files.iter() {
if let FileName::Real(name) = &file.name && let Some(lp) = name.local_path() {
if let FileName::Real(name) = &file.name
&& let Some(lp) = name.local_path()
&& file.cnum == LOCAL_CRATE
{
// [#8887](https://github.com/rust-lang/rust-clippy/issues/8887)
// Only check files in the current crate.
// Fix false positive that crate dependency in workspace sub directory
// is checked unintentionally.
let path = if lp.is_relative() {
lp
} else if let Ok(relative) = lp.strip_prefix(trim_to_src) {

View file

@ -92,10 +92,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View file

@ -0,0 +1,75 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, higher::If, is_from_proc_macro, source::snippet_opt};
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for empty `if` branches with no else branch.
///
/// ### Why is this bad?
/// It can be entirely omitted, and often the condition too.
///
/// ### Known issues
/// This will usually only suggest to remove the `if` statement, not the condition. Other lints
/// such as `no_effect` will take care of removing the condition if it's unnecessary.
///
/// ### Example
/// ```rust,ignore
/// if really_expensive_condition(&i) {}
/// if really_expensive_condition_with_side_effects(&mut i) {}
/// ```
/// Use instead:
/// ```rust,ignore
/// // <omitted>
/// really_expensive_condition_with_side_effects(&mut i);
/// ```
#[clippy::version = "1.72.0"]
pub NEEDLESS_IF,
complexity,
"checks for empty if branches"
}
declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]);
impl LateLintPass<'_> for NeedlessIf {
fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
if let StmtKind::Expr(expr) = stmt.kind
&& let Some(If {cond, then, r#else: None }) = If::hir(expr)
&& let ExprKind::Block(block, ..) = then.kind
&& block.stmts.is_empty()
&& block.expr.is_none()
&& !in_external_macro(cx.sess(), expr.span)
&& !is_from_proc_macro(cx, expr)
&& let Some(then_snippet) = snippet_opt(cx, then.span)
// Ignore
// - empty macro expansions
// - empty reptitions in macro expansions
// - comments
// - #[cfg]'d out code
&& then_snippet.chars().all(|ch| matches!(ch, '{' | '}') || ch.is_ascii_whitespace())
&& let Some(cond_snippet) = snippet_opt(cx, cond.span)
{
span_lint_and_sugg(
cx,
NEEDLESS_IF,
stmt.span,
"this `if` branch is empty",
"you can remove it",
if cond.can_have_side_effects() || !cx.tcx.hir().attrs(stmt.hir_id).is_empty() {
// `{ foo }` or `{ foo } && bar` placed into a statement position would be
// interpreted as a block statement, force it to be an expression
if cond_snippet.starts_with('{') {
format!("({cond_snippet});")
} else {
format!("{cond_snippet};")
}
} else {
String::new()
},
Applicability::MachineApplicable,
);
}
}
}

View file

@ -7,13 +7,12 @@ use clippy_utils::ty::{
use clippy_utils::{get_trait_def_id, is_self, paths};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Mutability, Node, PatKind, QPath, TyKind,
};
use rustc_hir::{HirIdMap, HirIdSet, LangItem};
use rustc_hir::{HirIdSet, LangItem};
use rustc_hir_typeck::expr_use_visitor as euv;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
@ -126,9 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
.filter_map(|pred| {
// Note that we do not want to deal with qualified predicates here.
match pred.kind().no_bound_vars() {
Some(ty::ClauseKind::Trait(pred)) if pred.def_id() != sized_trait => {
Some(pred)
},
Some(ty::ClauseKind::Trait(pred)) if pred.def_id() != sized_trait => Some(pred),
_ => None,
}
})
@ -136,11 +133,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
// Collect moved variables and spans which will need dereferencings from the
// function body.
let MovedVariablesCtxt {
moved_vars,
spans_need_deref,
..
} = {
let MovedVariablesCtxt { moved_vars } = {
let mut ctx = MovedVariablesCtxt::default();
let infcx = cx.tcx.infer_ctxt().build();
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
@ -211,7 +204,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
}
}
let deref_span = spans_need_deref.get(&canonical_id);
if_chain! {
if is_type_diagnostic_item(cx, ty, sym::Vec);
if let Some(clone_spans) =
@ -247,7 +239,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
}
// cannot be destructured, no need for `*` suggestion
assert!(deref_span.is_none());
return;
}
}
@ -275,23 +266,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
);
}
assert!(deref_span.is_none());
return;
}
}
let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
let spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
// Suggests adding `*` to dereference the added reference.
if let Some(deref_span) = deref_span {
spans.extend(
deref_span
.iter()
.copied()
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
);
spans.sort_by_key(|&(span, _)| span);
}
multispan_sugg(diag, "consider taking a reference instead", spans);
};
@ -320,9 +300,6 @@ fn requires_exact_signature(attrs: &[Attribute]) -> bool {
#[derive(Default)]
struct MovedVariablesCtxt {
moved_vars: HirIdSet,
/// Spans which need to be prefixed with `*` for dereferencing the
/// suggested additional reference.
spans_need_deref: HirIdMap<FxHashSet<Span>>,
}
impl MovedVariablesCtxt {

View file

@ -1,11 +1,16 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::is_lint_allowed;
use clippy_utils::peel_blocks;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::has_drop;
use clippy_utils::{get_parent_node, is_lint_allowed};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
use rustc_hir::{
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, FnRetTy, ItemKind, Node, PatKind, Stmt, StmtKind,
UnsafeSource,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -86,7 +91,43 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
if let StmtKind::Semi(expr) = stmt.kind {
if has_no_effect(cx, expr) {
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
span_lint_hir_and_then(
cx,
NO_EFFECT,
expr.hir_id,
stmt.span,
"statement with no effect",
|diag| {
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
if let Node::Item(item) = parent.1
&& let ItemKind::Fn(sig, ..) = item.kind
&& let FnRetTy::Return(ret_ty) = sig.decl.output
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
&& let [.., final_stmt] = block.stmts
&& final_stmt.hir_id == stmt.hir_id
{
let expr_ty = cx.typeck_results().expr_ty(expr);
let mut ret_ty = hir_ty_to_ty(cx.tcx, ret_ty);
// Remove `impl Future<Output = T>` to get `T`
if cx.tcx.ty_is_opaque_future(ret_ty) &&
let Some(true_ret_ty) = cx.tcx.infer_ctxt().build().get_impl_future_output_ty(ret_ty)
{
ret_ty = true_ret_ty;
}
if ret_ty == expr_ty {
diag.span_suggestion(
stmt.span.shrink_to_lo(),
"did you mean to return it?",
"return ",
Applicability::MaybeIncorrect,
);
}
}
}
},
);
return true;
}
} else if let StmtKind::Local(local) = stmt.kind {

View file

@ -166,15 +166,15 @@ fn is_value_unfrozen_raw<'tcx>(
// have a value that is a frozen variant with a generic param (an example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
// However, it prevents a number of false negatives that is, I think, important:
// 1. assoc consts in trait defs referring to consts of themselves
// (an example is `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
// 2. a path expr referring to assoc consts whose type is doesn't have
// any frozen variants in trait defs (i.e. without substitute for `Self`).
// (e.g. borrowing `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
// 3. similar to the false positive above;
// but the value is an unfrozen variant, or the type has no enums. (An example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT`
// and `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
// 1. assoc consts in trait defs referring to consts of themselves (an example is
// `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
// 2. a path expr referring to assoc consts whose type is doesn't have any frozen variants in trait
// defs (i.e. without substitute for `Self`). (e.g. borrowing
// `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
// 3. similar to the false positive above; but the value is an unfrozen variant, or the type has no
// enums. (An example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` and
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
// One might be able to prevent these FNs correctly, and replace this with `false`;
// e.g. implementing `has_frozen_variant` described above, and not running this function
// when the type doesn't have any frozen variants would be the 'correct' way for the 2nd

View file

@ -85,7 +85,7 @@ fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static
}
}
fn is_allowed(val: &Constant) -> bool {
fn is_allowed(val: &Constant<'_>) -> bool {
match val {
&Constant::F32(f) => f == 0.0 || f.is_infinite(),
&Constant::F64(f) => f == 0.0 || f.is_infinite(),

View file

@ -140,6 +140,9 @@ fn try_get_option_occurrence<'tcx>(
let (as_ref, as_mut) = match &expr.kind {
ExprKind::AddrOf(_, Mutability::Not, _) => (true, false),
ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true),
_ if let Some(mutb) = cx.typeck_results().expr_ty(expr).ref_mutability() => {
(mutb == Mutability::Not, mutb == Mutability::Mut)
}
_ => (bind_annotation == BindingAnnotation::REF, bind_annotation == BindingAnnotation::REF_MUT),
};

View file

@ -5,6 +5,7 @@ use clippy_utils::source::snippet_opt;
use clippy_utils::ty::expr_sig;
use clippy_utils::visitors::contains_unsafe_block;
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
use hir::LifetimeName;
use if_chain::if_chain;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::def_id::DefId;
@ -15,6 +16,7 @@ use rustc_hir::{
ImplItemKind, ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind,
TyKind, Unsafety,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_lint::{LateContext, LateLintPass};
@ -166,6 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
cx,
cx.tcx.fn_sig(item.owner_id).subst_identity().skip_binder().inputs(),
sig.decl.inputs,
&sig.decl.output,
&[],
)
.filter(|arg| arg.mutability() == Mutability::Not)
@ -218,7 +221,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
check_mut_from_ref(cx, sig, Some(body));
let decl = sig.decl;
let sig = cx.tcx.fn_sig(item_id).subst_identity().skip_binder();
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, &decl.output, body.params)
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
.collect();
let results = check_ptr_arg_usage(cx, body, &lint_args);
@ -407,29 +410,27 @@ impl<'tcx> DerefTy<'tcx> {
}
}
#[expect(clippy::too_many_lines)]
fn check_fn_args<'cx, 'tcx: 'cx>(
cx: &'cx LateContext<'tcx>,
tys: &'tcx [Ty<'tcx>],
hir_tys: &'tcx [hir::Ty<'tcx>],
ret_ty: &'tcx FnRetTy<'tcx>,
params: &'tcx [Param<'tcx>],
) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
tys.iter()
.zip(hir_tys.iter())
.enumerate()
.filter_map(|(i, (ty, hir_ty))| {
if_chain! {
if let ty::Ref(_, ty, mutability) = *ty.kind();
if let ty::Adt(adt, substs) = *ty.kind();
if let TyKind::Ref(lt, ref ty) = hir_ty.kind;
if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
.filter_map(move |(i, (ty, hir_ty))| {
if let ty::Ref(_, ty, mutability) = *ty.kind()
&& let ty::Adt(adt, substs) = *ty.kind()
&& let TyKind::Ref(lt, ref ty) = hir_ty.kind
&& let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind
// Check that the name as typed matches the actual name of the type.
// e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
if let [.., name] = path.segments;
if cx.tcx.item_name(adt.did()) == name.ident.name;
then {
&& let [.., name] = path.segments
&& cx.tcx.item_name(adt.did()) == name.ident.name
{
let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Vec) => (
@ -454,30 +455,65 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
DerefTy::Path,
),
Some(sym::Cow) if mutability == Mutability::Not => {
let ty_name = name.args
if let Some((lifetime, ty)) = name.args
.and_then(|args| {
args.args.iter().find_map(|a| match a {
GenericArg::Type(x) => Some(x),
_ => None,
})
if let [GenericArg::Lifetime(lifetime), ty] = args.args {
return Some((lifetime, ty));
}
None
})
.and_then(|arg| snippet_opt(cx, arg.span))
.unwrap_or_else(|| substs.type_at(1).to_string());
span_lint_hir_and_then(
cx,
PTR_ARG,
emission_id,
hir_ty.span,
"using a reference to `Cow` is not recommended",
|diag| {
diag.span_suggestion(
hir_ty.span,
"change this to",
format!("&{}{ty_name}", mutability.prefix_str()),
Applicability::Unspecified,
);
{
if !lifetime.is_anonymous()
&& let FnRetTy::Return(ret_ty) = ret_ty
&& let ret_ty = hir_ty_to_ty(cx.tcx, ret_ty)
&& ret_ty
.walk()
.filter_map(|arg| {
arg.as_region().and_then(|lifetime| {
match lifetime.kind() {
ty::ReEarlyBound(r) => Some(r.def_id),
ty::ReLateBound(_, r) => r.kind.get_id(),
ty::ReFree(r) => r.bound_region.get_id(),
ty::ReStatic
| ty::ReVar(_)
| ty::RePlaceholder(_)
| ty::ReErased
| ty::ReError(_) => None,
}
})
})
.any(|def_id| {
matches!(
lifetime.res,
LifetimeName::Param(param_def_id) if def_id
.as_local()
.is_some_and(|def_id| def_id == param_def_id),
)
})
{
// `&Cow<'a, T>` when the return type uses 'a is okay
return None;
}
);
let ty_name =
snippet_opt(cx, ty.span()).unwrap_or_else(|| substs.type_at(1).to_string());
span_lint_hir_and_then(
cx,
PTR_ARG,
emission_id,
hir_ty.span,
"using a reference to `Cow` is not recommended",
|diag| {
diag.span_suggestion(
hir_ty.span,
"change this to",
format!("&{}{ty_name}", mutability.prefix_str()),
Applicability::Unspecified,
);
}
);
}
return None;
},
_ => return None,
@ -495,7 +531,6 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
},
deref_ty,
});
}
}
None
})

View file

@ -1,19 +1,20 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor, path_to_local, path_to_local_id,
peel_blocks, peel_blocks_with_stmt,
};
use clippy_utils::{higher, is_path_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, Node, PatKind, PathSegment, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::declare_tool_lint;
use rustc_session::impl_lint_pass;
use rustc_span::{sym, symbol::Symbol};
declare_clippy_lint! {
@ -41,7 +42,16 @@ declare_clippy_lint! {
"checks for expressions that could be replaced by the question mark operator"
}
declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
#[derive(Default)]
pub struct QuestionMark {
/// Keeps track of how many try blocks we are in at any point during linting.
/// This allows us to answer the question "are we inside of a try block"
/// very quickly, without having to walk up the parent chain, by simply checking
/// if it is greater than zero.
/// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
try_block_depth_stack: Vec<u32>,
}
impl_lint_pass!(QuestionMark => [QUESTION_MARK]);
enum IfBlockType<'hir> {
/// An `if x.is_xxx() { a } else { b } ` expression.
@ -68,98 +78,6 @@ enum IfBlockType<'hir> {
),
}
/// Checks if the given expression on the given context matches the following structure:
///
/// ```ignore
/// if option.is_none() {
/// return None;
/// }
/// ```
///
/// ```ignore
/// if result.is_err() {
/// return result;
/// }
/// ```
///
/// If it matches, it will suggest to use the question mark operator instead
fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if !is_else_clause(cx.tcx, expr);
if let ExprKind::MethodCall(segment, caller, ..) = &cond.kind;
let caller_ty = cx.typeck_results().expr_ty(caller);
let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else);
if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.param_env) &&
!matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
let sugg = if let Some(else_inner) = r#else {
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
format!("Some({receiver_str}?)")
} else {
return;
}
} else {
format!("{receiver_str}{}?;", if by_ref { ".as_ref()" } else { "" })
};
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr);
if !is_else_clause(cx.tcx, expr);
if let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind;
if ddpos.as_opt_usize().is_none();
if let PatKind::Binding(BindingAnnotation(by_ref, _), bind_id, ident, None) = field.kind;
let caller_ty = cx.typeck_results().expr_ty(let_expr);
let if_block = IfBlockType::IfLet(
cx.qpath_res(path1, let_pat.hir_id),
caller_ty,
ident.name,
let_expr,
if_then,
if_else
);
if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
|| is_early_return(sym::Result, cx, &if_block);
if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none();
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
let sugg = format!(
"{receiver_str}{}?{}",
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
if requires_semi { ";" } else { "" }
);
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool {
match *if_block {
IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => {
@ -230,11 +148,147 @@ fn expr_return_none_or_err(
}
}
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !in_constant(cx, expr.hir_id) {
check_is_none_or_err_and_early_return(cx, expr);
check_if_let_some_or_err_and_early_return(cx, expr);
impl QuestionMark {
fn inside_try_block(&self) -> bool {
self.try_block_depth_stack.last() > Some(&0)
}
/// Checks if the given expression on the given context matches the following structure:
///
/// ```ignore
/// if option.is_none() {
/// return None;
/// }
/// ```
///
/// ```ignore
/// if result.is_err() {
/// return result;
/// }
/// ```
///
/// If it matches, it will suggest to use the question mark operator instead
fn check_is_none_or_err_and_early_return<'tcx>(&self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if !self.inside_try_block();
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if !is_else_clause(cx.tcx, expr);
if let ExprKind::MethodCall(segment, caller, ..) = &cond.kind;
let caller_ty = cx.typeck_results().expr_ty(caller);
let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else);
if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.param_env) &&
!matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
let sugg = if let Some(else_inner) = r#else {
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
format!("Some({receiver_str}?)")
} else {
return;
}
} else {
format!("{receiver_str}{}?;", if by_ref { ".as_ref()" } else { "" })
};
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
fn check_if_let_some_or_err_and_early_return<'tcx>(&self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if !self.inside_try_block();
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr);
if !is_else_clause(cx.tcx, expr);
if let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind;
if ddpos.as_opt_usize().is_none();
if let PatKind::Binding(BindingAnnotation(by_ref, _), bind_id, ident, None) = field.kind;
let caller_ty = cx.typeck_results().expr_ty(let_expr);
let if_block = IfBlockType::IfLet(
cx.qpath_res(path1, let_pat.hir_id),
caller_ty,
ident.name,
let_expr,
if_then,
if_else
);
if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
|| is_early_return(sym::Result, cx, &if_block);
if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none();
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
let sugg = format!(
"{receiver_str}{}?{}",
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
if requires_semi { ";" } else { "" }
);
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
}
fn is_try_block(cx: &LateContext<'_>, bl: &rustc_hir::Block<'_>) -> bool {
if let Some(expr) = bl.expr
&& let rustc_hir::ExprKind::Call(callee, _) = expr.kind
{
is_path_lang_item(cx, callee, LangItem::TryTraitFromOutput)
} else {
false
}
}
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !in_constant(cx, expr.hir_id) {
self.check_is_none_or_err_and_early_return(cx, expr);
self.check_if_let_some_or_err_and_early_return(cx, expr);
}
}
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx rustc_hir::Block<'tcx>) {
if is_try_block(cx, block) {
*self
.try_block_depth_stack
.last_mut()
.expect("blocks are always part of bodies and must have a depth") += 1;
}
}
fn check_body(&mut self, _: &LateContext<'tcx>, _: &'tcx rustc_hir::Body<'tcx>) {
self.try_block_depth_stack.push(0);
}
fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &'tcx rustc_hir::Body<'tcx>) {
self.try_block_depth_stack.pop();
}
fn check_block_post(&mut self, cx: &LateContext<'tcx>, block: &'tcx rustc_hir::Block<'tcx>) {
if is_try_block(cx, block) {
*self
.try_block_depth_stack
.last_mut()
.expect("blocks are always part of bodies and must have a depth") -= 1;
}
}
}

View file

@ -296,8 +296,8 @@ fn check_possible_range_contains(
}
}
struct RangeBounds<'a> {
val: Constant,
struct RangeBounds<'a, 'tcx> {
val: Constant<'tcx>,
expr: &'a Expr<'a>,
id: HirId,
name_span: Span,
@ -309,7 +309,7 @@ struct RangeBounds<'a> {
// Takes a binary expression such as x <= 2 as input
// Breaks apart into various pieces, such as the value of the number,
// hir id of the variable, and direction/inclusiveness of the operator
fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a>> {
fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a, 'tcx>> {
if let ExprKind::Binary(ref op, l, r) = ex.kind {
let (inclusive, ordering) = match op.node {
BinOpKind::Gt => (false, Ordering::Greater),

View file

@ -0,0 +1,143 @@
use std::{iter::once, ops::ControlFlow};
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet};
use rustc_ast::{
ast::{Expr, ExprKind},
token::LitKind,
};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
/// ### What it does
/// Checks for raw string literals where a string literal can be used instead.
///
/// ### Why is this bad?
/// It's just unnecessary, but there are many cases where using a raw string literal is more
/// idiomatic than a string literal, so it's opt-in.
///
/// ### Example
/// ```rust
/// let r = r"Hello, world!";
/// ```
/// Use instead:
/// ```rust
/// let r = "Hello, world!";
/// ```
#[clippy::version = "1.72.0"]
pub NEEDLESS_RAW_STRINGS,
restriction,
"suggests using a string literal when a raw string literal is unnecessary"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for raw string literals with an unnecessary amount of hashes around them.
///
/// ### Why is this bad?
/// It's just unnecessary, and makes it look like there's more escaping needed than is actually
/// necessary.
///
/// ### Example
/// ```rust
/// let r = r###"Hello, "world"!"###;
/// ```
/// Use instead:
/// ```rust
/// let r = r#"Hello, "world"!"#;
/// ```
#[clippy::version = "1.72.0"]
pub NEEDLESS_RAW_STRING_HASHES,
style,
"suggests reducing the number of hashes around a raw string literal"
}
impl_lint_pass!(RawStrings => [NEEDLESS_RAW_STRINGS, NEEDLESS_RAW_STRING_HASHES]);
pub struct RawStrings {
pub needless_raw_string_hashes_allow_one: bool,
}
impl EarlyLintPass for RawStrings {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if !in_external_macro(cx.sess(), expr.span)
&& let ExprKind::Lit(lit) = expr.kind
&& let LitKind::StrRaw(max) | LitKind::ByteStrRaw(max) | LitKind::CStrRaw(max) = lit.kind
{
let str = lit.symbol.as_str();
let prefix = match lit.kind {
LitKind::StrRaw(..) => "r",
LitKind::ByteStrRaw(..) => "br",
LitKind::CStrRaw(..) => "cr",
_ => unreachable!(),
};
if !snippet(cx, expr.span, prefix).trim().starts_with(prefix) {
return;
}
if !str.contains(['\\', '"']) {
span_lint_and_sugg(
cx,
NEEDLESS_RAW_STRINGS,
expr.span,
"unnecessary raw string literal",
"try",
format!("{}\"{}\"", prefix.replace('r', ""), lit.symbol),
Applicability::MachineApplicable,
);
return;
}
let req = {
let mut following_quote = false;
let mut req = 0;
// `once` so a raw string ending in hashes is still checked
let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| {
match b {
b'"' => (following_quote, req) = (true, 1),
// I'm a bit surprised the compiler didn't optimize this out, there's no
// branch but it still ends up doing an unnecessary comparison, it's:
// - cmp r9b,1h
// - sbb cl,-1h
// which will add 1 if it's true. With this change, it becomes:
// - add cl,r9b
// isn't that so much nicer?
b'#' => req += u8::from(following_quote),
_ => {
if following_quote {
following_quote = false;
if req == max {
return ControlFlow::Break(req);
}
return ControlFlow::Continue(acc.max(req));
}
},
}
ControlFlow::Continue(acc)
});
match num {
ControlFlow::Continue(num) | ControlFlow::Break(num) => num,
}
};
if req < max {
let hashes = "#".repeat(req as usize);
span_lint_and_sugg(
cx,
NEEDLESS_RAW_STRING_HASHES,
expr.span,
"unnecessary hashes around raw string literal",
"try",
format!(r#"{prefix}{hashes}"{}"{hashes}"#, lit.symbol),
Applicability::MachineApplicable,
);
}
}
}
}

View file

@ -57,7 +57,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.32.0"]
pub REDUNDANT_CLONE,
perf,
nursery,
"`clone()` of an owned value that is going to be dropped immediately"
}
@ -320,8 +320,6 @@ fn base_local_and_movability<'tcx>(
mir: &mir::Body<'tcx>,
place: mir::Place<'tcx>,
) -> (mir::Local, CannotMoveOut) {
use rustc_middle::mir::PlaceRef;
// Dereference. You cannot move things out from a borrowed value.
let mut deref = false;
// Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
@ -330,17 +328,14 @@ fn base_local_and_movability<'tcx>(
// underlying type implements Copy
let mut slice = false;
let PlaceRef { local, mut projection } = place.as_ref();
while let [base @ .., elem] = projection {
projection = base;
for (base, elem) in place.as_ref().iter_projections() {
let base_ty = base.ty(&mir.local_decls, cx.tcx).ty;
deref |= matches!(elem, mir::ProjectionElem::Deref);
field |= matches!(elem, mir::ProjectionElem::Field(..))
&& has_drop(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
slice |= matches!(elem, mir::ProjectionElem::Index(..))
&& !is_copy(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
field |= matches!(elem, mir::ProjectionElem::Field(..)) && has_drop(cx, base_ty);
slice |= matches!(elem, mir::ProjectionElem::Index(..)) && !is_copy(cx, base_ty);
}
(local, deref || field || slice)
(place.local, deref || field || slice)
}
#[derive(Default)]

View file

@ -1,14 +1,14 @@
use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::get_parent_expr;
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_ast::visit as ast_visit;
use rustc_ast::visit::Visitor as AstVisitor;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit as hir_visit;
use rustc_hir::intravisit::Visitor as HirVisitor;
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_hir::intravisit::Visitor;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -51,59 +51,136 @@ impl ReturnVisitor {
}
}
impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor {
fn visit_expr(&mut self, ex: &'ast ast::Expr) {
if let ast::ExprKind::Ret(_) | ast::ExprKind::Try(_) = ex.kind {
impl<'tcx> Visitor<'tcx> for ReturnVisitor {
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
if let hir::ExprKind::Ret(_) | hir::ExprKind::Match(.., hir::MatchSource::TryDesugar) = ex.kind {
self.found_return = true;
} else {
hir_visit::walk_expr(self, ex);
}
ast_visit::walk_expr(self, ex);
}
}
impl EarlyLintPass for RedundantClosureCall {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if_chain! {
if let ast::ExprKind::Call(ref paren, _) = expr.kind;
if let ast::ExprKind::Paren(ref closure) = paren.kind;
if let ast::ExprKind::Closure(box ast::Closure { ref asyncness, ref fn_decl, ref body, .. }) = closure.kind;
then {
let mut visitor = ReturnVisitor::new();
visitor.visit_expr(body);
if !visitor.found_return {
span_lint_and_then(
cx,
REDUNDANT_CLOSURE_CALL,
expr.span,
"try not to call a closure in the expression where it is declared",
|diag| {
if fn_decl.inputs.is_empty() {
let mut app = Applicability::MachineApplicable;
let mut hint = Sugg::ast(cx, body, "..", closure.span.ctxt(), &mut app);
if asyncness.is_async() {
// `async x` is a syntax error, so it becomes `async { x }`
if !matches!(body.kind, ast::ExprKind::Block(_, _)) {
hint = hint.blockify();
}
hint = hint.asyncify();
}
diag.span_suggestion(expr.span, "try doing something like", hint.to_string(), app);
}
},
);
}
}
}
/// Checks if the body is owned by an async closure
fn is_async_closure(body: &hir::Body<'_>) -> bool {
if let hir::ExprKind::Closure(closure) = body.value.kind
&& let [resume_ty] = closure.fn_decl.inputs
&& let hir::TyKind::Path(hir::QPath::LangItem(hir::LangItem::ResumeTy, ..)) = resume_ty.kind
{
true
} else {
false
}
}
/// Tries to find the innermost closure:
/// ```rust,ignore
/// (|| || || || 42)()()()()
/// ^^^^^^^^^^^^^^ given this nested closure expression
/// ^^^^^ we want to return this closure
/// ```
/// It also has a parameter for how many steps to go in at most, so as to
/// not take more closures than there are calls.
fn find_innermost_closure<'tcx>(
cx: &LateContext<'tcx>,
mut expr: &'tcx hir::Expr<'tcx>,
mut steps: usize,
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, hir::IsAsync)> {
let mut data = None;
while let hir::ExprKind::Closure(closure) = expr.kind
&& let body = cx.tcx.hir().body(closure.body)
&& {
let mut visitor = ReturnVisitor::new();
visitor.visit_expr(body.value);
!visitor.found_return
}
&& steps > 0
{
expr = body.value;
data = Some((body.value, closure.fn_decl, if is_async_closure(body) {
hir::IsAsync::Async
} else {
hir::IsAsync::NotAsync
}));
steps -= 1;
}
data
}
/// "Walks up" the chain of calls to find the outermost call expression, and returns the depth:
/// ```rust,ignore
/// (|| || || 3)()()()
/// ^^ this is the call expression we were given
/// ^^ this is what we want to return (and the depth is 3)
/// ```
fn get_parent_call_exprs<'tcx>(
cx: &LateContext<'tcx>,
mut expr: &'tcx hir::Expr<'tcx>,
) -> (&'tcx hir::Expr<'tcx>, usize) {
let mut depth = 1;
while let Some(parent) = get_parent_expr(cx, expr)
&& let hir::ExprKind::Call(recv, _) = parent.kind
&& expr.span == recv.span
{
expr = parent;
depth += 1;
}
(expr, depth)
}
impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let hir::ExprKind::Call(recv, _) = expr.kind
// don't lint if the receiver is a call, too.
// we do this in order to prevent linting multiple times; consider:
// `(|| || 1)()()`
// ^^ we only want to lint for this call (but we walk up the calls to consider both calls).
// without this check, we'd end up linting twice.
&& !matches!(recv.kind, hir::ExprKind::Call(..))
&& let (full_expr, call_depth) = get_parent_call_exprs(cx, expr)
&& let Some((body, fn_decl, generator_kind)) = find_innermost_closure(cx, recv, call_depth)
{
span_lint_and_then(
cx,
REDUNDANT_CLOSURE_CALL,
full_expr.span,
"try not to call a closure in the expression where it is declared",
|diag| {
if fn_decl.inputs.is_empty() {
let mut applicability = Applicability::MachineApplicable;
let mut hint = Sugg::hir_with_context(cx, body, full_expr.span.ctxt(), "..", &mut applicability);
if generator_kind.is_async()
&& let hir::ExprKind::Closure(closure) = body.kind
{
let async_closure_body = cx.tcx.hir().body(closure.body);
// `async x` is a syntax error, so it becomes `async { x }`
if !matches!(async_closure_body.value.kind, hir::ExprKind::Block(_, _)) {
hint = hint.blockify();
}
hint = hint.asyncify();
}
diag.span_suggestion(
full_expr.span,
"try doing something like",
hint.maybe_par(),
applicability
);
}
}
);
}
}
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
fn count_closure_usage<'tcx>(
cx: &LateContext<'tcx>,

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