From fb41bfa77405233da5ddd324091380e018e3d956 Mon Sep 17 00:00:00 2001 From: Jason Newcomb <jsnewcomb@pm.me> Date: Wed, 31 Aug 2022 09:24:45 -0400 Subject: [PATCH] Merge commit 'f51aade56f93175dde89177a92e3669ebd8e7592' into clippyup --- .github/workflows/clippy.yml | 1 + .github/workflows/clippy_bors.yml | 1 + CHANGELOG.md | 160 +++- clippy_dev/src/bless.rs | 2 +- clippy_dev/src/fmt.rs | 8 +- clippy_dev/src/new_lint.rs | 2 +- clippy_dev/src/update_lints.rs | 4 +- clippy_lints/src/as_underscore.rs | 74 -- clippy_lints/src/borrow_as_ptr.rs | 99 -- clippy_lints/src/borrow_deref_ref.rs | 7 +- clippy_lints/src/bytecount.rs | 103 -- clippy_lints/src/bytes_count_to_len.rs | 70 -- ...se_sensitive_file_extension_comparisons.rs | 86 -- clippy_lints/src/casts/as_underscore.rs | 25 + clippy_lints/src/casts/borrow_as_ptr.rs | 37 + .../src/casts/cast_slice_from_raw_parts.rs | 63 ++ clippy_lints/src/casts/mod.rs | 109 ++- clippy_lints/src/casts/unnecessary_cast.rs | 9 +- clippy_lints/src/dereference.rs | 312 ++++++- clippy_lints/src/derive.rs | 5 +- clippy_lints/src/doc_link_with_quotes.rs | 2 +- clippy_lints/src/duplicate_mod.rs | 2 +- clippy_lints/src/escape.rs | 13 +- clippy_lints/src/floating_point_arithmetic.rs | 22 +- clippy_lints/src/format.rs | 24 +- clippy_lints/src/format_args.rs | 94 +- clippy_lints/src/format_impl.rs | 11 +- clippy_lints/src/functions/mod.rs | 56 +- clippy_lints/src/functions/result.rs | 100 ++ clippy_lints/src/functions/result_unit_err.rs | 66 -- clippy_lints/src/get_first.rs | 68 -- clippy_lints/src/if_let_mutex.rs | 50 +- clippy_lints/src/if_then_some_else_none.rs | 90 +- clippy_lints/src/lib.register_all.rs | 30 +- clippy_lints/src/lib.register_complexity.rs | 9 +- clippy_lints/src/lib.register_correctness.rs | 8 +- clippy_lints/src/lib.register_lints.rs | 50 +- clippy_lints/src/lib.register_nursery.rs | 5 +- clippy_lints/src/lib.register_pedantic.rs | 11 +- clippy_lints/src/lib.register_perf.rs | 2 + clippy_lints/src/lib.register_restriction.rs | 6 +- clippy_lints/src/lib.register_style.rs | 6 +- clippy_lints/src/lib.register_suspicious.rs | 5 + clippy_lints/src/lib.rs | 53 +- clippy_lints/src/loops/needless_collect.rs | 34 +- clippy_lints/src/manual_async_fn.rs | 2 +- clippy_lints/src/manual_ok_or.rs | 95 -- clippy_lints/src/manual_string_new.rs | 140 +++ clippy_lints/src/map_clone.rs | 167 ---- clippy_lints/src/map_err_ignore.rs | 154 --- .../src/matches/match_like_matches.rs | 4 +- clippy_lints/src/matches/needless_match.rs | 22 +- clippy_lints/src/methods/bytecount.rs | 70 ++ .../src/methods/bytes_count_to_len.rs | 37 + ...se_sensitive_file_extension_comparisons.rs | 41 + .../src/methods/collapsible_str_replace.rs | 96 ++ clippy_lints/src/methods/expect_used.rs | 18 +- clippy_lints/src/methods/get_first.rs | 39 + .../iter_on_single_or_empty_collections.rs | 107 +++ clippy_lints/src/methods/manual_ok_or.rs | 64 ++ clippy_lints/src/methods/map_clone.rs | 122 +++ clippy_lints/src/methods/map_err_ignore.rs | 34 + clippy_lints/src/methods/mod.rs | 857 ++++++++++++++++- clippy_lints/src/methods/mut_mutex_lock.rs | 30 + .../src/{ => methods}/open_options.rs | 44 +- .../src/methods/option_map_unwrap_or.rs | 2 +- .../src/methods/path_buf_push_overwrite.rs | 37 + .../src/methods/range_zip_with_len.rs | 34 + clippy_lints/src/methods/repeat_once.rs | 52 ++ .../src/methods/stable_sort_primitive.rs | 31 + .../src/methods/suspicious_to_owned.rs | 36 + .../src/methods/uninit_assumed_init.rs | 4 +- clippy_lints/src/methods/unit_hash.rs | 29 + .../src/{ => methods}/unnecessary_sort_by.rs | 156 ++-- .../src/methods/unnecessary_to_owned.rs | 37 +- clippy_lints/src/methods/unwrap_used.rs | 20 +- .../src/methods/vec_resize_to_zero.rs | 45 + .../src/methods/verbose_file_reads.rs | 28 + .../misc_early/unneeded_wildcard_pattern.rs | 2 +- .../src/mismatching_type_param_order.rs | 2 +- clippy_lints/src/multi_assignments.rs | 65 ++ clippy_lints/src/mut_mutex_lock.rs | 70 -- clippy_lints/src/only_used_in_recursion.rs | 880 +++++++----------- clippy_lints/src/option_if_let_else.rs | 167 +++- clippy_lints/src/partialeq_to_none.rs | 3 +- clippy_lints/src/path_buf_push_overwrite.rs | 72 -- clippy_lints/src/question_mark.rs | 2 +- clippy_lints/src/ranges.rs | 73 +- clippy_lints/src/rc_clone_in_vec_init.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 4 +- .../src/redundant_static_lifetimes.rs | 12 +- clippy_lints/src/repeat_once.rs | 89 -- clippy_lints/src/returns.rs | 10 +- clippy_lints/src/self_named_constructors.rs | 4 +- clippy_lints/src/stable_sort_primitive.rs | 144 --- clippy_lints/src/trait_bounds.rs | 106 ++- clippy_lints/src/transmute/mod.rs | 25 + .../src/transmute/transmute_undefined_repr.rs | 400 ++++---- .../src/transmute/transmuting_null.rs | 61 ++ clippy_lints/src/transmuting_null.rs | 89 -- clippy_lints/src/unicode.rs | 7 + clippy_lints/src/uninit_vec.rs | 2 +- clippy_lints/src/unit_hash.rs | 78 -- clippy_lints/src/unnecessary_wraps.rs | 2 +- clippy_lints/src/unused_peekable.rs | 225 +++++ clippy_lints/src/unused_rounding.rs | 2 +- clippy_lints/src/utils/conf.rs | 6 +- clippy_lints/src/utils/internal_lints.rs | 4 +- .../internal_lints/metadata_collector.rs | 2 +- clippy_lints/src/vec_resize_to_zero.rs | 64 -- clippy_lints/src/verbose_file_reads.rs | 88 -- clippy_lints/src/write.rs | 129 ++- clippy_utils/Cargo.toml | 1 + clippy_utils/src/lib.rs | 29 +- clippy_utils/src/macros.rs | 678 ++++++++++---- clippy_utils/src/msrvs.rs | 4 +- clippy_utils/src/paths.rs | 2 +- clippy_utils/src/ty.rs | 60 +- rust-toolchain | 2 +- rustc_tools_util/src/lib.rs | 4 +- tests/check-fmt.rs | 2 +- tests/compile-test.rs | 19 +- tests/dogfood.rs | 4 +- tests/integration.rs | 4 +- tests/lint_message_convention.rs | 4 +- .../toml_unknown_key/conf_unknown_key.stderr | 1 + ...se_sensitive_file_extension_comparisons.rs | 10 +- ...ensitive_file_extension_comparisons.stderr | 12 +- tests/ui/cast_raw_slice_pointer_cast.fixed | 24 + tests/ui/cast_raw_slice_pointer_cast.rs | 24 + tests/ui/cast_raw_slice_pointer_cast.stderr | 46 + tests/ui/collapsible_str_replace.fixed | 73 ++ tests/ui/collapsible_str_replace.rs | 76 ++ tests/ui/collapsible_str_replace.stderr | 86 ++ tests/ui/expect.rs | 3 +- tests/ui/expect.stderr | 10 +- tests/ui/floating_point_exp.fixed | 1 + tests/ui/floating_point_exp.rs | 1 + tests/ui/floating_point_exp.stderr | 12 +- tests/ui/floating_point_log.fixed | 1 + tests/ui/floating_point_log.rs | 1 + tests/ui/floating_point_log.stderr | 54 +- tests/ui/floating_point_logbase.fixed | 1 + tests/ui/floating_point_logbase.rs | 1 + tests/ui/floating_point_logbase.stderr | 12 +- tests/ui/floating_point_powf.fixed | 3 + tests/ui/floating_point_powf.rs | 3 + tests/ui/floating_point_powf.stderr | 52 +- tests/ui/floating_point_powi.fixed | 1 + tests/ui/floating_point_powi.rs | 1 + tests/ui/floating_point_powi.stderr | 12 +- tests/ui/floating_point_rad.fixed | 5 + tests/ui/floating_point_rad.rs | 5 + tests/ui/floating_point_rad.stderr | 32 +- tests/ui/format.fixed | 2 +- tests/ui/format.rs | 2 +- tests/ui/format_args.fixed | 51 +- tests/ui/format_args.rs | 51 +- tests/ui/format_args.stderr | 56 +- tests/ui/identity_op.fixed | 2 +- tests/ui/identity_op.rs | 2 +- tests/ui/if_let_mutex.rs | 8 + tests/ui/if_let_mutex.stderr | 30 +- tests/ui/if_then_some_else_none.stderr | 8 +- tests/ui/iter_on_empty_collections.fixed | 63 ++ tests/ui/iter_on_empty_collections.rs | 63 ++ tests/ui/iter_on_empty_collections.stderr | 40 + tests/ui/iter_on_single_items.fixed | 63 ++ tests/ui/iter_on_single_items.rs | 63 ++ tests/ui/iter_on_single_items.stderr | 40 + tests/ui/manual_string_new.fixed | 63 ++ tests/ui/manual_string_new.rs | 63 ++ tests/ui/manual_string_new.stderr | 58 ++ tests/ui/match_expr_like_matches_macro.fixed | 25 + tests/ui/match_expr_like_matches_macro.rs | 25 + tests/ui/multi_assignments.rs | 9 + tests/ui/multi_assignments.stderr | 40 + tests/ui/needless_borrow.fixed | 117 ++- tests/ui/needless_borrow.rs | 117 ++- tests/ui/needless_borrow.stderr | 48 +- tests/ui/needless_collect_indirect.rs | 189 ++++ tests/ui/needless_collect_indirect.stderr | 119 ++- tests/ui/needless_match.fixed | 39 + tests/ui/needless_match.rs | 46 + tests/ui/needless_match.stderr | 23 +- tests/ui/needless_return.fixed | 10 +- tests/ui/needless_return.rs | 10 +- tests/ui/only_used_in_recursion.rs | 143 ++- tests/ui/only_used_in_recursion.stderr | 193 +++- tests/ui/only_used_in_recursion2.rs | 91 ++ tests/ui/only_used_in_recursion2.stderr | 63 ++ tests/ui/option_if_let_else.fixed | 9 + tests/ui/option_if_let_else.rs | 21 + tests/ui/option_if_let_else.stderr | 48 +- tests/ui/or_fun_call.fixed | 4 +- tests/ui/or_fun_call.rs | 4 +- tests/ui/or_fun_call.stderr | 6 +- tests/ui/partialeq_to_none.fixed | 12 + tests/ui/partialeq_to_none.rs | 12 + tests/ui/partialeq_to_none.stderr | 28 +- .../positional_named_format_parameters.fixed | 56 ++ .../ui/positional_named_format_parameters.rs | 56 ++ .../positional_named_format_parameters.stderr | 418 +++++++++ tests/ui/question_mark.fixed | 15 + tests/ui/question_mark.rs | 15 + tests/ui/regex.rs | 2 +- tests/ui/result_large_err.rs | 98 ++ tests/ui/result_large_err.stderr | 91 ++ tests/ui/same_item_push.rs | 1 + tests/ui/string_add.rs | 4 +- tests/ui/string_add_assign.fixed | 4 +- tests/ui/string_add_assign.rs | 4 +- tests/ui/suspicious_to_owned.rs | 62 ++ tests/ui/suspicious_to_owned.stderr | 42 + tests/ui/trait_duplication_in_bounds.fixed | 112 +++ tests/ui/trait_duplication_in_bounds.rs | 230 ++--- tests/ui/trait_duplication_in_bounds.stderr | 185 +--- .../trait_duplication_in_bounds_unfixable.rs | 166 ++++ ...ait_duplication_in_bounds_unfixable.stderr | 71 ++ tests/ui/transmute_undefined_repr.rs | 12 + tests/ui/transmute_undefined_repr.stderr | 38 +- tests/ui/unicode.fixed | 48 +- tests/ui/unicode.rs | 48 +- tests/ui/unicode.stderr | 46 +- tests/ui/unnecessary_cast.fixed | 9 + tests/ui/unnecessary_cast.rs | 9 + tests/ui/unnecessary_cast.stderr | 14 +- .../ui/unnecessary_owned_empty_strings.fixed | 1 + tests/ui/unnecessary_owned_empty_strings.rs | 1 + .../ui/unnecessary_owned_empty_strings.stderr | 2 +- tests/ui/unnecessary_to_owned.fixed | 28 + tests/ui/unnecessary_to_owned.rs | 28 + tests/ui/unused_peekable.rs | 144 +++ tests/ui/unused_peekable.stderr | 67 ++ tests/ui/unwrap.rs | 3 +- tests/ui/unwrap.stderr | 10 +- tests/ui/unwrap_expect_used.rs | 25 + tests/ui/unwrap_expect_used.stderr | 26 +- tests/ui/useless_conversion_try.rs | 4 +- tests/ui/useless_conversion_try.stderr | 2 +- tests/ui/vec_resize_to_zero.rs | 12 +- tests/ui/vec_resize_to_zero.stderr | 10 +- tests/ui/verbose_file_reads.rs | 2 +- tests/workspace.rs | 14 +- 244 files changed, 9305 insertions(+), 4024 deletions(-) delete mode 100644 clippy_lints/src/as_underscore.rs delete mode 100644 clippy_lints/src/borrow_as_ptr.rs delete mode 100644 clippy_lints/src/bytecount.rs delete mode 100644 clippy_lints/src/bytes_count_to_len.rs delete mode 100644 clippy_lints/src/case_sensitive_file_extension_comparisons.rs create mode 100644 clippy_lints/src/casts/as_underscore.rs create mode 100644 clippy_lints/src/casts/borrow_as_ptr.rs create mode 100644 clippy_lints/src/casts/cast_slice_from_raw_parts.rs create mode 100644 clippy_lints/src/functions/result.rs delete mode 100644 clippy_lints/src/functions/result_unit_err.rs delete mode 100644 clippy_lints/src/get_first.rs delete mode 100644 clippy_lints/src/manual_ok_or.rs create mode 100644 clippy_lints/src/manual_string_new.rs delete mode 100644 clippy_lints/src/map_clone.rs delete mode 100644 clippy_lints/src/map_err_ignore.rs create mode 100644 clippy_lints/src/methods/bytecount.rs create mode 100644 clippy_lints/src/methods/bytes_count_to_len.rs create mode 100644 clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs create mode 100644 clippy_lints/src/methods/collapsible_str_replace.rs create mode 100644 clippy_lints/src/methods/get_first.rs create mode 100644 clippy_lints/src/methods/iter_on_single_or_empty_collections.rs create mode 100644 clippy_lints/src/methods/manual_ok_or.rs create mode 100644 clippy_lints/src/methods/map_clone.rs create mode 100644 clippy_lints/src/methods/map_err_ignore.rs create mode 100644 clippy_lints/src/methods/mut_mutex_lock.rs rename clippy_lints/src/{ => methods}/open_options.rs (80%) create mode 100644 clippy_lints/src/methods/path_buf_push_overwrite.rs create mode 100644 clippy_lints/src/methods/range_zip_with_len.rs create mode 100644 clippy_lints/src/methods/repeat_once.rs create mode 100644 clippy_lints/src/methods/stable_sort_primitive.rs create mode 100644 clippy_lints/src/methods/suspicious_to_owned.rs create mode 100644 clippy_lints/src/methods/unit_hash.rs rename clippy_lints/src/{ => methods}/unnecessary_sort_by.rs (63%) create mode 100644 clippy_lints/src/methods/vec_resize_to_zero.rs create mode 100644 clippy_lints/src/methods/verbose_file_reads.rs create mode 100644 clippy_lints/src/multi_assignments.rs delete mode 100644 clippy_lints/src/mut_mutex_lock.rs delete mode 100644 clippy_lints/src/path_buf_push_overwrite.rs delete mode 100644 clippy_lints/src/repeat_once.rs delete mode 100644 clippy_lints/src/stable_sort_primitive.rs create mode 100644 clippy_lints/src/transmute/transmuting_null.rs delete mode 100644 clippy_lints/src/transmuting_null.rs delete mode 100644 clippy_lints/src/unit_hash.rs create mode 100644 clippy_lints/src/unused_peekable.rs delete mode 100644 clippy_lints/src/vec_resize_to_zero.rs delete mode 100644 clippy_lints/src/verbose_file_reads.rs create mode 100644 tests/ui/cast_raw_slice_pointer_cast.fixed create mode 100644 tests/ui/cast_raw_slice_pointer_cast.rs create mode 100644 tests/ui/cast_raw_slice_pointer_cast.stderr create mode 100644 tests/ui/collapsible_str_replace.fixed create mode 100644 tests/ui/collapsible_str_replace.rs create mode 100644 tests/ui/collapsible_str_replace.stderr create mode 100644 tests/ui/iter_on_empty_collections.fixed create mode 100644 tests/ui/iter_on_empty_collections.rs create mode 100644 tests/ui/iter_on_empty_collections.stderr create mode 100644 tests/ui/iter_on_single_items.fixed create mode 100644 tests/ui/iter_on_single_items.rs create mode 100644 tests/ui/iter_on_single_items.stderr create mode 100644 tests/ui/manual_string_new.fixed create mode 100644 tests/ui/manual_string_new.rs create mode 100644 tests/ui/manual_string_new.stderr create mode 100644 tests/ui/multi_assignments.rs create mode 100644 tests/ui/multi_assignments.stderr create mode 100644 tests/ui/only_used_in_recursion2.rs create mode 100644 tests/ui/only_used_in_recursion2.stderr create mode 100644 tests/ui/positional_named_format_parameters.fixed create mode 100644 tests/ui/positional_named_format_parameters.rs create mode 100644 tests/ui/positional_named_format_parameters.stderr create mode 100644 tests/ui/result_large_err.rs create mode 100644 tests/ui/result_large_err.stderr create mode 100644 tests/ui/suspicious_to_owned.rs create mode 100644 tests/ui/suspicious_to_owned.stderr create mode 100644 tests/ui/trait_duplication_in_bounds.fixed create mode 100644 tests/ui/trait_duplication_in_bounds_unfixable.rs create mode 100644 tests/ui/trait_duplication_in_bounds_unfixable.stderr create mode 100644 tests/ui/unused_peekable.rs create mode 100644 tests/ui/unused_peekable.stderr diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 0e27cc927ac..fac2c99714d 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -24,6 +24,7 @@ env: RUST_BACKTRACE: 1 CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 + CARGO_INCREMENTAL: 0 jobs: base: diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 97453303cd6..30607af4901 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -10,6 +10,7 @@ env: RUST_BACKTRACE: 1 CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 + CARGO_INCREMENTAL: 0 defaults: run: diff --git a/CHANGELOG.md b/CHANGELOG.md index 380cd451987..c488c142e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,157 @@ document. ## Unreleased / In Rust Nightly -[7c21f91b...master](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...master) +[d7b5cbf0...master](https://github.com/rust-lang/rust-clippy/compare/d7b5cbf0...master) + +## Rust 1.63 + +Current stable, released 2022-08-11 + +[7c21f91b...d7b5cbf0](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...d7b5cbf0) + +### New Lints + +* [`borrow_deref_ref`] + [#7930](https://github.com/rust-lang/rust-clippy/pull/7930) +* [`doc_link_with_quotes`] + [#8385](https://github.com/rust-lang/rust-clippy/pull/8385) +* [`no_effect_replace`] + [#8754](https://github.com/rust-lang/rust-clippy/pull/8754) +* [`rc_clone_in_vec_init`] + [#8769](https://github.com/rust-lang/rust-clippy/pull/8769) +* [`derive_partial_eq_without_eq`] + [#8796](https://github.com/rust-lang/rust-clippy/pull/8796) +* [`mismatching_type_param_order`] + [#8831](https://github.com/rust-lang/rust-clippy/pull/8831) +* [`duplicate_mod`] [#8832](https://github.com/rust-lang/rust-clippy/pull/8832) +* [`unused_rounding`] + [#8866](https://github.com/rust-lang/rust-clippy/pull/8866) +* [`get_first`] [#8882](https://github.com/rust-lang/rust-clippy/pull/8882) +* [`swap_ptr_to_ref`] + [#8916](https://github.com/rust-lang/rust-clippy/pull/8916) +* [`almost_complete_letter_range`] + [#8918](https://github.com/rust-lang/rust-clippy/pull/8918) +* [`needless_parens_on_range_literals`] + [#8933](https://github.com/rust-lang/rust-clippy/pull/8933) +* [`as_underscore`] [#8934](https://github.com/rust-lang/rust-clippy/pull/8934) + +### Moves and Deprecations + +* Rename `eval_order_dependence` to [`mixed_read_write_in_expression`], move to + `nursery` [#8621](https://github.com/rust-lang/rust-clippy/pull/8621) + +### Enhancements + +* [`undocumented_unsafe_blocks`]: Now also lints on unsafe trait implementations + [#8761](https://github.com/rust-lang/rust-clippy/pull/8761) +* [`empty_line_after_outer_attr`]: Now also lints on argumentless macros + [#8790](https://github.com/rust-lang/rust-clippy/pull/8790) +* [`expect_used`]: Now can be disabled in tests with the `allow-expect-in-tests` + option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802) +* [`unwrap_used`]: Now can be disabled in tests with the `allow-unwrap-in-tests` + option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802) +* [`disallowed_methods`]: Now also lints indirect usages + [#8852](https://github.com/rust-lang/rust-clippy/pull/8852) +* [`get_last_with_len`]: Now also lints `VecDeque` and any deref to slice + [#8862](https://github.com/rust-lang/rust-clippy/pull/8862) +* [`manual_range_contains`]: Now also lints on chains of `&&` and `||` + [#8884](https://github.com/rust-lang/rust-clippy/pull/8884) +* [`rc_clone_in_vec_init`]: Now also lints on `Weak` + [#8885](https://github.com/rust-lang/rust-clippy/pull/8885) +* [`dbg_macro`]: Introduce `allow-dbg-in-tests` config option + [#8897](https://github.com/rust-lang/rust-clippy/pull/8897) +* [`use_self`]: Now also lints on `TupleStruct` and `Struct` patterns + [#8899](https://github.com/rust-lang/rust-clippy/pull/8899) +* [`manual_find_map`] and [`manual_filter_map`]: Now also lints on more complex + method chains inside `map` + [#8930](https://github.com/rust-lang/rust-clippy/pull/8930) +* [`needless_return`]: Now also lints on macro expressions in return statements + [#8932](https://github.com/rust-lang/rust-clippy/pull/8932) +* [`doc_markdown`]: Users can now indicate, that the `doc-valid-idents` config + should extend the default and not replace it + [#8944](https://github.com/rust-lang/rust-clippy/pull/8944) +* [`disallowed_names`]: Users can now indicate, that the `disallowed-names` + config should extend the default and not replace it + [#8944](https://github.com/rust-lang/rust-clippy/pull/8944) +* [`never_loop`]: Now checks for `continue` in struct expression + [#9002](https://github.com/rust-lang/rust-clippy/pull/9002) + +### False Positive Fixes + +* [`useless_transmute`]: No longer lints on types with erased regions + [#8564](https://github.com/rust-lang/rust-clippy/pull/8564) +* [`vec_init_then_push`]: No longer lints when further extended + [#8699](https://github.com/rust-lang/rust-clippy/pull/8699) +* [`cmp_owned`]: No longer lints on `From::from` for `Copy` types + [#8807](https://github.com/rust-lang/rust-clippy/pull/8807) +* [`redundant_allocation`]: No longer lints on fat pointers that would become + thin pointers [#8813](https://github.com/rust-lang/rust-clippy/pull/8813) +* [`derive_partial_eq_without_eq`]: + * Handle differing predicates applied by `#[derive(PartialEq)]` and + `#[derive(Eq)]` + [#8869](https://github.com/rust-lang/rust-clippy/pull/8869) + * No longer lints on non-public types and better handles generics + [#8950](https://github.com/rust-lang/rust-clippy/pull/8950) +* [`empty_line_after_outer_attr`]: No longer lints empty lines in inner + string values [#8892](https://github.com/rust-lang/rust-clippy/pull/8892) +* [`branches_sharing_code`]: No longer lints when using different binding names + [#8901](https://github.com/rust-lang/rust-clippy/pull/8901) +* [`significant_drop_in_scrutinee`]: No longer lints on Try `?` and `await` + desugared expressions [#8902](https://github.com/rust-lang/rust-clippy/pull/8902) +* [`checked_conversions`]: No longer lints in `const` contexts + [#8907](https://github.com/rust-lang/rust-clippy/pull/8907) +* [`iter_overeager_cloned`]: No longer lints on `.cloned().flatten()` when + `T::Item` doesn't implement `IntoIterator` + [#8960](https://github.com/rust-lang/rust-clippy/pull/8960) + +### Suggestion Fixes/Improvements + +* [`vec_init_then_push`]: Suggest to remove `mut` binding when possible + [#8699](https://github.com/rust-lang/rust-clippy/pull/8699) +* [`manual_range_contains`]: Fix suggestion for integers with different signs + [#8763](https://github.com/rust-lang/rust-clippy/pull/8763) +* [`identity_op`]: Add parenthesis to suggestions where required + [#8786](https://github.com/rust-lang/rust-clippy/pull/8786) +* [`cast_lossless`]: No longer gives wrong suggestion on `usize`/`isize`->`f64` + [#8778](https://github.com/rust-lang/rust-clippy/pull/8778) +* [`rc_clone_in_vec_init`]: Add suggestion + [#8814](https://github.com/rust-lang/rust-clippy/pull/8814) +* The "unknown field" error messages for config files now wraps the field names + [#8823](https://github.com/rust-lang/rust-clippy/pull/8823) +* [`cast_abs_to_unsigned`]: Do not remove cast if it's required + [#8876](https://github.com/rust-lang/rust-clippy/pull/8876) +* [`significant_drop_in_scrutinee`]: Improve lint message for types that are not + references and not trivially clone-able + [#8902](https://github.com/rust-lang/rust-clippy/pull/8902) +* [`for_loops_over_fallibles`]: Now suggests the correct variant of `iter()`, + `iter_mut()` or `into_iter()` + [#8941](https://github.com/rust-lang/rust-clippy/pull/8941) + +### ICE Fixes + +* Fix ICE in [`let_unit_value`] when calling a `static`/`const` callable type + [#8835](https://github.com/rust-lang/rust-clippy/pull/8835) +* Fix ICEs on callable `static`/`const`s + [#8896](https://github.com/rust-lang/rust-clippy/pull/8896) +* [`needless_late_init`] + [#8912](https://github.com/rust-lang/rust-clippy/pull/8912) +* Fix ICE in shadow lints + [#8913](https://github.com/rust-lang/rust-clippy/pull/8913) + +### Documentation Improvements + +* Clippy has a [Book](https://doc.rust-lang.org/nightly/clippy/) now! + [#7359](https://github.com/rust-lang/rust-clippy/pull/7359) +* Add a *copy lint name*-button to Clippy's lint list + [#8839](https://github.com/rust-lang/rust-clippy/pull/8839) +* Display past names of renamed lints on Clippy's lint list + [#8843](https://github.com/rust-lang/rust-clippy/pull/8843) +* Add the ability to show the lint output in the lint list + [#8947](https://github.com/rust-lang/rust-clippy/pull/8947) ## Rust 1.62 -Current stable, released 2022-06-30 +Released 2022-06-30 [d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b) @@ -3481,6 +3627,7 @@ Released 2018-09-13 [`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss [`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes +[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp @@ -3496,6 +3643,7 @@ Released 2018-09-13 [`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if [`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if [`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match +[`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace [`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain [`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty [`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime @@ -3656,6 +3804,8 @@ Released 2018-09-13 [`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator [`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero +[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections +[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items [`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next [`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain @@ -3697,6 +3847,7 @@ Released 2018-09-13 [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat +[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap [`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or @@ -3747,6 +3898,7 @@ Released 2018-09-13 [`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic [`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one +[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl [`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate @@ -3827,6 +3979,7 @@ Released 2018-09-13 [`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none [`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite [`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch +[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence [`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl @@ -3872,6 +4025,7 @@ Released 2018-09-13 [`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts [`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs [`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used +[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else @@ -3930,6 +4084,7 @@ Released 2018-09-13 [`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl [`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings [`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn +[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned [`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting [`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref [`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments @@ -4002,6 +4157,7 @@ Released 2018-09-13 [`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label +[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable [`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit diff --git a/clippy_dev/src/bless.rs b/clippy_dev/src/bless.rs index f5c51b9474f..92b2771f3fe 100644 --- a/clippy_dev/src/bless.rs +++ b/clippy_dev/src/bless.rs @@ -37,7 +37,7 @@ fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) { return; } - let test_output_file = fs::read(&test_output_path).expect("Unable to read test output file"); + 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 { diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 3b27f061eb0..357cf6fc43a 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -46,7 +46,7 @@ pub fn run(check: bool, verbose: bool) { // dependency if fs::read_to_string(project_root.join("Cargo.toml")) .expect("Failed to read clippy Cargo.toml") - .contains(&"[target.'cfg(NOT_A_PLATFORM)'.dependencies]") + .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") { return Err(CliError::IntellijSetupActive); } @@ -193,10 +193,10 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { let args = &["--version"]; if context.verbose { - println!("{}", format_command(&program, &dir, args)); + println!("{}", format_command(program, &dir, args)); } - let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?; + let output = Command::new(program).current_dir(&dir).args(args.iter()).output()?; if output.status.success() { Ok(()) @@ -207,7 +207,7 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { Err(CliError::RustfmtNotInstalled) } else { Err(CliError::CommandFailed( - format_command(&program, &dir, args), + format_command(program, &dir, args), std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), )) } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 10a8f31f457..be05e67d724 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -155,7 +155,7 @@ fn to_camel_case(name: &str) -> String { name.split('_') .map(|s| { if s.is_empty() { - String::from("") + String::new() } else { [&s[0..1].to_uppercase(), &s[1..]].concat() } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 05e79a24188..c503142e5e4 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -418,7 +418,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io .expect("failed to find `impl_lint_pass` terminator"); impl_lint_pass_end += impl_lint_pass_start; - if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) { + if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) { let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); for c in content[lint_name_end..impl_lint_pass_end].chars() { // Remove trailing whitespace @@ -451,7 +451,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io } let mut content = - fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); + fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); eprintln!( "warn: you will have to manually remove any code related to `{}` from `{}`", diff --git a/clippy_lints/src/as_underscore.rs b/clippy_lints/src/as_underscore.rs deleted file mode 100644 index 0bdef9d0a7e..00000000000 --- a/clippy_lints/src/as_underscore.rs +++ /dev/null @@ -1,74 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, TyKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Check 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 - /// undetected du to the type being inferred. - /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. - /// - /// ### Example - /// ```rust - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as _); - /// ``` - /// Use instead: - /// ```rust - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as usize); - /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, - restriction, - "detects `as _` conversion" -} -declare_lint_pass!(AsUnderscore => [AS_UNDERSCORE]); - -impl<'tcx> LateLintPass<'tcx> for AsUnderscore { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if let ExprKind::Cast(_, ty) = expr.kind && let TyKind::Infer = ty.kind { - - let ty_resolved = cx.typeck_results().expr_ty(expr); - if let ty::Error(_) = ty_resolved.kind() { - span_lint_and_help( - cx, - AS_UNDERSCORE, - expr.span, - "using `as _` conversion", - None, - "consider giving the type explicitly", - ); - } else { - span_lint_and_then( - cx, - AS_UNDERSCORE, - expr.span, - "using `as _` conversion", - |diag| { - diag.span_suggestion( - ty.span, - "consider giving the type explicitly", - ty_resolved, - Applicability::MachineApplicable, - ); - } - ); - } - } - } -} diff --git a/clippy_lints/src/borrow_as_ptr.rs b/clippy_lints/src/borrow_as_ptr.rs deleted file mode 100644 index 0993adbae2e..00000000000 --- a/clippy_lints/src/borrow_as_ptr.rs +++ /dev/null @@ -1,99 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_no_std_crate; -use clippy_utils::source::snippet_opt; -use clippy_utils::{meets_msrv, msrvs}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `&expr as *const T` or - /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or - /// `ptr::addr_of_mut` instead. - /// - /// ### Why is this bad? - /// This would improve readability and avoid creating a reference - /// that points to an uninitialized value or unaligned place. - /// Read the `ptr::addr_of` docs for more information. - /// - /// ### Example - /// ```rust - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; - /// ``` - /// Use instead: - /// ```rust - /// let val = 1; - /// let p = std::ptr::addr_of!(val); - /// - /// let mut val_mut = 1; - /// let p_mut = std::ptr::addr_of_mut!(val_mut); - /// ``` - #[clippy::version = "1.60.0"] - pub BORROW_AS_PTR, - pedantic, - "borrowing just to cast to a raw pointer" -} - -impl_lint_pass!(BorrowAsPtr => [BORROW_AS_PTR]); - -pub struct BorrowAsPtr { - msrv: Option<RustcVersion>, -} - -impl BorrowAsPtr { - #[must_use] - pub fn new(msrv: Option<RustcVersion>) -> Self { - Self { msrv } - } -} - -impl<'tcx> LateLintPass<'tcx> for BorrowAsPtr { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) { - return; - } - - if expr.span.from_expansion() { - return; - } - - if_chain! { - if let ExprKind::Cast(left_expr, ty) = &expr.kind; - if let TyKind::Ptr(_) = ty.kind; - if let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = &left_expr.kind; - - then { - let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; - let macro_name = match mutability { - Mutability::Not => "addr_of", - Mutability::Mut => "addr_of_mut", - }; - - span_lint_and_sugg( - cx, - BORROW_AS_PTR, - expr.span, - "borrow as raw pointer", - "try", - format!( - "{}::ptr::{}!({})", - core_or_std, - macro_name, - snippet_opt(cx, e.span).unwrap() - ), - Applicability::MachineApplicable, - ); - } - } - } - - extract_msrv_attr!(LateContext); -} diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 937765b6614..c4520d00392 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -29,22 +29,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// fn foo(_x: &str) {} - /// /// let s = &String::new(); /// /// let a: &String = &* s; - /// foo(&*s); /// ``` /// /// Use instead: /// ```rust - /// # fn foo(_x: &str) {} /// # let s = &String::new(); /// let a: &String = s; - /// foo(&**s); /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.63.0"] pub BORROW_DEREF_REF, complexity, "deref on an immutable reference returns the same type as itself" diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs deleted file mode 100644 index 326ce34082a..00000000000 --- a/clippy_lints/src/bytecount.rs +++ /dev/null @@ -1,103 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::match_type; -use clippy_utils::visitors::is_local_used; -use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, UintTy}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for naive byte counts - /// - /// ### Why is this bad? - /// The [`bytecount`](https://crates.io/crates/bytecount) - /// crate has methods to count your bytes faster, especially for large slices. - /// - /// ### Known problems - /// If you have predominantly small slices, the - /// `bytecount::count(..)` method may actually be slower. However, if you can - /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be - /// faster in those cases. - /// - /// ### Example - /// ```rust - /// # let vec = vec![1_u8]; - /// let count = vec.iter().filter(|x| **x == 0u8).count(); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let vec = vec![1_u8]; - /// let count = bytecount::count(&vec, 0u8); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NAIVE_BYTECOUNT, - pedantic, - "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values" -} - -declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]); - -impl<'tcx> LateLintPass<'tcx> for ByteCount { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let ExprKind::MethodCall(count, [count_recv], _) = expr.kind; - if count.ident.name == sym::count; - if let ExprKind::MethodCall(filter, [filter_recv, filter_arg], _) = count_recv.kind; - if filter.ident.name == sym!(filter); - if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind; - let body = cx.tcx.hir().body(body); - if let [param] = body.params; - if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; - if let ExprKind::Binary(ref op, l, r) = body.value.kind; - if op.node == BinOpKind::Eq; - if match_type(cx, - cx.typeck_results().expr_ty(filter_recv).peel_refs(), - &paths::SLICE_ITER); - let operand_is_arg = |expr| { - let expr = peel_ref_operators(cx, peel_blocks(expr)); - path_to_local_id(expr, arg_id) - }; - let needle = if operand_is_arg(l) { - r - } else if operand_is_arg(r) { - l - } else { - return; - }; - if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); - if !is_local_used(cx, needle, arg_id); - then { - let haystack = if let ExprKind::MethodCall(path, args, _) = - filter_recv.kind { - let p = path.ident.name; - if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { - &args[0] - } else { - filter_recv - } - } else { - filter_recv - }; - let mut applicability = Applicability::MaybeIncorrect; - span_lint_and_sugg( - cx, - NAIVE_BYTECOUNT, - expr.span, - "you appear to be counting bytes the naive way", - "consider using the bytecount crate", - format!("bytecount::count({}, {})", - snippet_with_applicability(cx, haystack.span, "..", &mut applicability), - snippet_with_applicability(cx, needle.span, "..", &mut applicability)), - applicability, - ); - } - }; - } -} diff --git a/clippy_lints/src/bytes_count_to_len.rs b/clippy_lints/src/bytes_count_to_len.rs deleted file mode 100644 index d70dbf5b239..00000000000 --- a/clippy_lints/src/bytes_count_to_len.rs +++ /dev/null @@ -1,70 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{match_def_path, paths}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// It checks for `str::bytes().count()` and suggests replacing it with - /// `str::len()`. - /// - /// ### Why is this bad? - /// `str::bytes().count()` is longer and may not be as performant as using - /// `str::len()`. - /// - /// ### Example - /// ```rust - /// "hello".bytes().count(); - /// String::from("hello").bytes().count(); - /// ``` - /// Use instead: - /// ```rust - /// "hello".len(); - /// String::from("hello").len(); - /// ``` - #[clippy::version = "1.62.0"] - pub BYTES_COUNT_TO_LEN, - complexity, - "Using `bytes().count()` when `len()` performs the same functionality" -} - -declare_lint_pass!(BytesCountToLen => [BYTES_COUNT_TO_LEN]); - -impl<'tcx> LateLintPass<'tcx> for BytesCountToLen { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if_chain! { - if let hir::ExprKind::MethodCall(_, expr_args, _) = &expr.kind; - if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match_def_path(cx, expr_def_id, &paths::ITER_COUNT); - - if let [bytes_expr] = &**expr_args; - if let hir::ExprKind::MethodCall(_, bytes_args, _) = &bytes_expr.kind; - if let Some(bytes_def_id) = cx.typeck_results().type_dependent_def_id(bytes_expr.hir_id); - if match_def_path(cx, bytes_def_id, &paths::STR_BYTES); - - if let [str_expr] = &**bytes_args; - let ty = cx.typeck_results().expr_ty(str_expr).peel_refs(); - - if is_type_diagnostic_item(cx, ty, sym::String) || ty.kind() == &ty::Str; - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BYTES_COUNT_TO_LEN, - expr.span, - "using long and hard to read `.bytes().count()`", - "consider calling `.len()` instead", - format!("{}.len()", snippet_with_applicability(cx, str_expr.span, "..", &mut applicability)), - applicability - ); - } - }; - } -} diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs deleted file mode 100644 index 7eff71d5007..00000000000 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ /dev/null @@ -1,86 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use if_chain::if_chain; -use rustc_ast::ast::LitKind; -use rustc_hir::{Expr, ExprKind, PathSegment}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{source_map::Spanned, symbol::sym, Span}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `ends_with` with possible file extensions - /// and suggests to use a case-insensitive approach instead. - /// - /// ### Why is this bad? - /// `ends_with` is case-sensitive and may not detect files with a valid extension. - /// - /// ### Example - /// ```rust - /// fn is_rust_file(filename: &str) -> bool { - /// filename.ends_with(".rs") - /// } - /// ``` - /// Use instead: - /// ```rust - /// fn is_rust_file(filename: &str) -> bool { - /// let filename = std::path::Path::new(filename); - /// filename.extension() - /// .map(|ext| ext.eq_ignore_ascii_case("rs")) - /// .unwrap_or(false) - /// } - /// ``` - #[clippy::version = "1.51.0"] - pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - pedantic, - "Checks for calls to ends_with with case-sensitive file extensions" -} - -declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]); - -fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> { - if_chain! { - if let ExprKind::MethodCall(PathSegment { ident, .. }, [obj, extension, ..], span) = expr.kind; - if ident.as_str() == "ends_with"; - if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind; - if (2..=6).contains(&ext_literal.as_str().len()); - if ext_literal.as_str().starts_with('.'); - if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit()) - || ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()); - then { - let mut ty = ctx.typeck_results().expr_ty(obj); - ty = match ty.kind() { - ty::Ref(_, ty, ..) => *ty, - _ => ty - }; - - match ty.kind() { - ty::Str => { - return Some(span); - }, - ty::Adt(def, _) => { - if ctx.tcx.is_diagnostic_item(sym::String, def.did()) { - return Some(span); - } - }, - _ => { return None; } - } - } - } - None -} - -impl<'tcx> LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons { - fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) { - span_lint_and_help( - ctx, - CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - span, - "case-sensitive file extension comparison", - None, - "consider using a case-insensitive comparison instead", - ); - } - } -} diff --git a/clippy_lints/src/casts/as_underscore.rs b/clippy_lints/src/casts/as_underscore.rs new file mode 100644 index 00000000000..56e894c6261 --- /dev/null +++ b/clippy_lints/src/casts/as_underscore.rs @@ -0,0 +1,25 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::AS_UNDERSCORE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ty: &'tcx Ty<'_>) { + if matches!(ty.kind, TyKind::Infer) { + span_lint_and_then(cx, AS_UNDERSCORE, expr.span, "using `as _` conversion", |diag| { + let ty_resolved = cx.typeck_results().expr_ty(expr); + if let ty::Error(_) = ty_resolved.kind() { + diag.help("consider giving the type explicitly"); + } else { + diag.span_suggestion( + ty.span, + "consider giving the type explicitly", + ty_resolved, + Applicability::MachineApplicable, + ); + } + }); + } +} diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs new file mode 100644 index 00000000000..6e1f8cd64f0 --- /dev/null +++ b/clippy_lints/src/casts/borrow_as_ptr.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_no_std_crate; +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 super::BORROW_AS_PTR; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + cast_expr: &'tcx Expr<'_>, + cast_to: &'tcx Ty<'_>, +) { + if matches!(cast_to.kind, TyKind::Ptr(_)) + && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind + { + let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; + let macro_name = match mutability { + Mutability::Not => "addr_of", + Mutability::Mut => "addr_of_mut", + }; + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0; + + span_lint_and_sugg( + cx, + BORROW_AS_PTR, + expr.span, + "borrow as raw pointer", + "try", + format!("{}::ptr::{}!({})", core_or_std, macro_name, snip), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs new file mode 100644 index 00000000000..284ef165998 --- /dev/null +++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -0,0 +1,63 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{match_def_path, meets_msrv, msrvs, paths}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def_id::DefId, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_semver::RustcVersion; + +use super::CAST_SLICE_FROM_RAW_PARTS; + +enum RawPartsKind { + Immutable, + Mutable, +} + +fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> { + if match_def_path(cx, did, &paths::SLICE_FROM_RAW_PARTS) { + Some(RawPartsKind::Immutable) + } else if match_def_path(cx, did, &paths::SLICE_FROM_RAW_PARTS_MUT) { + Some(RawPartsKind::Mutable) + } else { + None + } +} + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_to: Ty<'_>, + msrv: Option<RustcVersion>, +) { + if_chain! { + if meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS); + if let ty::RawPtr(ptrty) = cast_to.kind(); + if let ty::Slice(_) = ptrty.ty.kind(); + if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); + if let Some(rpk) = raw_parts_kind(cx, fun_def_id); + then { + let func = match rpk { + RawPartsKind::Immutable => "from_raw_parts", + RawPartsKind::Mutable => "from_raw_parts_mut" + }; + let span = expr.span; + let mut applicability = Applicability::MachineApplicable; + let ptr = snippet_with_applicability(cx, ptr_arg.span, "ptr", &mut applicability); + let len = snippet_with_applicability(cx, len_arg.span, "len", &mut applicability); + span_lint_and_sugg( + cx, + CAST_SLICE_FROM_RAW_PARTS, + span, + &format!("casting the result of `{func}` to {cast_to}"), + "replace with", + format!("core::ptr::slice_{func}({ptr}, {len})"), + applicability + ); + } + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index af3798a0cc8..cc5d346b954 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -1,3 +1,5 @@ +mod as_underscore; +mod borrow_as_ptr; mod cast_abs_to_unsigned; mod cast_enum_constructor; mod cast_lossless; @@ -8,6 +10,7 @@ mod cast_ptr_alignment; mod cast_ref_to_mut; mod cast_sign_loss; mod cast_slice_different_sizes; +mod cast_slice_from_raw_parts; mod char_lit_as_u8; mod fn_to_numeric_cast; mod fn_to_numeric_cast_any; @@ -16,7 +19,7 @@ mod ptr_as_ptr; mod unnecessary_cast; mod utils; -use clippy_utils::is_hir_ty_cfg_dependant; +use clippy_utils::{is_hir_ty_cfg_dependant, meets_msrv, msrvs}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -506,6 +509,93 @@ declare_clippy_lint! { "casting the result of `abs()` to an unsigned integer can panic" } +declare_clippy_lint! { + /// ### What it does + /// Check 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 + /// undetected due to the type being inferred. + /// + /// The lint is allowed by default as using `_` is less wordy than always specifying the type. + /// + /// ### Example + /// ```rust + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as _); + /// ``` + /// Use instead: + /// ```rust + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as usize); + /// ``` + #[clippy::version = "1.63.0"] + pub AS_UNDERSCORE, + restriction, + "detects `as _` conversion" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `&expr as *const T` or + /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or + /// `ptr::addr_of_mut` instead. + /// + /// ### Why is this bad? + /// This would improve readability and avoid creating a reference + /// that points to an uninitialized value or unaligned place. + /// Read the `ptr::addr_of` docs for more information. + /// + /// ### Example + /// ```rust + /// let val = 1; + /// let p = &val as *const i32; + /// + /// let mut val_mut = 1; + /// let p_mut = &mut val_mut as *mut i32; + /// ``` + /// Use instead: + /// ```rust + /// let val = 1; + /// let p = std::ptr::addr_of!(val); + /// + /// let mut val_mut = 1; + /// let p_mut = std::ptr::addr_of_mut!(val_mut); + /// ``` + #[clippy::version = "1.60.0"] + pub BORROW_AS_PTR, + pedantic, + "borrowing just to cast to a raw pointer" +} +declare_clippy_lint! { + /// ### What it does + /// Checks for a raw slice being cast to a slice pointer + /// + /// ### Why is this bad? + /// This can result in multiple `&mut` references to the same location when only a pointer is + /// required. + /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require + /// the same [safety requirements] to be upheld. + /// + /// ### Example + /// ```rust,ignore + /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; + /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); + /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); + /// ``` + /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + #[clippy::version = "1.64.0"] + pub CAST_SLICE_FROM_RAW_PARTS, + suspicious, + "casting a slice created from a pointer and length to a slice pointer" +} + pub struct Casts { msrv: Option<RustcVersion>, } @@ -534,7 +624,10 @@ impl_lint_pass!(Casts => [ PTR_AS_PTR, CAST_ENUM_TRUNCATION, CAST_ENUM_CONSTRUCTOR, - CAST_ABS_TO_UNSIGNED + CAST_ABS_TO_UNSIGNED, + AS_UNDERSCORE, + BORROW_AS_PTR, + CAST_SLICE_FROM_RAW_PARTS ]); impl<'tcx> LateLintPass<'tcx> for Casts { @@ -547,8 +640,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts { return; } - if let ExprKind::Cast(cast_expr, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { + if let ExprKind::Cast(cast_expr, cast_to_hir) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to_hir) { return; } let (cast_from, cast_to) = ( @@ -559,7 +652,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) { return; } - + cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, self.msrv); fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); @@ -575,6 +668,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv); cast_enum_constructor::check(cx, expr, cast_expr, cast_from); } + + as_underscore::check(cx, expr, cast_to_hir); + + if meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) { + borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir); + } } cast_ref_to_mut::check(cx, expr); diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index fff7da8e33f..19d2e6e1d12 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -90,13 +90,20 @@ pub(super) fn check<'tcx>( fn lint_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; + let replaced_literal; + let matchless = if literal_str.contains(['(', ')']) { + replaced_literal = literal_str.replace(['(', ')'], ""); + &replaced_literal + } else { + literal_str + }; span_lint_and_sugg( cx, UNNECESSARY_CAST, expr.span, &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), "try", - format!("{}_{}", literal_str.trim_end_matches('.'), cast_to), + format!("{}_{}", matchless.trim_end_matches('.'), cast_to), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59f10247a11..1506ea604f0 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,24 +1,34 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, ty_sig, variant_of_res}; -use clippy_utils::{get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage}; +use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res}; +use clippy_utils::{ + fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, meets_msrv, msrvs, path_to_local, + walk_to_expr_usage, +}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, - ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, - TraitItemKind, TyKind, UnOp, + self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, + GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, + Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp, }; +use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Binder, BoundVariableKind, List, Ty, TyCtxt, TypeVisitable, TypeckResults}; +use rustc_middle::ty::{ + self, subst::Subst, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, + ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults, +}; +use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP}; -use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause}; +use std::collections::VecDeque; declare_clippy_lint! { /// ### What it does @@ -151,6 +161,7 @@ pub struct Dereferencing { /// been finished. Note we can't lint at the end of every body as they can be nested within each /// other. current_body: Option<BodyId>, + /// The list of locals currently being checked by the lint. /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted. /// This is needed for or patterns where one of the branches can be linted, but another can not @@ -158,6 +169,19 @@ pub struct Dereferencing { /// /// e.g. `m!(x) | Foo::Bar(ref x)` ref_locals: FxIndexMap<HirId, Option<RefPat>>, + + // `IntoIterator` for arrays requires Rust 1.53. + msrv: Option<RustcVersion>, +} + +impl Dereferencing { + #[must_use] + pub fn new(msrv: Option<RustcVersion>) -> Self { + Self { + msrv, + ..Dereferencing::default() + } + } } struct StateData { @@ -170,6 +194,7 @@ struct StateData { struct DerefedBorrow { count: usize, msg: &'static str, + snip_expr: Option<HirId>, } enum State { @@ -250,7 +275,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let (position, adjustments) = walk_parents(cx, expr); + let (position, adjustments) = walk_parents(cx, expr, self.msrv); match kind { RefOp::Deref => { @@ -331,20 +356,23 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { 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"; + let impl_msg = "the borrowed expression implements the required traits"; - let (required_refs, msg) = if position.can_auto_borrow() { - (1, if deref_count == 1 { borrow_msg } else { deref_msg }) + let (required_refs, msg, snip_expr) = if position.can_auto_borrow() { + (1, if deref_count == 1 { borrow_msg } else { deref_msg }, None) + } else if let Position::ImplArg(hir_id) = position { + (0, impl_msg, Some(hir_id)) } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = next_adjust.map(|a| &a.kind) { if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable() { - (3, deref_msg) + (3, deref_msg, None) } else { - (2, deref_msg) + (2, deref_msg, None) } } else { - (2, deref_msg) + (2, deref_msg, None) }; if deref_count >= required_refs { @@ -354,6 +382,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { // can't be removed without breaking the code. See earlier comment. count: deref_count - required_refs, msg, + snip_expr, }), StateData { span: expr.span, hir_id: expr.hir_id, position }, )); @@ -510,7 +539,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { spans: vec![pat.span], app, replacements: vec![(pat.span, snip.into())], - hir_id: pat.hir_id + hir_id: pat.hir_id, }), ); } @@ -542,6 +571,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { self.current_body = None; } } + + extract_msrv_attr!(LateContext); } fn try_parse_ref_op<'tcx>( @@ -594,6 +625,7 @@ enum Position { /// The method is defined on a reference type. e.g. `impl Foo for &T` MethodReceiverRefImpl, Callee, + ImplArg(HirId), FieldAccess(Symbol), Postfix, Deref, @@ -630,7 +662,7 @@ impl Position { | Self::Callee | Self::FieldAccess(_) | Self::Postfix => PREC_POSTFIX, - Self::Deref => PREC_PREFIX, + Self::ImplArg(_) | Self::Deref => PREC_PREFIX, Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p, } } @@ -639,8 +671,12 @@ impl Position { /// Walks up the parent expressions attempting to determine both how stable the auto-deref result /// is, and which adjustments will be applied to it. Note this will not consider auto-borrow /// locations as those follow different rules. -#[allow(clippy::too_many_lines)] -fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) { +#[expect(clippy::too_many_lines)] +fn walk_parents<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + msrv: Option<RustcVersion>, +) -> (Position, &'tcx [Adjustment<'tcx>]) { let mut adjustments = [].as_slice(); let mut precedence = 0i8; let ctxt = e.span.ctxt(); @@ -745,13 +781,20 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & .iter() .position(|arg| arg.hir_id == child_id) .zip(expr_sig(cx, func)) - .and_then(|(i, sig)| sig.input_with_hir(i)) - .map(|(hir_ty, ty)| match hir_ty { - // Type inference for closures can depend on how they're called. Only go by the explicit - // types here. - Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()), - None => ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) - .position_for_arg(), + .and_then(|(i, sig)| { + sig.input_with_hir(i).map(|(hir_ty, ty)| match hir_ty { + // Type inference for closures can depend on how they're called. Only go by the explicit + // types here. + Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()), + None => { + if let ty::Param(param_ty) = ty.skip_binder().kind() { + needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv) + } else { + ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) + .position_for_arg() + } + }, + }) }), ExprKind::MethodCall(_, args, _) => { let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); @@ -773,7 +816,7 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & .and_then(|subs| subs.get(1..)) { Some(subs) => cx.tcx.mk_substs(subs.iter().copied()), - None => cx.tcx.mk_substs([].iter()), + None => cx.tcx.mk_substs(std::iter::empty::<ty::subst::GenericArg<'_>>()), } && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() { // Trait methods taking `&self` sub_ty @@ -792,12 +835,17 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & Position::MethodReceiver } } else { - ty_auto_deref_stability( - cx, - cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i)), - precedence, - ) - .position_for_arg() + let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i]; + if let ty::Param(param_ty) = ty.kind() { + needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv) + } else { + ty_auto_deref_stability( + cx, + cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i)), + precedence, + ) + .position_for_arg() + } } }) }, @@ -948,6 +996,205 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { v.0 } +// Checks whether: +// * child is an expression of the form `&e` in an argument position requiring an `impl Trait` +// * `e`'s type implements `Trait` and is copyable +// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`. +// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to +// be moved, but it cannot be. +fn needless_borrow_impl_arg_position<'tcx>( + cx: &LateContext<'tcx>, + parent: &Expr<'tcx>, + arg_index: usize, + param_ty: ParamTy, + mut expr: &Expr<'tcx>, + precedence: i8, + msrv: Option<RustcVersion>, +) -> Position { + let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait(); + let sized_trait_def_id = cx.tcx.lang_items().sized_trait(); + + let Some(callee_def_id) = fn_def_id(cx, parent) else { return Position::Other(precedence) }; + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + let substs_with_expr_ty = cx + .typeck_results() + .node_substs(if let ExprKind::Call(callee, _) = parent.kind { + callee.hir_id + } else { + parent.hir_id + }); + + let predicates = cx.tcx.param_env(callee_def_id).caller_bounds(); + let projection_predicates = predicates + .iter() + .filter_map(|predicate| { + if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() { + Some(projection_predicate) + } else { + None + } + }) + .collect::<Vec<_>>(); + + let mut trait_with_ref_mut_self_method = false; + + // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return. + if predicates + .iter() + .filter_map(|predicate| { + if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() + && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx) + { + Some(trait_predicate.trait_ref.def_id) + } else { + None + } + }) + .inspect(|trait_def_id| { + trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id); + }) + .all(|trait_def_id| { + Some(trait_def_id) == destruct_trait_def_id + || Some(trait_def_id) == sized_trait_def_id + || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id) + }) + { + return Position::Other(precedence); + } + + // `substs_with_referent_ty` can be constructed outside of `check_referent` because the same + // elements are modified each time `check_referent` is called. + let mut substs_with_referent_ty = substs_with_expr_ty.to_vec(); + + let mut check_referent = |referent| { + let referent_ty = cx.typeck_results().expr_ty(referent); + + if !is_copy(cx, referent_ty) { + return false; + } + + // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321 + if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) { + return false; + } + + if !replace_types( + cx, + param_ty, + referent_ty, + fn_sig, + arg_index, + &projection_predicates, + &mut substs_with_referent_ty, + ) { + return false; + } + + predicates.iter().all(|predicate| { + if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() + && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id) + && let ty::Param(param_ty) = trait_predicate.self_ty().kind() + && let GenericArgKind::Type(ty) = substs_with_referent_ty[param_ty.index as usize].unpack() + && ty.is_array() + && !meets_msrv(msrv, msrvs::ARRAY_INTO_ITERATOR) + { + return false; + } + + let predicate = EarlyBinder(predicate).subst(cx.tcx, &substs_with_referent_ty); + let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate); + cx.tcx + .infer_ctxt() + .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation)) + }) + }; + + let mut needless_borrow = false; + while let ExprKind::AddrOf(_, _, referent) = expr.kind { + if !check_referent(referent) { + break; + } + expr = referent; + needless_borrow = true; + } + + if needless_borrow { + Position::ImplArg(expr.hir_id) + } else { + Position::Other(precedence) + } +} + +fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { + cx.tcx + .associated_items(trait_def_id) + .in_definition_order() + .any(|assoc_item| { + if assoc_item.fn_has_self_parameter { + let self_ty = cx.tcx.fn_sig(assoc_item.def_id).skip_binder().inputs()[0]; + matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut)) + } else { + false + } + }) +} + +// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting +// projected type that is a type parameter. Returns `false` if replacing the types would have an +// effect on the function signature beyond substituting `new_ty` for `param_ty`. +// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757 +fn replace_types<'tcx>( + cx: &LateContext<'tcx>, + param_ty: ParamTy, + new_ty: Ty<'tcx>, + fn_sig: FnSig<'tcx>, + arg_index: usize, + projection_predicates: &[ProjectionPredicate<'tcx>], + substs: &mut [ty::GenericArg<'tcx>], +) -> bool { + let mut replaced = BitSet::new_empty(substs.len()); + + let mut deque = VecDeque::with_capacity(substs.len()); + deque.push_back((param_ty, new_ty)); + + while let Some((param_ty, new_ty)) = deque.pop_front() { + // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in. + if !fn_sig + .inputs_and_output + .iter() + .enumerate() + .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx))) + { + return false; + } + + substs[param_ty.index as usize] = ty::GenericArg::from(new_ty); + + // The `replaced.insert(...)` check provides some protection against infinite loops. + if replaced.insert(param_ty.index) { + for projection_predicate in projection_predicates { + if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx) + && let ty::Term::Ty(term_ty) = projection_predicate.term + && let ty::Param(term_param_ty) = term_ty.kind() + { + let item_def_id = projection_predicate.projection_ty.item_def_id; + let assoc_item = cx.tcx.associated_item(item_def_id); + let projection = cx.tcx + .mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, &[])); + + if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection) + && substs[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty) + { + deque.push_back((*term_param_ty, projected_ty)); + } + } + } + } + } + + true +} + struct TyPosition<'tcx> { position: Position, ty: Option<Ty<'tcx>>, @@ -1086,7 +1333,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }, State::DerefedBorrow(state) => { let mut app = Applicability::MachineApplicable; - let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); + let snip_expr = state.snip_expr.map_or(expr, |hir_id| cx.tcx.hir().expect_expr(hir_id)); + let (snip, snip_is_macro) = snippet_with_context(cx, snip_expr.span, data.span.ctxt(), "..", &mut app); span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| { let calls_field = matches!(expr.kind, ExprKind::Field(..)) && matches!(data.position, Position::Callee); let sugg = if !snip_is_macro diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index a982990e418..9ca443b7dff 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -516,7 +516,10 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain( params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate { - trait_ref: TraitRef::new(eq_trait_id, tcx.mk_substs([tcx.mk_param_from_def(param)].into_iter())), + trait_ref: TraitRef::new( + eq_trait_id, + tcx.mk_substs(std::iter::once(tcx.mk_param_from_def(param))), + ), constness: BoundConstness::NotConst, polarity: ImplPolarity::Positive, }))) diff --git a/clippy_lints/src/doc_link_with_quotes.rs b/clippy_lints/src/doc_link_with_quotes.rs index cb07f57e870..0ff1d2755da 100644 --- a/clippy_lints/src/doc_link_with_quotes.rs +++ b/clippy_lints/src/doc_link_with_quotes.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// /// See also: [`foo`] /// fn bar() {} /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.63.0"] pub DOC_LINK_WITH_QUOTES, pedantic, "possible typo for an intra-doc link" diff --git a/clippy_lints/src/duplicate_mod.rs b/clippy_lints/src/duplicate_mod.rs index e1eb3b6324c..7ff7068f0b0 100644 --- a/clippy_lints/src/duplicate_mod.rs +++ b/clippy_lints/src/duplicate_mod.rs @@ -39,7 +39,7 @@ declare_clippy_lint! { /// // a.rs /// use crate::b; /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub DUPLICATE_MOD, suspicious, "file loaded as module multiple times" diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 1ac7bfba06b..327865e4c85 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_hir; -use clippy_utils::ty::contains_ty; use rustc_hir::intravisit; use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node, Pat, PatKind}; use rustc_infer::infer::TyCtxtInferExt; @@ -30,18 +29,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// # fn foo(bar: usize) {} - /// let x = Box::new(1); - /// foo(*x); - /// println!("{}", *x); + /// fn foo(x: Box<u32>) {} /// ``` /// /// Use instead: /// ```rust - /// # fn foo(bar: usize) {} - /// let x = 1; - /// foo(x); - /// println!("{}", x); + /// fn foo(x: u32) {} /// ``` #[clippy::version = "pre 1.29.0"] pub BOXED_LOCAL, @@ -172,7 +165,7 @@ impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { // skip if there is a `self` parameter binding to a type // that contains `Self` (i.e.: `self: Box<Self>`), see #4804 if let Some(trait_self_ty) = self.trait_self_ty { - if map.name(cmt.hir_id) == kw::SelfLower && contains_ty(cmt.place.ty(), trait_self_ty) { + if map.name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) { return; } } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index df9b41d2c98..bb50e8fcabb 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -172,7 +172,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { expr.span, "logarithm for bases 2, 10 and e can be computed more accurately", "consider using", - format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method), + format!("{}.{}()", Sugg::hir(cx, &args[0], "..").maybe_par(), method), Applicability::MachineApplicable, ); } @@ -263,13 +263,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { ( SUBOPTIMAL_FLOPS, "square-root of a number can be computed more efficiently and accurately", - format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")), + format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..").maybe_par()), ) } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { ( IMPRECISE_FLOPS, "cube-root of a number can be computed more accurately", - format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")), + format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..").maybe_par()), ) } else if let Some(exponent) = get_integer_from_float_constant(&value) { ( @@ -277,7 +277,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { "exponentiation with integer powers can be computed more efficiently", format!( "{}.powi({})", - Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &args[0], "..").maybe_par(), numeric_literal::format(&exponent.to_string(), None, false) ), ) @@ -327,7 +327,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { "consider using", format!( "{}.mul_add({}, {})", - Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &args[0], "..").maybe_par(), Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, other_addend, ".."), ), @@ -418,7 +418,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { "consider using", format!( "{}.exp_m1()", - Sugg::hir(cx, self_arg, "..") + Sugg::hir(cx, self_arg, "..").maybe_par() ), Applicability::MachineApplicable, ); @@ -550,11 +550,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { then { let positive_abs_sugg = ( "manual implementation of `abs` method", - format!("{}.abs()", Sugg::hir(cx, body, "..")), + format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), ); let negative_abs_sugg = ( "manual implementation of negation of `abs` method", - format!("-{}.abs()", Sugg::hir(cx, body, "..")), + format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), ); let sugg = if is_testing_positive(cx, cond, body) { if if_expr_positive { @@ -621,7 +621,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "log base can be expressed more clearly", "consider using", - format!("{}.log({})", Sugg::hir(cx, largs_self, ".."), Sugg::hir(cx, rargs_self, ".."),), + format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),), Applicability::MachineApplicable, ); } @@ -651,7 +651,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) { - let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")); + let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); if_chain! { if let ExprKind::Lit(ref literal) = mul_lhs.kind; if let ast::LitKind::Float(ref value, float_type) = literal.node; @@ -677,7 +677,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) { - let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")); + let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); if_chain! { if let ExprKind::Lit(ref literal) = mul_lhs.kind; if let ast::LitKind::Float(ref value, float_type) = literal.node; diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 925a8cb8dee..0c5851cdbed 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; -use clippy_utils::source::{snippet_opt, snippet_with_applicability}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; @@ -56,29 +56,27 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { }; let mut applicability = Applicability::MachineApplicable; - if format_args.value_args.is_empty() { - match *format_args.format_string_parts { + if format_args.args.is_empty() { + match *format_args.format_string.parts { [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), [_] => { - if let Some(s_src) = snippet_opt(cx, format_args.format_string_span) { - // Simulate macro expansion, converting {{ and }} to { and }. - let s_expand = s_src.replace("{{", "{").replace("}}", "}"); - let sugg = format!("{}.to_string()", s_expand); - span_useless_format(cx, call_site, sugg, applicability); - } + // Simulate macro expansion, converting {{ and }} to { and }. + let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}"); + let sugg = format!("{}.to_string()", s_expand); + span_useless_format(cx, call_site, sugg, applicability); }, [..] => {}, } - } else if let [value] = *format_args.value_args { + } else if let [arg] = &*format_args.args { + let value = arg.param.value; if_chain! { - if format_args.format_string_parts == [kw::Empty]; + if format_args.format_string.parts == [kw::Empty]; if match cx.typeck_results().expr_ty(value).peel_refs().kind() { ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()), ty::Str => true, _ => false, }; - if let Some(args) = format_args.args(); - if args.iter().all(|arg| arg.format_trait == sym::Display && !arg.has_string_formatting()); + if !arg.format.has_string_formatting(); then { let is_new_string = match value.kind { ExprKind::Binary(..) => true, diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 1e6feaac26c..9fb9fd99748 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; -use clippy_utils::macros::{is_format_macro, FormatArgsArg, FormatArgsExpn}; +use clippy_utils::macros::{is_format_macro, FormatArgsExpn}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::implements_trait; use if_chain::if_chain; +use itertools::Itertools; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; @@ -74,20 +75,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { if let Some(macro_def_id) = outermost_expn_data.macro_def_id; if is_format_macro(cx, macro_def_id); if let ExpnKind::Macro(_, name) = outermost_expn_data.kind; - if let Some(args) = format_args.args(); then { - for (i, arg) in args.iter().enumerate() { - if arg.format_trait != sym::Display { + for arg in &format_args.args { + if arg.format.has_string_formatting() { continue; } - if arg.has_string_formatting() { + if is_aliased(&format_args, arg.param.value.hir_id) { continue; } - if is_aliased(&args, i) { - continue; - } - check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.value); - check_to_string_in_format_args(cx, name, arg.value); + check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value); + check_to_string_in_format_args(cx, name, arg.param.value); } } } @@ -134,45 +131,56 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex if is_diag_trait_item(cx, method_def_id, sym::ToString); let receiver_ty = cx.typeck_results().expr_ty(receiver); if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display); + let (n_needed_derefs, target) = + count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter()); + if implements_trait(cx, target, display_trait_id, &[]); + if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait(); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - let (n_needed_derefs, target) = count_needed_derefs( - receiver_ty, - cx.typeck_results().expr_adjustments(receiver).iter(), - ); - if implements_trait(cx, target, display_trait_id, &[]) { - if n_needed_derefs == 0 { - span_lint_and_sugg( - cx, - TO_STRING_IN_FORMAT_ARGS, - value.span.with_lo(receiver.span.hi()), - &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name), - "remove this", - String::new(), - Applicability::MachineApplicable, - ); - } else { - span_lint_and_sugg( - cx, - TO_STRING_IN_FORMAT_ARGS, - value.span, - &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name), - "use this", - format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs), - Applicability::MachineApplicable, - ); - } + let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]); + if n_needed_derefs == 0 && !needs_ref { + span_lint_and_sugg( + cx, + TO_STRING_IN_FORMAT_ARGS, + value.span.with_lo(receiver.span.hi()), + &format!( + "`to_string` applied to a type that implements `Display` in `{}!` args", + name + ), + "remove this", + String::new(), + Applicability::MachineApplicable, + ); + } else { + span_lint_and_sugg( + cx, + TO_STRING_IN_FORMAT_ARGS, + value.span, + &format!( + "`to_string` applied to a type that implements `Display` in `{}!` args", + name + ), + "use this", + format!( + "{}{:*>width$}{}", + if needs_ref { "&" } else { "" }, + "", + receiver_snippet, + width = n_needed_derefs + ), + Applicability::MachineApplicable, + ); } } } } -// Returns true if `args[i]` "refers to" or "is referred to by" another argument. -fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool { - let value = args[i].value; - args.iter() - .enumerate() - .any(|(j, arg)| i != j && std::ptr::eq(value, arg.value)) +// Returns true if `hir_id` is referred to by multiple format params +fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool { + args.params() + .filter(|param| param.value.hir_id == hir_id) + .at_most_one() + .is_err() } fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 04b5be6c80e..d8bc0bf08f2 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn}; +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn}; use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -168,10 +168,9 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, if let macro_def_id = outer_macro.def_id; if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn); if is_format_macro(cx, macro_def_id); - if let Some(args) = format_args.args(); then { - for arg in args { - if arg.format_trait != impl_trait.name { + for arg in format_args.args { + if arg.format.r#trait != impl_trait.name { continue; } check_format_arg_self(cx, expr, &arg, impl_trait); @@ -180,11 +179,11 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, } } -fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) { +fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) { // Handle multiple dereferencing of references e.g. &&self // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) // Since the argument to fmt is itself a reference: &self - let reference = peel_ref_operators(cx, arg.value); + let reference = peel_ref_operators(cx, arg.param.value); let map = cx.tcx.hir(); // Is the reference self? if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 73261fb8a44..90911e0bf25 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -1,6 +1,6 @@ mod must_use; mod not_unsafe_ptr_arg_deref; -mod result_unit_err; +mod result; mod too_many_arguments; mod too_many_lines; @@ -217,17 +217,62 @@ declare_clippy_lint! { "public function returning `Result` with an `Err` type of `()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for functions that return `Result` with an unusually large + /// `Err`-variant. + /// + /// ### Why is this bad? + /// A `Result` is at least as large as the `Err`-variant. While we + /// expect that variant to be seldomly used, the compiler needs to reserve + /// and move that much memory every single time. + /// + /// ### Known problems + /// The size determined by Clippy is platform-dependent. + /// + /// ### Examples + /// ```rust + /// pub enum ParseError { + /// UnparsedBytes([u8; 512]), + /// UnexpectedEof, + /// } + /// + /// // The `Result` has at least 512 bytes, even in the `Ok`-case + /// pub fn parse() -> Result<(), ParseError> { + /// Ok(()) + /// } + /// ``` + /// should be + /// ``` + /// pub enum ParseError { + /// UnparsedBytes(Box<[u8; 512]>), + /// UnexpectedEof, + /// } + /// + /// // The `Result` is slightly larger than a pointer + /// pub fn parse() -> Result<(), ParseError> { + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.64.0"] + pub RESULT_LARGE_ERR, + perf, + "function returning `Result` with large `Err` type" +} + #[derive(Copy, Clone)] pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, + large_error_threshold: u64, } impl Functions { - pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self { + pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self { Self { too_many_arguments_threshold, too_many_lines_threshold, + large_error_threshold, } } } @@ -240,6 +285,7 @@ impl_lint_pass!(Functions => [ DOUBLE_MUST_USE, MUST_USE_CANDIDATE, RESULT_UNIT_ERR, + RESULT_LARGE_ERR, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -259,18 +305,18 @@ impl<'tcx> LateLintPass<'tcx> for Functions { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { must_use::check_item(cx, item); - result_unit_err::check_item(cx, item); + result::check_item(cx, item, self.large_error_threshold); } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { must_use::check_impl_item(cx, item); - result_unit_err::check_impl_item(cx, item); + result::check_impl_item(cx, item, self.large_error_threshold); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold); not_unsafe_ptr_arg_deref::check_trait_item(cx, item); must_use::check_trait_item(cx, item); - result_unit_err::check_trait_item(cx, item); + result::check_trait_item(cx, item, self.large_error_threshold); } } diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs new file mode 100644 index 00000000000..af520a493ed --- /dev/null +++ b/clippy_lints/src/functions/result.rs @@ -0,0 +1,100 @@ +use rustc_errors::Diagnostic; +use rustc_hir as hir; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{sym, Span}; +use rustc_typeck::hir_ty_to_ty; + +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; +use clippy_utils::trait_ref_of_method; +use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item}; + +use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR}; + +/// The type of the `Err`-variant in a `std::result::Result` returned by the +/// given `FnDecl` +fn result_err_ty<'tcx>( + cx: &LateContext<'tcx>, + decl: &hir::FnDecl<'tcx>, + item_span: Span, +) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> { + if !in_external_macro(cx.sess(), item_span) + && let hir::FnRetTy::Return(hir_ty) = decl.output + && let ty = hir_ty_to_ty(cx.tcx, hir_ty) + && is_type_diagnostic_item(cx, ty, sym::Result) + && let ty::Adt(_, substs) = ty.kind() + { + let err_ty = substs.type_at(1); + Some((hir_ty, err_ty)) + } else { + None + } +} + +pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) { + if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) + { + if cx.access_levels.is_exported(item.def_id) { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } +} + +pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) { + // Don't lint if method is a trait's implementation, we can't do anything about those + if let hir::ImplItemKind::Fn(ref sig, _) = item.kind + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) + && trait_ref_of_method(cx, item.def_id).is_none() + { + if cx.access_levels.is_exported(item.def_id) { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) { + if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) { + if cx.access_levels.is_exported(item.def_id) { + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } + } +} + +fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) { + if err_ty.is_unit() { + span_lint_and_help( + cx, + RESULT_UNIT_ERR, + fn_header_span, + "this returns a `Result<_, ()>`", + None, + "use a custom `Error` type instead", + ); + } +} + +fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) { + let ty_size = approx_ty_size(cx, err_ty); + if ty_size >= large_err_threshold { + span_lint_and_then( + cx, + RESULT_LARGE_ERR, + hir_ty_span, + "the `Err`-variant returned from this function is very large", + |diag: &mut Diagnostic| { + diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes")); + diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`")); + }, + ); + } +} diff --git a/clippy_lints/src/functions/result_unit_err.rs b/clippy_lints/src/functions/result_unit_err.rs deleted file mode 100644 index 2e63a1f920d..00000000000 --- a/clippy_lints/src/functions/result_unit_err.rs +++ /dev/null @@ -1,66 +0,0 @@ -use rustc_hir as hir; -use rustc_lint::{LateContext, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; -use rustc_span::{sym, Span}; -use rustc_typeck::hir_ty_to_ty; - -use if_chain::if_chain; - -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::trait_ref_of_method; -use clippy_utils::ty::is_type_diagnostic_item; - -use super::RESULT_UNIT_ERR; - -pub(super) fn check_item(cx: &LateContext<'_>, item: &hir::Item<'_>) { - if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &hir::ImplItem<'_>) { - if let hir::ImplItemKind::Fn(ref sig, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public && trait_ref_of_method(cx, item.def_id).is_none() { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>) { - if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) { - if_chain! { - if !in_external_macro(cx.sess(), item_span); - if let hir::FnRetTy::Return(ty) = decl.output; - let ty = hir_ty_to_ty(cx.tcx, ty); - if is_type_diagnostic_item(cx, ty, sym::Result); - if let ty::Adt(_, substs) = ty.kind(); - let err_ty = substs.type_at(1); - if err_ty.is_unit(); - then { - span_lint_and_help( - cx, - RESULT_UNIT_ERR, - fn_header_span, - "this returns a `Result<_, ()>`", - None, - "use a custom `Error` type instead", - ); - } - } -} diff --git a/clippy_lints/src/get_first.rs b/clippy_lints/src/get_first.rs deleted file mode 100644 index 529f7babaa5..00000000000 --- a/clippy_lints/src/get_first.rs +++ /dev/null @@ -1,68 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_slice_of_primitives, match_def_path, paths}; -use if_chain::if_chain; -use rustc_ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Spanned; - -declare_clippy_lint! { - /// ### What it does - /// Checks for using `x.get(0)` instead of - /// `x.first()`. - /// - /// ### Why is this bad? - /// Using `x.first()` is easier to read and has the same - /// result. - /// - /// ### Example - /// ```rust - /// let x = vec![2, 3, 5]; - /// let first_element = x.get(0); - /// ``` - /// - /// Use instead: - /// ```rust - /// let x = vec![2, 3, 5]; - /// let first_element = x.first(); - /// ``` - #[clippy::version = "1.63.0"] - pub GET_FIRST, - style, - "Using `x.get(0)` when `x.first()` is simpler" -} -declare_lint_pass!(GetFirst => [GET_FIRST]); - -impl<'tcx> LateLintPass<'tcx> for GetFirst { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if_chain! { - if let hir::ExprKind::MethodCall(_, [struct_calling_on, method_arg], _) = &expr.kind; - if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match_def_path(cx, expr_def_id, &paths::SLICE_GET); - - if let Some(_) = is_slice_of_primitives(cx, struct_calling_on); - if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = method_arg.kind; - - then { - let mut applicability = Applicability::MachineApplicable; - let slice_name = snippet_with_applicability( - cx, - struct_calling_on.span, "..", - &mut applicability, - ); - span_lint_and_sugg( - cx, - GET_FIRST, - expr.span, - &format!("accessing first element with `{0}.get(0)`", slice_name), - "try", - format!("{}.first()", slice_name), - applicability, - ); - } - } - } -} diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index e9501700784..4d703d691ac 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,8 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::SpanlessEq; use if_chain::if_chain; +use rustc_errors::Diagnostic; use rustc_hir::intravisit::{self as visit, Visitor}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -45,16 +46,8 @@ declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]); impl<'tcx> LateLintPass<'tcx> for IfLetMutex { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - let mut arm_visit = ArmVisitor { - mutex_lock_called: false, - found_mutex: None, - cx, - }; - let mut op_visit = OppVisitor { - mutex_lock_called: false, - found_mutex: None, - cx, - }; + let mut arm_visit = ArmVisitor { found_mutex: None, cx }; + let mut op_visit = OppVisitor { found_mutex: None, cx }; if let Some(higher::IfLet { let_expr, if_then, @@ -63,18 +56,28 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { }) = higher::IfLet::hir(cx, expr) { op_visit.visit_expr(let_expr); - if op_visit.mutex_lock_called { + if let Some(op_mutex) = op_visit.found_mutex { arm_visit.visit_expr(if_then); arm_visit.visit_expr(if_else); - if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) { - span_lint_and_help( + if let Some(arm_mutex) = arm_visit.found_mutex_if_same_as(op_mutex) { + let diag = |diag: &mut Diagnostic| { + diag.span_label( + op_mutex.span, + "this Mutex will remain locked for the entire `if let`-block...", + ); + diag.span_label( + arm_mutex.span, + "... and is tried to lock again here, which will always deadlock.", + ); + diag.help("move the lock call outside of the `if let ...` expression"); + }; + span_lint_and_then( cx, IF_LET_MUTEX, expr.span, "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", - None, - "move the lock call outside of the `if let ...` expression", + diag, ); } } @@ -84,7 +87,6 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { /// Checks if `Mutex::lock` is called in the `if let` expr. pub struct OppVisitor<'a, 'tcx> { - mutex_lock_called: bool, found_mutex: Option<&'tcx Expr<'tcx>>, cx: &'a LateContext<'tcx>, } @@ -93,7 +95,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) { self.found_mutex = Some(mutex); - self.mutex_lock_called = true; return; } visit::walk_expr(self, expr); @@ -102,7 +103,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> { /// Checks if `Mutex::lock` is called in any of the branches. pub struct ArmVisitor<'a, 'tcx> { - mutex_lock_called: bool, found_mutex: Option<&'tcx Expr<'tcx>>, cx: &'a LateContext<'tcx>, } @@ -111,7 +111,6 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) { self.found_mutex = Some(mutex); - self.mutex_lock_called = true; return; } visit::walk_expr(self, expr); @@ -119,9 +118,12 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> { } impl<'tcx, 'l> ArmVisitor<'tcx, 'l> { - fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool { - self.found_mutex - .map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)) + fn found_mutex_if_same_as(&self, op_mutex: &Expr<'_>) -> Option<&Expr<'_>> { + self.found_mutex.and_then(|arm_mutex| { + SpanlessEq::new(self.cx) + .eq_expr(op_mutex, arm_mutex) + .then_some(arm_mutex) + }) } } @@ -129,7 +131,7 @@ fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Opt if_chain! { if let ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind; if path.ident.as_str() == "lock"; - let ty = cx.typeck_results().expr_ty(self_arg); + let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); if is_type_diagnostic_item(cx, ty, sym::Mutex); then { Some(self_arg) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index b8d227855d9..11c43247868 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks}; -use if_chain::if_chain; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -11,10 +11,12 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { /// ### What it does - /// Checks for if-else that could be written to `bool::then`. + /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. /// /// ### Why is this bad? - /// Looks a little redundant. Using `bool::then` helps it have less lines of code. + /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. + /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated + /// in comparison to `bool::then`. /// /// ### Example /// ```rust @@ -39,7 +41,7 @@ declare_clippy_lint! { #[clippy::version = "1.53.0"] pub IF_THEN_SOME_ELSE_NONE, restriction, - "Finds if-else that could be written using `bool::then`" + "Finds if-else that could be written using either `bool::then` or `bool::then_some`" } pub struct IfThenSomeElseNone { @@ -56,7 +58,7 @@ impl IfThenSomeElseNone { impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !meets_msrv(self.msrv, msrvs::BOOL_THEN) { return; } @@ -70,43 +72,47 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { return; } - if_chain! { - if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr); - if let ExprKind::Block(then_block, _) = then.kind; - if let Some(then_expr) = then_block.expr; - if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind; - if let ExprKind::Path(ref then_call_qpath) = then_call.kind; - if is_lang_ctor(cx, then_call_qpath, OptionSome); - if let ExprKind::Path(ref qpath) = peel_blocks(els).kind; - if is_lang_ctor(cx, qpath, OptionNone); - if !stmts_contains_early_return(then_block.stmts); - then { - let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]"); - let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { - format!("({})", cond_snip) - } else { - cond_snip.into_owned() - }; - let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, ""); - let closure_body = if then_block.stmts.is_empty() { - arg_snip.into_owned() - } else { - format!("{{ /* snippet */ {} }}", arg_snip) - }; - let help = format!( - "consider using `bool::then` like: `{}.then(|| {})`", - cond_snip, - closure_body, - ); - span_lint_and_help( - cx, - IF_THEN_SOME_ELSE_NONE, - expr.span, - "this could be simplified with `bool::then`", - None, - &help, - ); - } + if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr) + && let ExprKind::Block(then_block, _) = then.kind + && let Some(then_expr) = then_block.expr + && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let ExprKind::Path(ref then_call_qpath) = then_call.kind + && is_lang_ctor(cx, then_call_qpath, OptionSome) + && let ExprKind::Path(ref qpath) = peel_blocks(els).kind + && is_lang_ctor(cx, qpath, OptionNone) + && !stmts_contains_early_return(then_block.stmts) + { + let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]"); + let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { + format!("({})", cond_snip) + } else { + cond_snip.into_owned() + }; + let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, ""); + let mut method_body = if then_block.stmts.is_empty() { + arg_snip.into_owned() + } else { + format!("{{ /* snippet */ {} }}", arg_snip) + }; + let method_name = if switch_to_eager_eval(cx, expr) && meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) { + "then_some" + } else { + method_body.insert_str(0, "|| "); + "then" + }; + + let help = format!( + "consider using `bool::{}` like: `{}.{}({})`", + method_name, cond_snip, method_name, method_body, + ); + span_lint_and_help( + cx, + IF_THEN_SOME_ELSE_NONE, + expr.span, + &format!("this could be simplified with `bool::{}`", method_name), + None, + &help, + ); } } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 01082cc8eeb..134cbbf7b5c 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -20,12 +20,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(booleans::OVERLY_COMPLEX_BOOL_EXPR), LintId::of(borrow_deref_ref::BORROW_DEREF_REF), - LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CAST_ABS_TO_UNSIGNED), LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), + LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::FN_TO_NUMERIC_CAST), LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), @@ -80,9 +80,9 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(functions::DOUBLE_MUST_USE), LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(functions::RESULT_LARGE_ERR), LintId::of(functions::RESULT_UNIT_ERR), LintId::of(functions::TOO_MANY_ARGUMENTS), - LintId::of(get_first::GET_FIRST), LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(infinite_iter::INFINITE_ITER), @@ -128,7 +128,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID), LintId::of(manual_retain::MANUAL_RETAIN), LintId::of(manual_strip::MANUAL_STRIP), - LintId::of(map_clone::MAP_CLONE), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(match_result_ok::MATCH_RESULT_OK), @@ -150,17 +149,20 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT), LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT), LintId::of(methods::BIND_INSTEAD_OF_MAP), + LintId::of(methods::BYTES_COUNT_TO_LEN), LintId::of(methods::BYTES_NTH), LintId::of(methods::CHARS_LAST_CMP), LintId::of(methods::CHARS_NEXT_CMP), LintId::of(methods::CLONE_DOUBLE_REF), LintId::of(methods::CLONE_ON_COPY), + LintId::of(methods::COLLAPSIBLE_STR_REPLACE), LintId::of(methods::ERR_EXPECT), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), LintId::of(methods::FLAT_MAP_IDENTITY), + LintId::of(methods::GET_FIRST), LintId::of(methods::GET_LAST_WITH_LEN), LintId::of(methods::INSPECT_FOR_EACH), LintId::of(methods::INTO_ITER_ON_REF), @@ -178,13 +180,16 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(methods::MANUAL_SPLIT_ONCE), LintId::of(methods::MANUAL_STR_REPEAT), + LintId::of(methods::MAP_CLONE), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::MAP_FLATTEN), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::MUT_MUTEX_LOCK), LintId::of(methods::NEEDLESS_OPTION_AS_DEREF), LintId::of(methods::NEEDLESS_OPTION_TAKE), LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::NEW_RET_NO_SELF), + LintId::of(methods::NONSENSICAL_OPEN_OPTIONS), LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), @@ -193,6 +198,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::OR_THEN_UNWRAP), + LintId::of(methods::RANGE_ZIP_WITH_LEN), + LintId::of(methods::REPEAT_ONCE), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SHOULD_IMPLEMENT_TRAIT), @@ -202,14 +209,18 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::STRING_EXTEND_CHARS), LintId::of(methods::SUSPICIOUS_MAP), LintId::of(methods::SUSPICIOUS_SPLITN), + LintId::of(methods::SUSPICIOUS_TO_OWNED), LintId::of(methods::UNINIT_ASSUMED_INIT), + LintId::of(methods::UNIT_HASH), LintId::of(methods::UNNECESSARY_FILTER_MAP), LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::UNNECESSARY_FOLD), LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS), + LintId::of(methods::UNNECESSARY_SORT_BY), LintId::of(methods::UNNECESSARY_TO_OWNED), LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT), LintId::of(methods::USELESS_ASREF), + LintId::of(methods::VEC_RESIZE_TO_ZERO), LintId::of(methods::WRONG_SELF_CONVENTION), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), @@ -224,8 +235,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), LintId::of(misc_early::ZERO_PREFIXED_LITERAL), LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION), + LintId::of(multi_assignments::MULTI_ASSIGNMENTS), LintId::of(mut_key::MUTABLE_KEY_TYPE), - LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), LintId::of(needless_bool::BOOL_COMPARISON), @@ -245,7 +256,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(octal_escapes::OCTAL_ESCAPES), - LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::ASSIGN_OP_PATTERN), LintId::of(operators::BAD_BIT_MASK), @@ -275,7 +286,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(question_mark::QUESTION_MARK), LintId::of(ranges::MANUAL_RANGE_CONTAINS), - LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), @@ -286,7 +296,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(reference::DEREF_ADDROF), LintId::of(regex::INVALID_REGEX), - LintId::of(repeat_once::REPEAT_ONCE), LintId::of(returns::LET_AND_RETURN), LintId::of(returns::NEEDLESS_RETURN), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), @@ -314,10 +323,10 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT), LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES), LintId::of(transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(transmute::TRANSMUTING_NULL), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::USELESS_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), - LintId::of(transmuting_null::TRANSMUTING_NULL), LintId::of(types::BORROWED_BOX), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), @@ -325,7 +334,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(types::VEC_BOX), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), - LintId::of(unit_hash::UNIT_HASH), LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(unit_types::LET_UNIT_VALUE), LintId::of(unit_types::UNIT_ARG), @@ -333,9 +341,9 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS), - LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(unused_peekable::UNUSED_PEEKABLE), LintId::of(unused_unit::UNUSED_UNIT), LintId::of(unwrap::PANICKING_UNWRAP), LintId::of(unwrap::UNNECESSARY_UNWRAP), @@ -343,7 +351,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(vec::USELESS_VEC), LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH), - LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO), + LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS), LintId::of(write::PRINTLN_EMPTY_STRING), LintId::of(write::PRINT_LITERAL), LintId::of(write::PRINT_WITH_NEWLINE), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 3784d3c68dc..aa247352f88 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -6,7 +6,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(attrs::DEPRECATED_CFG_ATTR), LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(borrow_deref_ref::BORROW_DEREF_REF), - LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::UNNECESSARY_CAST), LintId::of(dereference::EXPLICIT_AUTO_DEREF), @@ -33,6 +32,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), LintId::of(methods::BIND_INSTEAD_OF_MAP), + LintId::of(methods::BYTES_COUNT_TO_LEN), LintId::of(methods::CLONE_ON_COPY), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), @@ -51,10 +51,13 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OR_THEN_UNWRAP), + LintId::of(methods::RANGE_ZIP_WITH_LEN), + LintId::of(methods::REPEAT_ONCE), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), LintId::of(methods::UNNECESSARY_FIND_MAP), + LintId::of(methods::UNNECESSARY_SORT_BY), LintId::of(methods::USELESS_ASREF), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), @@ -69,6 +72,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(operators::DOUBLE_COMPARISONS), LintId::of(operators::DURATION_SUBSEC), LintId::of(operators::IDENTITY_OP), @@ -76,11 +80,9 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), - LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_slicing::REDUNDANT_SLICING), LintId::of(reference::DEREF_ADDROF), - LintId::of(repeat_once::REPEAT_ONCE), LintId::of(strings::STRING_FROM_UTF8_AS_BYTES), LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS), LintId::of(swap::MANUAL_SWAP), @@ -99,7 +101,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(types::TYPE_COMPLEXITY), LintId::of(types::VEC_BOX), LintId::of(unit_types::UNIT_ARG), - LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unwrap::UNNECESSARY_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 006275d1383..bb94037ec2e 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -39,12 +39,14 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT), LintId::of(methods::CLONE_DOUBLE_REF), LintId::of(methods::ITERATOR_STEP_BY_ZERO), + LintId::of(methods::NONSENSICAL_OPEN_OPTIONS), LintId::of(methods::SUSPICIOUS_SPLITN), LintId::of(methods::UNINIT_ASSUMED_INIT), + LintId::of(methods::UNIT_HASH), + LintId::of(methods::VEC_RESIZE_TO_ZERO), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), - LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::BAD_BIT_MASK), LintId::of(operators::CMP_NAN), @@ -62,17 +64,15 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(swap::ALMOST_SWAPPED), + LintId::of(transmute::TRANSMUTING_NULL), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), - LintId::of(transmuting_null::TRANSMUTING_NULL), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), - LintId::of(unit_hash::UNIT_HASH), LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(unit_types::UNIT_CMP), LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(unwrap::PANICKING_UNWRAP), - LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index c540573b802..fd20e016578 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -38,7 +38,6 @@ store.register_lints(&[ almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE, approx_const::APPROX_CONSTANT, as_conversions::AS_CONVERSIONS, - as_underscore::AS_UNDERSCORE, asm_syntax::INLINE_ASM_X86_ATT_SYNTAX, asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX, assertions_on_constants::ASSERTIONS_ON_CONSTANTS, @@ -59,16 +58,14 @@ store.register_lints(&[ bool_assert_comparison::BOOL_ASSERT_COMPARISON, booleans::NONMINIMAL_BOOL, booleans::OVERLY_COMPLEX_BOOL_EXPR, - borrow_as_ptr::BORROW_AS_PTR, borrow_deref_ref::BORROW_DEREF_REF, - bytecount::NAIVE_BYTECOUNT, - bytes_count_to_len::BYTES_COUNT_TO_LEN, cargo::CARGO_COMMON_METADATA, cargo::MULTIPLE_CRATE_VERSIONS, cargo::NEGATIVE_FEATURE_NAMES, cargo::REDUNDANT_FEATURE_NAMES, cargo::WILDCARD_DEPENDENCIES, - case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::AS_UNDERSCORE, + casts::BORROW_AS_PTR, casts::CAST_ABS_TO_UNSIGNED, casts::CAST_ENUM_CONSTRUCTOR, casts::CAST_ENUM_TRUNCATION, @@ -80,6 +77,7 @@ store.register_lints(&[ casts::CAST_REF_TO_MUT, casts::CAST_SIGN_LOSS, casts::CAST_SLICE_DIFFERENT_SIZES, + casts::CAST_SLICE_FROM_RAW_PARTS, casts::CHAR_LIT_AS_U8, casts::FN_TO_NUMERIC_CAST, casts::FN_TO_NUMERIC_CAST_ANY, @@ -173,11 +171,11 @@ store.register_lints(&[ functions::MUST_USE_CANDIDATE, functions::MUST_USE_UNIT, functions::NOT_UNSAFE_PTR_ARG_DEREF, + functions::RESULT_LARGE_ERR, functions::RESULT_UNIT_ERR, functions::TOO_MANY_ARGUMENTS, functions::TOO_MANY_LINES, future_not_send::FUTURE_NOT_SEND, - get_first::GET_FIRST, if_let_mutex::IF_LET_MUTEX, if_not_else::IF_NOT_ELSE, if_then_some_else_none::IF_THEN_SOME_ELSE_NONE, @@ -246,12 +244,10 @@ store.register_lints(&[ manual_bits::MANUAL_BITS, manual_instant_elapsed::MANUAL_INSTANT_ELAPSED, manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, - manual_ok_or::MANUAL_OK_OR, manual_rem_euclid::MANUAL_REM_EUCLID, manual_retain::MANUAL_RETAIN, + manual_string_new::MANUAL_STRING_NEW, manual_strip::MANUAL_STRIP, - map_clone::MAP_CLONE, - map_err_ignore::MAP_ERR_IGNORE, map_unit_fn::OPTION_MAP_UNIT_FN, map_unit_fn::RESULT_MAP_UNIT_FN, match_result_ok::MATCH_RESULT_OK, @@ -284,13 +280,16 @@ store.register_lints(&[ mem_replace::MEM_REPLACE_WITH_DEFAULT, mem_replace::MEM_REPLACE_WITH_UNINIT, methods::BIND_INSTEAD_OF_MAP, + methods::BYTES_COUNT_TO_LEN, methods::BYTES_NTH, + methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, methods::CHARS_LAST_CMP, methods::CHARS_NEXT_CMP, methods::CLONED_INSTEAD_OF_COPIED, methods::CLONE_DOUBLE_REF, methods::CLONE_ON_COPY, methods::CLONE_ON_REF_PTR, + methods::COLLAPSIBLE_STR_REPLACE, methods::ERR_EXPECT, methods::EXPECT_FUN_CALL, methods::EXPECT_USED, @@ -302,6 +301,7 @@ store.register_lints(&[ methods::FLAT_MAP_IDENTITY, methods::FLAT_MAP_OPTION, methods::FROM_ITER_INSTEAD_OF_COLLECT, + methods::GET_FIRST, methods::GET_LAST_WITH_LEN, methods::GET_UNWRAP, methods::IMPLICIT_CLONE, @@ -315,22 +315,30 @@ store.register_lints(&[ methods::ITER_NEXT_SLICE, methods::ITER_NTH, methods::ITER_NTH_ZERO, + methods::ITER_ON_EMPTY_COLLECTIONS, + methods::ITER_ON_SINGLE_ITEMS, methods::ITER_OVEREAGER_CLONED, methods::ITER_SKIP_NEXT, methods::ITER_WITH_DRAIN, methods::MANUAL_FILTER_MAP, methods::MANUAL_FIND_MAP, + methods::MANUAL_OK_OR, methods::MANUAL_SATURATING_ARITHMETIC, methods::MANUAL_SPLIT_ONCE, methods::MANUAL_STR_REPEAT, + methods::MAP_CLONE, methods::MAP_COLLECT_RESULT_UNIT, + methods::MAP_ERR_IGNORE, methods::MAP_FLATTEN, methods::MAP_IDENTITY, methods::MAP_UNWRAP_OR, + methods::MUT_MUTEX_LOCK, + methods::NAIVE_BYTECOUNT, methods::NEEDLESS_OPTION_AS_DEREF, methods::NEEDLESS_OPTION_TAKE, methods::NEEDLESS_SPLITN, methods::NEW_RET_NO_SELF, + methods::NONSENSICAL_OPEN_OPTIONS, methods::NO_EFFECT_REPLACE, methods::OBFUSCATED_IF_ELSE, methods::OK_EXPECT, @@ -339,25 +347,34 @@ store.register_lints(&[ methods::OPTION_MAP_OR_NONE, methods::OR_FUN_CALL, methods::OR_THEN_UNWRAP, + methods::PATH_BUF_PUSH_OVERWRITE, + methods::RANGE_ZIP_WITH_LEN, + methods::REPEAT_ONCE, methods::RESULT_MAP_OR_INTO_OPTION, methods::SEARCH_IS_SOME, methods::SHOULD_IMPLEMENT_TRAIT, methods::SINGLE_CHAR_ADD_STR, methods::SINGLE_CHAR_PATTERN, methods::SKIP_WHILE_NEXT, + methods::STABLE_SORT_PRIMITIVE, methods::STRING_EXTEND_CHARS, methods::SUSPICIOUS_MAP, methods::SUSPICIOUS_SPLITN, + methods::SUSPICIOUS_TO_OWNED, methods::UNINIT_ASSUMED_INIT, + methods::UNIT_HASH, methods::UNNECESSARY_FILTER_MAP, methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, methods::UNNECESSARY_JOIN, methods::UNNECESSARY_LAZY_EVALUATIONS, + methods::UNNECESSARY_SORT_BY, methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, methods::UNWRAP_USED, methods::USELESS_ASREF, + methods::VEC_RESIZE_TO_ZERO, + methods::VERBOSE_FILE_READS, methods::WRONG_SELF_CONVENTION, methods::ZST_OFFSET, minmax::MIN_MAX, @@ -384,9 +401,9 @@ store.register_lints(&[ mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION, module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, + multi_assignments::MULTI_ASSIGNMENTS, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, - mut_mutex_lock::MUT_MUTEX_LOCK, mut_reference::UNNECESSARY_MUT_PASSED, mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL, mutex_atomic::MUTEX_ATOMIC, @@ -418,7 +435,6 @@ store.register_lints(&[ nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES, octal_escapes::OCTAL_ESCAPES, only_used_in_recursion::ONLY_USED_IN_RECURSION, - open_options::NONSENSICAL_OPEN_OPTIONS, operators::ABSURD_EXTREME_COMPARISONS, operators::ARITHMETIC, operators::ASSIGN_OP_PATTERN, @@ -457,7 +473,6 @@ store.register_lints(&[ partialeq_to_none::PARTIALEQ_TO_NONE, pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE, pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF, - path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, pattern_type_mismatch::PATTERN_TYPE_MISMATCH, precedence::PRECEDENCE, ptr::CMP_NULL, @@ -470,7 +485,6 @@ store.register_lints(&[ ranges::MANUAL_RANGE_CONTAINS, ranges::RANGE_MINUS_ONE, ranges::RANGE_PLUS_ONE, - ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT, read_zero_byte_vec::READ_ZERO_BYTE_VEC, @@ -486,7 +500,6 @@ store.register_lints(&[ reference::DEREF_ADDROF, regex::INVALID_REGEX, regex::TRIVIAL_REGEX, - repeat_once::REPEAT_ONCE, return_self_not_must_use::RETURN_SELF_NOT_MUST_USE, returns::LET_AND_RETURN, returns::NEEDLESS_RETURN, @@ -501,7 +514,6 @@ store.register_lints(&[ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - stable_sort_primitive::STABLE_SORT_PRIMITIVE, std_instead_of_core::ALLOC_INSTEAD_OF_CORE, std_instead_of_core::STD_INSTEAD_OF_ALLOC, std_instead_of_core::STD_INSTEAD_OF_CORE, @@ -537,10 +549,10 @@ store.register_lints(&[ transmute::TRANSMUTE_PTR_TO_PTR, transmute::TRANSMUTE_PTR_TO_REF, transmute::TRANSMUTE_UNDEFINED_REPR, + transmute::TRANSMUTING_NULL, transmute::UNSOUND_COLLECTION_TRANSMUTE, transmute::USELESS_TRANSMUTE, transmute::WRONG_TRANSMUTE, - transmuting_null::TRANSMUTING_NULL, types::BORROWED_BOX, types::BOX_COLLECTION, types::LINKEDLIST, @@ -555,7 +567,6 @@ store.register_lints(&[ unicode::NON_ASCII_LITERAL, unicode::UNICODE_NOT_NFC, uninit_vec::UNINIT_VEC, - unit_hash::UNIT_HASH, unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD, unit_types::LET_UNIT_VALUE, unit_types::UNIT_ARG, @@ -564,12 +575,12 @@ store.register_lints(&[ unnamed_address::VTABLE_ADDRESS_COMPARISONS, unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, - unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, unnested_or_patterns::UNNESTED_OR_PATTERNS, unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, unused_async::UNUSED_ASYNC, unused_io_amount::UNUSED_IO_AMOUNT, + unused_peekable::UNUSED_PEEKABLE, unused_rounding::UNUSED_ROUNDING, unused_self::UNUSED_SELF, unused_unit::UNUSED_UNIT, @@ -581,10 +592,9 @@ store.register_lints(&[ useless_conversion::USELESS_CONVERSION, vec::USELESS_VEC, vec_init_then_push::VEC_INIT_THEN_PUSH, - vec_resize_to_zero::VEC_RESIZE_TO_ZERO, - verbose_file_reads::VERBOSE_FILE_READS, wildcard_imports::ENUM_GLOB_USE, wildcard_imports::WILDCARD_IMPORTS, + write::POSITIONAL_NAMED_FORMAT_PARAMETERS, write::PRINTLN_EMPTY_STRING, write::PRINT_LITERAL, write::PRINT_STDERR, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 91210b23afe..e319e7ee72c 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -14,16 +14,17 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE), + LintId::of(methods::ITER_ON_EMPTY_COLLECTIONS), + LintId::of(methods::ITER_ON_SINGLE_ITEMS), LintId::of(methods::ITER_WITH_DRAIN), + LintId::of(methods::PATH_BUF_PUSH_OVERWRITE), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutex_atomic::MUTEX_ATOMIC), LintId::of(mutex_atomic::MUTEX_INTEGER), LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES), - LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(option_if_let_else::OPTION_IF_LET_ELSE), - LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE), LintId::of(regex::TRIVIAL_REGEX), LintId::of(strings::STRING_LIT_AS_BYTES), diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index bd7d1a15ab4..584ccf55e51 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,9 +4,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(borrow_as_ptr::BORROW_AS_PTR), - LintId::of(bytecount::NAIVE_BYTECOUNT), - LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), + LintId::of(casts::BORROW_AS_PTR), LintId::of(casts::CAST_LOSSLESS), LintId::of(casts::CAST_POSSIBLE_TRUNCATION), LintId::of(casts::CAST_POSSIBLE_WRAP), @@ -50,20 +48,24 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(macro_use::MACRO_USE_IMPORTS), LintId::of(manual_assert::MANUAL_ASSERT), LintId::of(manual_instant_elapsed::MANUAL_INSTANT_ELAPSED), - LintId::of(manual_ok_or::MANUAL_OK_OR), + LintId::of(manual_string_new::MANUAL_STRING_NEW), LintId::of(matches::MATCH_BOOL), LintId::of(matches::MATCH_ON_VEC_ITEMS), LintId::of(matches::MATCH_SAME_ARMS), LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), LintId::of(matches::MATCH_WILD_ERR_ARM), LintId::of(matches::SINGLE_MATCH_ELSE), + LintId::of(methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), LintId::of(methods::CLONED_INSTEAD_OF_COPIED), LintId::of(methods::FILTER_MAP_NEXT), LintId::of(methods::FLAT_MAP_OPTION), LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT), LintId::of(methods::IMPLICIT_CLONE), LintId::of(methods::INEFFICIENT_TO_STRING), + LintId::of(methods::MANUAL_OK_OR), LintId::of(methods::MAP_UNWRAP_OR), + LintId::of(methods::NAIVE_BYTECOUNT), + LintId::of(methods::STABLE_SORT_PRIMITIVE), LintId::of(methods::UNNECESSARY_JOIN), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER), @@ -85,7 +87,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(ref_option_ref::REF_OPTION_REF), LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE), LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED), - LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE), LintId::of(strings::STRING_ADD_ASSIGN), LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index e1b90acb93c..195ce41e31e 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -7,12 +7,14 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(escape::BOXED_LOCAL), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(functions::RESULT_LARGE_ERR), LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(manual_retain::MANUAL_RETAIN), + LintId::of(methods::COLLAPSIBLE_STR_REPLACE), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index a7339ef2721..dd1e1e1a8e3 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -4,11 +4,11 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(as_conversions::AS_CONVERSIONS), - LintId::of(as_underscore::AS_UNDERSCORE), LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX), LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), LintId::of(assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES), LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON), + LintId::of(casts::AS_UNDERSCORE), LintId::of(casts::FN_TO_NUMERIC_CAST_ANY), LintId::of(create_dir::CREATE_DIR), LintId::of(dbg_macro::DBG_MACRO), @@ -30,7 +30,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(large_include_file::LARGE_INCLUDE_FILE), LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE), LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION), - LintId::of(map_err_ignore::MAP_ERR_IGNORE), LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS), LintId::of(matches::TRY_ERR), LintId::of(matches::WILDCARD_ENUM_MATCH_ARM), @@ -39,7 +38,9 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(methods::EXPECT_USED), LintId::of(methods::FILETYPE_IS_FILE), LintId::of(methods::GET_UNWRAP), + LintId::of(methods::MAP_ERR_IGNORE), LintId::of(methods::UNWRAP_USED), + LintId::of(methods::VERBOSE_FILE_READS), LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX), LintId::of(misc_early::UNNEEDED_FIELD_PATTERN), LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), @@ -81,7 +82,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(unicode::NON_ASCII_LITERAL), LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS), LintId::of(unwrap_in_result::UNWRAP_IN_RESULT), - LintId::of(verbose_file_reads::VERBOSE_FILE_READS), LintId::of(write::PRINT_STDERR), LintId::of(write::PRINT_STDOUT), LintId::of(write::USE_DEBUG), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index bfa654238f1..b5cb078e7a3 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -29,7 +29,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(functions::DOUBLE_MUST_USE), LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::RESULT_UNIT_ERR), - LintId::of(get_first::GET_FIRST), LintId::of(inherent_to_string::INHERENT_TO_STRING), LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS), LintId::of(len_zero::COMPARISON_TO_EMPTY), @@ -45,7 +44,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(manual_async_fn::MANUAL_ASYNC_FN), LintId::of(manual_bits::MANUAL_BITS), LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), - LintId::of(map_clone::MAP_CLONE), LintId::of(match_result_ok::MATCH_RESULT_OK), LintId::of(matches::COLLAPSIBLE_MATCH), LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH), @@ -61,6 +59,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::CHARS_LAST_CMP), LintId::of(methods::CHARS_NEXT_CMP), LintId::of(methods::ERR_EXPECT), + LintId::of(methods::GET_FIRST), LintId::of(methods::INTO_ITER_ON_REF), LintId::of(methods::IS_DIGIT_ASCII_RADIX), LintId::of(methods::ITER_CLONED_COLLECT), @@ -68,7 +67,9 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::ITER_NTH_ZERO), LintId::of(methods::ITER_SKIP_NEXT), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(methods::MAP_CLONE), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), + LintId::of(methods::MUT_MUTEX_LOCK), LintId::of(methods::NEW_RET_NO_SELF), LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), @@ -88,7 +89,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), LintId::of(misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(misc_early::REDUNDANT_PATTERN), - LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 964992bd94f..8f131bbf98b 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -11,6 +11,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(casts::CAST_ABS_TO_UNSIGNED), LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), + LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS), LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), LintId::of(drop_forget_ref::DROP_NON_DROP), LintId::of(drop_forget_ref::FORGET_NON_DROP), @@ -24,6 +25,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(loops::MUT_RANGE_BOUND), LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::SUSPICIOUS_MAP), + LintId::of(methods::SUSPICIOUS_TO_OWNED), + LintId::of(multi_assignments::MULTI_ASSIGNMENTS), LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS), @@ -32,4 +35,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF), + LintId::of(unused_peekable::UNUSED_PEEKABLE), + LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS), ]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ec5c73c1357..dfdaf90f09f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -171,7 +171,6 @@ mod renamed_lints; mod almost_complete_letter_range; mod approx_const; mod as_conversions; -mod as_underscore; mod asm_syntax; mod assertions_on_constants; mod assertions_on_result_states; @@ -181,12 +180,8 @@ mod await_holding_invalid; mod blocks_in_if_conditions; mod bool_assert_comparison; mod booleans; -mod borrow_as_ptr; mod borrow_deref_ref; -mod bytecount; -mod bytes_count_to_len; mod cargo; -mod case_sensitive_file_extension_comparisons; mod casts; mod checked_conversions; mod cognitive_complexity; @@ -239,7 +234,6 @@ mod from_over_into; mod from_str_radix_10; mod functions; mod future_not_send; -mod get_first; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; @@ -276,12 +270,10 @@ mod manual_async_fn; mod manual_bits; mod manual_instant_elapsed; mod manual_non_exhaustive; -mod manual_ok_or; mod manual_rem_euclid; mod manual_retain; +mod manual_string_new; mod manual_strip; -mod map_clone; -mod map_err_ignore; mod map_unit_fn; mod match_result_ok; mod matches; @@ -298,9 +290,9 @@ mod missing_enforced_import_rename; mod missing_inline; mod mixed_read_write_in_expression; mod module_style; +mod multi_assignments; mod mut_key; mod mut_mut; -mod mut_mutex_lock; mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; @@ -325,7 +317,6 @@ mod non_send_fields_in_send_ty; mod nonstandard_macro_braces; mod octal_escapes; mod only_used_in_recursion; -mod open_options; mod operators; mod option_env_unwrap; mod option_if_let_else; @@ -335,7 +326,6 @@ mod panic_unimplemented; mod partialeq_ne_impl; mod partialeq_to_none; mod pass_by_ref_or_value; -mod path_buf_push_overwrite; mod pattern_type_mismatch; mod precedence; mod ptr; @@ -355,7 +345,6 @@ mod redundant_static_lifetimes; mod ref_option_ref; mod reference; mod regex; -mod repeat_once; mod return_self_not_must_use; mod returns; mod same_name_method; @@ -367,7 +356,6 @@ mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; mod slow_vector_initialization; -mod stable_sort_primitive; mod std_instead_of_core; mod strings; mod strlen_on_c_strings; @@ -381,23 +369,21 @@ mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; mod transmute; -mod transmuting_null; mod types; mod undocumented_unsafe_blocks; mod unicode; mod uninit_vec; -mod unit_hash; mod unit_return_expecting_ord; mod unit_types; mod unnamed_address; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; -mod unnecessary_sort_by; mod unnecessary_wraps; mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; +mod unused_peekable; mod unused_rounding; mod unused_self; mod unused_unit; @@ -408,8 +394,6 @@ mod use_self; mod useless_conversion; mod vec; mod vec_init_then_push; -mod vec_resize_to_zero; -mod verbose_file_reads; mod wildcard_imports; mod write; mod zero_div_zero; @@ -597,7 +581,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions)); store.register_late_pass(|| Box::new(unicode::Unicode)); store.register_late_pass(|| Box::new(uninit_vec::UninitVec)); - store.register_late_pass(|| Box::new(unit_hash::UnitHash)); store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)); store.register_late_pass(|| Box::new(strings::StringAdd)); store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn)); @@ -635,8 +618,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(needless_question_mark::NeedlessQuestionMark)); store.register_late_pass(move || Box::new(casts::Casts::new(msrv))); store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv))); - store.register_late_pass(move || Box::new(map_clone::MapClone::new(msrv))); - store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount)); store.register_late_pass(|| Box::new(same_name_method::SameNameMethod)); let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length; @@ -646,7 +627,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: msrv, )) }); - store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore)); store.register_late_pass(|| Box::new(shadow::Shadow::default())); store.register_late_pass(|| Box::new(unit_types::UnitTypes)); store.register_late_pass(|| Box::new(loops::Loops)); @@ -654,7 +634,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(lifetimes::Lifetimes)); store.register_late_pass(|| Box::new(entry::HashMapPass)); store.register_late_pass(|| Box::new(minmax::MinMaxPass)); - store.register_late_pass(|| Box::new(open_options::OpenOptions)); store.register_late_pass(|| Box::new(zero_div_zero::ZeroDiv)); store.register_late_pass(|| Box::new(mutex_atomic::Mutex)); store.register_late_pass(|| Box::new(needless_update::NeedlessUpdate)); @@ -690,10 +669,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone()))); let too_many_arguments_threshold = conf.too_many_arguments_threshold; let too_many_lines_threshold = conf.too_many_lines_threshold; + let large_error_threshold = conf.large_error_threshold; store.register_late_pass(move || { Box::new(functions::Functions::new( too_many_arguments_threshold, too_many_lines_threshold, + large_error_threshold, )) }); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>(); @@ -720,7 +701,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ); store.register_late_pass(move || Box::new(pass_by_ref_or_value)); store.register_late_pass(|| Box::new(ref_option_ref::RefOptionRef)); - store.register_late_pass(|| Box::new(bytecount::ByteCount)); store.register_late_pass(|| Box::new(infinite_iter::InfiniteIter)); store.register_late_pass(|| Box::new(inline_fn_without_body::InlineFnWithoutBody)); store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default())); @@ -738,12 +718,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); store.register_late_pass(|| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|| Box::new(slow_vector_initialization::SlowVectorInit)); - store.register_late_pass(|| Box::new(unnecessary_sort_by::UnnecessarySortBy)); store.register_late_pass(move || Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api))); store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants)); store.register_late_pass(|| Box::new(assertions_on_result_states::AssertionsOnResultStates)); - store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull)); - store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite)); 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))); @@ -819,18 +796,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports; store.register_late_pass(move || Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports))); - store.register_late_pass(|| Box::new(verbose_file_reads::VerboseFileReads)); store.register_late_pass(|| Box::new(redundant_pub_crate::RedundantPubCrate::default())); store.register_late_pass(|| Box::new(unnamed_address::UnnamedAddress)); - store.register_late_pass(|| Box::new(dereference::Dereferencing::default())); + store.register_late_pass(move || Box::new(dereference::Dereferencing::new(msrv))); store.register_late_pass(|| Box::new(option_if_let_else::OptionIfLetElse)); store.register_late_pass(|| Box::new(future_not_send::FutureNotSend)); store.register_late_pass(|| Box::new(if_let_mutex::IfLetMutex)); store.register_late_pass(|| Box::new(if_not_else::IfNotElse)); store.register_late_pass(|| Box::new(equatable_if_let::PatternEquality)); - store.register_late_pass(|| Box::new(mut_mutex_lock::MutMutexLock)); store.register_late_pass(|| Box::new(manual_async_fn::ManualAsyncFn)); - store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero)); store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn)); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; store.register_early_pass(move || { @@ -842,10 +816,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher))); store.register_late_pass(|| Box::new(macro_use::MacroUseImports::default())); store.register_late_pass(|| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive)); - store.register_late_pass(|| Box::new(repeat_once::RepeatOnce)); store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult)); - store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr)); store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync)); let disallowed_methods = conf.disallowed_methods.clone(); @@ -857,9 +828,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(strings::StringToString)); store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues)); store.register_late_pass(|| Box::new(vec_init_then_push::VecInitThenPush::default())); - store.register_late_pass(|| { - Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons) - }); store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing)); store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv))); @@ -894,11 +862,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); store.register_late_pass(|| Box::new(init_numbered_fields::NumberedFields)); store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); - store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv))); store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes)); - store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); + store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion::default())); let allow_dbg_in_tests = conf.allow_dbg_in_tests; store.register_late_pass(move || Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests))); let cargo_ignore_publish = conf.cargo_ignore_publish; @@ -912,18 +879,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_late_pass(|| Box::new(format_push_string::FormatPushString)); - store.register_late_pass(|| Box::new(bytes_count_to_len::BytesCountToLen)); let max_include_file_size = conf.max_include_file_size; store.register_late_pass(move || Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size))); store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace)); store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default())); - store.register_late_pass(|| Box::new(get_first::GetFirst)); store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv))); store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef)); store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch)); - store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec)); store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv))); @@ -934,6 +898,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default())); store.register_late_pass(|| Box::new(manual_instant_elapsed::ManualInstantElapsed)); store.register_late_pass(|| Box::new(partialeq_to_none::PartialeqToNone)); + store.register_late_pass(|| Box::new(manual_string_new::ManualStringNew)); + store.register_late_pass(|| Box::new(unused_peekable::UnusedPeekable)); + store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index ddaffc75188..6d987f393fa 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -1,5 +1,6 @@ use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; +use clippy_utils::higher; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; @@ -184,10 +185,19 @@ struct IterFunctionVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { fn visit_block(&mut self, block: &'tcx Block<'tcx>) { for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) { + if check_loop_kind(expr).is_some() { + continue; + } self.visit_block_expr(expr, hir_id); } if let Some(expr) = block.expr { - self.visit_block_expr(expr, None); + if let Some(loop_kind) = check_loop_kind(expr) { + if let LoopKind::Conditional(block_expr) = loop_kind { + self.visit_block_expr(block_expr, None); + } + } else { + self.visit_block_expr(expr, None); + } } } @@ -264,6 +274,28 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { } } +enum LoopKind<'tcx> { + Conditional(&'tcx Expr<'tcx>), + Loop, +} + +fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option<LoopKind<'tcx>> { + if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) { + return Some(LoopKind::Conditional(let_expr)); + } + if let Some(higher::While { condition, .. }) = higher::While::hir(expr) { + return Some(LoopKind::Conditional(condition)); + } + if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) { + return Some(LoopKind::Conditional(arg)); + } + if let ExprKind::Loop { .. } = expr.kind { + return Some(LoopKind::Loop); + } + + None +} + impl<'tcx> IterFunctionVisitor<'_, 'tcx> { fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) { self.current_statement_hir_id = hir_id; diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index a0ca7e6ff1e..2502c8f880d 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -192,7 +192,7 @@ fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, match output.kind { TyKind::Tup(tys) if tys.is_empty() => { let sugg = "remove the return type"; - Some((sugg, "".into())) + Some((sugg, String::new())) }, _ => { let sugg = "return the output of the future directly"; diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs deleted file mode 100644 index cf5004399b8..00000000000 --- a/clippy_lints/src/manual_ok_or.rs +++ /dev/null @@ -1,95 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_lang_ctor, path_to_local_id}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::LangItem::{ResultErr, ResultOk}; -use rustc_hir::{Closure, Expr, ExprKind, PatKind}; -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}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// - /// Finds patterns that reimplement `Option::ok_or`. - /// - /// ### Why is this bad? - /// - /// Concise code helps focusing on behavior instead of boilerplate. - /// - /// ### Examples - /// ```rust - /// let foo: Option<i32> = None; - /// foo.map_or(Err("error"), |v| Ok(v)); - /// ``` - /// - /// Use instead: - /// ```rust - /// let foo: Option<i32> = None; - /// foo.ok_or("error"); - /// ``` - #[clippy::version = "1.49.0"] - pub MANUAL_OK_OR, - pedantic, - "finds patterns that can be encoded more concisely with `Option::ok_or`" -} - -declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]); - -impl<'tcx> LateLintPass<'tcx> for ManualOkOr { - fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { - if in_external_macro(cx.sess(), scrutinee.span) { - return; - } - - if_chain! { - if let ExprKind::MethodCall(method_segment, [receiver, or_expr, map_expr], _) = scrutinee.kind; - if method_segment.ident.name == sym!(map_or); - let ty = cx.typeck_results().expr_ty(receiver); - if is_type_diagnostic_item(cx, ty, sym::Option); - if is_ok_wrapping(cx, map_expr); - if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; - if is_lang_ctor(cx, err_path, ResultErr); - if let Some(method_receiver_snippet) = snippet_opt(cx, receiver.span); - if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); - if let Some(indent) = indent_of(cx, scrutinee.span); - then { - let reindented_err_arg_snippet = - reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); - span_lint_and_sugg( - cx, - MANUAL_OK_OR, - scrutinee.span, - "this pattern reimplements `Option::ok_or`", - "replace with", - format!( - "{}.ok_or({})", - method_receiver_snippet, - reindented_err_arg_snippet - ), - Applicability::MachineApplicable, - ); - } - } - } -} - -fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { - if let ExprKind::Path(ref qpath) = map_expr.kind { - if is_lang_ctor(cx, qpath, ResultOk) { - return true; - } - } - if_chain! { - if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind; - let body = cx.tcx.hir().body(body); - if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; - if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; - if is_lang_ctor(cx, ok_path, ResultOk); - then { path_to_local_id(ok_arg, param_id) } else { false } - } -} diff --git a/clippy_lints/src/manual_string_new.rs b/clippy_lints/src/manual_string_new.rs new file mode 100644 index 00000000000..a90eaa8fdcb --- /dev/null +++ b/clippy_lints/src/manual_string_new.rs @@ -0,0 +1,140 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::LitKind; +use rustc_errors::Applicability::MachineApplicable; +use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, symbol, Span}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`, + /// `String::from("")` and others. + /// + /// ### Why is this bad? + /// + /// Different ways of creating an empty string makes your code less standardized, which can + /// be confusing. + /// + /// ### Example + /// ```rust + /// let a = "".to_string(); + /// let b: String = "".into(); + /// ``` + /// Use instead: + /// ```rust + /// let a = String::new(); + /// let b = String::new(); + /// ``` + #[clippy::version = "1.65.0"] + pub MANUAL_STRING_NEW, + pedantic, + "empty String is being created manually" +} +declare_lint_pass!(ManualStringNew => [MANUAL_STRING_NEW]); + +impl LateLintPass<'_> for ManualStringNew { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + match ty.kind() { + ty::Adt(adt_def, _) if adt_def.is_struct() => { + if !cx.tcx.is_diagnostic_item(sym::String, adt_def.did()) { + return; + } + }, + _ => return, + } + + match expr.kind { + ExprKind::Call(func, args) => { + parse_call(cx, expr.span, func, args); + }, + ExprKind::MethodCall(path_segment, args, _) => { + parse_method_call(cx, expr.span, path_segment, args); + }, + _ => (), + } + } +} + +/// Checks if an expression's kind corresponds to an empty &str. +fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool { + if let ExprKind::Lit(lit) = expr_kind && + let LitKind::Str(value, _) = lit.node && + value == symbol::kw::Empty + { + return true; + } + + false +} + +fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { + span_lint_and_sugg( + cx, + MANUAL_STRING_NEW, + span, + "empty String is being created manually", + "consider using", + "String::new()".into(), + MachineApplicable, + ); +} + +/// Tries to parse an expression as a method call, emitting the warning if necessary. +fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, args: &[Expr<'_>]) { + if args.is_empty() { + // When parsing TryFrom::try_from(...).expect(...), we will have more than 1 arg. + return; + } + + let ident = path_segment.ident.as_str(); + let method_arg_kind = &args[0].kind; + if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { + warn_then_suggest(cx, span); + } else if let ExprKind::Call(func, args) = method_arg_kind { + // If our first argument is a function call itself, it could be an `unwrap`-like function. + // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc. + parse_call(cx, span, func, args); + } +} + +/// Tries to parse an expression as a function call, emitting the warning if necessary. +fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) { + if args.len() != 1 { + return; + } + + let arg_kind = &args[0].kind; + if let ExprKind::Path(qpath) = &func.kind { + if let QPath::TypeRelative(_, _) = qpath { + // String::from(...) or String::try_from(...) + if let QPath::TypeRelative(ty, path_seg) = qpath && + [sym::from, sym::try_from].contains(&path_seg.ident.name) && + let TyKind::Path(qpath) = &ty.kind && + let QPath::Resolved(_, path) = qpath && + let [path_seg] = path.segments && + path_seg.ident.name == sym::String && + is_expr_kind_empty_str(arg_kind) + { + warn_then_suggest(cx, span); + } + } else if let QPath::Resolved(_, path) = qpath { + // From::from(...) or TryFrom::try_from(...) + if let [path_seg1, path_seg2] = path.segments && + is_expr_kind_empty_str(arg_kind) && ( + (path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) || + (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from) + ) + { + warn_then_suggest(cx, span); + } + } + } +} diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs deleted file mode 100644 index 95c312f1fe2..00000000000 --- a/clippy_lints/src/map_clone.rs +++ /dev/null @@ -1,167 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; -use clippy_utils::{is_trait_method, meets_msrv, msrvs, peel_blocks}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::mir::Mutability; -use rustc_middle::ty; -use rustc_middle::ty::adjustment::Adjust; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::Ident; -use rustc_span::{sym, Span}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `map(|x| x.clone())` or - /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, - /// and suggests `cloned()` or `copied()` instead - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely - /// - /// ### Example - /// ```rust - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.map(|i| *i); - /// ``` - /// - /// The correct use would be: - /// - /// ```rust - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.cloned(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MAP_CLONE, - style, - "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" -} - -pub struct MapClone { - msrv: Option<RustcVersion>, -} - -impl_lint_pass!(MapClone => [MAP_CLONE]); - -impl MapClone { - pub fn new(msrv: Option<RustcVersion>) -> Self { - Self { msrv } - } -} - -impl<'tcx> LateLintPass<'tcx> for MapClone { - fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if_chain! { - if let hir::ExprKind::MethodCall(method, args, _) = e.kind; - if args.len() == 2; - if method.ident.name == sym::map; - let ty = cx.typeck_results().expr_ty(&args[0]); - if is_type_diagnostic_item(cx, ty, sym::Option) || is_trait_method(cx, e, sym::Iterator); - if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = args[1].kind; - then { - let closure_body = cx.tcx.hir().body(body); - let closure_expr = peel_blocks(&closure_body.value); - match closure_body.params[0].pat.kind { - hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding( - hir::BindingAnnotation::Unannotated, .., name, None - ) = inner.kind { - if ident_eq(name, closure_expr) { - self.lint_explicit_closure(cx, e.span, args[0].span, true); - } - }, - hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { - match closure_expr.kind { - hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { - if ident_eq(name, inner) { - if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { - self.lint_explicit_closure(cx, e.span, args[0].span, true); - } - } - }, - hir::ExprKind::MethodCall(method, [obj], _) => if_chain! { - if ident_eq(name, obj) && method.ident.name == sym::clone; - if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); - if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); - if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); - // no autoderefs - if !cx.typeck_results().expr_adjustments(obj).iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); - then { - let obj_ty = cx.typeck_results().expr_ty(obj); - if let ty::Ref(_, ty, mutability) = obj_ty.kind() { - if matches!(mutability, Mutability::Not) { - let copy = is_copy(cx, *ty); - self.lint_explicit_closure(cx, e.span, args[0].span, copy); - } - } else { - lint_needless_cloning(cx, e.span, args[0].span); - } - } - }, - _ => {}, - } - }, - _ => {}, - } - } - } - } - - extract_msrv_attr!(LateContext); -} - -fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { - if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind { - path.segments.len() == 1 && path.segments[0].ident == name - } else { - false - } -} - -fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { - span_lint_and_sugg( - cx, - MAP_CLONE, - root.trim_start(receiver).unwrap(), - "you are needlessly cloning iterator elements", - "remove the `map` call", - String::new(), - Applicability::MachineApplicable, - ); -} - -impl MapClone { - fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) { - let mut applicability = Applicability::MachineApplicable; - - let (message, sugg_method) = if is_copy && meets_msrv(self.msrv, msrvs::ITERATOR_COPIED) { - ("you are using an explicit closure for copying elements", "copied") - } else { - ("you are using an explicit closure for cloning elements", "cloned") - }; - - span_lint_and_sugg( - cx, - MAP_CLONE, - replace, - message, - &format!("consider calling the dedicated `{}` method", sugg_method), - format!( - "{}.{}()", - snippet_with_applicability(cx, root, "..", &mut applicability), - sugg_method, - ), - applicability, - ); - } -} diff --git a/clippy_lints/src/map_err_ignore.rs b/clippy_lints/src/map_err_ignore.rs deleted file mode 100644 index 1e542447c96..00000000000 --- a/clippy_lints/src/map_err_ignore.rs +++ /dev/null @@ -1,154 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map_err(|_| Some::Enum)` - /// - /// ### Why is this bad? - /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error - /// - /// ### Example - /// Before: - /// ```rust - /// use std::fmt; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible, - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error {} - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::<i32>() - /// .map_err(|_| Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - /// - /// After: - /// ```rust - /// use std::{fmt, num::ParseIntError}; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible(ParseIntError), - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible(_) => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error { - /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - /// match self { - /// Error::Indivisible(source) => Some(source), - /// _ => None, - /// } - /// } - /// } - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::<i32>() - /// .map_err(Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub MAP_ERR_IGNORE, - restriction, - "`map_err` should not ignore the original error" -} - -declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]); - -impl<'tcx> LateLintPass<'tcx> for MapErrIgnore { - // do not try to lint if this is from a macro or desugaring - fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { - if e.span.from_expansion() { - return; - } - - // check if this is a method call (e.g. x.foo()) - if let ExprKind::MethodCall(method, [_, arg], _) = e.kind { - // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1] - // Enum::Variant[2])) - if method.ident.name == sym!(map_err) { - // make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span - // fields - if let ExprKind::Closure(&Closure { - capture_clause, - body, - fn_decl_span, - .. - }) = arg.kind - { - // check if this is by Reference (meaning there's no move statement) - if capture_clause == CaptureBy::Ref { - // Get the closure body to check the parameters and values - let closure_body = cx.tcx.hir().body(body); - // make sure there's only one parameter (`|_|`) - if closure_body.params.len() == 1 { - // make sure that parameter is the wild token (`_`) - if let PatKind::Wild = closure_body.params[0].pat.kind { - // span the area of the closure capture and warn that the - // original error will be thrown away - span_lint_and_help( - cx, - MAP_ERR_IGNORE, - fn_decl_span, - "`map_err(|_|...` wildcard pattern discards the original error", - None, - "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", - ); - } - } - } - } - } - } - } -} diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 0da4833f1df..34cc082687e 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_wild; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::span_contains_comment; use rustc_ast::{Attribute, LitKind}; use rustc_errors::Applicability; use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat}; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; use rustc_span::source_map::Spanned; @@ -76,6 +77,7 @@ where >, { if_chain! { + if !span_contains_comment(cx.sess().source_map(), expr.span); if iter.len() >= 2; if cx.typeck_results().expr_ty(expr).is_bool(); if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index fa19cddd35e..6f037339ec7 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -8,7 +8,7 @@ use clippy_utils::{ }; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath}; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::sym; use rustc_typeck::hir_ty_to_ty; @@ -65,8 +65,26 @@ pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool { for arm in arms { let arm_expr = peel_blocks_with_stmt(arm.body); + + if let Some(guard_expr) = &arm.guard { + match guard_expr { + // gives up if `pat if expr` can have side effects + Guard::If(if_cond) => { + if if_cond.can_have_side_effects() { + return false; + } + }, + // gives up `pat if let ...` arm + Guard::IfLet(_) => { + return false; + }, + }; + } + if let PatKind::Wild = arm.pat.kind { - return eq_expr_value(cx, match_expr, strip_return(arm_expr)); + if !eq_expr_value(cx, match_expr, strip_return(arm_expr)) { + return false; + } } else if !pat_same_as_expr(arm.pat, arm_expr) { return false; } diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints/src/methods/bytecount.rs new file mode 100644 index 00000000000..6a7c63d76f7 --- /dev/null +++ b/clippy_lints/src/methods/bytecount.rs @@ -0,0 +1,70 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::match_type; +use clippy_utils::visitors::is_local_used; +use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, UintTy}; +use rustc_span::sym; + +use super::NAIVE_BYTECOUNT; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + filter_recv: &'tcx Expr<'_>, + filter_arg: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind; + let body = cx.tcx.hir().body(body); + if let [param] = body.params; + if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; + if let ExprKind::Binary(ref op, l, r) = body.value.kind; + if op.node == BinOpKind::Eq; + if match_type(cx, + cx.typeck_results().expr_ty(filter_recv).peel_refs(), + &paths::SLICE_ITER); + let operand_is_arg = |expr| { + let expr = peel_ref_operators(cx, peel_blocks(expr)); + path_to_local_id(expr, arg_id) + }; + let needle = if operand_is_arg(l) { + r + } else if operand_is_arg(r) { + l + } else { + return; + }; + if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); + if !is_local_used(cx, needle, arg_id); + then { + let haystack = if let ExprKind::MethodCall(path, args, _) = + filter_recv.kind { + let p = path.ident.name; + if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { + &args[0] + } else { + filter_recv + } + } else { + filter_recv + }; + let mut applicability = Applicability::MaybeIncorrect; + span_lint_and_sugg( + cx, + NAIVE_BYTECOUNT, + expr.span, + "you appear to be counting bytes the naive way", + "consider using the bytecount crate", + format!("bytecount::count({}, {})", + snippet_with_applicability(cx, haystack.span, "..", &mut applicability), + snippet_with_applicability(cx, needle.span, "..", &mut applicability)), + applicability, + ); + } + }; +} diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs new file mode 100644 index 00000000000..fcfc25b523d --- /dev/null +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +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; +use rustc_span::sym; + +use super::BYTES_COUNT_TO_LEN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + count_recv: &'tcx hir::Expr<'_>, + bytes_recv: &'tcx hir::Expr<'_>, +) { + if_chain! { + if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(bytes_id); + if cx.tcx.type_of(impl_id).is_str(); + let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs(); + if ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BYTES_COUNT_TO_LEN, + expr.span, + "using long and hard to read `.bytes().count()`", + "consider calling `.len()` instead", + format!("{}.len()", snippet_with_applicability(cx, bytes_recv.span, "..", &mut applicability)), + applicability + ); + } + }; +} diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs new file mode 100644 index 00000000000..b3c2c7c9a2d --- /dev/null +++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -0,0 +1,41 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::{source_map::Spanned, symbol::sym, Span}; + +use super::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + call_span: Span, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_str(); + if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = arg.kind; + if (2..=6).contains(&ext_literal.as_str().len()); + let ext_str = ext_literal.as_str(); + if ext_str.starts_with('.'); + if ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit()) + || ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()); + let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if recv_ty.is_str() || is_type_diagnostic_item(cx, recv_ty, sym::String); + then { + span_lint_and_help( + cx, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + call_span, + "case-sensitive file extension comparison", + None, + "consider using a case-insensitive comparison instead", + ); + } + } +} diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints/src/methods/collapsible_str_replace.rs new file mode 100644 index 00000000000..561033be5b6 --- /dev/null +++ b/clippy_lints/src/methods/collapsible_str_replace.rs @@ -0,0 +1,96 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{eq_expr_value, get_parent_expr}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use std::collections::VecDeque; + +use super::method_call; +use super::COLLAPSIBLE_STR_REPLACE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + from: &'tcx hir::Expr<'tcx>, + to: &'tcx hir::Expr<'tcx>, +) { + let replace_methods = collect_replace_calls(cx, expr, to); + if replace_methods.methods.len() > 1 { + let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind(); + // If the parent node's `to` argument is the same as the `to` argument + // of the last replace call in the current chain, don't lint as it was already linted + if let Some(parent) = get_parent_expr(cx, expr) + && let Some(("replace", [_, current_from, current_to], _)) = method_call(parent) + && eq_expr_value(cx, to, current_to) + && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind() + { + return; + } + + check_consecutive_replace_calls(cx, expr, &replace_methods, to); + } +} + +struct ReplaceMethods<'tcx> { + methods: VecDeque<&'tcx hir::Expr<'tcx>>, + from_args: VecDeque<&'tcx hir::Expr<'tcx>>, +} + +fn collect_replace_calls<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + to_arg: &'tcx hir::Expr<'tcx>, +) -> ReplaceMethods<'tcx> { + let mut methods = VecDeque::new(); + let mut from_args = VecDeque::new(); + + let _: Option<()> = for_each_expr(expr, |e| { + if let Some(("replace", [_, from, to], _)) = method_call(e) { + if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() { + methods.push_front(e); + from_args.push_front(from); + ControlFlow::Continue(()) + } else { + ControlFlow::BREAK + } + } else { + ControlFlow::Continue(()) + } + }); + + ReplaceMethods { methods, from_args } +} + +/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint. +fn check_consecutive_replace_calls<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + replace_methods: &ReplaceMethods<'tcx>, + to_arg: &'tcx hir::Expr<'tcx>, +) { + let from_args = &replace_methods.from_args; + let from_arg_reprs: Vec<String> = from_args + .iter() + .map(|from_arg| snippet(cx, from_arg.span, "..").to_string()) + .collect(); + let app = Applicability::MachineApplicable; + let earliest_replace_call = replace_methods.methods.front().unwrap(); + if let Some((_, [..], span_lo)) = method_call(earliest_replace_call) { + span_lint_and_sugg( + cx, + COLLAPSIBLE_STR_REPLACE, + expr.span.with_lo(span_lo.lo()), + "used consecutive `str::replace` call", + "replace with", + format!( + "replace([{}], {})", + from_arg_reprs.join(", "), + snippet(cx, to_arg.span, ".."), + ), + app, + ); + } +} diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs index 5ef08ca6290..d59fefa1ddc 100644 --- a/clippy_lints/src/methods/expect_used.rs +++ b/clippy_lints/src/methods/expect_used.rs @@ -7,18 +7,26 @@ use rustc_span::sym; use super::EXPECT_USED; -/// lint use of `expect()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_expect_in_tests: bool) { +/// lint use of `expect()` or `expect_err` for `Result` and `expect()` for `Option`. +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + is_err: bool, + allow_expect_in_tests: bool, +) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { Some((EXPECT_USED, "an Option", "None", "")) } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((EXPECT_USED, "a Result", "Err", "an ")) + Some((EXPECT_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an ")) } else { None }; + let method = if is_err { "expect_err" } else { "expect" }; + if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { return; } @@ -28,7 +36,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr cx, lint, expr.span, - &format!("used `expect()` on `{kind}` value"), + &format!("used `{method}()` on `{kind}` value"), None, &format!("if this value is {none_prefix}`{none_value}`, it will panic"), ); diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints/src/methods/get_first.rs new file mode 100644 index 00000000000..4de77de7404 --- /dev/null +++ b/clippy_lints/src/methods/get_first.rs @@ -0,0 +1,39 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_slice_of_primitives; +use clippy_utils::source::snippet_with_applicability; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::GET_FIRST; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_slice(); + if let Some(_) = is_slice_of_primitives(cx, recv); + if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind; + then { + let mut app = Applicability::MachineApplicable; + let slice_name = snippet_with_applicability(cx, recv.span, "..", &mut app); + span_lint_and_sugg( + cx, + GET_FIRST, + expr.span, + &format!("accessing first element with `{0}.get(0)`", slice_name), + "try", + format!("{}.first()", slice_name), + app, + ); + } + } +} diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs new file mode 100644 index 00000000000..cea7b0d82ff --- /dev/null +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -0,0 +1,107 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::{get_expr_use_or_unification_node, is_lang_ctor, is_no_std_crate}; + +use rustc_errors::Applicability; +use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; + +use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; + +enum IterType { + Iter, + IterMut, + IntoIter, +} + +impl IterType { + fn ref_prefix(&self) -> &'static str { + match self { + Self::Iter => "&", + Self::IterMut => "&mut ", + Self::IntoIter => "", + } + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) { + let item = match &recv.kind { + ExprKind::Array(v) if v.len() <= 1 => v.first(), + ExprKind::Path(p) => { + if is_lang_ctor(cx, p, OptionNone) { + None + } else { + return; + } + }, + ExprKind::Call(f, some_args) if some_args.len() == 1 => { + if let ExprKind::Path(p) = &f.kind { + if is_lang_ctor(cx, p, OptionSome) { + Some(&some_args[0]) + } else { + return; + } + } else { + return; + } + }, + _ => return, + }; + let iter_type = match method_name { + "iter" => IterType::Iter, + "iter_mut" => IterType::IterMut, + "into_iter" => IterType::IntoIter, + _ => return, + }; + + let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { + Some((Node::Expr(parent), child_id)) => match parent.kind { + ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, + ExprKind::If(_, _, _) + | ExprKind::Match(_, _, _) + | ExprKind::Closure(_) + | ExprKind::Ret(_) + | ExprKind::Break(_, _) => true, + _ => false, + }, + Some((Node::Stmt(_) | Node::Local(_), _)) => false, + _ => true, + }; + + if is_unified { + return; + } + + if let Some(i) = item { + let sugg = format!( + "{}::iter::once({}{})", + if is_no_std_crate(cx) { "core" } else { "std" }, + iter_type.ref_prefix(), + snippet(cx, i.span, "...") + ); + span_lint_and_sugg( + cx, + ITER_ON_SINGLE_ITEMS, + expr.span, + &format!("`{method_name}` call on a collection with only one item"), + "try", + sugg, + Applicability::MaybeIncorrect, + ); + } else { + span_lint_and_sugg( + cx, + ITER_ON_EMPTY_COLLECTIONS, + expr.span, + &format!("`{method_name}` call on an empty collection"), + "try", + if is_no_std_crate(cx) { + "core::iter::empty()".to_string() + } else { + "std::iter::empty()".to_string() + }, + Applicability::MaybeIncorrect, + ); + } +} diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs new file mode 100644 index 00000000000..ffd2f4a38b8 --- /dev/null +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -0,0 +1,64 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_lang_ctor, path_to_local_id}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{ResultErr, ResultOk}; +use rustc_hir::{Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::MANUAL_OK_OR; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'_>, + or_expr: &'tcx Expr<'_>, + map_expr: &'tcx Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Option); + if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, [err_arg]) = or_expr.kind; + if is_lang_ctor(cx, err_path, ResultErr); + if is_ok_wrapping(cx, map_expr); + if let Some(recv_snippet) = snippet_opt(cx, recv.span); + if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); + if let Some(indent) = indent_of(cx, expr.span); + then { + let reindented_err_arg_snippet = reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); + span_lint_and_sugg( + cx, + MANUAL_OK_OR, + expr.span, + "this pattern reimplements `Option::ok_or`", + "replace with", + format!( + "{}.ok_or({})", + recv_snippet, + reindented_err_arg_snippet + ), + Applicability::MachineApplicable, + ); + } + } +} + +fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { + if let ExprKind::Path(ref qpath) = map_expr.kind { + if is_lang_ctor(cx, qpath, ResultOk) { + return true; + } + } + if_chain! { + if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind; + let body = cx.tcx.hir().body(body); + if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; + if is_lang_ctor(cx, ok_path, ResultOk); + then { path_to_local_id(ok_arg, param_id) } else { false } + } +} diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs new file mode 100644 index 00000000000..ffedda95ff8 --- /dev/null +++ b/clippy_lints/src/methods/map_clone.rs @@ -0,0 +1,122 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs, peel_blocks}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::mir::Mutability; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_semver::RustcVersion; +use rustc_span::symbol::Ident; +use rustc_span::{sym, Span}; + +use super::MAP_CLONE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'_>, + e: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + msrv: Option<RustcVersion>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id); + if cx.tcx.impl_of_method(method_id) + .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id), sym::Option)) + || is_diag_trait_item(cx, method_id, sym::Iterator); + if let hir::ExprKind::Closure(&hir::Closure{ body, .. }) = arg.kind; + then { + let closure_body = cx.tcx.hir().body(body); + let closure_expr = peel_blocks(&closure_body.value); + match closure_body.params[0].pat.kind { + hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, .., name, None + ) = inner.kind { + if ident_eq(name, closure_expr) { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); + } + }, + hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { + match closure_expr.kind { + hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { + if ident_eq(name, inner) { + if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); + } + } + }, + hir::ExprKind::MethodCall(method, [obj], _) => if_chain! { + if ident_eq(name, obj) && method.ident.name == sym::clone; + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); + if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); + if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); + // no autoderefs + if !cx.typeck_results().expr_adjustments(obj).iter() + .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); + then { + let obj_ty = cx.typeck_results().expr_ty(obj); + if let ty::Ref(_, ty, mutability) = obj_ty.kind() { + if matches!(mutability, Mutability::Not) { + let copy = is_copy(cx, *ty); + lint_explicit_closure(cx, e.span, recv.span, copy, msrv); + } + } else { + lint_needless_cloning(cx, e.span, recv.span); + } + } + }, + _ => {}, + } + }, + _ => {}, + } + } + } +} + +fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind { + path.segments.len() == 1 && path.segments[0].ident == name + } else { + false + } +} + +fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { + span_lint_and_sugg( + cx, + MAP_CLONE, + root.trim_start(receiver).unwrap(), + "you are needlessly cloning iterator elements", + "remove the `map` call", + String::new(), + Applicability::MachineApplicable, + ); +} + +fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: Option<RustcVersion>) { + let mut applicability = Applicability::MachineApplicable; + + let (message, sugg_method) = if is_copy && meets_msrv(msrv, msrvs::ITERATOR_COPIED) { + ("you are using an explicit closure for copying elements", "copied") + } else { + ("you are using an explicit closure for cloning elements", "cloned") + }; + + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + message, + &format!("consider calling the dedicated `{}` method", sugg_method), + format!( + "{}.{}()", + snippet_with_applicability(cx, root, "..", &mut applicability), + sugg_method, + ), + applicability, + ); +} diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs new file mode 100644 index 00000000000..1fb6617145e --- /dev/null +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::MAP_ERR_IGNORE; + +pub(super) fn check<'tcx>(cx: &LateContext<'_>, e: &Expr<'_>, arg: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Result) + && let ExprKind::Closure(&Closure { + capture_clause: CaptureBy::Ref, + body, + fn_decl_span, + .. + }) = arg.kind + && let closure_body = cx.tcx.hir().body(body) + && let [param] = closure_body.params + && let PatKind::Wild = param.pat.kind + { + // span the area of the closure capture and warn that the + // original error will be thrown away + span_lint_and_help( + cx, + MAP_ERR_IGNORE, + fn_decl_span, + "`map_err(|_|...` wildcard pattern discards the original error", + None, + "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5ac6b09f0aa..a0d190a58af 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1,5 +1,8 @@ mod bind_instead_of_map; +mod bytecount; +mod bytes_count_to_len; mod bytes_nth; +mod case_sensitive_file_extension_comparisons; mod chars_cmp; mod chars_cmp_with_unwrap; mod chars_last_cmp; @@ -9,6 +12,7 @@ mod chars_next_cmp_with_unwrap; mod clone_on_copy; mod clone_on_ref_ptr; mod cloned_instead_of_copied; +mod collapsible_str_replace; mod err_expect; mod expect_fun_call; mod expect_used; @@ -21,6 +25,7 @@ mod filter_next; mod flat_map_identity; mod flat_map_option; mod from_iter_instead_of_collect; +mod get_first; mod get_last_with_len; mod get_unwrap; mod implicit_clone; @@ -33,55 +38,72 @@ mod iter_count; mod iter_next_slice; mod iter_nth; mod iter_nth_zero; +mod iter_on_single_or_empty_collections; mod iter_overeager_cloned; mod iter_skip_next; mod iter_with_drain; mod iterator_step_by_zero; +mod manual_ok_or; mod manual_saturating_arithmetic; mod manual_str_repeat; +mod map_clone; mod map_collect_result_unit; +mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod mut_mutex_lock; mod needless_option_as_deref; mod needless_option_take; mod no_effect_replace; mod obfuscated_if_else; mod ok_expect; +mod open_options; mod option_as_ref_deref; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; mod or_then_unwrap; +mod path_buf_push_overwrite; +mod range_zip_with_len; +mod repeat_once; mod search_is_some; mod single_char_add_str; mod single_char_insert_string; mod single_char_pattern; mod single_char_push_string; mod skip_while_next; +mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; mod suspicious_map; mod suspicious_splitn; +mod suspicious_to_owned; mod uninit_assumed_init; +mod unit_hash; mod unnecessary_filter_map; mod unnecessary_fold; mod unnecessary_iter_cloned; mod unnecessary_join; mod unnecessary_lazy_eval; +mod unnecessary_sort_by; mod unnecessary_to_owned; mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; mod utils; +mod vec_resize_to_zero; +mod verbose_file_reads; mod wrong_self_convention; mod zst_offset; use bind_instead_of_map::BindInsteadOfMap; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty}; +use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::{ + contains_return, get_trait_def_id, is_trait_method, iter_input_pats, meets_msrv, msrvs, paths, return_ty, +}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::def::Res; @@ -117,6 +139,32 @@ declare_clippy_lint! { "used `cloned` where `copied` could be used instead" } +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive calls to `str::replace` (2 or more) + /// that can be collapsed into a single call. + /// + /// ### Why is this bad? + /// Consecutive `str::replace` calls scan the string multiple times + /// with repetitive code. + /// + /// ### Example + /// ```rust + /// let hello = "hesuo worpd" + /// .replace('s', "l") + /// .replace("u", "l") + /// .replace('p', "l"); + /// ``` + /// Use instead: + /// ```rust + /// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l"); + /// ``` + #[clippy::version = "1.64.0"] + pub COLLAPSIBLE_STR_REPLACE, + perf, + "collapse consecutive calls to str::replace (2 or more) into a single call" +} + declare_clippy_lint! { /// ### What it does /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed. @@ -173,7 +221,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `.unwrap()` calls on `Option`s and on `Result`s. + /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. /// /// ### Why is this bad? /// It is better to handle the `None` or `Err` case, @@ -223,7 +271,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `.expect()` calls on `Option`s and `Result`s. + /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. /// /// ### Why is this bad? /// Usually it is better to handle the `None` or `Err` case. @@ -2006,6 +2054,55 @@ declare_clippy_lint! { "replace `.iter().count()` with `.len()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. + /// + /// ### Why is this bad? + /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` + /// itself, without taking ownership of the `Cow` contents (i.e. + /// it's equivalent to calling `Cow::clone`). + /// The similarly named `into_owned` method, on the other hand, + /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` + /// into a `Cow::Owned`. + /// + /// Given the potential ambiguity, consider replacing `to_owned` + /// with `clone` for better readability or, if getting a `Cow::Owned` + /// was the original intent, using `into_owned` instead. + /// + /// ### Example + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.to_owned(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// Use instead: + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.clone(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// or + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.into_owned(); + /// assert!(matches!(data, String)) + /// ``` + #[clippy::version = "1.65.0"] + pub SUSPICIOUS_TO_OWNED, + suspicious, + "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to [`splitn`] @@ -2269,7 +2366,7 @@ declare_clippy_lint! { /// "1234".replace("12", "12"); /// "1234".replacen("12", "12", 1); /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub NO_EFFECT_REPLACE, suspicious, "replace with no effect" @@ -2304,6 +2401,640 @@ declare_clippy_lint! { more clearly with `if .. else ..`" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item + /// + /// ### Why is this bad? + /// + /// It is simpler to use the once function from the standard library: + /// + /// ### Example + /// + /// ```rust + /// let a = [123].iter(); + /// let b = Some(123).into_iter(); + /// ``` + /// Use instead: + /// ```rust + /// use std::iter; + /// let a = iter::once(&123); + /// let b = iter::once(123); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.64.0"] + pub ITER_ON_SINGLE_ITEMS, + nursery, + "Iterator for array of length 1" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections + /// + /// ### Why is this bad? + /// + /// It is simpler to use the empty function from the standard library: + /// + /// ### Example + /// + /// ```rust + /// use std::{slice, option}; + /// let a: slice::Iter<i32> = [].iter(); + /// let f: option::IntoIter<i32> = None.into_iter(); + /// ``` + /// Use instead: + /// ```rust + /// use std::iter; + /// let a: iter::Empty<i32> = iter::empty(); + /// let b: iter::Empty<i32> = iter::empty(); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.64.0"] + pub ITER_ON_EMPTY_COLLECTIONS, + nursery, + "Iterator for empty array" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for naive byte counts + /// + /// ### Why is this bad? + /// The [`bytecount`](https://crates.io/crates/bytecount) + /// crate has methods to count your bytes faster, especially for large slices. + /// + /// ### Known problems + /// If you have predominantly small slices, the + /// `bytecount::count(..)` method may actually be slower. However, if you can + /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be + /// faster in those cases. + /// + /// ### Example + /// ```rust + /// # let vec = vec![1_u8]; + /// let count = vec.iter().filter(|x| **x == 0u8).count(); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let vec = vec![1_u8]; + /// let count = bytecount::count(&vec, 0u8); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NAIVE_BYTECOUNT, + pedantic, + "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values" +} + +declare_clippy_lint! { + /// ### What it does + /// It checks for `str::bytes().count()` and suggests replacing it with + /// `str::len()`. + /// + /// ### Why is this bad? + /// `str::bytes().count()` is longer and may not be as performant as using + /// `str::len()`. + /// + /// ### Example + /// ```rust + /// "hello".bytes().count(); + /// String::from("hello").bytes().count(); + /// ``` + /// Use instead: + /// ```rust + /// "hello".len(); + /// String::from("hello").len(); + /// ``` + #[clippy::version = "1.62.0"] + pub BYTES_COUNT_TO_LEN, + complexity, + "Using `bytes().count()` when `len()` performs the same functionality" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// ### Why is this bad? + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// ### Example + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// let filename = std::path::Path::new(filename); + /// filename.extension() + /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for using `x.get(0)` instead of + /// `x.first()`. + /// + /// ### Why is this bad? + /// Using `x.first()` is easier to read and has the same + /// result. + /// + /// ### Example + /// ```rust + /// let x = vec![2, 3, 5]; + /// let first_element = x.get(0); + /// ``` + /// + /// Use instead: + /// ```rust + /// let x = vec![2, 3, 5]; + /// let first_element = x.first(); + /// ``` + #[clippy::version = "1.63.0"] + pub GET_FIRST, + style, + "Using `x.get(0)` when `x.first()` is simpler" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// ### Why is this bad? + /// + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// ### Examples + /// ```rust + /// let foo: Option<i32> = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let foo: Option<i32> = None; + /// foo.ok_or("error"); + /// ``` + #[clippy::version = "1.49.0"] + pub MANUAL_OK_OR, + pedantic, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `map(|x| x.clone())` or + /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, + /// and suggests `cloned()` or `copied()` instead + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely + /// + /// ### Example + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.cloned(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MAP_CLONE, + style, + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map_err(|_| Some::Enum)` + /// + /// ### Why is this bad? + /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// + /// ### Example + /// Before: + /// ```rust + /// use std::fmt; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible, + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error {} + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::<i32>() + /// .map_err(|_| Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + /// + /// After: + /// ```rust + /// use std::{fmt, num::ParseIntError}; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible(ParseIntError), + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible(_) => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// match self { + /// Error::Indivisible(source) => Some(source), + /// _ => None, + /// } + /// } + /// } + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::<i32>() + /// .map_err(Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub MAP_ERR_IGNORE, + restriction, + "`map_err` should not ignore the original error" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `&mut Mutex::lock` calls + /// + /// ### Why is this bad? + /// `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// ### Example + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let value = value_mutex.get_mut().unwrap(); + /// *value += 1; + /// ``` + #[clippy::version = "1.49.0"] + pub MUT_MUTEX_LOCK, + style, + "`&mut Mutex::lock` does unnecessary locking" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// ### Why is this bad? + /// In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// ### Example + /// ```rust + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().read(true).truncate(true); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NONSENSICAL_OPEN_OPTIONS, + correctness, + "nonsensical combination of options for opening a file" +} + +declare_clippy_lint! { + /// ### What it does + ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// ### Why is this bad? + /// Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// ### Example + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("bar"); + /// assert_eq!(x, PathBuf::from("/foo/bar")); + /// ``` + #[clippy::version = "1.36.0"] + pub PATH_BUF_PUSH_OVERWRITE, + nursery, + "calling `push` with file system root on `PathBuf` can overwrite it" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// ### Why is this bad? + /// The code is better expressed with `.enumerate()`. + /// + /// ### Example + /// ```rust + /// # let x = vec![1]; + /// let _ = x.iter().zip(0..x.len()); + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = vec![1]; + /// let _ = x.iter().enumerate(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.repeat(1)` and suggest the following method for each types. + /// - `.to_string()` for `str` + /// - `.clone()` for `String` + /// - `.to_vec()` for `slice` + /// + /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if + /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) + /// + /// ### Why is this bad? + /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning + /// the string is the intention behind this, `clone()` should be used. + /// + /// ### Example + /// ```rust + /// fn main() { + /// let x = String::from("hello world").repeat(1); + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn main() { + /// let x = String::from("hello world").clone(); + /// } + /// ``` + #[clippy::version = "1.47.0"] + pub REPEAT_ONCE, + complexity, + "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " +} + +declare_clippy_lint! { + /// ### What it does + /// When sorting primitive values (integers, bools, chars, as well + /// as arrays, slices, and tuples of such items), it is typically better to + /// use an unstable sort than a stable sort. + /// + /// ### Why is this bad? + /// Typically, using a stable sort consumes more memory and cpu cycles. + /// Because values which compare equal are identical, preserving their + /// relative order (the guarantee that a stable sort provides) means + /// nothing, while the extra costs still apply. + /// + /// ### Known problems + /// + /// As pointed out in + /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), + /// a stable sort can instead be significantly faster for certain scenarios + /// (eg. when a sorted vector is extended with new data and resorted). + /// + /// For more information and benchmarking results, please refer to the + /// issue linked above. + /// + /// ### Example + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort(); + /// ``` + /// Use instead: + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort_unstable(); + /// ``` + #[clippy::version = "1.47.0"] + pub STABLE_SORT_PRIMITIVE, + pedantic, + "use of sort() when sort_unstable() is equivalent" +} + +declare_clippy_lint! { + /// ### What it does + /// Detects `().hash(_)`. + /// + /// ### Why is this bad? + /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. + /// + /// ### Example + /// ```rust + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => ().hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + /// Use instead: + /// ```rust + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => 0_u8.hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + #[clippy::version = "1.58.0"] + pub UNIT_HASH, + correctness, + "hashing a unit value, which does nothing" +} + +declare_clippy_lint! { + /// ### What it does + /// Detects uses of `Vec::sort_by` passing in a closure + /// which compares the two arguments, either directly or indirectly. + /// + /// ### Why is this bad? + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and a more complicated + /// closure. + /// + /// ### Known problems + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already + /// imported by a use statement, then it will need to be added manually. + /// + /// ### Example + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec<A> = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); + /// ``` + /// Use instead: + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec<A> = Vec::new(); + /// vec.sort_by_key(|a| a.foo()); + /// ``` + #[clippy::version = "1.46.0"] + pub UNNECESSARY_SORT_BY, + complexity, + "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" +} + +declare_clippy_lint! { + /// ### What it does + /// Finds occurrences of `Vec::resize(0, an_int)` + /// + /// ### Why is this bad? + /// This is probably an argument inversion mistake. + /// + /// ### Example + /// ```rust + /// vec!(1, 2, 3, 4, 5).resize(0, 5) + /// ``` + /// + /// Use instead: + /// ```rust + /// vec!(1, 2, 3, 4, 5).clear() + /// ``` + #[clippy::version = "1.46.0"] + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for use of File::read_to_end and File::read_to_string. + /// + /// ### Why is this bad? + /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// + /// ### Example + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # use std::fs; + /// let mut bytes = fs::read("foo.txt").unwrap(); + /// ``` + #[clippy::version = "1.44.0"] + pub VERBOSE_FILE_READS, + restriction, + "use of `File::read_to_end` or `File::read_to_string`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>, @@ -2347,6 +3078,7 @@ impl_lint_pass!(Methods => [ CLONE_ON_COPY, CLONE_ON_REF_PTR, CLONE_DOUBLE_REF, + COLLAPSIBLE_STR_REPLACE, ITER_OVEREAGER_CLONED, CLONED_INSTEAD_OF_COPIED, FLAT_MAP_OPTION, @@ -2393,6 +3125,7 @@ impl_lint_pass!(Methods => [ FROM_ITER_INSTEAD_OF_COLLECT, INSPECT_FOR_EACH, IMPLICIT_CLONE, + SUSPICIOUS_TO_OWNED, SUSPICIOUS_SPLITN, MANUAL_STR_REPEAT, EXTEND_WITH_DRAIN, @@ -2406,6 +3139,25 @@ impl_lint_pass!(Methods => [ NEEDLESS_OPTION_TAKE, NO_EFFECT_REPLACE, OBFUSCATED_IF_ELSE, + ITER_ON_SINGLE_ITEMS, + ITER_ON_EMPTY_COLLECTIONS, + NAIVE_BYTECOUNT, + BYTES_COUNT_TO_LEN, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + GET_FIRST, + MANUAL_OK_OR, + MAP_CLONE, + MAP_ERR_IGNORE, + MUT_MUTEX_LOCK, + NONSENSICAL_OPEN_OPTIONS, + PATH_BUF_PUSH_OVERWRITE, + RANGE_ZIP_WITH_LEN, + REPEAT_ONCE, + STABLE_SORT_PRIMITIVE, + UNIT_HASH, + UNNECESSARY_SORT_BY, + VEC_RESIZE_TO_ZERO, + VERBOSE_FILE_READS, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2541,7 +3293,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if contains_adt_constructor(ret_ty, self_adt) { return; } - } else if contains_ty(ret_ty, self_ty) { + } else if ret_ty.contains(self_ty) { return; } @@ -2559,7 +3311,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if contains_adt_constructor(assoc_ty, self_adt) { return; } - } else if contains_ty(assoc_ty, self_ty) { + } else if assoc_ty.contains(self_ty) { return; } } @@ -2608,7 +3360,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if let TraitItemKind::Fn(_, _) = item.kind; let ret_ty = return_ty(cx, item.hir_id()); let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder(); - if !contains_ty(ret_ty, self_ty); + if !ret_ty.contains(self_ty); then { span_lint( @@ -2660,22 +3412,30 @@ impl Methods { }, _ => {}, }, - ("count", []) => match method_call(recv) { + ("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false), Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { iter_count::check(cx, expr, recv2, name2); }, Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), + Some(("filter", [recv2, arg], _)) => bytecount::check(cx, expr, recv2, arg), + Some(("bytes", [recv2], _)) => bytes_count_to_len::check(cx, expr, recv, recv2), _ => {}, }, ("drain", [arg]) => { iter_with_drain::check(cx, expr, recv, span, arg); }, + ("ends_with", [arg]) => { + if let ExprKind::MethodCall(_, _, span) = expr.kind { + 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, self.msrv, span, err_span), - _ => expect_used::check(cx, expr, recv, self.allow_expect_in_tests), + _ => expect_used::check(cx, expr, recv, false, 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); @@ -2702,12 +3462,21 @@ impl Methods { inspect_for_each::check(cx, expr, span2); } }, - ("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg), + ("get", [arg]) => { + get_first::check(cx, expr, recv, arg); + get_last_with_len::check(cx, expr, recv, arg); + }, ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("hash", [arg]) => { + unit_hash::check(cx, expr, recv, arg); + }, ("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv), ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("iter" | "iter_mut" | "into_iter", []) => { + iter_on_single_or_empty_collections::check(cx, expr, name, recv); + }, ("join", [join_arg]) => { if let Some(("collect", _, span)) = method_call(recv) { unnecessary_join::check(cx, expr, recv, join_arg, span); @@ -2720,7 +3489,15 @@ impl Methods { } } }, + ("lock", []) => { + mut_mutex_lock::check(cx, expr, recv, span); + }, (name @ ("map" | "map_err"), [m_arg]) => { + if name == "map" { + map_clone::check(cx, expr, recv, m_arg, self.msrv); + } else { + map_err_ignore::check(cx, expr, m_arg); + } if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) { match (name, args) { ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv), @@ -2736,7 +3513,10 @@ impl Methods { } map_identity::check(cx, expr, recv, m_arg, name, span); }, - ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), + ("map_or", [def, map]) => { + option_map_or_none::check(cx, expr, recv, def, map); + manual_ok_or::check(cx, expr, recv, def, map); + }, ("next", []) => { if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { match (name2, args2) { @@ -2758,11 +3538,46 @@ impl Methods { _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("open", [_]) => { + open_options::check(cx, expr, recv); + }, ("or_else", [arg]) => { if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) { unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); } }, + ("push", [arg]) => { + path_buf_push_overwrite::check(cx, expr, arg); + }, + ("read_to_end", [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG); + }, + ("read_to_string", [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG); + }, + ("repeat", [arg]) => { + repeat_once::check(cx, expr, recv, arg); + }, + (name @ ("replace" | "replacen"), [arg1, arg2] | [arg1, arg2, _]) => { + no_effect_replace::check(cx, expr, arg1, arg2); + + // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint + if name == "replace" && let Some(("replace", ..)) = method_call(recv) { + collapsible_str_replace::check(cx, expr, arg1, arg2); + } + }, + ("resize", [count_arg, default_arg]) => { + vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span); + }, + ("sort", []) => { + stable_sort_primitive::check(cx, expr, recv); + }, + ("sort_by", [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, false); + }, + ("sort_unstable_by", [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, true); + }, ("splitn" | "rsplitn", [count_arg, pat_arg]) => { if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { suspicious_splitn::check(cx, name, expr, recv, count); @@ -2789,7 +3604,12 @@ impl Methods { } unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); }, - ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { + ("to_owned", []) => { + if !suspicious_to_owned::check(cx, expr, recv) { + implicit_clone::check(cx, name, expr, recv); + } + }, + ("to_os_string" | "to_path_buf" | "to_vec", []) => { implicit_clone::check(cx, name, expr, recv); }, ("unwrap", []) => { @@ -2805,8 +3625,9 @@ impl Methods { }, _ => {}, } - unwrap_used::check(cx, expr, recv, self.allow_unwrap_in_tests); + 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()..]); @@ -2827,8 +3648,12 @@ impl Methods { unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); }, }, - ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => { - no_effect_replace::check(cx, expr, arg1, arg2); + ("zip", [arg]) => { + if let ExprKind::MethodCall(name, [iter_recv], _) = recv.kind + && name.ident.name == sym::iter + { + range_zip_with_len::check(cx, expr, iter_recv, arg); + } }, _ => {}, } diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs new file mode 100644 index 00000000000..bd8458a222e --- /dev/null +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -0,0 +1,30 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::{sym, Span}; + +use super::MUT_MUTEX_LOCK; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { + if_chain! { + if let ty::Ref(_, _, Mutability::Mut) = cx.typeck_results().expr_ty(recv).kind(); + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Mutex); + then { + span_lint_and_sugg( + cx, + MUT_MUTEX_LOCK, + name_span, + "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", + "change this to", + "get_mut".to_owned(), + Applicability::MaybeIncorrect, + ); + } + } +} diff --git a/clippy_lints/src/open_options.rs b/clippy_lints/src/methods/open_options.rs similarity index 80% rename from clippy_lints/src/open_options.rs rename to clippy_lints/src/methods/open_options.rs index 5a0b5042018..c3112823e34 100644 --- a/clippy_lints/src/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -3,43 +3,19 @@ use clippy_utils::paths; use clippy_utils::ty::match_type; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_lint::LateContext; use rustc_span::source_map::{Span, Spanned}; -declare_clippy_lint! { - /// ### What it does - /// Checks for duplicate open options as well as combinations - /// that make no sense. - /// - /// ### Why is this bad? - /// In the best case, the code will be harder to read than - /// necessary. I don't know the worst case. - /// - /// ### Example - /// ```rust - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().read(true).truncate(true); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NONSENSICAL_OPEN_OPTIONS, - correctness, - "nonsensical combination of options for opening a file" -} +use super::NONSENSICAL_OPEN_OPTIONS; -declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]); - -impl<'tcx> LateLintPass<'tcx> for OpenOptions { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::MethodCall(path, [self_arg, ..], _) = &e.kind { - let obj_ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) { - let mut options = Vec::new(); - get_open_options(cx, self_arg, &mut options); - check_open_options(cx, &options, e.span); - } - } +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && match_type(cx, cx.tcx.type_of(impl_id), &paths::OPEN_OPTIONS) + { + let mut options = Vec::new(); + get_open_options(cx, recv, &mut options); + check_open_options(cx, &options, e.span); } } diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 6c641af59f9..3c4002a3aef 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -78,7 +78,7 @@ pub(super) fn check<'tcx>( map_span, String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }), ), - (expr.span.with_lo(unwrap_recv.span.hi()), String::from("")), + (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), ]; if !unwrap_snippet_none { diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs new file mode 100644 index 00000000000..0cc28c0dcb3 --- /dev/null +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use std::path::{Component, Path}; + +use super::PATH_BUF_PUSH_OVERWRITE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::PathBuf); + if let ExprKind::Lit(ref lit) = arg.kind; + if let LitKind::Str(ref path_lit, _) = lit.node; + if let pushed_path = Path::new(path_lit.as_str()); + if let Some(pushed_path_lit) = pushed_path.to_str(); + if pushed_path.has_root(); + if let Some(root) = pushed_path.components().next(); + if root == Component::RootDir; + then { + span_lint_and_sugg( + cx, + PATH_BUF_PUSH_OVERWRITE, + lit.span, + "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", + "try", + format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints/src/methods/range_zip_with_len.rs new file mode 100644 index 00000000000..00a2a0d14d1 --- /dev/null +++ b/clippy_lints/src/methods/range_zip_with_len.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::source::snippet; +use clippy_utils::{higher, SpanlessEq}; +use clippy_utils::{is_integer_const, is_trait_method}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::RANGE_ZIP_WITH_LEN; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { + if_chain! { + if is_trait_method(cx, expr, sym::Iterator); + // range expression in `.zip()` call: `0..x.len()` + if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg); + if is_integer_const(cx, start, 0); + // `.len()` call + if let ExprKind::MethodCall(len_path, [len_recv], _) = end.kind; + if len_path.ident.name == sym::len; + // `.iter()` and `.len()` called on same `Path` + if let ExprKind::Path(QPath::Resolved(_, iter_path)) = recv.kind; + if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind; + if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments); + then { + span_lint(cx, + RANGE_ZIP_WITH_LEN, + expr.span, + &format!("it is more idiomatic to use `{}.iter().enumerate()`", + snippet(cx, recv.span, "_")) + ); + } + } +} diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints/src/methods/repeat_once.rs new file mode 100644 index 00000000000..0a14f9216ab --- /dev/null +++ b/clippy_lints/src/methods/repeat_once.rs @@ -0,0 +1,52 @@ +use clippy_utils::consts::{constant_context, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::REPEAT_ONCE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + repeat_arg: &'tcx Expr<'_>, +) { + if constant_context(cx, cx.typeck_results()).expr(repeat_arg) == Some(Constant::Int(1)) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if ty.is_str() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on str", + "consider using `.to_string()` instead", + format!("{}.to_string()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if ty.builtin_index().is_some() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on slice", + "consider using `.to_vec()` instead", + format!("{}.to_vec()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if is_type_diagnostic_item(cx, ty, sym::String) { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on a string literal", + "consider using `.clone()` instead", + format!("{}.clone()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints/src/methods/stable_sort_primitive.rs new file mode 100644 index 00000000000..91951c65bb3 --- /dev/null +++ b/clippy_lints/src/methods/stable_sort_primitive.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_slice_of_primitives; +use clippy_utils::source::snippet_with_context; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +use super::STABLE_SORT_PRIMITIVE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && cx.tcx.type_of(impl_id).is_slice() + && let Some(slice_type) = is_slice_of_primitives(cx, recv) + { + span_lint_and_then( + cx, + STABLE_SORT_PRIMITIVE, + e.span, + &format!("used `sort` on primitive type `{}`", slice_type), + |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snip = snippet_with_context(cx, recv.span, e.span.ctxt(), "..", &mut app).0; + diag.span_suggestion(e.span, "try", format!("{}.sort_unstable()", recv_snip), app); + diag.note( + "an unstable sort typically performs faster without any observable difference for this data type", + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints/src/methods/suspicious_to_owned.rs new file mode 100644 index 00000000000..6b306fbf008 --- /dev/null +++ b/clippy_lints/src/methods/suspicious_to_owned.rs @@ -0,0 +1,36 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_diag_trait_item; +use clippy_utils::source::snippet_with_context; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::sym; + +use super::SUSPICIOUS_TO_OWNED; + +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { + if_chain! { + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if is_diag_trait_item(cx, method_def_id, sym::ToOwned); + let input_type = cx.typeck_results().expr_ty(expr); + if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind(); + if cx.tcx.is_diagnostic_item(sym::Cow, adt.did()); + then { + let mut app = Applicability::MaybeIncorrect; + let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; + span_lint_and_sugg( + cx, + SUSPICIOUS_TO_OWNED, + expr.span, + &format!("this `to_owned` call clones the {0} itself and does not cause the {0} contents to become owned", input_type), + "consider using, depending on intent", + format!("{0}.clone()` or `{0}.into_owned()", recv_snip), + app, + ); + return true; + } + } + false +} diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index 77d21f1d373..a1c6294737c 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty}; +use clippy_utils::{is_path_diagnostic_item, ty::is_uninit_value_valid_for_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_lint::LateContext; @@ -12,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if_chain! { if let hir::ExprKind::Call(callee, args) = recv.kind; if args.is_empty(); - if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); + if is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)); then { span_lint( diff --git a/clippy_lints/src/methods/unit_hash.rs b/clippy_lints/src/methods/unit_hash.rs new file mode 100644 index 00000000000..3c7955bc469 --- /dev/null +++ b/clippy_lints/src/methods/unit_hash.rs @@ -0,0 +1,29 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_trait_method; +use clippy_utils::source::snippet; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::UNIT_HASH; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { + if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { + span_lint_and_then( + cx, + UNIT_HASH, + expr.span, + "this call to `hash` on the unit type will do nothing", + |diag| { + diag.span_suggestion( + expr.span, + "remove the call to `hash` or consider using", + format!("0_u8.hash({})", snippet(cx, arg.span, ".."),), + Applicability::MaybeIncorrect, + ); + diag.note("the implementation of `Hash` for `()` is a no-op"); + }, + ); + } +} diff --git a/clippy_lints/src/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs similarity index 63% rename from clippy_lints/src/unnecessary_sort_by.rs rename to clippy_lints/src/methods/unnecessary_sort_by.rs index ea5aadbbca1..1966990bd77 100644 --- a/clippy_lints/src/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -1,51 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_trait_method; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::implements_trait; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::ty::{self, subst::GenericArgKind}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; -declare_clippy_lint! { - /// ### What it does - /// Detects uses of `Vec::sort_by` passing in a closure - /// which compares the two arguments, either directly or indirectly. - /// - /// ### Why is this bad? - /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if - /// possible) than to use `Vec::sort_by` and a more complicated - /// closure. - /// - /// ### Known problems - /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already - /// imported by a use statement, then it will need to be added manually. - /// - /// ### Example - /// ```rust - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec<A> = Vec::new(); - /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); - /// ``` - /// Use instead: - /// ```rust - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec<A> = Vec::new(); - /// vec.sort_by_key(|a| a.foo()); - /// ``` - #[clippy::version = "1.46.0"] - pub UNNECESSARY_SORT_BY, - complexity, - "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" -} - -declare_lint_pass!(UnnecessarySortBy => [UNNECESSARY_SORT_BY]); +use super::UNNECESSARY_SORT_BY; enum LintTrigger { Sort(SortDetection), @@ -54,7 +20,6 @@ enum LintTrigger { struct SortDetection { vec_name: String, - unstable: bool, } struct SortByKeyDetection { @@ -62,7 +27,6 @@ struct SortByKeyDetection { closure_arg: String, closure_body: String, reverse: bool, - unstable: bool, } /// Detect if the two expressions are mirrored (identical, except one @@ -150,20 +114,20 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident } } -fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> { +fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option<LintTrigger> { if_chain! { - if let ExprKind::MethodCall(name_ident, args, _) = &expr.kind; - if let name = name_ident.ident.name.to_ident_string(); - if name == "sort_by" || name == "sort_unstable_by"; - if let [vec, Expr { kind: ExprKind::Closure(Closure { body: closure_body_id, .. }), .. }] = args; - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(vec), sym::Vec); - if let closure_body = cx.tcx.hir().body(*closure_body_id); + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_slice(); + if let ExprKind::Closure(&Closure { body, .. }) = arg.kind; + if let closure_body = cx.tcx.hir().body(body); if let &[ Param { pat: Pat { kind: PatKind::Binding(_, _, left_ident, _), .. }, ..}, Param { pat: Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. } ] = &closure_body.params; - if let ExprKind::MethodCall(method_path, [ref left_expr, ref right_expr], _) = &closure_body.value.kind; + if let ExprKind::MethodCall(method_path, [left_expr, right_expr], _) = closure_body.value.kind; if method_path.ident.name == sym::cmp; + if is_trait_method(cx, &closure_body.value, sym::Ord); then { let (closure_body, closure_arg, reverse) = if mirrored_exprs( left_expr, @@ -177,19 +141,18 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> { } else { return None; }; - let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); - let unstable = name == "sort_unstable_by"; + let vec_name = Sugg::hir(cx, recv, "..").to_string(); if_chain! { - if let ExprKind::Path(QPath::Resolved(_, Path { - segments: [PathSegment { ident: left_name, .. }], .. - })) = &left_expr.kind; - if left_name == left_ident; - if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[]) - }); + if let ExprKind::Path(QPath::Resolved(_, Path { + segments: [PathSegment { ident: left_name, .. }], .. + })) = &left_expr.kind; + if left_name == left_ident; + if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[]) + }); then { - return Some(LintTrigger::Sort(SortDetection { vec_name, unstable })); + return Some(LintTrigger::Sort(SortDetection { vec_name })); } } @@ -199,7 +162,6 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> { closure_arg, closure_body, reverse, - unstable, })); } } @@ -213,46 +175,50 @@ fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(ty.kind(), ty::Ref(..)) || ty.walk().any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))) } -impl LateLintPass<'_> for UnnecessarySortBy { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - match detect_lint(cx, expr) { - Some(LintTrigger::SortByKey(trigger)) => span_lint_and_sugg( - cx, - UNNECESSARY_SORT_BY, - expr.span, - "use Vec::sort_by_key here instead", - "try", - format!( - "{}.sort{}_by_key(|{}| {})", - trigger.vec_name, - if trigger.unstable { "_unstable" } else { "" }, - trigger.closure_arg, - if trigger.reverse { - format!("std::cmp::Reverse({})", trigger.closure_body) - } else { - trigger.closure_body.to_string() - }, - ), +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, + is_unstable: bool, +) { + match detect_lint(cx, expr, recv, arg) { + Some(LintTrigger::SortByKey(trigger)) => span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort_by_key here instead", + "try", + format!( + "{}.sort{}_by_key(|{}| {})", + trigger.vec_name, + if is_unstable { "_unstable" } else { "" }, + trigger.closure_arg, if trigger.reverse { - Applicability::MaybeIncorrect + format!("std::cmp::Reverse({})", trigger.closure_body) } else { - Applicability::MachineApplicable + trigger.closure_body.to_string() }, ), - Some(LintTrigger::Sort(trigger)) => span_lint_and_sugg( - cx, - UNNECESSARY_SORT_BY, - expr.span, - "use Vec::sort here instead", - "try", - format!( - "{}.sort{}()", - trigger.vec_name, - if trigger.unstable { "_unstable" } else { "" }, - ), - Applicability::MachineApplicable, + if trigger.reverse { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }, + ), + Some(LintTrigger::Sort(trigger)) => span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort here instead", + "try", + format!( + "{}.sort{}()", + trigger.vec_name, + if is_unstable { "_unstable" } else { "" }, ), - None => {}, - } + Applicability::MachineApplicable, + ), + None => {}, } } diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index b3276f1394e..44bf8435294 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -3,11 +3,10 @@ use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{ - contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, + get_associated_type, get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, peel_mid_ty_refs, }; -use clippy_utils::{meets_msrv, msrvs}; - use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; +use clippy_utils::{meets_msrv, msrvs}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -279,7 +278,19 @@ fn check_other_call_arg<'tcx>( &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..], call_substs, ); - implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + // if `expr` is a `String` and generic target is [u8], skip + // (https://github.com/rust-lang/rust-clippy/issues/9317). + if let [subst] = composed_substs[..] + && let GenericArgKind::Type(arg_ty) = subst.unpack() + && arg_ty.is_slice() + && let inner_ty = arg_ty.builtin_index().unwrap() + && let ty::Uint(ty::UintTy::U8) = inner_ty.kind() + && let self_ty = cx.typeck_results().expr_ty(expr).peel_refs() + && is_type_diagnostic_item(cx, self_ty, sym::String) { + false + } else { + implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + } } else { false }; @@ -292,7 +303,7 @@ fn check_other_call_arg<'tcx>( // (https://github.com/rust-lang/rust-clippy/issues/8507). if (n_refs == 0 && !receiver_ty.is_ref()) || trait_predicate.def_id() != as_ref_trait_id - || !contains_ty(fn_sig.output(), input); + || !fn_sig.output().contains(input); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { span_lint_and_sugg( @@ -360,25 +371,15 @@ fn get_input_traits_and_projections<'tcx>( ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) { let mut trait_predicates = Vec::new(); let mut projection_predicates = Vec::new(); - for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() { - // `substs` should have 1 + n elements. The first is the type on the left hand side of an - // `as`. The remaining n are trait parameters. - let is_input_substs = |substs: SubstsRef<'tcx>| { - if_chain! { - if let Some(arg) = substs.iter().next(); - if let GenericArgKind::Type(arg_ty) = arg.unpack(); - if arg_ty == input; - then { true } else { false } - } - }; + for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() { match predicate.kind().skip_binder() { PredicateKind::Trait(trait_predicate) => { - if is_input_substs(trait_predicate.trait_ref.substs) { + if trait_predicate.trait_ref.self_ty() == input { trait_predicates.push(trait_predicate); } }, PredicateKind::Projection(projection_predicate) => { - if is_input_substs(projection_predicate.projection_ty.substs) { + if projection_predicate.projection_ty.self_ty() == input { projection_predicates.push(projection_predicate); } }, diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs index ce1a52e5480..ee17f2d7889 100644 --- a/clippy_lints/src/methods/unwrap_used.rs +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -7,18 +7,26 @@ use rustc_span::sym; use super::{EXPECT_USED, UNWRAP_USED}; -/// lint use of `unwrap()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) { +/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`. +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + is_err: bool, + allow_unwrap_in_tests: bool, +) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { Some((UNWRAP_USED, "an Option", "None", "")) } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((UNWRAP_USED, "a Result", "Err", "an ")) + Some((UNWRAP_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an ")) } else { None }; + let method_suffix = if is_err { "_err" } else { "" }; + if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { return; } @@ -27,7 +35,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { format!( "if you don't want to handle the `{none_value}` case gracefully, consider \ - using `expect()` to provide a better panic message" + using `expect{method_suffix}()` to provide a better panic message" ) } else { format!("if this value is {none_prefix}`{none_value}`, it will panic") @@ -37,7 +45,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr cx, lint, expr.span, - &format!("used `unwrap()` on `{kind}` value"), + &format!("used `unwrap{method_suffix}()` on `{kind}` value"), None, &help, ); diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints/src/methods/vec_resize_to_zero.rs new file mode 100644 index 00000000000..02d8364cb29 --- /dev/null +++ b/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -0,0 +1,45 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; +use rustc_span::{sym, Span}; + +use super::VEC_RESIZE_TO_ZERO; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + count_arg: &'tcx Expr<'_>, + default_arg: &'tcx Expr<'_>, + name_span: Span, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Vec); + if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = count_arg.kind; + if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = default_arg.kind; + then { + let method_call_span = expr.span.with_lo(name_span.lo()); + span_lint_and_then( + cx, + VEC_RESIZE_TO_ZERO, + expr.span, + "emptying a vector with `resize`", + |db| { + db.help("the arguments may be inverted..."); + db.span_suggestion( + method_call_span, + "...or you can empty the vector with", + "clear()".to_string(), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/methods/verbose_file_reads.rs b/clippy_lints/src/methods/verbose_file_reads.rs new file mode 100644 index 00000000000..2fe5ae9a9ad --- /dev/null +++ b/clippy_lints/src/methods/verbose_file_reads.rs @@ -0,0 +1,28 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_trait_method; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::VERBOSE_FILE_READS; + +pub(super) const READ_TO_END_MSG: (&str, &str) = ("use of `File::read_to_end`", "consider using `fs::read` instead"); +pub(super) const READ_TO_STRING_MSG: (&str, &str) = ( + "use of `File::read_to_string`", + "consider using `fs::read_to_string` instead", +); + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + (msg, help): (&str, &str), +) { + if is_trait_method(cx, expr, sym::IoRead) + && matches!(recv.kind, ExprKind::Path(QPath::Resolved(None, _))) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(recv).peel_refs(), sym::File) + { + span_lint_and_help(cx, VERBOSE_FILE_READS, expr.span, msg, None, help); + } +} diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index df044538fe1..7c4ae746e90 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -46,7 +46,7 @@ fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { "these patterns are unneeded as the `..` pattern can match those elements" }, if only_one { "remove it" } else { "remove them" }, - "".to_string(), + String::new(), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index f763e0d24c9..020efeaebf0 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -40,7 +40,7 @@ declare_clippy_lint! { /// } /// impl<A, B> Foo<A, B> {} /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub MISMATCHING_TYPE_PARAM_ORDER, pedantic, "type parameter positioned inconsistently between type def and impl block" diff --git a/clippy_lints/src/multi_assignments.rs b/clippy_lints/src/multi_assignments.rs new file mode 100644 index 00000000000..81eb1a085ae --- /dev/null +++ b/clippy_lints/src/multi_assignments.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for nested assignments. + /// + /// ### Why is this bad? + /// While this is in most cases already a type mismatch, + /// the result of an assignment being `()` can throw off people coming from languages like python or C, + /// where such assignments return a copy of the assigned value. + /// + /// ### Example + /// ```rust + ///# let (a, b); + /// a = b = 42; + /// ``` + /// Use instead: + /// ```rust + ///# let (a, b); + /// b = 42; + /// a = b; + /// ``` + #[clippy::version = "1.65.0"] + pub MULTI_ASSIGNMENTS, + suspicious, + "instead of using `a = b = c;` use `a = c; b = c;`" +} + +declare_lint_pass!(MultiAssignments => [MULTI_ASSIGNMENTS]); + +fn strip_paren_blocks(expr: &Expr) -> &Expr { + match &expr.kind { + ExprKind::Paren(e) => strip_paren_blocks(e), + ExprKind::Block(b, _) => { + if let [ + Stmt { + kind: StmtKind::Expr(e), + .. + }, + ] = &b.stmts[..] + { + strip_paren_blocks(e) + } else { + expr + } + }, + _ => expr, + } +} + +impl EarlyLintPass for MultiAssignments { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Assign(target, source, _) = &expr.kind { + if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(target).kind { + span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); + }; + if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(source).kind { + span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); + } + }; + } +} diff --git a/clippy_lints/src/mut_mutex_lock.rs b/clippy_lints/src/mut_mutex_lock.rs deleted file mode 100644 index b7f981faa2d..00000000000 --- a/clippy_lints/src/mut_mutex_lock.rs +++ /dev/null @@ -1,70 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `&mut Mutex::lock` calls - /// - /// ### Why is this bad? - /// `Mutex::lock` is less efficient than - /// calling `Mutex::get_mut`. In addition you also have a statically - /// guarantee that the mutex isn't locked, instead of just a runtime - /// guarantee. - /// - /// ### Example - /// ```rust - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let mut value = value_mutex.lock().unwrap(); - /// *value += 1; - /// ``` - /// Use instead: - /// ```rust - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let value = value_mutex.get_mut().unwrap(); - /// *value += 1; - /// ``` - #[clippy::version = "1.49.0"] - pub MUT_MUTEX_LOCK, - style, - "`&mut Mutex::lock` does unnecessary locking" -} - -declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]); - -impl<'tcx> LateLintPass<'tcx> for MutMutexLock { - fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { - if_chain! { - if let ExprKind::MethodCall(path, [self_arg, ..], _) = &ex.kind; - if path.ident.name == sym!(lock); - let ty = cx.typeck_results().expr_ty(self_arg); - if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); - if is_type_diagnostic_item(cx, *inner_ty, sym::Mutex); - then { - span_lint_and_sugg( - cx, - MUT_MUTEX_LOCK, - path.ident.span, - "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", - "change this to", - "get_mut".to_owned(), - Applicability::MaybeIncorrect, - ); - } - } - } -} diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 413a740be25..774a3540d1e 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -1,25 +1,16 @@ -use std::collections::VecDeque; - -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_lint_allowed; -use itertools::{izip, Itertools}; -use rustc_ast::{walk_list, Label, Mutability}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{get_expr_use_or_unification_node, get_parent_node, path_def_id, path_to_local, path_to_local_id}; +use core::cell::Cell; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; -use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor}; -use rustc_hir::{ - Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, - PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp, -}; +use rustc_hir::hir_id::HirIdMap; +use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_middle::ty::{Ty, TyCtxt, TypeckResults}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::kw; -use rustc_span::symbol::Ident; +use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::{kw, Ident}; use rustc_span::Span; declare_clippy_lint! { @@ -89,572 +80,323 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.61.0"] pub ONLY_USED_IN_RECURSION, - nursery, + complexity, "arguments that is only used in recursion can be removed" } -declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); +impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); + +#[derive(Clone, Copy)] +enum FnKind { + Fn, + TraitFn, + // This is a hack. Ideally we would store a `SubstsRef<'tcx>` type here, but a lint pass must be `'static`. + // Substitutions are, however, interned. This allows us to store the pointer as a `usize` when comparing for + // equality. + ImplTraitFn(usize), +} + +struct Param { + /// The function this is a parameter for. + fn_id: DefId, + fn_kind: FnKind, + /// The index of this parameter. + idx: usize, + ident: Ident, + /// Whether this parameter should be linted. Set by `Params::flag_for_linting`. + apply_lint: Cell<bool>, + /// All the uses of this parameter. + uses: Vec<Usage>, +} +impl Param { + fn new(fn_id: DefId, fn_kind: FnKind, idx: usize, ident: Ident) -> Self { + Self { + fn_id, + fn_kind, + idx, + ident, + apply_lint: Cell::new(true), + uses: Vec::new(), + } + } +} + +#[derive(Debug)] +struct Usage { + span: Span, + idx: usize, +} +impl Usage { + fn new(span: Span, idx: usize) -> Self { + Self { span, idx } + } +} + +/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the +/// `DefId` of the function paired with the parameter's index. +#[derive(Default)] +struct Params { + params: Vec<Param>, + by_id: HirIdMap<usize>, + by_fn: FxHashMap<(DefId, usize), usize>, +} +impl Params { + fn insert(&mut self, param: Param, id: HirId) { + let idx = self.params.len(); + self.by_id.insert(id, idx); + self.by_fn.insert((param.fn_id, param.idx), idx); + self.params.push(param); + } + + fn remove_by_id(&mut self, id: HirId) { + if let Some(param) = self.get_by_id_mut(id) { + param.uses = Vec::new(); + let key = (param.fn_id, param.idx); + self.by_fn.remove(&key); + self.by_id.remove(&id); + } + } + + fn get_by_id_mut(&mut self, id: HirId) -> Option<&mut Param> { + self.params.get_mut(*self.by_id.get(&id)?) + } + + fn get_by_fn(&self, id: DefId, idx: usize) -> Option<&Param> { + self.params.get(*self.by_fn.get(&(id, idx))?) + } + + fn clear(&mut self) { + self.params.clear(); + self.by_id.clear(); + self.by_fn.clear(); + } + + /// Sets the `apply_lint` flag on each parameter. + fn flag_for_linting(&mut self) { + // Stores the list of parameters currently being resolved. Needed to avoid cycles. + let mut eval_stack = Vec::new(); + for param in &self.params { + self.try_disable_lint_for_param(param, &mut eval_stack); + } + } + + // Use by calling `flag_for_linting`. + fn try_disable_lint_for_param(&self, param: &Param, eval_stack: &mut Vec<usize>) -> bool { + if !param.apply_lint.get() { + true + } else if param.uses.is_empty() { + // Don't lint on unused parameters. + param.apply_lint.set(false); + true + } else if eval_stack.contains(¶m.idx) { + // Already on the evaluation stack. Returning false will continue to evaluate other dependencies. + false + } else { + eval_stack.push(param.idx); + // Check all cases when used at a different parameter index. + // Needed to catch cases like: `fn f(x: u32, y: u32) { f(y, x) }` + for usage in param.uses.iter().filter(|u| u.idx != param.idx) { + if self + .get_by_fn(param.fn_id, usage.idx) + // If the parameter can't be found, then it's used for more than just recursion. + .map_or(true, |p| self.try_disable_lint_for_param(p, eval_stack)) + { + param.apply_lint.set(false); + eval_stack.pop(); + return true; + } + } + eval_stack.pop(); + false + } + } +} + +#[derive(Default)] +pub struct OnlyUsedInRecursion { + /// Track the top-level body entered. Needed to delay reporting when entering nested bodies. + entered_body: Option<HirId>, + params: Params, +} impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - decl: &'tcx rustc_hir::FnDecl<'tcx>, - body: &'tcx Body<'tcx>, - _: Span, - id: HirId, - ) { - if is_lint_allowed(cx, ONLY_USED_IN_RECURSION, id) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) { + if body.value.span.from_expansion() { return; } - if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind { - let def_id = id.owner.to_def_id(); - let data = cx.tcx.def_path(def_id).data; - - if data.len() > 1 { - match data.get(data.len() - 2) { - Some(DisambiguatedDefPathData { - data: DefPathData::Impl, - disambiguator, - }) if *disambiguator != 0 => return, - _ => {}, - } - } - - let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None); - - let ty_res = cx.typeck_results(); - let param_span = body - .params - .iter() - .flat_map(|param| { - let mut v = Vec::new(); - param.pat.each_binding(|_, hir_id, span, ident| { - v.push((hir_id, span, ident)); - }); - v - }) - .skip(if has_self { 1 } else { 0 }) - .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_')) - .collect_vec(); - - let params = body.params.iter().map(|param| param.pat).collect(); - - let mut visitor = SideEffectVisit { - graph: FxHashMap::default(), - has_side_effect: FxHashSet::default(), - ret_vars: Vec::new(), - contains_side_effect: false, - break_vars: FxHashMap::default(), - params, - fn_ident: ident, - fn_def_id: def_id, - is_method: matches!(kind, FnKind::Method(..)), - has_self, - ty_res, - tcx: cx.tcx, - visited_exprs: FxHashSet::default(), - }; - - visitor.visit_expr(&body.value); - let vars = std::mem::take(&mut visitor.ret_vars); - // this would set the return variables to side effect - visitor.add_side_effect(vars); - - let mut queue = visitor.has_side_effect.iter().copied().collect::<VecDeque<_>>(); - - // a simple BFS to check all the variables that have side effect - while let Some(id) = queue.pop_front() { - if let Some(next) = visitor.graph.get(&id) { - for i in next { - if !visitor.has_side_effect.contains(i) { - visitor.has_side_effect.insert(*i); - queue.push_back(*i); - } - } - } - } - - for (id, span, ident) in param_span { - // if the variable is not used in recursion, it would be marked as unused - if !visitor.has_side_effect.contains(&id) { - let mut queue = VecDeque::new(); - let mut visited = FxHashSet::default(); - - queue.push_back(id); - - // a simple BFS to check the graph can reach to itself - // if it can't, it means the variable is never used in recursion - while let Some(id) = queue.pop_front() { - if let Some(next) = visitor.graph.get(&id) { - for i in next { - if !visited.contains(i) { - visited.insert(id); - queue.push_back(*i); - } - } - } - } - - if visited.contains(&id) { - span_lint_and_sugg( - cx, - ONLY_USED_IN_RECURSION, - span, - "parameter is only used in recursion", - "if this is intentional, prefix with an underscore", - format!("_{}", ident.name.as_str()), - Applicability::MaybeIncorrect, - ); - } - } - } - } - } -} - -pub fn is_primitive(ty: Ty<'_>) -> bool { - let ty = ty.peel_refs(); - ty.is_primitive() || ty.is_str() -} - -pub fn is_array(ty: Ty<'_>) -> bool { - let ty = ty.peel_refs(); - ty.is_array() || ty.is_array_slice() -} - -/// This builds the graph of side effect. -/// The edge `a -> b` means if `a` has side effect, `b` will have side effect. -/// -/// There are some example in following code: -/// ```rust, ignore -/// let b = 1; -/// let a = b; // a -> b -/// let (c, d) = (a, b); // c -> b, d -> b -/// -/// let e = if a == 0 { // e -> a -/// c // e -> c -/// } else { -/// d // e -> d -/// }; -/// ``` -pub struct SideEffectVisit<'tcx> { - graph: FxHashMap<HirId, FxHashSet<HirId>>, - has_side_effect: FxHashSet<HirId>, - // bool for if the variable was dereferenced from mutable reference - ret_vars: Vec<(HirId, bool)>, - contains_side_effect: bool, - // break label - break_vars: FxHashMap<Ident, Vec<(HirId, bool)>>, - params: Vec<&'tcx Pat<'tcx>>, - fn_ident: Ident, - fn_def_id: DefId, - is_method: bool, - has_self: bool, - ty_res: &'tcx TypeckResults<'tcx>, - tcx: TyCtxt<'tcx>, - visited_exprs: FxHashSet<HirId>, -} - -impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> { - fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { - match s.kind { - StmtKind::Local(Local { - pat, init: Some(init), .. - }) => { - self.visit_pat_expr(pat, init, false); - }, - StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => { - walk_stmt(self, s); - }, - StmtKind::Local(_) => {}, - } - self.ret_vars.clear(); - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if !self.visited_exprs.insert(ex.hir_id) { - return; - } - match ex.kind { - ExprKind::Array(exprs) | ExprKind::Tup(exprs) => { - self.ret_vars = exprs - .iter() - .flat_map(|expr| { - self.visit_expr(expr); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - }, - ExprKind::Call(callee, args) => self.visit_fn(callee, args), - ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args), - ExprKind::Binary(_, lhs, rhs) => { - self.visit_bin_op(lhs, rhs); - }, - ExprKind::Unary(op, expr) => self.visit_un_op(op, expr), - ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false), - ExprKind::If(bind, then_expr, else_expr) => { - self.visit_if(bind, then_expr, else_expr); - }, - ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms), - // since analysing the closure is not easy, just set all variables in it to side-effect - ExprKind::Closure(&Closure { body, .. }) => { - let body = self.tcx.hir().body(body); - self.visit_body(body); - let vars = std::mem::take(&mut self.ret_vars); - self.add_side_effect(vars); - }, - ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => { - self.visit_block_label(block, label); - }, - ExprKind::Assign(bind, expr, _) => { - self.visit_assign(bind, expr); - }, - ExprKind::AssignOp(_, bind, expr) => { - self.visit_assign(bind, expr); - self.visit_bin_op(bind, expr); - }, - ExprKind::Field(expr, _) => { - self.visit_expr(expr); - if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { - self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + // `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions. + // It can't be renamed, and it can't be removed without removing it from multiple functions. + let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) { + Some(Node::Item(i)) => (i.def_id.to_def_id(), FnKind::Fn, 0), + Some(Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(ref sig, _), + def_id, + .. + })) => ( + def_id.to_def_id(), + FnKind::TraitFn, + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ), + Some(Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(ref sig, _), + def_id, + .. + })) => { + #[allow(trivial_casts)] + if let Some(Node::Item(item)) = get_parent_node(cx.tcx, cx.tcx.hir().local_def_id_to_hir_id(def_id)) + && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.def_id) + && let Some(trait_item_id) = cx.tcx.associated_item(def_id).trait_item_def_id + { + ( + trait_item_id, + FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const _ as usize), + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ) + } else { + (def_id.to_def_id(), FnKind::Fn, 0) } }, - ExprKind::Index(expr, index) => { - self.visit_expr(expr); - let mut vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(index); - self.ret_vars.append(&mut vars); - - if !is_array(self.ty_res.expr_ty(expr)) { - self.add_side_effect(self.ret_vars.clone()); - } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { - self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); - } - }, - ExprKind::Break(dest, Some(expr)) => { - self.visit_expr(expr); - if let Some(label) = dest.label { - self.break_vars - .entry(label.ident) - .or_insert(Vec::new()) - .append(&mut self.ret_vars); - } - self.contains_side_effect = true; - }, - ExprKind::Ret(Some(expr)) => { - self.visit_expr(expr); - let vars = std::mem::take(&mut self.ret_vars); - self.add_side_effect(vars); - self.contains_side_effect = true; - }, - ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => { - self.contains_side_effect = true; - }, - ExprKind::Struct(_, exprs, expr) => { - let mut ret_vars = exprs - .iter() - .flat_map(|field| { - self.visit_expr(field.expr); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - - walk_list!(self, visit_expr, expr); - self.ret_vars.append(&mut ret_vars); - }, - _ => walk_expr(self, ex), - } - } - - fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) { - if let Res::Local(id) = path.res { - self.ret_vars.push((id, false)); - } - } -} - -impl<'tcx> SideEffectVisit<'tcx> { - fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) { - // Just support array and tuple unwrapping for now. - // - // ex) `(a, b) = (c, d);` - // The graph would look like this: - // a -> c - // b -> d - // - // This would minimize the connection of the side-effect graph. - match (&lhs.kind, &rhs.kind) { - (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => { - // if not, it is a compile error - debug_assert!(lhs.len() == rhs.len()); - izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs)); - }, - // in other assigns, we have to connect all each other - // because they can be connected somehow - _ => { - self.visit_expr(lhs); - let lhs_vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(rhs); - let rhs_vars = std::mem::take(&mut self.ret_vars); - self.connect_assign(&lhs_vars, &rhs_vars, false); - }, - } - } - - fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option<Label>) { - self.visit_block(block); - let _ = label.and_then(|label| { - self.break_vars - .remove(&label.ident) - .map(|mut break_vars| self.ret_vars.append(&mut break_vars)) - }); - } - - fn visit_bin_op(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) { - self.visit_expr(lhs); - let mut ret_vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(rhs); - self.ret_vars.append(&mut ret_vars); - - // the binary operation between non primitive values are overloaded operators - // so they can have side-effects - if !is_primitive(self.ty_res.expr_ty(lhs)) || !is_primitive(self.ty_res.expr_ty(rhs)) { - self.ret_vars.iter().for_each(|id| { - self.has_side_effect.insert(id.0); - }); - self.contains_side_effect = true; - } - } - - fn visit_un_op(&mut self, op: UnOp, expr: &'tcx Expr<'tcx>) { - self.visit_expr(expr); - let ty = self.ty_res.expr_ty(expr); - // dereferencing a reference has no side-effect - if !is_primitive(ty) && !matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(..))) { - self.add_side_effect(self.ret_vars.clone()); - } - - if matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(_, _, Mutability::Mut))) { - self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); - } - } - - fn visit_pat_expr(&mut self, pat: &'tcx Pat<'tcx>, expr: &'tcx Expr<'tcx>, connect_self: bool) { - match (&pat.kind, &expr.kind) { - (PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) => { - self.ret_vars = izip!(*pats, *exprs) - .flat_map(|(pat, expr)| { - self.visit_pat_expr(pat, expr, connect_self); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - }, - (PatKind::Slice(front_exprs, _, back_exprs), ExprKind::Array(exprs)) => { - let mut vars = izip!(*front_exprs, *exprs) - .flat_map(|(pat, expr)| { - self.visit_pat_expr(pat, expr, connect_self); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - self.ret_vars = izip!(back_exprs.iter().rev(), exprs.iter().rev()) - .flat_map(|(pat, expr)| { - self.visit_pat_expr(pat, expr, connect_self); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - self.ret_vars.append(&mut vars); - }, - _ => { - let mut lhs_vars = Vec::new(); - pat.each_binding(|_, id, _, _| lhs_vars.push((id, false))); - self.visit_expr(expr); - let rhs_vars = std::mem::take(&mut self.ret_vars); - self.connect_assign(&lhs_vars, &rhs_vars, connect_self); - self.ret_vars = rhs_vars; - }, - } - } - - fn visit_fn(&mut self, callee: &'tcx Expr<'tcx>, args: &'tcx [Expr<'tcx>]) { - self.visit_expr(callee); - let mut ret_vars = std::mem::take(&mut self.ret_vars); - self.add_side_effect(ret_vars.clone()); - - let mut is_recursive = false; - - if_chain! { - if !self.has_self; - if let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind; - if let Res::Def(DefKind::Fn, def_id) = path.res; - if self.fn_def_id == def_id; - then { - is_recursive = true; - } - } - - if_chain! { - if !self.has_self && self.is_method; - if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = callee.kind; - if segment.ident == self.fn_ident; - if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind; - if let Res::SelfTy{ .. } = path.res; - then { - is_recursive = true; - } - } - - if is_recursive { - izip!(self.params.clone(), args).for_each(|(pat, expr)| { - self.visit_pat_expr(pat, expr, true); - self.ret_vars.clear(); - }); - } else { - // This would set arguments used in closure that does not have side-effect. - // Closure itself can be detected whether there is a side-effect, but the - // value of variable that is holding closure can change. - // So, we just check the variables. - self.ret_vars = args - .iter() - .flat_map(|expr| { - self.visit_expr(expr); - std::mem::take(&mut self.ret_vars) - }) - .collect_vec() - .into_iter() - .map(|id| { - self.has_side_effect.insert(id.0); - id - }) - .collect(); - self.contains_side_effect = true; - } - - self.ret_vars.append(&mut ret_vars); - } - - fn visit_method_call(&mut self, path: &'tcx PathSegment<'tcx>, args: &'tcx [Expr<'tcx>]) { - if_chain! { - if self.is_method; - if path.ident == self.fn_ident; - if let ExprKind::Path(QPath::Resolved(_, path)) = args.first().unwrap().kind; - if let Res::Local(..) = path.res; - let ident = path.segments.last().unwrap().ident; - if ident.name == kw::SelfLower; - then { - izip!(self.params.clone(), args.iter()) - .for_each(|(pat, expr)| { - self.visit_pat_expr(pat, expr, true); - self.ret_vars.clear(); - }); - } else { - self.ret_vars = args - .iter() - .flat_map(|expr| { - self.visit_expr(expr); - std::mem::take(&mut self.ret_vars) - }) - .collect_vec() - .into_iter() - .map(|a| { - self.has_side_effect.insert(a.0); - a - }) - .collect(); - self.contains_side_effect = true; - } - } - } - - fn visit_if(&mut self, bind: &'tcx Expr<'tcx>, then_expr: &'tcx Expr<'tcx>, else_expr: Option<&'tcx Expr<'tcx>>) { - let contains_side_effect = self.contains_side_effect; - self.contains_side_effect = false; - self.visit_expr(bind); - let mut vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(then_expr); - let mut then_vars = std::mem::take(&mut self.ret_vars); - walk_list!(self, visit_expr, else_expr); - if self.contains_side_effect { - self.add_side_effect(vars.clone()); - } - self.contains_side_effect |= contains_side_effect; - self.ret_vars.append(&mut vars); - self.ret_vars.append(&mut then_vars); - } - - fn visit_match(&mut self, expr: &'tcx Expr<'tcx>, arms: &'tcx [Arm<'tcx>]) { - self.visit_expr(expr); - let mut expr_vars = std::mem::take(&mut self.ret_vars); - self.ret_vars = arms + _ => return, + }; + body.params .iter() - .flat_map(|arm| { - let contains_side_effect = self.contains_side_effect; - self.contains_side_effect = false; - // this would visit `expr` multiple times - // but couldn't think of a better way - self.visit_pat_expr(arm.pat, expr, false); - let mut vars = std::mem::take(&mut self.ret_vars); - let _ = arm.guard.as_ref().map(|guard| { - self.visit_expr(match guard { - Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => expr, - }); - vars.append(&mut self.ret_vars); - }); - self.visit_expr(arm.body); - if self.contains_side_effect { - self.add_side_effect(vars.clone()); - self.add_side_effect(expr_vars.clone()); - } - self.contains_side_effect |= contains_side_effect; - vars.append(&mut self.ret_vars); - vars + .enumerate() + .skip(skip_params) + .filter_map(|(idx, p)| match p.pat.kind { + PatKind::Binding(_, id, ident, None) if !ident.as_str().starts_with('_') => { + Some((id, Param::new(fn_id, fn_kind, idx, ident))) + }, + _ => None, }) - .collect(); - self.ret_vars.append(&mut expr_vars); + .for_each(|(id, param)| self.params.insert(param, id)); + if self.entered_body.is_none() { + self.entered_body = Some(body.value.hir_id); + } } - fn connect_assign(&mut self, lhs: &[(HirId, bool)], rhs: &[(HirId, bool)], connect_self: bool) { - // if mutable dereference is on assignment it can have side-effect - // (this can lead to parameter mutable dereference and change the original value) - // too hard to detect whether this value is from parameter, so this would all - // check mutable dereference assignment to side effect - lhs.iter().filter(|(_, b)| *b).for_each(|(id, _)| { - self.has_side_effect.insert(*id); - self.contains_side_effect = true; - }); - - // there is no connection - if lhs.is_empty() || rhs.is_empty() { - return; - } - - // by connected rhs in cycle, the connections would decrease - // from `n * m` to `n + m` - // where `n` and `m` are length of `lhs` and `rhs`. - - // unwrap is possible since rhs is not empty - let rhs_first = rhs.first().unwrap(); - for (id, _) in lhs.iter() { - if connect_self || *id != rhs_first.0 { - self.graph - .entry(*id) - .or_insert_with(FxHashSet::default) - .insert(rhs_first.0); + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) { + if let Some(id) = path_to_local(e) + && let Some(param) = self.params.get_by_id_mut(id) + { + let typeck = cx.typeck_results(); + let span = e.span; + let mut e = e; + loop { + match get_expr_use_or_unification_node(cx.tcx, e) { + None | Some((Node::Stmt(_), _)) => return, + Some((Node::Expr(parent), child_id)) => match parent.kind { + // Recursive call. Track which index the parameter is used in. + ExprKind::Call(callee, args) + if path_def_id(cx, callee).map_or(false, |id| { + id == param.fn_id + && has_matching_substs(param.fn_kind, typeck.node_substs(callee.hir_id)) + }) => + { + if let Some(idx) = args.iter().position(|arg| arg.hir_id == child_id) { + param.uses.push(Usage::new(span, idx)); + } + return; + }, + ExprKind::MethodCall(_, args, _) + if typeck.type_dependent_def_id(parent.hir_id).map_or(false, |id| { + id == param.fn_id + && has_matching_substs(param.fn_kind, typeck.node_substs(parent.hir_id)) + }) => + { + if let Some(idx) = args.iter().position(|arg| arg.hir_id == child_id) { + param.uses.push(Usage::new(span, idx)); + } + return; + }, + // Assignment to a parameter is fine. + ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => { + return; + }, + // Parameter update e.g. `x = x + 1` + ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs) + if rhs.hir_id == child_id && path_to_local_id(lhs, id) => + { + return; + }, + // Side-effect free expressions. Walk to the parent expression. + ExprKind::Binary(_, lhs, rhs) + if typeck.expr_ty(lhs).is_primitive() && typeck.expr_ty(rhs).is_primitive() => + { + e = parent; + continue; + }, + ExprKind::Unary(_, arg) if typeck.expr_ty(arg).is_primitive() => { + e = parent; + continue; + }, + ExprKind::AddrOf(..) | ExprKind::Cast(..) => { + e = parent; + continue; + }, + // Only allow field accesses without auto-deref + ExprKind::Field(..) if typeck.adjustments().get(child_id).is_none() => { + e = parent; + continue + } + _ => (), + }, + _ => (), + } + self.params.remove_by_id(id); + return; } } - - let rhs = rhs.iter(); - izip!(rhs.clone().cycle().skip(1), rhs).for_each(|(from, to)| { - if connect_self || from.0 != to.0 { - self.graph.entry(from.0).or_insert_with(FxHashSet::default).insert(to.0); - } - }); } - fn add_side_effect(&mut self, v: Vec<(HirId, bool)>) { - for (id, _) in v { - self.has_side_effect.insert(id); - self.contains_side_effect = true; + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) { + if self.entered_body == Some(body.value.hir_id) { + self.entered_body = None; + self.params.flag_for_linting(); + for param in &self.params.params { + if param.apply_lint.get() { + span_lint_and_then( + cx, + ONLY_USED_IN_RECURSION, + param.ident.span, + "parameter is only used in recursion", + |diag| { + if param.ident.name != kw::SelfLower { + diag.span_suggestion( + param.ident.span, + "if this is intentional, prefix it with an underscore", + format!("_{}", param.ident.name), + Applicability::MaybeIncorrect, + ); + } + diag.span_note( + param.uses.iter().map(|x| x.span).collect::<Vec<_>>(), + "parameter used here", + ); + }, + ); + } + } + self.params.clear(); } } } + +fn has_matching_substs(kind: FnKind, substs: SubstsRef<'_>) -> bool { + match kind { + FnKind::Fn => true, + FnKind::TraitFn => substs.iter().enumerate().all(|(idx, subst)| match subst.unpack() { + GenericArgKind::Lifetime(_) => true, + GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx), + GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx), + }), + #[allow(trivial_casts)] + FnKind::ImplTraitFn(expected_substs) => substs as *const _ as usize == expected_substs, + } +} diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 44f153cffac..9602d0d1d2e 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -1,21 +1,22 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ can_move_expr_to_closure, eager_or_lazy, higher, in_constant, is_else_clause, is_lang_ctor, peel_blocks, peel_hir_expr_while, CaptureKind, }; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::LangItem::OptionSome; -use rustc_hir::{def::Res, BindingAnnotation, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp}; +use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::{ + def::Res, Arm, BindingAnnotation, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath, UnOp, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more + /// Lints usage of `if let Some(v) = ... { y } else { x }` and + /// `match .. { Some(v) => y, None/_ => x }` which are more /// idiomatically done with `Option::map_or` (if the else bit is a pure /// expression) or `Option::map_or_else` (if the else bit is an impure /// expression). @@ -39,6 +40,10 @@ declare_clippy_lint! { /// } else { /// 5 /// }; + /// let _ = match optional { + /// Some(val) => val + 1, + /// None => 5 + /// }; /// let _ = if let Some(foo) = optional { /// foo /// } else { @@ -53,11 +58,14 @@ declare_clippy_lint! { /// # let optional: Option<u32> = Some(0); /// # fn do_complicated_function() -> u32 { 5 }; /// let _ = optional.map_or(5, |foo| foo); + /// let _ = optional.map_or(5, |val| val + 1); /// let _ = optional.map_or_else(||{ /// let y = do_complicated_function(); /// y*y /// }, |foo| foo); /// ``` + // FIXME: Before moving this lint out of nursery, the lint name needs to be updated. It now also + // covers matches and `Result`. #[clippy::version = "1.47.0"] pub OPTION_IF_LET_ELSE, nursery, @@ -66,19 +74,21 @@ declare_clippy_lint! { declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]); -/// Returns true iff the given expression is the result of calling `Result::ok` -fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { - if let ExprKind::MethodCall(path, &[ref receiver], _) = &expr.kind { - path.ident.name.as_str() == "ok" - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::Result) - } else { - false - } -} - -/// A struct containing information about occurrences of the -/// `if let Some(..) = .. else` construct that this lint detects. -struct OptionIfLetElseOccurrence { +/// A struct containing information about occurrences of construct that this lint detects +/// +/// Such as: +/// +/// ```ignore +/// if let Some(..) = {..} else {..} +/// ``` +/// or +/// ```ignore +/// match x { +/// Some(..) => {..}, +/// None/_ => {..} +/// } +/// ``` +struct OptionOccurence { option: String, method_sugg: String, some_expr: String, @@ -99,43 +109,38 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo ) } -/// If this expression is the option if let/else construct we're detecting, then -/// this function returns an `OptionIfLetElseOccurrence` struct with details if -/// this construct is found, or None if this construct is not found. -fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurrence> { +fn try_get_option_occurence<'tcx>( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + expr: &Expr<'_>, + if_then: &'tcx Expr<'_>, + if_else: &'tcx Expr<'_>, +) -> Option<OptionOccurence> { + let cond_expr = match expr.kind { + ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr, + _ => expr, + }; + let inner_pat = try_get_inner_pat(cx, pat)?; if_chain! { - if !expr.span.from_expansion(); // Don't lint macros, because it behaves weirdly - if !in_constant(cx, expr.hir_id); - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) - = higher::IfLet::hir(cx, expr); - if !is_else_clause(cx.tcx, expr); - if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already - if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind; - if is_lang_ctor(cx, struct_qpath, OptionSome); - if let PatKind::Binding(bind_annotation, _, id, None) = &inner_pat.kind; + if let PatKind::Binding(bind_annotation, _, id, None) = inner_pat.kind; if let Some(some_captures) = can_move_expr_to_closure(cx, if_then); if let Some(none_captures) = can_move_expr_to_closure(cx, if_else); if some_captures .iter() .filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2))) .all(|(x, y)| x.is_imm_ref() && y.is_imm_ref()); - then { - let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; + let capture_mut = if bind_annotation == BindingAnnotation::Mutable { "mut " } else { "" }; let some_body = peel_blocks(if_then); let none_body = peel_blocks(if_else); let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" }; let capture_name = id.name.to_ident_string(); - let (as_ref, as_mut) = match &let_expr.kind { + let (as_ref, as_mut) = match &expr.kind { ExprKind::AddrOf(_, Mutability::Not, _) => (true, false), ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true), - _ => (bind_annotation == &BindingAnnotation::Ref, bind_annotation == &BindingAnnotation::RefMut), - }; - let cond_expr = match let_expr.kind { - // Pointer dereferencing happens automatically, so we can omit it in the suggestion - ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr, - _ => let_expr, + _ => (bind_annotation == BindingAnnotation::Ref, bind_annotation == BindingAnnotation::RefMut), }; + // Check if captures the closure will need conflict with borrows made in the scrutinee. // TODO: check all the references made in the scrutinee expression. This will require interacting // with the borrow checker. Currently only `<local>[.<field>]*` is checked for. @@ -154,30 +159,100 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> } } } - Some(OptionIfLetElseOccurrence { + + return Some(OptionOccurence { option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut), method_sugg: method_sugg.to_string(), some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir_with_macro_callsite(cx, some_body, "..")), none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")), - }) + }); + } + } + + None +} + +fn try_get_inner_pat<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<&'tcx Pat<'tcx>> { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { + if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk) { + return Some(inner_pat); + } + } + None +} + +/// If this expression is the option if let/else construct we're detecting, then +/// this function returns an `OptionOccurence` struct with details if +/// this construct is found, or None if this construct is not found. +fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> { + if let Some(higher::IfLet { + let_pat, + let_expr, + if_then, + if_else: Some(if_else), + }) = higher::IfLet::hir(cx, expr) + { + if !is_else_clause(cx.tcx, expr) { + return try_get_option_occurence(cx, let_pat, let_expr, if_then, if_else); + } + } + None +} + +fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> { + if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { + if let Some((let_pat, if_then, if_else)) = try_convert_match(cx, arms) { + return try_get_option_occurence(cx, let_pat, ex, if_then, if_else); + } + } + None +} + +fn try_convert_match<'tcx>( + cx: &LateContext<'tcx>, + arms: &[Arm<'tcx>], +) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if arms.len() == 2 { + return if is_none_or_err_arm(cx, &arms[1]) { + Some((arms[0].pat, arms[0].body, arms[1].body)) + } else if is_none_or_err_arm(cx, &arms[0]) { + Some((arms[1].pat, arms[1].body, arms[0].body)) } else { None - } + }; + } + None +} + +fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + match arm.pat.kind { + PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), + PatKind::TupleStruct(ref qpath, [first_pat], _) => { + is_lang_ctor(cx, qpath, ResultErr) && matches!(first_pat.kind, PatKind::Wild) + }, + PatKind::Wild => true, + _ => false, } } impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - if let Some(detection) = detect_option_if_let_else(cx, expr) { + // Don't lint macros and constants + if expr.span.from_expansion() || in_constant(cx, expr.hir_id) { + return; + } + + let detection = detect_option_if_let_else(cx, expr).or_else(|| detect_option_match(cx, expr)); + if let Some(det) = detection { span_lint_and_sugg( cx, OPTION_IF_LET_ELSE, expr.span, - format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(), + format!("use Option::{} instead of an if let/else", det.method_sugg).as_str(), "try", format!( "{}.{}({}, {})", - detection.option, detection.method_sugg, detection.none_expr, detection.some_expr, + det.option, det.method_sugg, det.none_expr, det.some_expr ), Applicability::MaybeIncorrect, ); diff --git a/clippy_lints/src/partialeq_to_none.rs b/clippy_lints/src/partialeq_to_none.rs index eee7642068d..000b0ba7a14 100644 --- a/clippy_lints/src/partialeq_to_none.rs +++ b/clippy_lints/src/partialeq_to_none.rs @@ -53,7 +53,8 @@ impl<'tcx> LateLintPass<'tcx> for PartialeqToNone { // If the expression is a literal `Option::None` let is_none_ctor = |expr: &Expr<'_>| { - matches!(&peel_hir_expr_refs(expr).0.kind, + !expr.span.from_expansion() + && matches!(&peel_hir_expr_refs(expr).0.kind, ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone)) }; diff --git a/clippy_lints/src/path_buf_push_overwrite.rs b/clippy_lints/src/path_buf_push_overwrite.rs deleted file mode 100644 index bc6a918f703..00000000000 --- a/clippy_lints/src/path_buf_push_overwrite.rs +++ /dev/null @@ -1,72 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::sym; -use std::path::{Component, Path}; - -declare_clippy_lint! { - /// ### What it does - ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) - /// calls on `PathBuf` that can cause overwrites. - /// - /// ### Why is this bad? - /// Calling `push` with a root path at the start can overwrite the - /// previous defined path. - /// - /// ### Example - /// ```rust - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// x.push("/bar"); - /// assert_eq!(x, PathBuf::from("/bar")); - /// ``` - /// Could be written: - /// - /// ```rust - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// x.push("bar"); - /// assert_eq!(x, PathBuf::from("/foo/bar")); - /// ``` - #[clippy::version = "1.36.0"] - pub PATH_BUF_PUSH_OVERWRITE, - nursery, - "calling `push` with file system root on `PathBuf` can overwrite it" -} - -declare_lint_pass!(PathBufPushOverwrite => [PATH_BUF_PUSH_OVERWRITE]); - -impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::MethodCall(path, [recv, get_index_arg], _) = expr.kind; - if path.ident.name == sym!(push); - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::PathBuf); - if let ExprKind::Lit(ref lit) = get_index_arg.kind; - if let LitKind::Str(ref path_lit, _) = lit.node; - if let pushed_path = Path::new(path_lit.as_str()); - if let Some(pushed_path_lit) = pushed_path.to_str(); - if pushed_path.has_root(); - if let Some(root) = pushed_path.components().next(); - if root == Component::RootDir; - then { - span_lint_and_sugg( - cx, - PATH_BUF_PUSH_OVERWRITE, - lit.span, - "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", - "try", - format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), - Applicability::MachineApplicable, - ); - } - } - } -} diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 964a057f00d..b432ccb1ee3 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -123,7 +123,7 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: 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], None) = let_pat.kind; - if let PatKind::Binding(annot, bind_id, ident, _) = field.kind; + if let PatKind::Binding(annot, bind_id, ident, None) = field.kind; let caller_ty = cx.typeck_results().expr_ty(let_expr); let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else); if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id)) diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index fbf842c339e..490f345d297 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -1,46 +1,20 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::higher; use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local}; -use clippy_utils::{higher, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, PathSegment, QPath}; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; -use rustc_span::sym; use std::cmp::Ordering; -declare_clippy_lint! { - /// ### What it does - /// Checks for zipping a collection with the range of - /// `0.._.len()`. - /// - /// ### Why is this bad? - /// The code is better expressed with `.enumerate()`. - /// - /// ### Example - /// ```rust - /// # let x = vec![1]; - /// let _ = x.iter().zip(0..x.len()); - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = vec![1]; - /// let _ = x.iter().enumerate(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub RANGE_ZIP_WITH_LEN, - complexity, - "zipping iterator with a range when `enumerate()` would do" -} - declare_clippy_lint! { /// ### What it does /// Checks for exclusive ranges where 1 is added to the @@ -198,7 +172,6 @@ impl Ranges { } impl_lint_pass!(Ranges => [ - RANGE_ZIP_WITH_LEN, RANGE_PLUS_ONE, RANGE_MINUS_ONE, REVERSED_EMPTY_RANGES, @@ -207,16 +180,10 @@ impl_lint_pass!(Ranges => [ impl<'tcx> LateLintPass<'tcx> for Ranges { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - match expr.kind { - ExprKind::MethodCall(path, args, _) => { - check_range_zip_with_len(cx, path, args, expr.span); - }, - ExprKind::Binary(ref op, l, r) => { - if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) { - check_possible_range_contains(cx, op.node, l, r, expr, expr.span); - } - }, - _ => {}, + if let ExprKind::Binary(ref op, l, r) = expr.kind { + if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) { + check_possible_range_contains(cx, op.node, l, r, expr, expr.span); + } } check_exclusive_range_plus_one(cx, expr); @@ -380,34 +347,6 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R None } -fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) { - if_chain! { - if path.ident.as_str() == "zip"; - if let [iter, zip_arg] = args; - // `.iter()` call - if let ExprKind::MethodCall(iter_path, [iter_caller, ..], _) = iter.kind; - if iter_path.ident.name == sym::iter; - // range expression in `.zip()` call: `0..x.len()` - if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg); - if is_integer_const(cx, start, 0); - // `.len()` call - if let ExprKind::MethodCall(len_path, [len_caller], _) = end.kind; - if len_path.ident.name == sym::len; - // `.iter()` and `.len()` called on same `Path` - if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_caller.kind; - if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_caller.kind; - if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments); - then { - span_lint(cx, - RANGE_ZIP_WITH_LEN, - span, - &format!("it is more idiomatic to use `{}.iter().enumerate()`", - snippet(cx, iter_caller.span, "_")) - ); - } - } -} - // exclusive range plus one: `x..(y+1)` fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { diff --git a/clippy_lints/src/rc_clone_in_vec_init.rs b/clippy_lints/src/rc_clone_in_vec_init.rs index 8db8c4e9b78..e82aa3a7b98 100644 --- a/clippy_lints/src/rc_clone_in_vec_init.rs +++ b/clippy_lints/src/rc_clone_in_vec_init.rs @@ -41,7 +41,7 @@ declare_clippy_lint! { /// let data = std::rc::Rc::new("some data".to_string()); /// let v = vec![data; 100]; /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub RC_CLONE_IN_VEC_INIT, suspicious, "initializing reference-counted pointer in `vec![elem; len]`" diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index db6c97f3739..8693ca9af83 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -11,6 +11,8 @@ use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::subst::GenericArg; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::iter; + declare_clippy_lint! { /// ### What it does /// Checks for redundant slicing expressions which use the full range, and @@ -134,7 +136,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.param_env, - cx.tcx.mk_projection(target_id, cx.tcx.mk_substs([GenericArg::from(indexed_ty)].into_iter())), + cx.tcx.mk_projection(target_id, cx.tcx.mk_substs(iter::once(GenericArg::from(indexed_ty)))), ) { if deref_ty == expr_ty { let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index f8801f769e8..2d751c27467 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -48,15 +48,15 @@ impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]); impl RedundantStaticLifetimes { // Recursively visit types - fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { + fn visit_type(ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { match ty.kind { // Be careful of nested structures (arrays and tuples) TyKind::Array(ref ty, _) | TyKind::Slice(ref ty) => { - self.visit_type(ty, cx, reason); + Self::visit_type(ty, cx, reason); }, TyKind::Tup(ref tup) => { for tup_ty in tup { - self.visit_type(tup_ty, cx, reason); + Self::visit_type(tup_ty, cx, reason); } }, // This is what we are looking for ! @@ -87,7 +87,7 @@ impl RedundantStaticLifetimes { _ => {}, } } - self.visit_type(&borrow_type.ty, cx, reason); + Self::visit_type(&borrow_type.ty, cx, reason); }, _ => {}, } @@ -102,13 +102,13 @@ impl EarlyLintPass for RedundantStaticLifetimes { if !item.span.from_expansion() { if let ItemKind::Const(_, ref var_type, _) = item.kind { - self.visit_type(var_type, cx, "constants have by default a `'static` lifetime"); + Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime"); // Don't check associated consts because `'static` cannot be elided on those (issue // #2438) } if let ItemKind::Static(ref var_type, _, _) = item.kind { - self.visit_type(var_type, cx, "statics have by default a `'static` lifetime"); + Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } } diff --git a/clippy_lints/src/repeat_once.rs b/clippy_lints/src/repeat_once.rs deleted file mode 100644 index 898c70ace66..00000000000 --- a/clippy_lints/src/repeat_once.rs +++ /dev/null @@ -1,89 +0,0 @@ -use clippy_utils::consts::{constant_context, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_errors::Applicability; -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 `.repeat(1)` and suggest the following method for each types. - /// - `.to_string()` for `str` - /// - `.clone()` for `String` - /// - `.to_vec()` for `slice` - /// - /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if - /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) - /// - /// ### Why is this bad? - /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning - /// the string is the intention behind this, `clone()` should be used. - /// - /// ### Example - /// ```rust - /// fn main() { - /// let x = String::from("hello world").repeat(1); - /// } - /// ``` - /// Use instead: - /// ```rust - /// fn main() { - /// let x = String::from("hello world").clone(); - /// } - /// ``` - #[clippy::version = "1.47.0"] - pub REPEAT_ONCE, - complexity, - "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " -} - -declare_lint_pass!(RepeatOnce => [REPEAT_ONCE]); - -impl<'tcx> LateLintPass<'tcx> for RepeatOnce { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::MethodCall(path, [receiver, count], _) = &expr.kind; - if path.ident.name == sym!(repeat); - if constant_context(cx, cx.typeck_results()).expr(count) == Some(Constant::Int(1)); - if !receiver.span.from_expansion(); - then { - let ty = cx.typeck_results().expr_ty(receiver).peel_refs(); - if ty.is_str() { - span_lint_and_sugg( - cx, - REPEAT_ONCE, - expr.span, - "calling `repeat(1)` on str", - "consider using `.to_string()` instead", - format!("{}.to_string()", snippet(cx, receiver.span, r#""...""#)), - Applicability::MachineApplicable, - ); - } else if ty.builtin_index().is_some() { - span_lint_and_sugg( - cx, - REPEAT_ONCE, - expr.span, - "calling `repeat(1)` on slice", - "consider using `.to_vec()` instead", - format!("{}.to_vec()", snippet(cx, receiver.span, r#""...""#)), - Applicability::MachineApplicable, - ); - } else if is_type_diagnostic_item(cx, ty, sym::String) { - span_lint_and_sugg( - cx, - REPEAT_ONCE, - expr.span, - "calling `repeat(1)` on a string literal", - "consider using `.clone()` instead", - format!("{}.clone()", snippet(cx, receiver.span, r#""...""#)), - Applicability::MachineApplicable, - ); - } - } - } - } -} diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 1d9a2abf706..1926661c596 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::{fn_def_id, path_to_local_id}; use if_chain::if_chain; -use rustc_ast::ast::Attribute; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind}; @@ -11,7 +10,6 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -152,10 +150,6 @@ impl<'tcx> LateLintPass<'tcx> for Return { } } -fn attr_is_cfg(attr: &Attribute) -> bool { - attr.meta_item_list().is_some() && attr.has_name(sym::cfg) -} - fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) { if let Some(expr) = block.expr { check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); @@ -178,9 +172,7 @@ fn check_final_expr<'tcx>( match expr.kind { // simple return is always "bad" ExprKind::Ret(ref inner) => { - // allow `#[cfg(a)] return a; #[cfg(b)] return b;` - let attrs = cx.tcx.hir().attrs(expr.hir_id); - if !attrs.iter().any(attr_is_cfg) { + if cx.tcx.hir().attrs(expr.hir_id).is_empty() { let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); if !borrows { emit_return_lint( diff --git a/clippy_lints/src/self_named_constructors.rs b/clippy_lints/src/self_named_constructors.rs index d07c26d7c89..9cea4d88067 100644 --- a/clippy_lints/src/self_named_constructors.rs +++ b/clippy_lints/src/self_named_constructors.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::return_ty; -use clippy_utils::ty::{contains_adt_constructor, contains_ty}; +use clippy_utils::ty::contains_adt_constructor; use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors { if !contains_adt_constructor(ret_ty, self_adt) { return; } - } else if !contains_ty(ret_ty, self_ty) { + } else if !ret_ty.contains(self_ty) { return; } diff --git a/clippy_lints/src/stable_sort_primitive.rs b/clippy_lints/src/stable_sort_primitive.rs deleted file mode 100644 index 6d54935f81a..00000000000 --- a/clippy_lints/src/stable_sort_primitive.rs +++ /dev/null @@ -1,144 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_slice_of_primitives, sugg::Sugg}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// When sorting primitive values (integers, bools, chars, as well - /// as arrays, slices, and tuples of such items), it is typically better to - /// use an unstable sort than a stable sort. - /// - /// ### Why is this bad? - /// Typically, using a stable sort consumes more memory and cpu cycles. - /// Because values which compare equal are identical, preserving their - /// relative order (the guarantee that a stable sort provides) means - /// nothing, while the extra costs still apply. - /// - /// ### Known problems - /// - /// As pointed out in - /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), - /// a stable sort can instead be significantly faster for certain scenarios - /// (eg. when a sorted vector is extended with new data and resorted). - /// - /// For more information and benchmarking results, please refer to the - /// issue linked above. - /// - /// ### Example - /// ```rust - /// let mut vec = vec![2, 1, 3]; - /// vec.sort(); - /// ``` - /// Use instead: - /// ```rust - /// let mut vec = vec![2, 1, 3]; - /// vec.sort_unstable(); - /// ``` - #[clippy::version = "1.47.0"] - pub STABLE_SORT_PRIMITIVE, - pedantic, - "use of sort() when sort_unstable() is equivalent" -} - -declare_lint_pass!(StableSortPrimitive => [STABLE_SORT_PRIMITIVE]); - -/// The three "kinds" of sorts -enum SortingKind { - Vanilla, - /* The other kinds of lint are currently commented out because they - * can map distinct values to equal ones. If the key function is - * provably one-to-one, or if the Cmp function conserves equality, - * then they could be linted on, but I don't know if we can check - * for that. */ - - /* ByKey, - * ByCmp, */ -} -impl SortingKind { - /// The name of the stable version of this kind of sort - fn stable_name(&self) -> &str { - match self { - SortingKind::Vanilla => "sort", - /* SortingKind::ByKey => "sort_by_key", - * SortingKind::ByCmp => "sort_by", */ - } - } - /// The name of the unstable version of this kind of sort - fn unstable_name(&self) -> &str { - match self { - SortingKind::Vanilla => "sort_unstable", - /* SortingKind::ByKey => "sort_unstable_by_key", - * SortingKind::ByCmp => "sort_unstable_by", */ - } - } - /// Takes the name of a function call and returns the kind of sort - /// that corresponds to that function name (or None if it isn't) - fn from_stable_name(name: &str) -> Option<SortingKind> { - match name { - "sort" => Some(SortingKind::Vanilla), - // "sort_by" => Some(SortingKind::ByCmp), - // "sort_by_key" => Some(SortingKind::ByKey), - _ => None, - } - } -} - -/// A detected instance of this lint -struct LintDetection { - slice_name: String, - method: SortingKind, - method_args: String, - slice_type: String, -} - -fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> { - if_chain! { - if let ExprKind::MethodCall(method_name, [slice, args @ ..], _) = &expr.kind; - if let Some(method) = SortingKind::from_stable_name(method_name.ident.name.as_str()); - if let Some(slice_type) = is_slice_of_primitives(cx, slice); - then { - let args_str = args.iter().map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", "); - Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type }) - } else { - None - } - } -} - -impl LateLintPass<'_> for StableSortPrimitive { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let Some(detection) = detect_stable_sort_primitive(cx, expr) { - span_lint_and_then( - cx, - STABLE_SORT_PRIMITIVE, - expr.span, - format!( - "used `{}` on primitive type `{}`", - detection.method.stable_name(), - detection.slice_type, - ) - .as_str(), - |diag| { - diag.span_suggestion( - expr.span, - "try", - format!( - "{}.{}({})", - detection.slice_name, - detection.method.unstable_name(), - detection.method_args, - ), - Applicability::MachineApplicable, - ); - diag.note( - "an unstable sort typically performs faster without any observable difference for this data type", - ); - }, - ); - } - } -} diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 0a42a31fb8c..2ffa022b04f 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -4,7 +4,7 @@ use clippy_utils::{SpanlessEq, SpanlessHash}; use core::hash::{Hash, Hasher}; use if_chain::if_chain; use itertools::Itertools; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -15,6 +15,7 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{BytePos, Span}; +use std::collections::hash_map::Entry; declare_clippy_lint! { /// ### What it does @@ -103,7 +104,6 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) { self.check_type_repetition(cx, gen); check_trait_bound_duplication(cx, gen); - check_bounds_or_where_duplication(cx, gen); } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { @@ -234,35 +234,61 @@ impl TraitBounds { } fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { - if gen.span.from_expansion() || gen.params.is_empty() || gen.predicates.is_empty() { + if gen.span.from_expansion() { return; } - let mut map = FxHashMap::<_, Vec<_>>::default(); - for predicate in gen.predicates { + // Explanation: + // fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) + // where T: Clone + Default, { unimplemented!(); } + // ^^^^^^^^^^^^^^^^^^ + // | + // collects each of these where clauses into a set keyed by generic name and comparable trait + // eg. (T, Clone) + let where_predicates = gen + .predicates + .iter() + .filter_map(|pred| { + if_chain! { + if pred.in_where_clause(); + if let WherePredicate::BoundPredicate(bound_predicate) = pred; + if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind; + then { + return Some( + rollup_traits(cx, bound_predicate.bounds, "these where clauses contain repeated elements") + .into_iter().map(|(trait_ref, _)| (path.res, trait_ref))) + } + } + None + }) + .flatten() + .collect::<FxHashSet<_>>(); + + // Explanation: + // fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) ... + // ^^^^^^^^^^^^^^^^^^ ^^^^^^^ + // | + // compare trait bounds keyed by generic name and comparable trait to collected where + // predicates eg. (T, Clone) + for predicate in gen.predicates.iter().filter(|pred| !pred.in_where_clause()) { if_chain! { - if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; + if let WherePredicate::BoundPredicate(bound_predicate) = predicate; if bound_predicate.origin != PredicateOrigin::ImplTrait; if !bound_predicate.span.from_expansion(); - if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind; - if let Some(segment) = segments.first(); + if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind; then { - for (res_where, _, span_where) in bound_predicate.bounds.iter().filter_map(get_trait_info_from_bound) { - let trait_resolutions_direct = map.entry(segment.ident).or_default(); - if let Some((_, span_direct)) = trait_resolutions_direct - .iter() - .find(|(res_direct, _)| *res_direct == res_where) { + let traits = rollup_traits(cx, bound_predicate.bounds, "these bounds contain repeated elements"); + for (trait_ref, span) in traits { + let key = (path.res, trait_ref); + if where_predicates.contains(&key) { span_lint_and_help( cx, TRAIT_DUPLICATION_IN_BOUNDS, - *span_direct, + span, "this trait bound is already specified in the where clause", None, "consider removing this trait bound", - ); - } - else { - trait_resolutions_direct.push((res_where, span_where)); + ); } } } @@ -270,23 +296,11 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { } } -#[derive(PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] struct ComparableTraitRef(Res, Vec<Res>); - -fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { - if gen.span.from_expansion() { - return; - } - - for predicate in gen.predicates { - if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate { - let msg = if predicate.in_where_clause() { - "these where clauses contain repeated elements" - } else { - "these bounds contain repeated elements" - }; - rollup_traits(cx, bound_predicate.bounds, msg); - } +impl Default for ComparableTraitRef { + fn default() -> Self { + Self(Res::Err, Vec::new()) } } @@ -331,7 +345,7 @@ fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef { ) } -fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) { +fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) -> Vec<(ComparableTraitRef, Span)> { let mut map = FxHashMap::default(); let mut repeated_res = false; @@ -343,23 +357,33 @@ fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) { } }; + let mut i = 0usize; for bound in bounds.iter().filter_map(only_comparable_trait_refs) { let (comparable_bound, span_direct) = bound; - if map.insert(comparable_bound, span_direct).is_some() { - repeated_res = true; + match map.entry(comparable_bound) { + Entry::Occupied(_) => repeated_res = true, + Entry::Vacant(e) => { + e.insert((span_direct, i)); + i += 1; + }, } } + // Put bounds in source order + let mut comparable_bounds = vec![Default::default(); map.len()]; + for (k, (v, i)) in map { + comparable_bounds[i] = (k, v); + } + if_chain! { if repeated_res; if let [first_trait, .., last_trait] = bounds; then { let all_trait_span = first_trait.span().to(last_trait.span()); - let mut traits = map.values() - .filter_map(|span| snippet_opt(cx, *span)) + let traits = comparable_bounds.iter() + .filter_map(|&(_, span)| snippet_opt(cx, span)) .collect::<Vec<_>>(); - traits.sort_unstable(); let traits = traits.join(" + "); span_lint_and_sugg( @@ -373,4 +397,6 @@ fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) { ); } } + + comparable_bounds } diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 5f3e98144f4..424a6e9264e 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -9,6 +9,7 @@ mod transmute_ptr_to_ref; mod transmute_ref_to_ref; mod transmute_undefined_repr; mod transmutes_expressible_as_ptr_casts; +mod transmuting_null; mod unsound_collection_transmute; mod useless_transmute; mod utils; @@ -386,6 +387,28 @@ declare_clippy_lint! { "transmute to or from a type with an undefined representation" } +declare_clippy_lint! { + /// ### What it does + /// Checks for transmute calls which would receive a null pointer. + /// + /// ### Why is this bad? + /// Transmuting a null pointer is undefined behavior. + /// + /// ### Known problems + /// Not all cases can be detected at the moment of this writing. + /// For example, variables which hold a null pointer and are then fed to a `transmute` + /// call, aren't detectable yet. + /// + /// ### Example + /// ```rust + /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) }; + /// ``` + #[clippy::version = "1.35.0"] + pub TRANSMUTING_NULL, + correctness, + "transmutes from a null pointer to a reference, which is undefined behavior" +} + pub struct Transmute { msrv: Option<RustcVersion>, } @@ -404,6 +427,7 @@ impl_lint_pass!(Transmute => [ UNSOUND_COLLECTION_TRANSMUTE, TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, TRANSMUTE_UNDEFINED_REPR, + TRANSMUTING_NULL, ]); impl Transmute { #[must_use] @@ -436,6 +460,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { let linted = wrong_transmute::check(cx, e, from_ty, to_ty) | crosspointer_transmute::check(cx, e, from_ty, to_ty) + | transmuting_null::check(cx, e, arg, to_ty) | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv) | transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 20b348fc14f..b6d7d9f5b42 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -5,9 +5,9 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::subst::{Subst, SubstsRef}; use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy}; -use rustc_span::Span; +use rustc_span::DUMMY_SP; -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, @@ -18,116 +18,89 @@ pub(super) fn check<'tcx>( let mut to_ty = cx.tcx.erase_regions(to_ty_orig); while from_ty != to_ty { - match reduce_refs(cx, e.span, from_ty, to_ty) { - ReducedTys::FromFatPtr { - unsized_ty, - to_ty: to_sub_ty, - } => match reduce_ty(cx, to_sub_ty) { - ReducedTy::TypeErasure => break, - ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, - ReducedTy::Ref(to_sub_ty) => { - from_ty = unsized_ty; - to_ty = to_sub_ty; - continue; - }, - _ => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute from `{}` which has an undefined layout", from_ty_orig), - |diag| { - if from_ty_orig.peel_refs() != unsized_ty { - diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); - } - }, - ); - return true; - }, + let reduced_tys = reduce_refs(cx, from_ty, to_ty); + match (reduce_ty(cx, reduced_tys.from_ty), reduce_ty(cx, reduced_tys.to_ty)) { + // Various forms of type erasure. + (ReducedTy::TypeErasure { raw_ptr_only: false }, _) + | (_, ReducedTy::TypeErasure { raw_ptr_only: false }) => return false, + (ReducedTy::TypeErasure { .. }, _) if reduced_tys.from_raw_ptr => return false, + (_, ReducedTy::TypeErasure { .. }) if reduced_tys.to_raw_ptr => return false, + + // `Repr(C)` <-> unordered type. + // If the first field of the `Repr(C)` type matches then the transmute is ok + (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty)) + | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; }, - ReducedTys::ToFatPtr { - unsized_ty, - from_ty: from_sub_ty, - } => match reduce_ty(cx, from_sub_ty) { - ReducedTy::TypeErasure => break, - ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, - ReducedTy::Ref(from_sub_ty) => { - from_ty = from_sub_ty; - to_ty = unsized_ty; - continue; - }, - _ => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute to `{}` which has an undefined layout", to_ty_orig), - |diag| { - if to_ty_orig.peel_refs() != unsized_ty { - diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); - } - }, - ); - return true; - }, + (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; }, - ReducedTys::ToPtr { - from_ty: from_sub_ty, - to_ty: to_sub_ty, - } => match reduce_ty(cx, from_sub_ty) { - ReducedTy::UnorderedFields(from_ty) => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute from `{}` which has an undefined layout", from_ty_orig), - |diag| { - if from_ty_orig.peel_refs() != from_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); - } - }, - ); - return true; - }, - ReducedTy::Ref(from_sub_ty) => { - from_ty = from_sub_ty; - to_ty = to_sub_ty; - continue; - }, - _ => break, + (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) + if reduced_tys.from_fat_ptr => + { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; }, - ReducedTys::FromPtr { - from_ty: from_sub_ty, - to_ty: to_sub_ty, - } => match reduce_ty(cx, to_sub_ty) { - ReducedTy::UnorderedFields(to_ty) => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute to `{}` which has an undefined layout", to_ty_orig), - |diag| { - if to_ty_orig.peel_refs() != to_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); - } - }, - ); - return true; - }, - ReducedTy::Ref(to_sub_ty) => { - from_ty = from_sub_ty; - to_ty = to_sub_ty; - continue; - }, - _ => break, + + // ptr <-> ptr + (ReducedTy::Other(from_sub_ty), ReducedTy::Other(to_sub_ty)) + if matches!(from_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) + && matches!(to_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) => + { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; }, - ReducedTys::Other { - from_ty: from_sub_ty, - to_ty: to_sub_ty, - } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) { - (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false, - (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { - let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs)) + + // fat ptr <-> (*size, *size) + (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty)) + if reduced_tys.from_fat_ptr && is_size_pair(to_ty) => + { + return false; + }, + (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_)) + if reduced_tys.to_fat_ptr && is_size_pair(from_ty) => + { + return false; + }, + + // fat ptr -> some struct | some struct -> fat ptr + (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if from_ty_orig.peel_refs() != from_ty.peel_refs() { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + }, + ); + return true; + }, + (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute to `{}` which has an undefined layout", to_ty_orig), + |diag| { + if to_ty_orig.peel_refs() != to_ty.peel_refs() { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } + }, + ); + return true; + }, + + (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { + let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs)) = (from_ty.kind(), to_ty.kind()) && from_def == to_def { @@ -138,79 +111,72 @@ pub(super) fn check<'tcx>( } else { None }; - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!( - "transmute from `{}` to `{}`, both of which have an undefined layout", - from_ty_orig, to_ty_orig - ), - |diag| { - if let Some(same_adt_did) = same_adt_did { - diag.note(&format!( - "two instances of the same generic type (`{}`) may have different layouts", - cx.tcx.item_name(same_adt_did) - )); - } else { - if from_ty_orig.peel_refs() != from_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); - } - if to_ty_orig.peel_refs() != to_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); - } - } - }, - ); - return true; - }, - ( - ReducedTy::UnorderedFields(from_ty), - ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_), - ) => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute from `{}` which has an undefined layout", from_ty_orig), - |diag| { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!( + "transmute from `{}` to `{}`, both of which have an undefined layout", + from_ty_orig, to_ty_orig + ), + |diag| { + if let Some(same_adt_did) = same_adt_did { + diag.note(&format!( + "two instances of the same generic type (`{}`) may have different layouts", + cx.tcx.item_name(same_adt_did) + )); + } else { if from_ty_orig.peel_refs() != from_ty { diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); } - }, - ); - return true; - }, - ( - ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_), - ReducedTy::UnorderedFields(to_ty), - ) => { - span_lint_and_then( - cx, - TRANSMUTE_UNDEFINED_REPR, - e.span, - &format!("transmute into `{}` which has an undefined layout", to_ty_orig), - |diag| { if to_ty_orig.peel_refs() != to_ty { diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); } - }, - ); - return true; - }, - (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => { - from_ty = from_sub_ty; - to_ty = to_sub_ty; - continue; - }, - ( - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, - ) - | ( - ReducedTy::UnorderedFields(_) | ReducedTy::Param, - ReducedTy::UnorderedFields(_) | ReducedTy::Param, - ) => break, + } + }, + ); + return true; + }, + ( + ReducedTy::UnorderedFields(from_ty), + ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true }, + ) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if from_ty_orig.peel_refs() != from_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + }, + ); + return true; + }, + ( + ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true }, + ReducedTy::UnorderedFields(to_ty), + ) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute into `{}` which has an undefined layout", to_ty_orig), + |diag| { + if to_ty_orig.peel_refs() != to_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } + }, + ); + return true; + }, + ( + ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true }, + ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true }, + ) + | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => { + break; }, } } @@ -218,64 +184,64 @@ pub(super) fn check<'tcx>( false } -enum ReducedTys<'tcx> { - FromFatPtr { unsized_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, - ToFatPtr { unsized_ty: Ty<'tcx>, from_ty: Ty<'tcx> }, - ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, - FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, - Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, +#[expect(clippy::struct_excessive_bools)] +struct ReducedTys<'tcx> { + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + from_raw_ptr: bool, + to_raw_ptr: bool, + from_fat_ptr: bool, + to_fat_ptr: bool, } /// Remove references so long as both types are references. -fn reduce_refs<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - mut from_ty: Ty<'tcx>, - mut to_ty: Ty<'tcx>, -) -> ReducedTys<'tcx> { - loop { - return match (from_ty.kind(), to_ty.kind()) { +fn reduce_refs<'tcx>(cx: &LateContext<'tcx>, mut from_ty: Ty<'tcx>, mut to_ty: Ty<'tcx>) -> ReducedTys<'tcx> { + let mut from_raw_ptr = false; + let mut to_raw_ptr = false; + let (from_fat_ptr, to_fat_ptr) = loop { + break match (from_ty.kind(), to_ty.kind()) { ( &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })), &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })), ) => { + from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_)); from_ty = from_sub_ty; + to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_)); to_ty = to_sub_ty; continue; }, (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _) - if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) => + if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env) => { - ReducedTys::FromFatPtr { unsized_ty, to_ty } + (true, false) }, (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }))) - if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) => + if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env) => { - ReducedTys::ToFatPtr { unsized_ty, from_ty } + (false, true) }, - (&(ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. })), _) => { - ReducedTys::FromPtr { from_ty, to_ty } - }, - (_, &(ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. }))) => { - ReducedTys::ToPtr { from_ty, to_ty } - }, - _ => ReducedTys::Other { from_ty, to_ty }, + _ => (false, false), }; + }; + ReducedTys { + from_ty, + to_ty, + from_raw_ptr, + to_raw_ptr, + from_fat_ptr, + to_fat_ptr, } } enum ReducedTy<'tcx> { /// The type can be used for type erasure. - TypeErasure, + TypeErasure { raw_ptr_only: bool }, /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero /// sized fields with a defined order. - OrderedFields(Ty<'tcx>), + /// The second value is the first non-zero sized type. + OrderedFields(Ty<'tcx>, Option<Ty<'tcx>>), /// The type is a struct containing multiple non-zero sized fields with no defined order. UnorderedFields(Ty<'tcx>), - /// The type is a reference to the contained type. - Ref(Ty<'tcx>), - /// The type is a generic parameter. - Param, /// Any other type. Other(Ty<'tcx>), } @@ -285,16 +251,18 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> loop { ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty); return match *ty.kind() { - ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure, + ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => { + ReducedTy::TypeErasure { raw_ptr_only: false } + }, ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { ty = sub_ty; continue; }, - ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure, + ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false }, ty::Tuple(args) => { let mut iter = args.iter(); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { - return ReducedTy::OrderedFields(ty); + return ReducedTy::OrderedFields(ty, None); }; if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; @@ -309,27 +277,25 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .iter() .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs)); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { - return ReducedTy::TypeErasure; + return ReducedTy::TypeErasure { raw_ptr_only: false }; }; if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; continue; } if def.repr().inhibit_struct_field_reordering_opt() { - ReducedTy::OrderedFields(ty) + ReducedTy::OrderedFields(ty, Some(sized_ty)) } else { ReducedTy::UnorderedFields(ty) } }, ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => { - ReducedTy::TypeErasure + ReducedTy::TypeErasure { raw_ptr_only: false } }, // TODO: Check if the conversion to or from at least one of a union's fields is valid. - ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure, - ty::Foreign(_) => ReducedTy::TypeErasure, - ty::Ref(_, ty, _) => ReducedTy::Ref(ty), - ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), - ty::Param(_) => ReducedTy::Param, + ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure { raw_ptr_only: false }, + ty::Foreign(_) | ty::Param(_) => ReducedTy::TypeErasure { raw_ptr_only: false }, + ty::Int(_) | ty::Uint(_) => ReducedTy::TypeErasure { raw_ptr_only: true }, _ => ReducedTy::Other(ty), }; } diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs new file mode 100644 index 00000000000..d8e349af7af --- /dev/null +++ b/clippy_lints/src/transmute/transmuting_null.rs @@ -0,0 +1,61 @@ +use clippy_utils::consts::{constant_context, Constant}; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_path_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::symbol::sym; + +use super::TRANSMUTING_NULL; + +const LINT_MSG: &str = "transmuting a known null pointer into a reference"; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>, to_ty: Ty<'tcx>) -> bool { + if !to_ty.is_ref() { + return false; + } + + // Catching transmute over constants that resolve to `null`. + let mut const_eval_context = constant_context(cx, cx.typeck_results()); + if_chain! { + if let ExprKind::Path(ref _qpath) = arg.kind; + if let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg); + if x == 0; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); + return true; + } + } + + // Catching: + // `std::mem::transmute(0 as *const i32)` + if_chain! { + if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind; + if let ExprKind::Lit(ref lit) = inner_expr.kind; + if let LitKind::Int(0, _) = lit.node; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); + return true; + } + } + + // Catching: + // `std::mem::transmute(std::ptr::null::<i32>())` + if_chain! { + if let ExprKind::Call(func1, []) = arg.kind; + if is_path_diagnostic_item(cx, func1, sym::ptr_null); + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); + return true; + } + } + + // FIXME: + // Also catch transmutations of variables which are known nulls. + // To do this, MIR const propagation seems to be the better tool. + // Whenever MIR const prop routines are more developed, this will + // become available. As of this writing (25/03/19) it is not yet. + false +} diff --git a/clippy_lints/src/transmuting_null.rs b/clippy_lints/src/transmuting_null.rs deleted file mode 100644 index 7939dfedc3a..00000000000 --- a/clippy_lints/src/transmuting_null.rs +++ /dev/null @@ -1,89 +0,0 @@ -use clippy_utils::consts::{constant_context, Constant}; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_expr_diagnostic_item; -use if_chain::if_chain; -use rustc_ast::LitKind; -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}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for transmute calls which would receive a null pointer. - /// - /// ### Why is this bad? - /// Transmuting a null pointer is undefined behavior. - /// - /// ### Known problems - /// Not all cases can be detected at the moment of this writing. - /// For example, variables which hold a null pointer and are then fed to a `transmute` - /// call, aren't detectable yet. - /// - /// ### Example - /// ```rust - /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) }; - /// ``` - #[clippy::version = "1.35.0"] - pub TRANSMUTING_NULL, - correctness, - "transmutes from a null pointer to a reference, which is undefined behavior" -} - -declare_lint_pass!(TransmutingNull => [TRANSMUTING_NULL]); - -const LINT_MSG: &str = "transmuting a known null pointer into a reference"; - -impl<'tcx> LateLintPass<'tcx> for TransmutingNull { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if_chain! { - if let ExprKind::Call(func, [arg]) = expr.kind; - if is_expr_diagnostic_item(cx, func, sym::transmute); - - then { - // Catching transmute over constants that resolve to `null`. - let mut const_eval_context = constant_context(cx, cx.typeck_results()); - if_chain! { - if let ExprKind::Path(ref _qpath) = arg.kind; - if let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg); - if x == 0; - then { - span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) - } - } - - // Catching: - // `std::mem::transmute(0 as *const i32)` - if_chain! { - if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind; - if let ExprKind::Lit(ref lit) = inner_expr.kind; - if let LitKind::Int(0, _) = lit.node; - then { - span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) - } - } - - // Catching: - // `std::mem::transmute(std::ptr::null::<i32>())` - if_chain! { - if let ExprKind::Call(func1, []) = arg.kind; - if is_expr_diagnostic_item(cx, func1, sym::ptr_null); - then { - span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) - } - } - - // FIXME: - // Also catch transmutations of variables which are known nulls. - // To do this, MIR const propagation seems to be the better tool. - // Whenever MIR const prop routines are more developed, this will - // become available. As of this writing (25/03/19) it is not yet. - } - } - } -} diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index cc64d17be05..8980283e5c8 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_lint_allowed; +use clippy_utils::macros::span_is_local; use clippy_utils::source::snippet; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -98,6 +99,10 @@ fn escape<T: Iterator<Item = char>>(s: T) -> String { } fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) { + if !span_is_local(span) { + return; + } + let string = snippet(cx, span, ""); if string.chars().any(|c| ['\u{200B}', '\u{ad}', '\u{2060}'].contains(&c)) { span_lint_and_sugg( @@ -113,6 +118,7 @@ fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) { Applicability::MachineApplicable, ); } + if string.chars().any(|c| c as u32 > 0x7F) { span_lint_and_sugg( cx, @@ -128,6 +134,7 @@ fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) { Applicability::MachineApplicable, ); } + if is_lint_allowed(cx, NON_ASCII_LITERAL, id) && string.chars().zip(string.nfc()).any(|(a, b)| a != b) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index 9f4c5555f11..9a41603f2f4 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -45,7 +45,7 @@ declare_clippy_lint! { /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000); /// vec.set_len(1000); // `MaybeUninit` can be uninitialized /// ``` - /// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available: + /// 3. If you are on 1.60.0 or later, `Vec::spare_capacity_mut()` is available: /// ```rust,ignore /// let mut vec: Vec<u8> = Vec::with_capacity(1000); /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]` diff --git a/clippy_lints/src/unit_hash.rs b/clippy_lints/src/unit_hash.rs deleted file mode 100644 index 88ca0cb20a1..00000000000 --- a/clippy_lints/src/unit_hash.rs +++ /dev/null @@ -1,78 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; -use rustc_errors::Applicability; -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 - /// Detects `().hash(_)`. - /// - /// ### Why is this bad? - /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. - /// - /// ### Example - /// ```rust - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => ().hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - /// Use instead: - /// ```rust - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => 0_u8.hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - #[clippy::version = "1.58.0"] - pub UNIT_HASH, - correctness, - "hashing a unit value, which does nothing" -} -declare_lint_pass!(UnitHash => [UNIT_HASH]); - -impl<'tcx> LateLintPass<'tcx> for UnitHash { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if_chain! { - if let ExprKind::MethodCall(name_ident, args, _) = &expr.kind; - if name_ident.ident.name == sym::hash; - if let [recv, state_param] = args; - if cx.typeck_results().expr_ty(recv).is_unit(); - then { - span_lint_and_then( - cx, - UNIT_HASH, - expr.span, - "this call to `hash` on the unit type will do nothing", - |diag| { - diag.span_suggestion( - expr.span, - "remove the call to `hash` or consider using", - format!( - "0_u8.hash({})", - snippet(cx, state_param.span, ".."), - ), - Applicability::MaybeIncorrect, - ); - diag.note("the implementation of `Hash` for `()` is a no-op"); - } - ); - } - } - } -} diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index f4f5a4336a3..a5afbb8ff9d 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -130,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { ( ret_expr.span, if inner_type.is_unit() { - "".to_string() + String::new() } else { snippet(cx, arg.span.source_callsite(), "..").to_string() } diff --git a/clippy_lints/src/unused_peekable.rs b/clippy_lints/src/unused_peekable.rs new file mode 100644 index 00000000000..ac73173697e --- /dev/null +++ b/clippy_lints/src/unused_peekable.rs @@ -0,0 +1,225 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::{match_type, peel_mid_ty_refs_is_mutable}; +use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, paths, peel_ref_operators}; +use rustc_ast::Mutability; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::lang_items::LangItem; +use rustc_hir::{Block, Expr, ExprKind, HirId, Local, Node, PatKind, PathSegment, StmtKind}; +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 the creation of a `peekable` iterator that is never `.peek()`ed + /// + /// ### Why is this bad? + /// Creating a peekable iterator without using any of its methods is likely a mistake, + /// or just a leftover after a refactor. + /// + /// ### Example + /// ```rust + /// let collection = vec![1, 2, 3]; + /// let iter = collection.iter().peekable(); + /// + /// for item in iter { + /// // ... + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// let collection = vec![1, 2, 3]; + /// let iter = collection.iter(); + /// + /// for item in iter { + /// // ... + /// } + /// ``` + #[clippy::version = "1.64.0"] + pub UNUSED_PEEKABLE, + suspicious, + "creating a peekable iterator without using any of its methods" +} + +declare_lint_pass!(UnusedPeekable => [UNUSED_PEEKABLE]); + +impl<'tcx> LateLintPass<'tcx> for UnusedPeekable { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { + // Don't lint `Peekable`s returned from a block + if let Some(expr) = block.expr + && let Some(ty) = cx.typeck_results().expr_ty_opt(peel_ref_operators(cx, expr)) + && match_type(cx, ty, &paths::PEEKABLE) + { + return; + } + + for (idx, stmt) in block.stmts.iter().enumerate() { + if !stmt.span.from_expansion() + && let StmtKind::Local(local) = stmt.kind + && let PatKind::Binding(_, binding, ident, _) = local.pat.kind + && let Some(init) = local.init + && !init.span.from_expansion() + && let Some(ty) = cx.typeck_results().expr_ty_opt(init) + && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) + && match_type(cx, ty, &paths::PEEKABLE) + { + let mut vis = PeekableVisitor::new(cx, binding); + + if idx + 1 == block.stmts.len() && block.expr.is_none() { + return; + } + + for stmt in &block.stmts[idx..] { + vis.visit_stmt(stmt); + } + + if let Some(expr) = block.expr { + vis.visit_expr(expr); + } + + if !vis.found_peek_call { + span_lint_and_help( + cx, + UNUSED_PEEKABLE, + ident.span, + "`peek` never called on `Peekable` iterator", + None, + "consider removing the call to `peekable`" + ); + } + } + } + } +} + +struct PeekableVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + expected_hir_id: HirId, + found_peek_call: bool, +} + +impl<'a, 'tcx> PeekableVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>, expected_hir_id: HirId) -> Self { + Self { + cx, + expected_hir_id, + found_peek_call: false, + } + } +} + +impl<'tcx> Visitor<'_> for PeekableVisitor<'_, 'tcx> { + fn visit_expr(&mut self, ex: &'_ Expr<'_>) { + if self.found_peek_call { + return; + } + + if path_to_local_id(ex, self.expected_hir_id) { + for (_, node) in self.cx.tcx.hir().parent_iter(ex.hir_id) { + match node { + Node::Expr(expr) => { + match expr.kind { + // some_function(peekable) + // + // If the Peekable is passed to a function, stop + ExprKind::Call(_, args) => { + if let Some(func_did) = fn_def_id(self.cx, expr) + && let Ok(into_iter_did) = self + .cx + .tcx + .lang_items() + .require(LangItem::IntoIterIntoIter) + && func_did == into_iter_did + { + // Probably a for loop desugar, stop searching + return; + } + + if args.iter().any(|arg| { + matches!(arg.kind, ExprKind::Path(_)) && arg_is_mut_peekable(self.cx, arg) + }) { + self.found_peek_call = true; + return; + } + }, + // Catch anything taking a Peekable mutably + ExprKind::MethodCall( + PathSegment { + ident: method_name_ident, + .. + }, + [self_arg, remaining_args @ ..], + _, + ) => { + let method_name = method_name_ident.name.as_str(); + + // `Peekable` methods + if matches!(method_name, "peek" | "peek_mut" | "next_if" | "next_if_eq") + && arg_is_mut_peekable(self.cx, self_arg) + { + self.found_peek_call = true; + return; + } + + // foo.some_method() excluding Iterator methods + if remaining_args.iter().any(|arg| arg_is_mut_peekable(self.cx, arg)) + && !is_trait_method(self.cx, expr, sym::Iterator) + { + self.found_peek_call = true; + return; + } + + // foo.by_ref(), keep checking for `peek` + if method_name == "by_ref" { + continue; + } + + return; + }, + ExprKind::AddrOf(_, Mutability::Mut, _) | ExprKind::Unary(..) | ExprKind::DropTemps(_) => { + }, + ExprKind::AddrOf(_, Mutability::Not, _) => return, + _ => { + self.found_peek_call = true; + return; + }, + } + }, + Node::Local(Local { init: Some(init), .. }) => { + if arg_is_mut_peekable(self.cx, init) { + self.found_peek_call = true; + return; + } + + break; + }, + Node::Stmt(stmt) => match stmt.kind { + StmtKind::Expr(_) | StmtKind::Semi(_) => {}, + _ => { + self.found_peek_call = true; + return; + }, + }, + Node::Block(_) | Node::ExprField(_) => {}, + _ => { + break; + }, + } + } + } + + walk_expr(self, ex); + } +} + +fn arg_is_mut_peekable(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool { + if let Some(ty) = cx.typeck_results().expr_ty_opt(arg) + && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) + && match_type(cx, ty, &paths::PEEKABLE) + { + true + } else { + false + } +} diff --git a/clippy_lints/src/unused_rounding.rs b/clippy_lints/src/unused_rounding.rs index e1ec357838d..b8a5d4ea8c9 100644 --- a/clippy_lints/src/unused_rounding.rs +++ b/clippy_lints/src/unused_rounding.rs @@ -22,7 +22,7 @@ declare_clippy_lint! { /// ```rust /// let x = 1f32; /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub UNUSED_ROUNDING, nursery, "Uselessly rounding a whole number floating-point literal" diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 3faae9ac0d2..84e65d5fa0b 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -350,7 +350,7 @@ define_Conf! { /// Lint: DISALLOWED_SCRIPT_IDENTS. /// /// The list of unicode scripts allowed to be used in the scope. - (allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()), + (allowed_scripts: Vec<String> = vec!["Latin".to_string()]), /// Lint: NON_SEND_FIELDS_IN_SEND_TY. /// /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. @@ -379,6 +379,10 @@ define_Conf! { /// /// Whether `dbg!` should be allowed in test functions (allow_dbg_in_tests: bool = false), + /// Lint: RESULT_LARGE_ERR + /// + /// The maximum size of the `Err`-variant in a `Result` returned from a function + (large_error_threshold: u64 = 128), } /// Search for the configuration file. diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 5dcacd604be..eb34085a2ab 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -593,8 +593,8 @@ fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Opt attrs.iter().find_map(|attr| { if_chain! { // Identify attribute - if let ast::AttrKind::Normal(ref attr_kind, _) = &attr.kind; - if let [tool_name, attr_name] = &attr_kind.path.segments[..]; + if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind; + if let [tool_name, attr_name] = &attr_kind.item.path.segments[..]; if tool_name.ident.name == sym::clippy; if attr_name.ident.name == sym::version; if let Some(version) = attr.value_str(); diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 92cf42c7ad4..b1148bccc2a 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -797,7 +797,7 @@ fn get_lint_group_and_level_or_lint( let result = cx.lint_store.check_lint_name( lint_name, Some(sym::clippy), - &[Ident::with_dummy_span(sym::clippy)].into_iter().collect(), + &std::iter::once(Ident::with_dummy_span(sym::clippy)).collect(), ); if let CheckLintNameResult::Tool(Ok(lint_lst)) = result { if let Some(group) = get_lint_group(cx, lint_lst[0]) { diff --git a/clippy_lints/src/vec_resize_to_zero.rs b/clippy_lints/src/vec_resize_to_zero.rs deleted file mode 100644 index 0fee3e812d2..00000000000 --- a/clippy_lints/src/vec_resize_to_zero.rs +++ /dev/null @@ -1,64 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{match_def_path, paths}; -use if_chain::if_chain; -use rustc_ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Spanned; - -declare_clippy_lint! { - /// ### What it does - /// Finds occurrences of `Vec::resize(0, an_int)` - /// - /// ### Why is this bad? - /// This is probably an argument inversion mistake. - /// - /// ### Example - /// ```rust - /// vec!(1, 2, 3, 4, 5).resize(0, 5) - /// ``` - /// - /// Use instead: - /// ```rust - /// vec!(1, 2, 3, 4, 5).clear() - /// ``` - #[clippy::version = "1.46.0"] - pub VEC_RESIZE_TO_ZERO, - correctness, - "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" -} - -declare_lint_pass!(VecResizeToZero => [VEC_RESIZE_TO_ZERO]); - -impl<'tcx> LateLintPass<'tcx> for VecResizeToZero { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - if let hir::ExprKind::MethodCall(path_segment, args, _) = expr.kind; - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match_def_path(cx, method_def_id, &paths::VEC_RESIZE) && args.len() == 3; - if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = args[1].kind; - if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = args[2].kind; - then { - let method_call_span = expr.span.with_lo(path_segment.ident.span.lo()); - span_lint_and_then( - cx, - VEC_RESIZE_TO_ZERO, - expr.span, - "emptying a vector with `resize`", - |db| { - db.help("the arguments may be inverted..."); - db.span_suggestion( - method_call_span, - "...or you can empty the vector with", - "clear()".to_string(), - Applicability::MaybeIncorrect, - ); - }, - ); - } - } - } -} diff --git a/clippy_lints/src/verbose_file_reads.rs b/clippy_lints/src/verbose_file_reads.rs deleted file mode 100644 index afd0077a658..00000000000 --- a/clippy_lints/src/verbose_file_reads.rs +++ /dev/null @@ -1,88 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::paths; -use clippy_utils::ty::match_type; -use if_chain::if_chain; -use rustc_hir::{Expr, ExprKind, QPath}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for use of File::read_to_end and File::read_to_string. - /// - /// ### Why is this bad? - /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. - /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) - /// - /// ### Example - /// ```rust,no_run - /// # use std::io::Read; - /// # use std::fs::File; - /// let mut f = File::open("foo.txt").unwrap(); - /// let mut bytes = Vec::new(); - /// f.read_to_end(&mut bytes).unwrap(); - /// ``` - /// Can be written more concisely as - /// ```rust,no_run - /// # use std::fs; - /// let mut bytes = fs::read("foo.txt").unwrap(); - /// ``` - #[clippy::version = "1.44.0"] - pub VERBOSE_FILE_READS, - restriction, - "use of `File::read_to_end` or `File::read_to_string`" -} - -declare_lint_pass!(VerboseFileReads => [VERBOSE_FILE_READS]); - -impl<'tcx> LateLintPass<'tcx> for VerboseFileReads { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if is_file_read_to_end(cx, expr) { - span_lint_and_help( - cx, - VERBOSE_FILE_READS, - expr.span, - "use of `File::read_to_end`", - None, - "consider using `fs::read` instead", - ); - } else if is_file_read_to_string(cx, expr) { - span_lint_and_help( - cx, - VERBOSE_FILE_READS, - expr.span, - "use of `File::read_to_string`", - None, - "consider using `fs::read_to_string` instead", - ); - } - } -} - -fn is_file_read_to_end<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - if_chain! { - if let ExprKind::MethodCall(method_name, [recv, ..], _) = expr.kind; - if method_name.ident.as_str() == "read_to_end"; - if let ExprKind::Path(QPath::Resolved(None, _)) = &recv.kind; - let ty = cx.typeck_results().expr_ty(recv); - if match_type(cx, ty, &paths::FILE); - then { - return true - } - } - false -} - -fn is_file_read_to_string<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - if_chain! { - if let ExprKind::MethodCall(method_name, exprs, _) = expr.kind; - if method_name.ident.as_str() == "read_to_string"; - if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; - let ty = cx.typeck_results().expr_ty(&exprs[0]); - if match_type(cx, ty, &paths::FILE); - then { - return true - } - } - false -} diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index fa2383066f3..5533840b166 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -3,8 +3,9 @@ use std::iter; use std::ops::{Deref, Range}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability}; use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle}; +use rustc_ast::ptr::P; use rustc_ast::token::{self, LitKind}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, DiagnosticBuilder}; @@ -256,6 +257,28 @@ declare_clippy_lint! { "writing a literal with a format string" } +declare_clippy_lint! { + /// ### What it does + /// This lint warns when a named parameter in a format string is used as a positional one. + /// + /// ### Why is this bad? + /// It may be confused for an assignment and obfuscates which parameter is being used. + /// + /// ### Example + /// ```rust + /// println!("{}", x = 10); + /// ``` + /// + /// Use instead: + /// ```rust + /// println!("{x}", x = 10); + /// ``` + #[clippy::version = "1.63.0"] + pub POSITIONAL_NAMED_FORMAT_PARAMETERS, + suspicious, + "named parameter in a format string is used positionally" +} + #[derive(Default)] pub struct Write { in_debug_impl: bool, @@ -270,7 +293,8 @@ impl_lint_pass!(Write => [ PRINT_LITERAL, WRITE_WITH_NEWLINE, WRITELN_EMPTY_STRING, - WRITE_LITERAL + WRITE_LITERAL, + POSITIONAL_NAMED_FORMAT_PARAMETERS, ]); impl EarlyLintPass for Write { @@ -408,6 +432,7 @@ fn newline_span(fmtstr: &StrLit) -> (Span, bool) { #[derive(Default)] struct SimpleFormatArgs { unnamed: Vec<Vec<Span>>, + complex_unnamed: Vec<Vec<Span>>, named: Vec<(Symbol, Vec<Span>)>, } impl SimpleFormatArgs { @@ -419,6 +444,10 @@ impl SimpleFormatArgs { }) } + fn get_complex_unnamed(&self) -> impl Iterator<Item = &[Span]> { + self.complex_unnamed.iter().map(Vec::as_slice) + } + fn get_named(&self, n: &Path) -> &[Span] { self.named.iter().find(|x| *n == x.0).map_or(&[], |x| x.1.as_slice()) } @@ -479,6 +508,61 @@ impl SimpleFormatArgs { }, }; } + + fn push_to_complex(&mut self, span: Span, position: usize) { + if self.complex_unnamed.len() <= position { + self.complex_unnamed.resize_with(position, Vec::new); + self.complex_unnamed.push(vec![span]); + } else { + let args: &mut Vec<Span> = &mut self.complex_unnamed[position]; + args.push(span); + } + } + + fn push_complex( + &mut self, + cx: &EarlyContext<'_>, + arg: rustc_parse_format::Argument<'_>, + str_lit_span: Span, + fmt_span: Span, + ) { + use rustc_parse_format::{ArgumentImplicitlyIs, ArgumentIs, CountIsParam}; + + let snippet = snippet_opt(cx, fmt_span); + + let end = snippet + .as_ref() + .and_then(|s| s.find(':')) + .or_else(|| fmt_span.hi().0.checked_sub(fmt_span.lo().0 + 1).map(|u| u as usize)); + + if let (ArgumentIs(n) | ArgumentImplicitlyIs(n), Some(end)) = (arg.position, end) { + let span = fmt_span.from_inner(InnerSpan::new(1, end)); + self.push_to_complex(span, n); + }; + + if let (CountIsParam(n), Some(span)) = (arg.format.precision, arg.format.precision_span) { + // We need to do this hack as precision spans should be converted from .* to .foo$ + let hack = if snippet.as_ref().and_then(|s| s.find('*')).is_some() { + 0 + } else { + 1 + }; + + let span = str_lit_span.from_inner(InnerSpan { + start: span.start + 1, + end: span.end - hack, + }); + self.push_to_complex(span, n); + }; + + if let (CountIsParam(n), Some(span)) = (arg.format.width, arg.format.width_span) { + let span = str_lit_span.from_inner(InnerSpan { + start: span.start, + end: span.end - 1, + }); + self.push_to_complex(span, n); + }; + } } impl Write { @@ -511,8 +595,8 @@ impl Write { // FIXME: modify rustc's fmt string parser to give us the current span span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); } - args.push(arg, span); + args.push_complex(cx, arg, str_lit.span, span); } parser.errors.is_empty().then_some(args) @@ -566,6 +650,7 @@ impl Write { let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL }; let mut unnamed_args = args.get_unnamed(); + let mut complex_unnamed_args = args.get_complex_unnamed(); loop { if !parser.eat(&token::Comma) { return (Some(fmtstr), expr); @@ -577,11 +662,20 @@ impl Write { } else { return (Some(fmtstr), None); }; + let complex_unnamed_arg = complex_unnamed_args.next(); + let (fmt_spans, lit) = match &token_expr.kind { ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit), - ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) { - (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit), - _ => continue, + ExprKind::Assign(lhs, rhs, _) => { + if let Some(span) = complex_unnamed_arg { + for x in span { + Self::report_positional_named_param(cx, *x, lhs, rhs); + } + } + match (&lhs.kind, &rhs.kind) { + (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit), + _ => continue, + } }, _ => { unnamed_args.next(); @@ -637,6 +731,29 @@ impl Write { } } + fn report_positional_named_param(cx: &EarlyContext<'_>, span: Span, lhs: &P<Expr>, _rhs: &P<Expr>) { + if let ExprKind::Path(_, _p) = &lhs.kind { + let mut applicability = Applicability::MachineApplicable; + let name = snippet_with_applicability(cx, lhs.span, "name", &mut applicability); + // We need to do this hack as precision spans should be converted from .* to .foo$ + let hack = snippet(cx, span, "").contains('*'); + + span_lint_and_sugg( + cx, + POSITIONAL_NAMED_FORMAT_PARAMETERS, + span, + &format!("named parameter {} is used as a positional parameter", name), + "replace it with", + if hack { + format!("{}$", name) + } else { + format!("{}", name) + }, + applicability, + ); + }; + } + fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) { if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) { if fmt_str.symbol == kw::Empty { diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index a688050f63a..c36bca06507 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] arrayvec = { version = "0.7", default-features = false } if_chain = "1.0" +itertools = "0.10.1" rustc-semver = "1.1" [features] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 313f1f1d9a6..997e773b5da 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -28,6 +28,7 @@ extern crate rustc_infer; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_middle; +extern crate rustc_parse_format; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; @@ -87,6 +88,7 @@ use rustc_hir::{ Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, }; +use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; use rustc_middle::ty as rustc_ty; @@ -104,6 +106,7 @@ use rustc_semver::RustcVersion; use rustc_session::Session; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::original_sp; +use rustc_span::source_map::SourceMap; use rustc_span::sym; use rustc_span::symbol::{kw, Symbol}; use rustc_span::{Span, DUMMY_SP}; @@ -372,15 +375,19 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { /// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path. /// -/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item. +/// Please use `is_path_diagnostic_item` if the target is a diagnostic item. pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool { path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments)) } -/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given -/// diagnostic item. -pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { - path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) +/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if +/// it matches the given diagnostic item. +pub fn is_path_diagnostic_item<'tcx>( + cx: &LateContext<'_>, + maybe_path: &impl MaybePath<'tcx>, + diag_item: Symbol, +) -> bool { + path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) } /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the @@ -2274,6 +2281,18 @@ pub fn walk_to_expr_usage<'tcx, T>( None } +/// Checks whether a given span has any comment token +/// This checks for all types of comment: line "//", block "/**", doc "///" "//!" +pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool { + let Ok(snippet) = sm.span_to_snippet(span) else { return false }; + return tokenize(&snippet).any(|token| { + matches!( + token.kind, + TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } + ) + }); +} + macro_rules! op_utils { ($($name:ident $assign:ident)*) => { /// Binary operation traits like `LangItem::Add` diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index a268e339bb1..e5ca3545540 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -1,16 +1,21 @@ #![allow(clippy::similar_names)] // `expr` and `expn` +use crate::is_path_diagnostic_item; +use crate::source::snippet_opt; use crate::visitors::expr_visitor_no_bodies; use arrayvec::ArrayVec; -use if_chain::if_chain; +use itertools::{izip, Either, Itertools}; use rustc_ast::ast::LitKind; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; +use rustc_lexer::unescape::unescape_literal; +use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use rustc_lint::LateContext; +use rustc_parse_format::{self as rpf, Alignment}; use rustc_span::def_id::DefId; use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; -use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol}; +use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol}; use std::ops::ControlFlow; const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ @@ -332,121 +337,495 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> } } -/// A parsed `format_args!` expansion +/// The format string doesn't exist in the HIR, so we reassemble it from source code #[derive(Debug)] -pub struct FormatArgsExpn<'tcx> { - /// Span of the first argument, the format string - pub format_string_span: Span, - /// The format string split by formatted args like `{..}` - pub format_string_parts: Vec<Symbol>, - /// Values passed after the format string - pub value_args: Vec<&'tcx Expr<'tcx>>, - /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`) - pub formatters: Vec<(usize, Symbol)>, - /// List of `fmt::v1::Argument { .. }` expressions. If this is empty, - /// then `formatters` represents the format args (`{..}`). - /// If this is non-empty, it represents the format args, and the `position` - /// parameters within the struct expressions are indexes of `formatters`. - pub specs: Vec<&'tcx Expr<'tcx>>, +pub struct FormatString { + /// Span of the whole format string literal, including `[r#]"`. + pub span: Span, + /// Snippet of the whole format string literal, including `[r#]"`. + pub snippet: String, + /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side. + pub style: Option<usize>, + /// The unescaped value of the format string, e.g. `"val – {}"` for the literal + /// `"val \u{2013} {}"`. + pub unescaped: String, + /// The format string split by format args like `{..}`. + pub parts: Vec<Symbol>, } -impl<'tcx> FormatArgsExpn<'tcx> { - /// Parses an expanded `format_args!` or `format_args_nl!` invocation - pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> { - macro_backtrace(expr.span).find(|macro_call| { - matches!( - cx.tcx.item_name(macro_call.def_id), - sym::const_format_args | sym::format_args | sym::format_args_nl - ) - })?; - let mut format_string_span: Option<Span> = None; - let mut format_string_parts: Vec<Symbol> = Vec::new(); - let mut value_args: Vec<&Expr<'_>> = Vec::new(); - let mut formatters: Vec<(usize, Symbol)> = Vec::new(); - let mut specs: Vec<&Expr<'_>> = Vec::new(); - expr_visitor_no_bodies(|e| { - // if we're still inside of the macro definition... - if e.span.ctxt() == expr.span.ctxt() { - // ArgumentV1::new_<format_trait>(<value>) - if_chain! { - if let ExprKind::Call(callee, [val]) = e.kind; - if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind; - if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind; - if path.segments.last().unwrap().ident.name == sym::ArgumentV1; - if seg.ident.name.as_str().starts_with("new_"); - then { - let val_idx = if_chain! { - if val.span.ctxt() == expr.span.ctxt(); - if let ExprKind::Field(_, field) = val.kind; - if let Ok(idx) = field.name.as_str().parse(); - then { - // tuple index - idx - } else { - // assume the value expression is passed directly - formatters.len() - } - }; - let fmt_trait = match seg.ident.name.as_str() { - "new_display" => "Display", - "new_debug" => "Debug", - "new_lower_exp" => "LowerExp", - "new_upper_exp" => "UpperExp", - "new_octal" => "Octal", - "new_pointer" => "Pointer", - "new_binary" => "Binary", - "new_lower_hex" => "LowerHex", - "new_upper_hex" => "UpperHex", - _ => unreachable!(), - }; - formatters.push((val_idx, Symbol::intern(fmt_trait))); - } +impl FormatString { + fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> { + // format_args!(r"a {} b \", 1); + // + // expands to + // + // ::core::fmt::Arguments::new_v1(&["a ", " b \\"], + // &[::core::fmt::ArgumentV1::new_display(&1)]); + // + // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"` + let span = pieces.span; + let snippet = snippet_opt(cx, span)?; + + let (inner, style) = match tokenize(&snippet).next()?.kind { + TokenKind::Literal { kind, .. } => { + let style = match kind { + LiteralKind::Str { .. } => None, + LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()), + _ => return None, + }; + + let start = style.map_or(1, |n| 2 + n); + let end = snippet.len() - style.map_or(1, |n| 1 + n); + + (&snippet[start..end], style) + }, + _ => return None, + }; + + let mode = if style.is_some() { + unescape::Mode::RawStr + } else { + unescape::Mode::Str + }; + + let mut unescaped = String::with_capacity(inner.len()); + unescape_literal(inner, mode, &mut |_, ch| { + unescaped.push(ch.unwrap()); + }); + + let mut parts = Vec::new(); + expr_visitor_no_bodies(|expr| { + if let ExprKind::Lit(lit) = &expr.kind { + if let LitKind::Str(symbol, _) = lit.node { + parts.push(symbol); } - if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind { - if path.segments.last().unwrap().ident.name == sym::Argument { - specs.push(e); - } - } - // walk through the macro expansion - return true; } - // assume that the first expr with a differing context represents - // (and has the span of) the format string - if format_string_span.is_none() { - format_string_span = Some(e.span); - let span = e.span; - // walk the expr and collect string literals which are format string parts - expr_visitor_no_bodies(|e| { - if e.span.ctxt() != span.ctxt() { - // defensive check, probably doesn't happen - return false; - } - if let ExprKind::Lit(lit) = &e.kind { - if let LitKind::Str(symbol, _s) = lit.node { - format_string_parts.push(symbol); - } - } - true - }) - .visit_expr(e); - } else { - // assume that any further exprs with a differing context are value args - value_args.push(e); - } - // don't walk anything not from the macro expansion (e.a. inputs) - false + + true }) - .visit_expr(expr); - Some(FormatArgsExpn { - format_string_span: format_string_span?, - format_string_parts, + .visit_expr(pieces); + + Some(Self { + span, + snippet, + style, + unescaped, + parts, + }) + } +} + +struct FormatArgsValues<'tcx> { + /// See `FormatArgsExpn::value_args` + value_args: Vec<&'tcx Expr<'tcx>>, + /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in + /// `value_args` + pos_to_value_index: Vec<usize>, + /// Used to check if a value is declared inline & to resolve `InnerSpan`s. + format_string_span: SpanData, +} + +impl<'tcx> FormatArgsValues<'tcx> { + fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self { + let mut pos_to_value_index = Vec::new(); + let mut value_args = Vec::new(); + expr_visitor_no_bodies(|expr| { + if expr.span.ctxt() == args.span.ctxt() { + // ArgumentV1::new_<format_trait>(<val>) + // ArgumentV1::from_usize(<val>) + if let ExprKind::Call(callee, [val]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind + && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind + && path.segments.last().unwrap().ident.name == sym::ArgumentV1 + { + let val_idx = if val.span.ctxt() == expr.span.ctxt() + && let ExprKind::Field(_, field) = val.kind + && let Ok(idx) = field.name.as_str().parse() + { + // tuple index + idx + } else { + // assume the value expression is passed directly + pos_to_value_index.len() + }; + + pos_to_value_index.push(val_idx); + } + + true + } else { + // assume that any expr with a differing span is a value + value_args.push(expr); + + false + } + }) + .visit_expr(args); + + Self { value_args, - formatters, - specs, + pos_to_value_index, + format_string_span, + } + } +} + +/// The positions of a format argument's value, precision and width +/// +/// A position is an index into the second argument of `Arguments::new_v1[_formatted]` +#[derive(Debug, Default, Copy, Clone)] +struct ParamPosition { + /// The position stored in `rt::v1::Argument::position`. + value: usize, + /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`. + width: Option<usize>, + /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`. + precision: Option<usize>, +} + +/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)` +fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> { + fn parse_count(expr: &Expr<'_>) -> Option<usize> { + // ::core::fmt::rt::v1::Count::Param(1usize), + if let ExprKind::Call(ctor, [val]) = expr.kind + && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind + && path.segments.last()?.ident.name == sym::Param + && let ExprKind::Lit(lit) = &val.kind + && let LitKind::Int(pos, _) = lit.node + { + Some(pos as usize) + } else { + None + } + } + + if let ExprKind::AddrOf(.., array) = fmt_arg.kind + && let ExprKind::Array(specs) = array.kind + { + Some(specs.iter().map(|spec| { + let mut position = ParamPosition::default(); + + // ::core::fmt::rt::v1::Argument { + // position: 0usize, + // format: ::core::fmt::rt::v1::FormatSpec { + // .. + // precision: ::core::fmt::rt::v1::Count::Implied, + // width: ::core::fmt::rt::v1::Count::Implied, + // }, + // } + + // TODO: this can be made much nicer next sync with `Visitor::visit_expr_field` + if let ExprKind::Struct(_, fields, _) = spec.kind { + for field in fields { + match (field.ident.name, &field.expr.kind) { + (sym::position, ExprKind::Lit(lit)) => { + if let LitKind::Int(pos, _) = lit.node { + position.value = pos as usize; + } + }, + (sym::format, &ExprKind::Struct(_, spec_fields, _)) => { + for spec_field in spec_fields { + match spec_field.ident.name { + sym::precision => { + position.precision = parse_count(spec_field.expr); + }, + sym::width => { + position.width = parse_count(spec_field.expr); + }, + _ => {}, + } + } + }, + _ => {}, + } + } + } + + position + })) + } else { + None + } +} + +/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan` +fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span { + Span::new( + base.lo + BytePos::from_usize(inner.start), + base.lo + BytePos::from_usize(inner.end), + base.ctxt, + base.parent, + ) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FormatParamKind { + /// An implicit parameter , such as `{}` or `{:?}`. + Implicit, + /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`, + /// `{:.0$}` or `{:.*}`. + Numbered, + /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`. + Named(Symbol), + /// An implicit named parameter, such as the `y` in `format!("{y}")`. + NamedInline(Symbol), +} + +/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g. +/// +/// ``` +/// let precision = 2; +/// format!("{:.precision$}", 0.1234); +/// ``` +/// +/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234` +/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2` +#[derive(Debug, Copy, Clone)] +pub struct FormatParam<'tcx> { + /// The expression this parameter refers to. + pub value: &'tcx Expr<'tcx>, + /// How this parameter refers to its `value`. + pub kind: FormatParamKind, + /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters. + /// + /// ```text + /// format!("{}, { }, {0}, {name}", ...); + /// ^ ~~ ~ ~~~~ + /// ``` + pub span: Span, +} + +impl<'tcx> FormatParam<'tcx> { + fn new( + mut kind: FormatParamKind, + position: usize, + inner: rpf::InnerSpan, + values: &FormatArgsValues<'tcx>, + ) -> Option<Self> { + let value_index = *values.pos_to_value_index.get(position)?; + let value = *values.value_args.get(value_index)?; + let span = span_from_inner(values.format_string_span, inner); + + // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points + // into the format string + if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) { + kind = FormatParamKind::NamedInline(name); + } + + Some(Self { value, kind, span }) + } +} + +/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and +/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers. +#[derive(Debug, Copy, Clone)] +pub enum Count<'tcx> { + /// Specified with a literal number, stores the value. + Is(usize, Span), + /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be + /// `FormatParamKind::Numbered`. + Param(FormatParam<'tcx>), + /// Not specified. + Implied, +} + +impl<'tcx> Count<'tcx> { + fn new( + count: rpf::Count<'_>, + position: Option<usize>, + inner: Option<rpf::InnerSpan>, + values: &FormatArgsValues<'tcx>, + ) -> Option<Self> { + Some(match count { + rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)), + rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new( + FormatParamKind::Named(Symbol::intern(name)), + position?, + span, + values, + )?), + rpf::Count::CountIsParam(_) => { + Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?) + }, + rpf::Count::CountImplied => Self::Implied, }) } - /// Finds a nested call to `format_args!` within a `format!`-like macro call + pub fn is_implied(self) -> bool { + matches!(self, Count::Implied) + } + + pub fn param(self) -> Option<FormatParam<'tcx>> { + match self { + Count::Param(param) => Some(param), + _ => None, + } + } +} + +/// Specification for the formatting of an argument in the format string. See +/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings. +#[derive(Debug)] +pub struct FormatSpec<'tcx> { + /// Optionally specified character to fill alignment with. + pub fill: Option<char>, + /// Optionally specified alignment. + pub align: Alignment, + /// Packed version of various flags provided, see [`rustc_parse_format::Flag`]. + pub flags: u32, + /// Represents either the maximum width or the integer precision. + pub precision: Count<'tcx>, + /// The minimum width, will be padded according to `width`/`align` + pub width: Count<'tcx>, + /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for + /// `{:?}`. + pub r#trait: Symbol, + pub trait_span: Option<Span>, +} + +impl<'tcx> FormatSpec<'tcx> { + fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> { + Some(Self { + fill: spec.fill, + align: spec.align, + flags: spec.flags, + precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?, + width: Count::new(spec.width, positions.width, spec.width_span, values)?, + r#trait: match spec.ty { + "" => sym::Display, + "?" => sym::Debug, + "o" => sym!(Octal), + "x" => sym!(LowerHex), + "X" => sym!(UpperHex), + "p" => sym::Pointer, + "b" => sym!(Binary), + "e" => sym!(LowerExp), + "E" => sym!(UpperExp), + _ => return None, + }, + trait_span: spec + .ty_span + .map(|span| span_from_inner(values.format_string_span, span)), + }) + } + + /// Returns true if this format spec would change the contents of a string when formatted + pub fn has_string_formatting(&self) -> bool { + self.r#trait != sym::Display || !self.width.is_implied() || !self.precision.is_implied() + } +} + +/// A format argument, such as `{}`, `{foo:?}`. +#[derive(Debug)] +pub struct FormatArg<'tcx> { + /// The parameter the argument refers to. + pub param: FormatParam<'tcx>, + /// How to format `param`. + pub format: FormatSpec<'tcx>, + /// span of the whole argument, `{..}`. + pub span: Span, +} + +/// A parsed `format_args!` expansion. +#[derive(Debug)] +pub struct FormatArgsExpn<'tcx> { + /// The format string literal. + pub format_string: FormatString, + // The format arguments, such as `{:?}`. + pub args: Vec<FormatArg<'tcx>>, + /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will + /// include this added newline. + pub newline: bool, + /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for + /// `format!("{x} {} {y}", 1, z + 2)`. + value_args: Vec<&'tcx Expr<'tcx>>, +} + +impl<'tcx> FormatArgsExpn<'tcx> { + pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> { + let macro_name = macro_backtrace(expr.span) + .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) + .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?; + let newline = macro_name == sym::format_args_nl; + + // ::core::fmt::Arguments::new_v1(pieces, args) + // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg) + if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind + && is_path_diagnostic_item(cx, ty, sym::Arguments) + && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted") + { + let format_string = FormatString::new(cx, pieces)?; + + let mut parser = rpf::Parser::new( + &format_string.unescaped, + format_string.style, + Some(format_string.snippet.clone()), + // `format_string.unescaped` does not contain the appended newline + false, + rpf::ParseMode::Format, + ); + + let parsed_args = parser + .by_ref() + .filter_map(|piece| match piece { + rpf::Piece::NextArgument(a) => Some(a), + rpf::Piece::String(_) => None, + }) + .collect_vec(); + if !parser.errors.is_empty() { + return None; + } + + let positions = if let Some(fmt_arg) = rest.first() { + // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse + // them. + + Either::Left(parse_rt_fmt(fmt_arg)?) + } else { + // If no format specs are given, the positions are in the given order and there are + // no `precision`/`width`s to consider. + + Either::Right((0..).map(|n| ParamPosition { + value: n, + width: None, + precision: None, + })) + }; + + let values = FormatArgsValues::new(args, format_string.span.data()); + + let args = izip!(positions, parsed_args, parser.arg_places) + .map(|(position, parsed_arg, arg_span)| { + Some(FormatArg { + param: FormatParam::new( + match parsed_arg.position { + rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit, + rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered, + // NamedInline is handled by `FormatParam::new()` + rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)), + }, + position.value, + parsed_arg.position_span, + &values, + )?, + format: FormatSpec::new(parsed_arg.format, position, &values)?, + span: span_from_inner(values.format_string_span, arg_span), + }) + }) + .collect::<Option<Vec<_>>>()?; + + Some(Self { + format_string, + args, + value_args: values.value_args, + newline, + }) + } else { + None + } + } + pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> { let mut format_args = None; expr_visitor_no_bodies(|e| { @@ -466,88 +845,23 @@ impl<'tcx> FormatArgsExpn<'tcx> { format_args } - /// Returns a vector of `FormatArgsArg`. - pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> { - if self.specs.is_empty() { - let args = std::iter::zip(&self.value_args, &self.formatters) - .map(|(value, &(_, format_trait))| FormatArgsArg { - value, - format_trait, - spec: None, - }) - .collect(); - return Some(args); - } - self.specs - .iter() - .map(|spec| { - if_chain! { - // struct `core::fmt::rt::v1::Argument` - if let ExprKind::Struct(_, fields, _) = spec.kind; - if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position); - if let ExprKind::Lit(lit) = &position_field.expr.kind; - if let LitKind::Int(position, _) = lit.node; - if let Ok(i) = usize::try_from(position); - if let Some(&(j, format_trait)) = self.formatters.get(i); - then { - Some(FormatArgsArg { - value: self.value_args[j], - format_trait, - spec: Some(spec), - }) - } else { - None - } - } - }) - .collect() - } - /// Source callsite span of all inputs pub fn inputs_span(&self) -> Span { match *self.value_args { - [] => self.format_string_span, + [] => self.format_string.span, [.., last] => self - .format_string_span - .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())), + .format_string + .span + .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())), } } -} -/// Type representing a `FormatArgsExpn`'s format arguments -pub struct FormatArgsArg<'tcx> { - /// An element of `value_args` according to `position` - pub value: &'tcx Expr<'tcx>, - /// An element of `args` according to `position` - pub format_trait: Symbol, - /// An element of `specs` - pub spec: Option<&'tcx Expr<'tcx>>, -} - -impl<'tcx> FormatArgsArg<'tcx> { - /// Returns true if any formatting parameters are used that would have an effect on strings, - /// like `{:+2}` instead of just `{}`. - pub fn has_string_formatting(&self) -> bool { - self.spec.map_or(false, |spec| { - // `!` because these conditions check that `self` is unformatted. - !if_chain! { - // struct `core::fmt::rt::v1::Argument` - if let ExprKind::Struct(_, fields, _) = spec.kind; - if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format); - // struct `core::fmt::rt::v1::FormatSpec` - if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind; - if subfields.iter().all(|field| match field.ident.name { - sym::precision | sym::width => match field.expr.kind { - ExprKind::Path(QPath::Resolved(_, path)) => { - path.segments.last().unwrap().ident.name == sym::Implied - } - _ => false, - } - _ => true, - }); - then { true } else { false } - } - }) + /// Iterator of all format params, both values and those referenced by `width`/`precision`s. + pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> { + self.args + .iter() + .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()]) + .flatten() } } diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 9e238c6f1ac..62020e21c81 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -13,7 +13,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,62,0 { BOOL_THEN_SOME } - 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN } + 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR } 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } 1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS } 1,50,0 { BOOL_THEN } @@ -32,8 +32,8 @@ msrv_aliases! { 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,28,0 { FROM_BOOL } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,24,0 { IS_ASCII_DIGIT } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } 1,16,0 { STR_REPEAT } - 1,24,0 { IS_ASCII_DIGIT } } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 8d697a301c4..fb0d34e02ee 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -71,7 +71,6 @@ pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"]; pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"]; pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; -#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; #[cfg(feature = "internal")] pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; @@ -96,6 +95,7 @@ pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwL pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"]; pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"]; +pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekable"]; pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"]; #[cfg_attr(not(unix), allow(clippy::invalid_paths))] pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"]; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e7d670766a0..5a7f9568441 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -43,14 +43,6 @@ pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool } } -/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty` -pub fn contains_ty<'tcx>(ty: Ty<'tcx>, other_ty: Ty<'tcx>) -> bool { - ty.walk().any(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => other_ty == inner_ty, - GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, - }) -} - /// Walks into `ty` and returns `true` if any inner type is an instance of the given adt /// constructor. pub fn contains_adt_constructor<'tcx>(ty: Ty<'tcx>, adt: AdtDef<'tcx>) -> bool { @@ -410,7 +402,7 @@ pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { peel(ty, 0) } -/// Peels off all references on the type.Returns the underlying type, the number of references +/// Peels off all references on the type. Returns the underlying type, the number of references /// removed, and whether the pointer is ultimately mutable or not. pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { @@ -839,3 +831,53 @@ pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tc }) .unwrap_or(false) } + +/// Comes up with an "at least" guesstimate for the type's size, not taking into +/// account the layout of type parameters. +pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { + use rustc_middle::ty::layout::LayoutOf; + if !is_normalizable(cx, cx.param_env, ty) { + return 0; + } + match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) { + (Ok(size), _) => size, + (Err(_), ty::Tuple(list)) => list.as_substs().types().map(|t| approx_ty_size(cx, t)).sum(), + (Err(_), ty::Array(t, n)) => { + n.try_eval_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t) + }, + (Err(_), ty::Adt(def, subst)) if def.is_struct() => def + .variants() + .iter() + .map(|v| { + v.fields + .iter() + .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst))) + .sum::<u64>() + }) + .sum(), + (Err(_), ty::Adt(def, subst)) if def.is_enum() => def + .variants() + .iter() + .map(|v| { + v.fields + .iter() + .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst))) + .sum::<u64>() + }) + .max() + .unwrap_or_default(), + (Err(_), ty::Adt(def, subst)) if def.is_union() => def + .variants() + .iter() + .map(|v| { + v.fields + .iter() + .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst))) + .max() + .unwrap_or_default() + }) + .max() + .unwrap_or_default(), + (Err(_), _) => 0, + } +} diff --git a/rust-toolchain b/rust-toolchain index 7e14df4feea..85b60fefd60 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-08-11" +channel = "nightly-2022-08-27" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/rustc_tools_util/src/lib.rs b/rustc_tools_util/src/lib.rs index 5f289918a7c..429dddc42ea 100644 --- a/rustc_tools_util/src/lib.rs +++ b/rustc_tools_util/src/lib.rs @@ -84,7 +84,7 @@ impl std::fmt::Debug for VersionInfo { #[must_use] pub fn get_commit_hash() -> Option<String> { std::process::Command::new("git") - .args(&["rev-parse", "--short", "HEAD"]) + .args(["rev-parse", "--short", "HEAD"]) .output() .ok() .and_then(|r| String::from_utf8(r.stdout).ok()) @@ -93,7 +93,7 @@ pub fn get_commit_hash() -> Option<String> { #[must_use] pub fn get_commit_date() -> Option<String> { std::process::Command::new("git") - .args(&["log", "-1", "--date=short", "--pretty=format:%cd"]) + .args(["log", "-1", "--date=short", "--pretty=format:%cd"]) .output() .ok() .and_then(|r| String::from_utf8(r.stdout).ok()) diff --git a/tests/check-fmt.rs b/tests/check-fmt.rs index 0defd45b68b..e106583de4a 100644 --- a/tests/check-fmt.rs +++ b/tests/check-fmt.rs @@ -13,7 +13,7 @@ fn fmt() { let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let output = Command::new("cargo") .current_dir(root_dir) - .args(&["dev", "fmt", "--check"]) + .args(["dev", "fmt", "--check"]) .output() .unwrap(); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 92ac1a2be56..ba6186e599e 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -393,8 +393,8 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[ "search_is_some.rs", "single_component_path_imports_nested_first.rs", "string_add.rs", + "suspicious_to_owned.rs", "toplevel_ref_arg_non_rustfix.rs", - "trait_duplication_in_bounds.rs", "unit_arg.rs", "unnecessary_clone.rs", "unnecessary_lazy_eval_unfixable.rs", @@ -404,16 +404,23 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[ ]; fn check_rustfix_coverage() { - let missing_coverage_path = Path::new("target/debug/test/ui/rustfix_missing_coverage.txt"); + let missing_coverage_path = Path::new("debug/test/ui/rustfix_missing_coverage.txt"); + let missing_coverage_path = if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") { + PathBuf::from(target_dir).join(missing_coverage_path) + } else { + missing_coverage_path.to_path_buf() + }; if let Ok(missing_coverage_contents) = std::fs::read_to_string(missing_coverage_path) { assert!(RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS.iter().is_sorted_by_key(Path::new)); - for rs_path in missing_coverage_contents.lines() { - if Path::new(rs_path).starts_with("tests/ui/crashes") { + for rs_file in missing_coverage_contents.lines() { + let rs_path = Path::new(rs_file); + if rs_path.starts_with("tests/ui/crashes") { continue; } - let filename = Path::new(rs_path).strip_prefix("tests/ui/").unwrap(); + assert!(rs_path.starts_with("tests/ui/"), "{:?}", rs_file); + let filename = rs_path.strip_prefix("tests/ui/").unwrap(); assert!( RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS .binary_search_by_key(&filename, Path::new) @@ -421,7 +428,7 @@ fn check_rustfix_coverage() { "`{}` runs `MachineApplicable` diagnostics but is missing a `run-rustfix` annotation. \ Please either add `// run-rustfix` at the top of the file or add the file to \ `RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS` in `tests/compile-test.rs`.", - rs_path, + rs_file, ); } } diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 5697e8680cd..961525bbd91 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -87,11 +87,11 @@ fn run_clippy_for_package(project: &str, args: &[&str]) { if cfg!(feature = "internal") { // internal lints only exist if we build with the internal feature - command.args(&["-D", "clippy::internal"]); + command.args(["-D", "clippy::internal"]); } else { // running a clippy built without internal lints on the clippy source // that contains e.g. `allow(clippy::invalid_paths)` - command.args(&["-A", "unknown_lints"]); + command.args(["-A", "unknown_lints"]); } let output = command.output().unwrap(); diff --git a/tests/integration.rs b/tests/integration.rs index c64425fa01a..23a9bef3ccc 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -19,7 +19,7 @@ fn integration_test() { repo_dir.push(crate_name); let st = Command::new("git") - .args(&[ + .args([ OsStr::new("clone"), OsStr::new("--depth=1"), OsStr::new(&repo_url), @@ -37,7 +37,7 @@ fn integration_test() { .current_dir(repo_dir) .env("RUST_BACKTRACE", "full") .env("CARGO_TARGET_DIR", target_dir) - .args(&[ + .args([ "clippy", "--all-targets", "--all-features", diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index c3aae1a9aa2..2e0f4e76075 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -19,7 +19,7 @@ impl Message { // we don't want the first letter after "error: ", "help: " ... to be capitalized // also no punctuation (except for "?" ?) at the end of a line static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| { - RegexSet::new(&[ + RegexSet::new([ r"error: [A-Z]", r"help: [A-Z]", r"warning: [A-Z]", @@ -37,7 +37,7 @@ impl Message { // sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or // we want to ask a question ending in "?" static EXCEPTIONS_SET: LazyLock<RegexSet> = LazyLock::new(|| { - RegexSet::new(&[ + RegexSet::new([ r"\.\.\.$", r".*C-like enum variant discriminant is not portable to 32-bit targets", r".*Intel x86 assembly syntax used", diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 9f8e778b3b9..a52a0b5289f 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -19,6 +19,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie enforced-import-renames enum-variant-name-threshold enum-variant-size-threshold + large-error-threshold literal-representation-threshold max-fn-params-bools max-include-file-size diff --git a/tests/ui/case_sensitive_file_extension_comparisons.rs b/tests/ui/case_sensitive_file_extension_comparisons.rs index 0d65071af15..6f0485b5279 100644 --- a/tests/ui/case_sensitive_file_extension_comparisons.rs +++ b/tests/ui/case_sensitive_file_extension_comparisons.rs @@ -14,31 +14,31 @@ fn is_rust_file(filename: &str) -> bool { fn main() { // std::string::String and &str should trigger the lint failure with .ext12 - let _ = String::from("").ends_with(".ext12"); + let _ = String::new().ends_with(".ext12"); let _ = "str".ends_with(".ext12"); // The test struct should not trigger the lint failure with .ext12 TestStruct {}.ends_with(".ext12"); // std::string::String and &str should trigger the lint failure with .EXT12 - let _ = String::from("").ends_with(".EXT12"); + let _ = String::new().ends_with(".EXT12"); let _ = "str".ends_with(".EXT12"); // The test struct should not trigger the lint failure with .EXT12 TestStruct {}.ends_with(".EXT12"); // Should not trigger the lint failure with .eXT12 - let _ = String::from("").ends_with(".eXT12"); + let _ = String::new().ends_with(".eXT12"); let _ = "str".ends_with(".eXT12"); TestStruct {}.ends_with(".eXT12"); // Should not trigger the lint failure with .EXT123 (too long) - let _ = String::from("").ends_with(".EXT123"); + let _ = String::new().ends_with(".EXT123"); let _ = "str".ends_with(".EXT123"); TestStruct {}.ends_with(".EXT123"); // Shouldn't fail if it doesn't start with a dot - let _ = String::from("").ends_with("a.ext"); + let _ = String::new().ends_with("a.ext"); let _ = "str".ends_with("a.extA"); TestStruct {}.ends_with("a.ext"); } diff --git a/tests/ui/case_sensitive_file_extension_comparisons.stderr b/tests/ui/case_sensitive_file_extension_comparisons.stderr index 05b98169f2d..5d9a043edb9 100644 --- a/tests/ui/case_sensitive_file_extension_comparisons.stderr +++ b/tests/ui/case_sensitive_file_extension_comparisons.stderr @@ -8,10 +8,10 @@ LL | filename.ends_with(".rs") = help: consider using a case-insensitive comparison instead error: case-sensitive file extension comparison - --> $DIR/case_sensitive_file_extension_comparisons.rs:17:30 + --> $DIR/case_sensitive_file_extension_comparisons.rs:17:27 | -LL | let _ = String::from("").ends_with(".ext12"); - | ^^^^^^^^^^^^^^^^^^^ +LL | let _ = String::new().ends_with(".ext12"); + | ^^^^^^^^^^^^^^^^^^^ | = help: consider using a case-insensitive comparison instead @@ -24,10 +24,10 @@ LL | let _ = "str".ends_with(".ext12"); = help: consider using a case-insensitive comparison instead error: case-sensitive file extension comparison - --> $DIR/case_sensitive_file_extension_comparisons.rs:24:30 + --> $DIR/case_sensitive_file_extension_comparisons.rs:24:27 | -LL | let _ = String::from("").ends_with(".EXT12"); - | ^^^^^^^^^^^^^^^^^^^ +LL | let _ = String::new().ends_with(".EXT12"); + | ^^^^^^^^^^^^^^^^^^^ | = help: consider using a case-insensitive comparison instead diff --git a/tests/ui/cast_raw_slice_pointer_cast.fixed b/tests/ui/cast_raw_slice_pointer_cast.fixed new file mode 100644 index 00000000000..b70c1912951 --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast.fixed @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::cast_slice_from_raw_parts)] + +#[allow(unused_imports, unused_unsafe)] +fn main() { + let mut vec = vec![0u8; 1]; + let ptr: *const u8 = vec.as_ptr(); + let mptr = vec.as_mut_ptr(); + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + { + use core::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + } + { + use std::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + } +} diff --git a/tests/ui/cast_raw_slice_pointer_cast.rs b/tests/ui/cast_raw_slice_pointer_cast.rs new file mode 100644 index 00000000000..c1b316765c9 --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast.rs @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::cast_slice_from_raw_parts)] + +#[allow(unused_imports, unused_unsafe)] +fn main() { + let mut vec = vec![0u8; 1]; + let ptr: *const u8 = vec.as_ptr(); + let mptr = vec.as_mut_ptr(); + let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *const [u8] }; + let _: *const [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) } as *const [u8]; + { + use core::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + } + { + use std::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + } +} diff --git a/tests/ui/cast_raw_slice_pointer_cast.stderr b/tests/ui/cast_raw_slice_pointer_cast.stderr new file mode 100644 index 00000000000..f07801c197c --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast.stderr @@ -0,0 +1,46 @@ +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:9:35 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *const [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | + = note: `-D clippy::cast-slice-from-raw-parts` implied by `-D warnings` + +error: casting the result of `from_raw_parts_mut` to *mut [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:10:35 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:11:26 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:14:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:16:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:20:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> $DIR/cast_raw_slice_pointer_cast.rs:22:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: aborting due to 7 previous errors + diff --git a/tests/ui/collapsible_str_replace.fixed b/tests/ui/collapsible_str_replace.fixed new file mode 100644 index 00000000000..49fc9a9629e --- /dev/null +++ b/tests/ui/collapsible_str_replace.fixed @@ -0,0 +1,73 @@ +// run-rustfix + +#![warn(clippy::collapsible_str_replace)] + +fn get_filter() -> char { + 'u' +} + +fn main() { + let d = 'd'; + let p = 'p'; + let s = 's'; + let u = 'u'; + let l = "l"; + + let mut iter = ["l", "z"].iter(); + + // LINT CASES + let _ = "hesuo worpd".replace(['s', 'u'], "l"); + + let _ = "hesuo worpd".replace(['s', 'u'], l); + + let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l"); + + let _ = "hesuo worpd" + .replace(['s', 'u', 'p', 'd'], "l"); + + let _ = "hesuo world".replace([s, 'u'], "l"); + + let _ = "hesuo worpd".replace([s, 'u', 'p'], "l"); + + let _ = "hesuo worpd".replace([s, u, 'p'], "l"); + + let _ = "hesuo worpd".replace([s, u, p], "l"); + + let _ = "hesuo worlp".replace(['s', 'u'], "l").replace('p', "d"); + + let _ = "hesuo worpd".replace('s', "x").replace(['u', 'p'], "l"); + + // Note: Future iterations could lint `replace(|c| matches!(c, "su" | 'd' | 'p'), "l")` + let _ = "hesudo worpd".replace("su", "l").replace(['d', 'p'], "l"); + + let _ = "hesudo worpd".replace([d, 'p'], "l").replace("su", "l"); + + let _ = "hesuo world".replace([get_filter(), 's'], "l"); + + // NO LINT CASES + let _ = "hesuo world".replace('s', "l").replace('u', "p"); + + let _ = "hesuo worpd".replace('s', "l").replace('p', l); + + let _ = "hesudo worpd".replace('d', "l").replace("su", "l").replace('p', "l"); + + // Note: Future iterations of `collapsible_str_replace` might lint this and combine to `[s, u, p]` + let _ = "hesuo worpd".replace([s, u], "l").replace([u, p], "l"); + + let _ = "hesuo worpd".replace(['s', 'u'], "l").replace(['u', 'p'], "l"); + + let _ = "hesuo worpd".replace('s', "l").replace(['u', 'p'], "l"); + + let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l").replace('r', "l"); + + let _ = "hesuo worpd".replace(['s', 'u', 'p'], l).replace('r', l); + + let _ = "hesuo worpd".replace(['s', u, 'p'], "l").replace('r', "l"); + + let _ = "hesuo worpd".replace([s, u], "l").replace(p, "l"); + + // Regression test + let _ = "hesuo worpd" + .replace('u', iter.next().unwrap()) + .replace('s', iter.next().unwrap()); +} diff --git a/tests/ui/collapsible_str_replace.rs b/tests/ui/collapsible_str_replace.rs new file mode 100644 index 00000000000..e3e25c4146f --- /dev/null +++ b/tests/ui/collapsible_str_replace.rs @@ -0,0 +1,76 @@ +// run-rustfix + +#![warn(clippy::collapsible_str_replace)] + +fn get_filter() -> char { + 'u' +} + +fn main() { + let d = 'd'; + let p = 'p'; + let s = 's'; + let u = 'u'; + let l = "l"; + + let mut iter = ["l", "z"].iter(); + + // LINT CASES + let _ = "hesuo worpd".replace('s', "l").replace('u', "l"); + + let _ = "hesuo worpd".replace('s', l).replace('u', l); + + let _ = "hesuo worpd".replace('s', "l").replace('u', "l").replace('p', "l"); + + let _ = "hesuo worpd" + .replace('s', "l") + .replace('u', "l") + .replace('p', "l") + .replace('d', "l"); + + let _ = "hesuo world".replace(s, "l").replace('u', "l"); + + let _ = "hesuo worpd".replace(s, "l").replace('u', "l").replace('p', "l"); + + let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace('p', "l"); + + let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace(p, "l"); + + let _ = "hesuo worlp".replace('s', "l").replace('u', "l").replace('p', "d"); + + let _ = "hesuo worpd".replace('s', "x").replace('u', "l").replace('p', "l"); + + // Note: Future iterations could lint `replace(|c| matches!(c, "su" | 'd' | 'p'), "l")` + let _ = "hesudo worpd".replace("su", "l").replace('d', "l").replace('p', "l"); + + let _ = "hesudo worpd".replace(d, "l").replace('p', "l").replace("su", "l"); + + let _ = "hesuo world".replace(get_filter(), "l").replace('s', "l"); + + // NO LINT CASES + let _ = "hesuo world".replace('s', "l").replace('u', "p"); + + let _ = "hesuo worpd".replace('s', "l").replace('p', l); + + let _ = "hesudo worpd".replace('d', "l").replace("su", "l").replace('p', "l"); + + // Note: Future iterations of `collapsible_str_replace` might lint this and combine to `[s, u, p]` + let _ = "hesuo worpd".replace([s, u], "l").replace([u, p], "l"); + + let _ = "hesuo worpd".replace(['s', 'u'], "l").replace(['u', 'p'], "l"); + + let _ = "hesuo worpd".replace('s', "l").replace(['u', 'p'], "l"); + + let _ = "hesuo worpd".replace(['s', 'u', 'p'], "l").replace('r', "l"); + + let _ = "hesuo worpd".replace(['s', 'u', 'p'], l).replace('r', l); + + let _ = "hesuo worpd".replace(['s', u, 'p'], "l").replace('r', "l"); + + let _ = "hesuo worpd".replace([s, u], "l").replace(p, "l"); + + // Regression test + let _ = "hesuo worpd" + .replace('u', iter.next().unwrap()) + .replace('s', iter.next().unwrap()); +} diff --git a/tests/ui/collapsible_str_replace.stderr b/tests/ui/collapsible_str_replace.stderr new file mode 100644 index 00000000000..8e3daf3b898 --- /dev/null +++ b/tests/ui/collapsible_str_replace.stderr @@ -0,0 +1,86 @@ +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:19:27 + | +LL | let _ = "hesuo worpd".replace('s', "l").replace('u', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], "l")` + | + = note: `-D clippy::collapsible-str-replace` implied by `-D warnings` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:21:27 + | +LL | let _ = "hesuo worpd".replace('s', l).replace('u', l); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], l)` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:23:27 + | +LL | let _ = "hesuo worpd".replace('s', "l").replace('u', "l").replace('p', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u', 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:26:10 + | +LL | .replace('s', "l") + | __________^ +LL | | .replace('u', "l") +LL | | .replace('p', "l") +LL | | .replace('d', "l"); + | |__________________________^ help: replace with: `replace(['s', 'u', 'p', 'd'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:31:27 + | +LL | let _ = "hesuo world".replace(s, "l").replace('u', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, 'u'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:33:27 + | +LL | let _ = "hesuo worpd".replace(s, "l").replace('u', "l").replace('p', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, 'u', 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:35:27 + | +LL | let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace('p', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, u, 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:37:27 + | +LL | let _ = "hesuo worpd".replace(s, "l").replace(u, "l").replace(p, "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([s, u, p], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:39:27 + | +LL | let _ = "hesuo worlp".replace('s', "l").replace('u', "l").replace('p', "d"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['s', 'u'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:41:45 + | +LL | let _ = "hesuo worpd".replace('s', "x").replace('u', "l").replace('p', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['u', 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:44:47 + | +LL | let _ = "hesudo worpd".replace("su", "l").replace('d', "l").replace('p', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace(['d', 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:46:28 + | +LL | let _ = "hesudo worpd".replace(d, "l").replace('p', "l").replace("su", "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([d, 'p'], "l")` + +error: used consecutive `str::replace` call + --> $DIR/collapsible_str_replace.rs:48:27 + | +LL | let _ = "hesuo world".replace(get_filter(), "l").replace('s', "l"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `replace([get_filter(), 's'], "l")` + +error: aborting due to 13 previous errors + diff --git a/tests/ui/expect.rs b/tests/ui/expect.rs index 1073acf6f0c..d742595e14d 100644 --- a/tests/ui/expect.rs +++ b/tests/ui/expect.rs @@ -6,8 +6,9 @@ fn expect_option() { } fn expect_result() { - let res: Result<u8, ()> = Ok(0); + let res: Result<u8, u8> = Ok(0); let _ = res.expect(""); + let _ = res.expect_err(""); } fn main() { diff --git a/tests/ui/expect.stderr b/tests/ui/expect.stderr index ab28aac4556..904c0904645 100644 --- a/tests/ui/expect.stderr +++ b/tests/ui/expect.stderr @@ -15,5 +15,13 @@ LL | let _ = res.expect(""); | = help: if this value is an `Err`, it will panic -error: aborting due to 2 previous errors +error: used `expect_err()` on `a Result` value + --> $DIR/expect.rs:11:13 + | +LL | let _ = res.expect_err(""); + | ^^^^^^^^^^^^^^^^^^ + | + = help: if this value is an `Ok`, it will panic + +error: aborting due to 3 previous errors diff --git a/tests/ui/floating_point_exp.fixed b/tests/ui/floating_point_exp.fixed index ae7805fdf01..c86a502d15f 100644 --- a/tests/ui/floating_point_exp.fixed +++ b/tests/ui/floating_point_exp.fixed @@ -5,6 +5,7 @@ fn main() { let x = 2f32; let _ = x.exp_m1(); let _ = x.exp_m1() + 2.0; + let _ = (x as f32).exp_m1() + 2.0; // Cases where the lint shouldn't be applied let _ = x.exp() - 2.0; let _ = x.exp() - 1.0 * 2.0; diff --git a/tests/ui/floating_point_exp.rs b/tests/ui/floating_point_exp.rs index 27e0b9bcbc9..e59589f912a 100644 --- a/tests/ui/floating_point_exp.rs +++ b/tests/ui/floating_point_exp.rs @@ -5,6 +5,7 @@ fn main() { let x = 2f32; let _ = x.exp() - 1.0; let _ = x.exp() - 1.0 + 2.0; + let _ = (x as f32).exp() - 1.0 + 2.0; // Cases where the lint shouldn't be applied let _ = x.exp() - 2.0; let _ = x.exp() - 1.0 * 2.0; diff --git a/tests/ui/floating_point_exp.stderr b/tests/ui/floating_point_exp.stderr index 5cd999ad47c..f84eede1987 100644 --- a/tests/ui/floating_point_exp.stderr +++ b/tests/ui/floating_point_exp.stderr @@ -13,16 +13,22 @@ LL | let _ = x.exp() - 1.0 + 2.0; | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` error: (e.pow(x) - 1) can be computed more accurately - --> $DIR/floating_point_exp.rs:13:13 + --> $DIR/floating_point_exp.rs:8:13 + | +LL | let _ = (x as f32).exp() - 1.0 + 2.0; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).exp_m1()` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:14:13 | LL | let _ = x.exp() - 1.0; | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` error: (e.pow(x) - 1) can be computed more accurately - --> $DIR/floating_point_exp.rs:14:13 + --> $DIR/floating_point_exp.rs:15:13 | LL | let _ = x.exp() - 1.0 + 2.0; | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/floating_point_log.fixed b/tests/ui/floating_point_log.fixed index 5b487bb8fcf..4def9300bb7 100644 --- a/tests/ui/floating_point_log.fixed +++ b/tests/ui/floating_point_log.fixed @@ -12,6 +12,7 @@ fn check_log_base() { let _ = x.ln(); let _ = x.log2(); let _ = x.ln(); + let _ = (x as f32).log2(); let x = 1f64; let _ = x.log2(); diff --git a/tests/ui/floating_point_log.rs b/tests/ui/floating_point_log.rs index 01181484e7d..1e04caa7d2a 100644 --- a/tests/ui/floating_point_log.rs +++ b/tests/ui/floating_point_log.rs @@ -12,6 +12,7 @@ fn check_log_base() { let _ = x.log(std::f32::consts::E); let _ = x.log(TWO); let _ = x.log(E); + let _ = (x as f32).log(2f32); let x = 1f64; let _ = x.log(2f64); diff --git a/tests/ui/floating_point_log.stderr b/tests/ui/floating_point_log.stderr index 96e5a154441..89800a13a6e 100644 --- a/tests/ui/floating_point_log.stderr +++ b/tests/ui/floating_point_log.stderr @@ -31,25 +31,31 @@ LL | let _ = x.log(E); | ^^^^^^^^ help: consider using: `x.ln()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> $DIR/floating_point_log.rs:17:13 + --> $DIR/floating_point_log.rs:15:13 + | +LL | let _ = (x as f32).log(2f32); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).log2()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:18:13 | LL | let _ = x.log(2f64); | ^^^^^^^^^^^ help: consider using: `x.log2()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> $DIR/floating_point_log.rs:18:13 + --> $DIR/floating_point_log.rs:19:13 | LL | let _ = x.log(10f64); | ^^^^^^^^^^^^ help: consider using: `x.log10()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> $DIR/floating_point_log.rs:19:13 + --> $DIR/floating_point_log.rs:20:13 | LL | let _ = x.log(std::f64::consts::E); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:24:13 + --> $DIR/floating_point_log.rs:25:13 | LL | let _ = (1f32 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` @@ -57,118 +63,118 @@ LL | let _ = (1f32 + 2.).ln(); = note: `-D clippy::imprecise-flops` implied by `-D warnings` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:25:13 + --> $DIR/floating_point_log.rs:26:13 | LL | let _ = (1f32 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:26:13 + --> $DIR/floating_point_log.rs:27:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:27:13 + --> $DIR/floating_point_log.rs:28:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:28:13 + --> $DIR/floating_point_log.rs:29:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:29:13 + --> $DIR/floating_point_log.rs:30:13 | LL | let _ = (1.0 + x.powi(3) / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(3) / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:30:13 + --> $DIR/floating_point_log.rs:31:13 | LL | let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(std::f32::consts::E - 1.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:31:13 + --> $DIR/floating_point_log.rs:32:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:32:13 + --> $DIR/floating_point_log.rs:33:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:33:13 + --> $DIR/floating_point_log.rs:34:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:34:13 + --> $DIR/floating_point_log.rs:35:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:42:13 + --> $DIR/floating_point_log.rs:43:13 | LL | let _ = (1f64 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:43:13 + --> $DIR/floating_point_log.rs:44:13 | LL | let _ = (1f64 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:44:13 + --> $DIR/floating_point_log.rs:45:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:45:13 + --> $DIR/floating_point_log.rs:46:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:46:13 + --> $DIR/floating_point_log.rs:47:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:47:13 + --> $DIR/floating_point_log.rs:48:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:48:13 + --> $DIR/floating_point_log.rs:49:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:49:13 + --> $DIR/floating_point_log.rs:50:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> $DIR/floating_point_log.rs:50:13 + --> $DIR/floating_point_log.rs:51:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` -error: aborting due to 28 previous errors +error: aborting due to 29 previous errors diff --git a/tests/ui/floating_point_logbase.fixed b/tests/ui/floating_point_logbase.fixed index 13962a272d4..936462f9406 100644 --- a/tests/ui/floating_point_logbase.fixed +++ b/tests/ui/floating_point_logbase.fixed @@ -5,6 +5,7 @@ fn main() { let x = 3f32; let y = 5f32; let _ = x.log(y); + let _ = (x as f32).log(y); let _ = x.log(y); let _ = x.log(y); let _ = x.log(y); diff --git a/tests/ui/floating_point_logbase.rs b/tests/ui/floating_point_logbase.rs index 26bc20d5370..0b56fa8fa41 100644 --- a/tests/ui/floating_point_logbase.rs +++ b/tests/ui/floating_point_logbase.rs @@ -5,6 +5,7 @@ fn main() { let x = 3f32; let y = 5f32; let _ = x.ln() / y.ln(); + let _ = (x as f32).ln() / y.ln(); let _ = x.log2() / y.log2(); let _ = x.log10() / y.log10(); let _ = x.log(5f32) / y.log(5f32); diff --git a/tests/ui/floating_point_logbase.stderr b/tests/ui/floating_point_logbase.stderr index 78354c2f62d..384e3554cbb 100644 --- a/tests/ui/floating_point_logbase.stderr +++ b/tests/ui/floating_point_logbase.stderr @@ -9,20 +9,26 @@ LL | let _ = x.ln() / y.ln(); error: log base can be expressed more clearly --> $DIR/floating_point_logbase.rs:8:13 | +LL | let _ = (x as f32).ln() / y.ln(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).log(y)` + +error: log base can be expressed more clearly + --> $DIR/floating_point_logbase.rs:9:13 + | LL | let _ = x.log2() / y.log2(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` error: log base can be expressed more clearly - --> $DIR/floating_point_logbase.rs:9:13 + --> $DIR/floating_point_logbase.rs:10:13 | LL | let _ = x.log10() / y.log10(); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` error: log base can be expressed more clearly - --> $DIR/floating_point_logbase.rs:10:13 + --> $DIR/floating_point_logbase.rs:11:13 | LL | let _ = x.log(5f32) / y.log(5f32); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)` -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/floating_point_powf.fixed b/tests/ui/floating_point_powf.fixed index b0641a100cd..7efe10a10f9 100644 --- a/tests/ui/floating_point_powf.fixed +++ b/tests/ui/floating_point_powf.fixed @@ -11,10 +11,13 @@ fn main() { let _ = (-3.1f32).exp(); let _ = x.sqrt(); let _ = x.cbrt(); + let _ = (x as f32).cbrt(); let _ = x.powi(3); let _ = x.powi(-2); let _ = x.powi(16_777_215); let _ = x.powi(-16_777_215); + let _ = (x as f32).powi(-16_777_215); + let _ = (x as f32).powi(3); // Cases where the lint shouldn't be applied let _ = x.powf(2.1); let _ = x.powf(-2.1); diff --git a/tests/ui/floating_point_powf.rs b/tests/ui/floating_point_powf.rs index a0a2c973900..445080417f2 100644 --- a/tests/ui/floating_point_powf.rs +++ b/tests/ui/floating_point_powf.rs @@ -11,10 +11,13 @@ fn main() { let _ = std::f32::consts::E.powf(-3.1); let _ = x.powf(1.0 / 2.0); let _ = x.powf(1.0 / 3.0); + let _ = (x as f32).powf(1.0 / 3.0); let _ = x.powf(3.0); let _ = x.powf(-2.0); let _ = x.powf(16_777_215.0); let _ = x.powf(-16_777_215.0); + let _ = (x as f32).powf(-16_777_215.0); + let _ = (x as f32).powf(3.0); // Cases where the lint shouldn't be applied let _ = x.powf(2.1); let _ = x.powf(-2.1); diff --git a/tests/ui/floating_point_powf.stderr b/tests/ui/floating_point_powf.stderr index 2422eb911e9..6ee696e6ada 100644 --- a/tests/ui/floating_point_powf.stderr +++ b/tests/ui/floating_point_powf.stderr @@ -50,101 +50,119 @@ LL | let _ = x.powf(1.0 / 3.0); | = note: `-D clippy::imprecise-flops` implied by `-D warnings` -error: exponentiation with integer powers can be computed more efficiently +error: cube-root of a number can be computed more accurately --> $DIR/floating_point_powf.rs:14:13 | +LL | let _ = (x as f32).powf(1.0 / 3.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).cbrt()` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:15:13 + | LL | let _ = x.powf(3.0); | ^^^^^^^^^^^ help: consider using: `x.powi(3)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:15:13 + --> $DIR/floating_point_powf.rs:16:13 | LL | let _ = x.powf(-2.0); | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:16:13 + --> $DIR/floating_point_powf.rs:17:13 | LL | let _ = x.powf(16_777_215.0); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(16_777_215)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:17:13 + --> $DIR/floating_point_powf.rs:18:13 | LL | let _ = x.powf(-16_777_215.0); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-16_777_215)` +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:19:13 + | +LL | let _ = (x as f32).powf(-16_777_215.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).powi(-16_777_215)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:20:13 + | +LL | let _ = (x as f32).powf(3.0); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).powi(3)` + error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:25:13 + --> $DIR/floating_point_powf.rs:28:13 | LL | let _ = 2f64.powf(x); | ^^^^^^^^^^^^ help: consider using: `x.exp2()` error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:26:13 + --> $DIR/floating_point_powf.rs:29:13 | LL | let _ = 2f64.powf(3.1); | ^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp2()` error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:27:13 + --> $DIR/floating_point_powf.rs:30:13 | LL | let _ = 2f64.powf(-3.1); | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp2()` error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:28:13 + --> $DIR/floating_point_powf.rs:31:13 | LL | let _ = std::f64::consts::E.powf(x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()` error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:29:13 + --> $DIR/floating_point_powf.rs:32:13 | LL | let _ = std::f64::consts::E.powf(3.1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp()` error: exponent for bases 2 and e can be computed more accurately - --> $DIR/floating_point_powf.rs:30:13 + --> $DIR/floating_point_powf.rs:33:13 | LL | let _ = std::f64::consts::E.powf(-3.1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp()` error: square-root of a number can be computed more efficiently and accurately - --> $DIR/floating_point_powf.rs:31:13 + --> $DIR/floating_point_powf.rs:34:13 | LL | let _ = x.powf(1.0 / 2.0); | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()` error: cube-root of a number can be computed more accurately - --> $DIR/floating_point_powf.rs:32:13 + --> $DIR/floating_point_powf.rs:35:13 | LL | let _ = x.powf(1.0 / 3.0); | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:33:13 + --> $DIR/floating_point_powf.rs:36:13 | LL | let _ = x.powf(3.0); | ^^^^^^^^^^^ help: consider using: `x.powi(3)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:34:13 + --> $DIR/floating_point_powf.rs:37:13 | LL | let _ = x.powf(-2.0); | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:35:13 + --> $DIR/floating_point_powf.rs:38:13 | LL | let _ = x.powf(-2_147_483_648.0); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-2_147_483_648)` error: exponentiation with integer powers can be computed more efficiently - --> $DIR/floating_point_powf.rs:36:13 + --> $DIR/floating_point_powf.rs:39:13 | LL | let _ = x.powf(2_147_483_647.0); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2_147_483_647)` -error: aborting due to 24 previous errors +error: aborting due to 27 previous errors diff --git a/tests/ui/floating_point_powi.fixed b/tests/ui/floating_point_powi.fixed index 85f7c531e39..5758db7c6c8 100644 --- a/tests/ui/floating_point_powi.fixed +++ b/tests/ui/floating_point_powi.fixed @@ -8,6 +8,7 @@ fn main() { let y = 4f32; let _ = x.mul_add(x, y); let _ = y.mul_add(y, x); + let _ = (y as f32).mul_add(y as f32, x); let _ = x.mul_add(x, y).sqrt(); let _ = y.mul_add(y, x).sqrt(); // Cases where the lint shouldn't be applied diff --git a/tests/ui/floating_point_powi.rs b/tests/ui/floating_point_powi.rs index ece61d1bec4..5926bf1b000 100644 --- a/tests/ui/floating_point_powi.rs +++ b/tests/ui/floating_point_powi.rs @@ -8,6 +8,7 @@ fn main() { let y = 4f32; let _ = x.powi(2) + y; let _ = x + y.powi(2); + let _ = x + (y as f32).powi(2); let _ = (x.powi(2) + y).sqrt(); let _ = (x + y.powi(2)).sqrt(); // Cases where the lint shouldn't be applied diff --git a/tests/ui/floating_point_powi.stderr b/tests/ui/floating_point_powi.stderr index 37d840988bb..a3c74544212 100644 --- a/tests/ui/floating_point_powi.stderr +++ b/tests/ui/floating_point_powi.stderr @@ -15,14 +15,20 @@ LL | let _ = x + y.powi(2); error: multiply and add expressions can be calculated more efficiently and accurately --> $DIR/floating_point_powi.rs:11:13 | -LL | let _ = (x.powi(2) + y).sqrt(); - | ^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` +LL | let _ = x + (y as f32).powi(2); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(y as f32).mul_add(y as f32, x)` error: multiply and add expressions can be calculated more efficiently and accurately --> $DIR/floating_point_powi.rs:12:13 | +LL | let _ = (x.powi(2) + y).sqrt(); + | ^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_powi.rs:13:13 + | LL | let _ = (x + y.powi(2)).sqrt(); | ^^^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)` -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/floating_point_rad.fixed b/tests/ui/floating_point_rad.fixed index ce91fe176c6..27674b8a455 100644 --- a/tests/ui/floating_point_rad.fixed +++ b/tests/ui/floating_point_rad.fixed @@ -8,6 +8,11 @@ pub const fn const_context() { let _ = x * 180f32 / std::f32::consts::PI; } +pub fn issue9391(degrees: i64) { + let _ = (degrees as f64).to_radians(); + let _ = (degrees as f64).to_degrees(); +} + fn main() { let x = 3f32; let _ = x.to_degrees(); diff --git a/tests/ui/floating_point_rad.rs b/tests/ui/floating_point_rad.rs index 8f323498614..f1ea73df398 100644 --- a/tests/ui/floating_point_rad.rs +++ b/tests/ui/floating_point_rad.rs @@ -8,6 +8,11 @@ pub const fn const_context() { let _ = x * 180f32 / std::f32::consts::PI; } +pub fn issue9391(degrees: i64) { + let _ = degrees as f64 * std::f64::consts::PI / 180.0; + let _ = degrees as f64 * 180.0 / std::f64::consts::PI; +} + fn main() { let x = 3f32; let _ = x * 180f32 / std::f32::consts::PI; diff --git a/tests/ui/floating_point_rad.stderr b/tests/ui/floating_point_rad.stderr index f12d3d23f3a..979442f2c24 100644 --- a/tests/ui/floating_point_rad.stderr +++ b/tests/ui/floating_point_rad.stderr @@ -1,40 +1,52 @@ -error: conversion to degrees can be done more accurately - --> $DIR/floating_point_rad.rs:13:13 +error: conversion to radians can be done more accurately + --> $DIR/floating_point_rad.rs:12:13 | -LL | let _ = x * 180f32 / std::f32::consts::PI; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_degrees()` +LL | let _ = degrees as f64 * std::f64::consts::PI / 180.0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(degrees as f64).to_radians()` | = note: `-D clippy::suboptimal-flops` implied by `-D warnings` error: conversion to degrees can be done more accurately - --> $DIR/floating_point_rad.rs:14:13 + --> $DIR/floating_point_rad.rs:13:13 + | +LL | let _ = degrees as f64 * 180.0 / std::f64::consts::PI; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(degrees as f64).to_degrees()` + +error: conversion to degrees can be done more accurately + --> $DIR/floating_point_rad.rs:18:13 + | +LL | let _ = x * 180f32 / std::f32::consts::PI; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_degrees()` + +error: conversion to degrees can be done more accurately + --> $DIR/floating_point_rad.rs:19:13 | LL | let _ = 90. * 180f64 / std::f64::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `90.0_f64.to_degrees()` error: conversion to degrees can be done more accurately - --> $DIR/floating_point_rad.rs:15:13 + --> $DIR/floating_point_rad.rs:20:13 | LL | let _ = 90.5 * 180f64 / std::f64::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `90.5_f64.to_degrees()` error: conversion to radians can be done more accurately - --> $DIR/floating_point_rad.rs:16:13 + --> $DIR/floating_point_rad.rs:21:13 | LL | let _ = x * std::f32::consts::PI / 180f32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_radians()` error: conversion to radians can be done more accurately - --> $DIR/floating_point_rad.rs:17:13 + --> $DIR/floating_point_rad.rs:22:13 | LL | let _ = 90. * std::f32::consts::PI / 180f32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `90.0_f64.to_radians()` error: conversion to radians can be done more accurately - --> $DIR/floating_point_rad.rs:18:13 + --> $DIR/floating_point_rad.rs:23:13 | LL | let _ = 90.5 * std::f32::consts::PI / 180f32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `90.5_f64.to_radians()` -error: aborting due to 6 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed index 6b754f3bd71..b56d6aec508 100644 --- a/tests/ui/format.fixed +++ b/tests/ui/format.fixed @@ -33,7 +33,7 @@ fn main() { format!("foo {}", "bar"); format!("{} bar", "foo"); - let arg: String = "".to_owned(); + let arg = String::new(); arg.to_string(); format!("{:?}", arg); // Don't warn about debug. format!("{:8}", arg); diff --git a/tests/ui/format.rs b/tests/ui/format.rs index ca9826b356e..4c1a3a840ed 100644 --- a/tests/ui/format.rs +++ b/tests/ui/format.rs @@ -35,7 +35,7 @@ fn main() { format!("foo {}", "bar"); format!("{} bar", "foo"); - let arg: String = "".to_owned(); + let arg = String::new(); format!("{}", arg); format!("{:?}", arg); // Don't warn about debug. format!("{:8}", arg); diff --git a/tests/ui/format_args.fixed b/tests/ui/format_args.fixed index 69b5e1c722e..e1c2d4d70be 100644 --- a/tests/ui/format_args.fixed +++ b/tests/ui/format_args.fixed @@ -1,8 +1,6 @@ // run-rustfix -#![allow(unreachable_code)] -#![allow(unused_macros)] -#![allow(unused_variables)] +#![allow(unused)] #![allow(clippy::assertions_on_constants)] #![allow(clippy::eq_op)] #![allow(clippy::print_literal)] @@ -115,3 +113,50 @@ fn main() { // https://github.com/rust-lang/rust-clippy/issues/7903 println!("{foo}{foo:?}", foo = "foo".to_string()); } + +fn issue8643(vendor_id: usize, product_id: usize, name: &str) { + println!( + "{:<9} {:<10} {}", + format!("0x{:x}", vendor_id), + format!("0x{:x}", product_id), + name + ); +} + +// https://github.com/rust-lang/rust-clippy/issues/8855 +mod issue_8855 { + #![allow(dead_code)] + + struct A {} + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "test") + } + } + + fn main() { + let a = A {}; + let b = A {}; + + let x = format!("{} {}", a, b); + dbg!(x); + + let x = format!("{:>6} {:>6}", a, b.to_string()); + dbg!(x); + } +} + +// https://github.com/rust-lang/rust-clippy/issues/9256 +mod issue_9256 { + #![allow(dead_code)] + + fn print_substring(original: &str) { + assert!(original.len() > 10); + println!("{}", &original[..10]); + } + + fn main() { + print_substring("Hello, world!"); + } +} diff --git a/tests/ui/format_args.rs b/tests/ui/format_args.rs index 3a434c5bf00..b9a4d66c28a 100644 --- a/tests/ui/format_args.rs +++ b/tests/ui/format_args.rs @@ -1,8 +1,6 @@ // run-rustfix -#![allow(unreachable_code)] -#![allow(unused_macros)] -#![allow(unused_variables)] +#![allow(unused)] #![allow(clippy::assertions_on_constants)] #![allow(clippy::eq_op)] #![allow(clippy::print_literal)] @@ -115,3 +113,50 @@ fn main() { // https://github.com/rust-lang/rust-clippy/issues/7903 println!("{foo}{foo:?}", foo = "foo".to_string()); } + +fn issue8643(vendor_id: usize, product_id: usize, name: &str) { + println!( + "{:<9} {:<10} {}", + format!("0x{:x}", vendor_id), + format!("0x{:x}", product_id), + name + ); +} + +// https://github.com/rust-lang/rust-clippy/issues/8855 +mod issue_8855 { + #![allow(dead_code)] + + struct A {} + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "test") + } + } + + fn main() { + let a = A {}; + let b = A {}; + + let x = format!("{} {}", a, b.to_string()); + dbg!(x); + + let x = format!("{:>6} {:>6}", a, b.to_string()); + dbg!(x); + } +} + +// https://github.com/rust-lang/rust-clippy/issues/9256 +mod issue_9256 { + #![allow(dead_code)] + + fn print_substring(original: &str) { + assert!(original.len() > 10); + println!("{}", original[..10].to_string()); + } + + fn main() { + print_substring("Hello, world!"); + } +} diff --git a/tests/ui/format_args.stderr b/tests/ui/format_args.stderr index c0cbca50795..aa6e3659b43 100644 --- a/tests/ui/format_args.stderr +++ b/tests/ui/format_args.stderr @@ -1,5 +1,5 @@ error: `to_string` applied to a type that implements `Display` in `format!` args - --> $DIR/format_args.rs:76:72 + --> $DIR/format_args.rs:74:72 | LL | let _ = format!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this @@ -7,124 +7,136 @@ LL | let _ = format!("error: something failed at {}", Location::caller().to_ = note: `-D clippy::to-string-in-format-args` implied by `-D warnings` error: `to_string` applied to a type that implements `Display` in `write!` args - --> $DIR/format_args.rs:80:27 + --> $DIR/format_args.rs:78:27 | LL | Location::caller().to_string() | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `writeln!` args - --> $DIR/format_args.rs:85:27 + --> $DIR/format_args.rs:83:27 | LL | Location::caller().to_string() | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `print!` args - --> $DIR/format_args.rs:87:63 + --> $DIR/format_args.rs:85:63 | LL | print!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:88:65 + --> $DIR/format_args.rs:86:65 | LL | println!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `eprint!` args - --> $DIR/format_args.rs:89:64 + --> $DIR/format_args.rs:87:64 | LL | eprint!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `eprintln!` args - --> $DIR/format_args.rs:90:66 + --> $DIR/format_args.rs:88:66 | LL | eprintln!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `format_args!` args - --> $DIR/format_args.rs:91:77 + --> $DIR/format_args.rs:89:77 | LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `assert!` args - --> $DIR/format_args.rs:92:70 + --> $DIR/format_args.rs:90:70 | LL | assert!(true, "error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `assert_eq!` args - --> $DIR/format_args.rs:93:73 + --> $DIR/format_args.rs:91:73 | LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `assert_ne!` args - --> $DIR/format_args.rs:94:73 + --> $DIR/format_args.rs:92:73 | LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `panic!` args - --> $DIR/format_args.rs:95:63 + --> $DIR/format_args.rs:93:63 | LL | panic!("error: something failed at {}", Location::caller().to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:96:20 + --> $DIR/format_args.rs:94:20 | LL | println!("{}", X(1).to_string()); | ^^^^^^^^^^^^^^^^ help: use this: `*X(1)` error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:97:20 + --> $DIR/format_args.rs:95:20 | LL | println!("{}", Y(&X(1)).to_string()); | ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))` error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:98:24 + --> $DIR/format_args.rs:96:24 | LL | println!("{}", Z(1).to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:99:20 + --> $DIR/format_args.rs:97:20 | LL | println!("{}", x.to_string()); | ^^^^^^^^^^^^^ help: use this: `**x` error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:100:20 + --> $DIR/format_args.rs:98:20 | LL | println!("{}", x_ref.to_string()); | ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref` error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:102:39 + --> $DIR/format_args.rs:100:39 | LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar"); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:103:52 + --> $DIR/format_args.rs:101:52 | LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string()); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:104:39 + --> $DIR/format_args.rs:102:39 | LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo"); | ^^^^^^^^^^^^ help: remove this error: `to_string` applied to a type that implements `Display` in `println!` args - --> $DIR/format_args.rs:105:52 + --> $DIR/format_args.rs:103:52 | LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string()); | ^^^^^^^^^^^^ help: remove this -error: aborting due to 21 previous errors +error: `to_string` applied to a type that implements `Display` in `format!` args + --> $DIR/format_args.rs:142:38 + | +LL | let x = format!("{} {}", a, b.to_string()); + | ^^^^^^^^^^^^ help: remove this + +error: `to_string` applied to a type that implements `Display` in `println!` args + --> $DIR/format_args.rs:156:24 + | +LL | println!("{}", original[..10].to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use this: `&original[..10]` + +error: aborting due to 23 previous errors diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 5f9cebe212a..fa564e23cd2 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -68,7 +68,7 @@ fn main() { &x; x; - let mut a = A("".into()); + let mut a = A(String::new()); let b = a << 0; // no error: non-integer 1 * Meter; // no error: non-integer diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index ca799c9cfac..3d06d2a73b6 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -68,7 +68,7 @@ fn main() { &x >> 0; x >> &0; - let mut a = A("".into()); + let mut a = A(String::new()); let b = a << 0; // no error: non-integer 1 * Meter; // no error: non-integer diff --git a/tests/ui/if_let_mutex.rs b/tests/ui/if_let_mutex.rs index 6cbfafbb38b..321feb0224e 100644 --- a/tests/ui/if_let_mutex.rs +++ b/tests/ui/if_let_mutex.rs @@ -39,4 +39,12 @@ fn if_let_different_mutex() { }; } +fn mutex_ref(mutex: &Mutex<i32>) { + if let Ok(i) = mutex.lock() { + do_stuff(i); + } else { + let _x = mutex.lock(); + }; +} + fn main() {} diff --git a/tests/ui/if_let_mutex.stderr b/tests/ui/if_let_mutex.stderr index e9c4d916332..8a4d5dbac59 100644 --- a/tests/ui/if_let_mutex.stderr +++ b/tests/ui/if_let_mutex.stderr @@ -1,10 +1,14 @@ error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock --> $DIR/if_let_mutex.rs:10:5 | -LL | / if let Err(locked) = m.lock() { +LL | if let Err(locked) = m.lock() { + | ^ - this Mutex will remain locked for the entire `if let`-block... + | _____| + | | LL | | do_stuff(locked); LL | | } else { LL | | let lock = m.lock().unwrap(); + | | - ... and is tried to lock again here, which will always deadlock. LL | | do_stuff(lock); LL | | }; | |_____^ @@ -15,15 +19,35 @@ LL | | }; error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock --> $DIR/if_let_mutex.rs:22:5 | -LL | / if let Some(locked) = m.lock().unwrap().deref() { +LL | if let Some(locked) = m.lock().unwrap().deref() { + | ^ - this Mutex will remain locked for the entire `if let`-block... + | _____| + | | LL | | do_stuff(locked); LL | | } else { LL | | let lock = m.lock().unwrap(); + | | - ... and is tried to lock again here, which will always deadlock. LL | | do_stuff(lock); LL | | }; | |_____^ | = help: move the lock call outside of the `if let ...` expression -error: aborting due to 2 previous errors +error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock + --> $DIR/if_let_mutex.rs:43:5 + | +LL | if let Ok(i) = mutex.lock() { + | ^ ----- this Mutex will remain locked for the entire `if let`-block... + | _____| + | | +LL | | do_stuff(i); +LL | | } else { +LL | | let _x = mutex.lock(); + | | ----- ... and is tried to lock again here, which will always deadlock. +LL | | }; + | |_____^ + | + = help: move the lock call outside of the `if let ...` expression + +error: aborting due to 3 previous errors diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index 8cb22d569f4..c22ace30d2d 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -27,21 +27,21 @@ LL | | }; | = help: consider using `bool::then` like: `matches!(true, true).then(|| { /* snippet */ matches!(true, false) })` -error: this could be simplified with `bool::then` +error: this could be simplified with `bool::then_some` --> $DIR/if_then_some_else_none.rs:23:28 | LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider using `bool::then` like: `(o < 32).then(|| o)` + = help: consider using `bool::then_some` like: `(o < 32).then_some(o)` -error: this could be simplified with `bool::then` +error: this could be simplified with `bool::then_some` --> $DIR/if_then_some_else_none.rs:27:13 | LL | let _ = if !x { Some(0) } else { None }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider using `bool::then` like: `(!x).then(|| 0)` + = help: consider using `bool::then_some` like: `(!x).then_some(0)` error: this could be simplified with `bool::then` --> $DIR/if_then_some_else_none.rs:82:13 diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed new file mode 100644 index 00000000000..bd9b07aefbf --- /dev/null +++ b/tests/ui/iter_on_empty_collections.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::iter_on_empty_collections)] +#![allow(clippy::iter_next_slice, clippy::redundant_clone)] + +fn array() { + assert_eq!(std::iter::empty().next(), Option::<i32>::None); + assert_eq!(std::iter::empty().next(), Option::<&mut i32>::None); + assert_eq!(std::iter::empty().next(), Option::<&i32>::None); + assert_eq!(std::iter::empty().next(), Option::<i32>::None); + assert_eq!(std::iter::empty().next(), Option::<&mut i32>::None); + assert_eq!(std::iter::empty().next(), Option::<&i32>::None); + + // Don't trigger on non-iter methods + let _: Option<String> = None.clone(); + let _: [String; 0] = [].clone(); + + // Don't trigger on match or if branches + let _ = match 123 { + 123 => [].iter(), + _ => ["test"].iter(), + }; + + let _ = if false { ["test"].iter() } else { [].iter() }; +} + +macro_rules! in_macros { + () => { + assert_eq!([].into_iter().next(), Option::<i32>::None); + assert_eq!([].iter_mut().next(), Option::<&mut i32>::None); + assert_eq!([].iter().next(), Option::<&i32>::None); + assert_eq!(None.into_iter().next(), Option::<i32>::None); + assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None); + assert_eq!(None.iter().next(), Option::<&i32>::None); + }; +} + +// Don't trigger on a `None` that isn't std's option +mod custom_option { + #[allow(unused)] + enum CustomOption { + Some(i32), + None, + } + + impl CustomOption { + fn iter(&self) {} + fn iter_mut(&mut self) {} + fn into_iter(self) {} + } + use CustomOption::*; + + pub fn custom_option() { + None.iter(); + None.iter_mut(); + None.into_iter(); + } +} + +fn main() { + array(); + custom_option::custom_option(); + in_macros!(); +} diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs new file mode 100644 index 00000000000..e15ba94bd46 --- /dev/null +++ b/tests/ui/iter_on_empty_collections.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::iter_on_empty_collections)] +#![allow(clippy::iter_next_slice, clippy::redundant_clone)] + +fn array() { + assert_eq!([].into_iter().next(), Option::<i32>::None); + assert_eq!([].iter_mut().next(), Option::<&mut i32>::None); + assert_eq!([].iter().next(), Option::<&i32>::None); + assert_eq!(None.into_iter().next(), Option::<i32>::None); + assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None); + assert_eq!(None.iter().next(), Option::<&i32>::None); + + // Don't trigger on non-iter methods + let _: Option<String> = None.clone(); + let _: [String; 0] = [].clone(); + + // Don't trigger on match or if branches + let _ = match 123 { + 123 => [].iter(), + _ => ["test"].iter(), + }; + + let _ = if false { ["test"].iter() } else { [].iter() }; +} + +macro_rules! in_macros { + () => { + assert_eq!([].into_iter().next(), Option::<i32>::None); + assert_eq!([].iter_mut().next(), Option::<&mut i32>::None); + assert_eq!([].iter().next(), Option::<&i32>::None); + assert_eq!(None.into_iter().next(), Option::<i32>::None); + assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None); + assert_eq!(None.iter().next(), Option::<&i32>::None); + }; +} + +// Don't trigger on a `None` that isn't std's option +mod custom_option { + #[allow(unused)] + enum CustomOption { + Some(i32), + None, + } + + impl CustomOption { + fn iter(&self) {} + fn iter_mut(&mut self) {} + fn into_iter(self) {} + } + use CustomOption::*; + + pub fn custom_option() { + None.iter(); + None.iter_mut(); + None.into_iter(); + } +} + +fn main() { + array(); + custom_option::custom_option(); + in_macros!(); +} diff --git a/tests/ui/iter_on_empty_collections.stderr b/tests/ui/iter_on_empty_collections.stderr new file mode 100644 index 00000000000..cbd61176956 --- /dev/null +++ b/tests/ui/iter_on_empty_collections.stderr @@ -0,0 +1,40 @@ +error: `into_iter` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:6:16 + | +LL | assert_eq!([].into_iter().next(), Option::<i32>::None); + | ^^^^^^^^^^^^^^ help: try: `std::iter::empty()` + | + = note: `-D clippy::iter-on-empty-collections` implied by `-D warnings` + +error: `iter_mut` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:7:16 + | +LL | assert_eq!([].iter_mut().next(), Option::<&mut i32>::None); + | ^^^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: `iter` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:8:16 + | +LL | assert_eq!([].iter().next(), Option::<&i32>::None); + | ^^^^^^^^^ help: try: `std::iter::empty()` + +error: `into_iter` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:9:16 + | +LL | assert_eq!(None.into_iter().next(), Option::<i32>::None); + | ^^^^^^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: `iter_mut` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:10:16 + | +LL | assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None); + | ^^^^^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: `iter` call on an empty collection + --> $DIR/iter_on_empty_collections.rs:11:16 + | +LL | assert_eq!(None.iter().next(), Option::<&i32>::None); + | ^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/iter_on_single_items.fixed b/tests/ui/iter_on_single_items.fixed new file mode 100644 index 00000000000..1fa4b03641b --- /dev/null +++ b/tests/ui/iter_on_single_items.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::iter_on_single_items)] +#![allow(clippy::iter_next_slice, clippy::redundant_clone)] + +fn array() { + assert_eq!(std::iter::once(123).next(), Some(123)); + assert_eq!(std::iter::once(&mut 123).next(), Some(&mut 123)); + assert_eq!(std::iter::once(&123).next(), Some(&123)); + assert_eq!(std::iter::once(123).next(), Some(123)); + assert_eq!(std::iter::once(&mut 123).next(), Some(&mut 123)); + assert_eq!(std::iter::once(&123).next(), Some(&123)); + + // Don't trigger on non-iter methods + let _: Option<String> = Some("test".to_string()).clone(); + let _: [String; 1] = ["test".to_string()].clone(); + + // Don't trigger on match or if branches + let _ = match 123 { + 123 => [].iter(), + _ => ["test"].iter(), + }; + + let _ = if false { ["test"].iter() } else { [].iter() }; +} + +macro_rules! in_macros { + () => { + assert_eq!([123].into_iter().next(), Some(123)); + assert_eq!([123].iter_mut().next(), Some(&mut 123)); + assert_eq!([123].iter().next(), Some(&123)); + assert_eq!(Some(123).into_iter().next(), Some(123)); + assert_eq!(Some(123).iter_mut().next(), Some(&mut 123)); + assert_eq!(Some(123).iter().next(), Some(&123)); + }; +} + +// Don't trigger on a `Some` that isn't std's option +mod custom_option { + #[allow(unused)] + enum CustomOption { + Some(i32), + None, + } + + impl CustomOption { + fn iter(&self) {} + fn iter_mut(&mut self) {} + fn into_iter(self) {} + } + use CustomOption::*; + + pub fn custom_option() { + Some(3).iter(); + Some(3).iter_mut(); + Some(3).into_iter(); + } +} + +fn main() { + array(); + custom_option::custom_option(); + in_macros!(); +} diff --git a/tests/ui/iter_on_single_items.rs b/tests/ui/iter_on_single_items.rs new file mode 100644 index 00000000000..ea96d8066c5 --- /dev/null +++ b/tests/ui/iter_on_single_items.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::iter_on_single_items)] +#![allow(clippy::iter_next_slice, clippy::redundant_clone)] + +fn array() { + assert_eq!([123].into_iter().next(), Some(123)); + assert_eq!([123].iter_mut().next(), Some(&mut 123)); + assert_eq!([123].iter().next(), Some(&123)); + assert_eq!(Some(123).into_iter().next(), Some(123)); + assert_eq!(Some(123).iter_mut().next(), Some(&mut 123)); + assert_eq!(Some(123).iter().next(), Some(&123)); + + // Don't trigger on non-iter methods + let _: Option<String> = Some("test".to_string()).clone(); + let _: [String; 1] = ["test".to_string()].clone(); + + // Don't trigger on match or if branches + let _ = match 123 { + 123 => [].iter(), + _ => ["test"].iter(), + }; + + let _ = if false { ["test"].iter() } else { [].iter() }; +} + +macro_rules! in_macros { + () => { + assert_eq!([123].into_iter().next(), Some(123)); + assert_eq!([123].iter_mut().next(), Some(&mut 123)); + assert_eq!([123].iter().next(), Some(&123)); + assert_eq!(Some(123).into_iter().next(), Some(123)); + assert_eq!(Some(123).iter_mut().next(), Some(&mut 123)); + assert_eq!(Some(123).iter().next(), Some(&123)); + }; +} + +// Don't trigger on a `Some` that isn't std's option +mod custom_option { + #[allow(unused)] + enum CustomOption { + Some(i32), + None, + } + + impl CustomOption { + fn iter(&self) {} + fn iter_mut(&mut self) {} + fn into_iter(self) {} + } + use CustomOption::*; + + pub fn custom_option() { + Some(3).iter(); + Some(3).iter_mut(); + Some(3).into_iter(); + } +} + +fn main() { + array(); + custom_option::custom_option(); + in_macros!(); +} diff --git a/tests/ui/iter_on_single_items.stderr b/tests/ui/iter_on_single_items.stderr new file mode 100644 index 00000000000..d6c54711636 --- /dev/null +++ b/tests/ui/iter_on_single_items.stderr @@ -0,0 +1,40 @@ +error: `into_iter` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:6:16 + | +LL | assert_eq!([123].into_iter().next(), Some(123)); + | ^^^^^^^^^^^^^^^^^ help: try: `std::iter::once(123)` + | + = note: `-D clippy::iter-on-single-items` implied by `-D warnings` + +error: `iter_mut` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:7:16 + | +LL | assert_eq!([123].iter_mut().next(), Some(&mut 123)); + | ^^^^^^^^^^^^^^^^ help: try: `std::iter::once(&mut 123)` + +error: `iter` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:8:16 + | +LL | assert_eq!([123].iter().next(), Some(&123)); + | ^^^^^^^^^^^^ help: try: `std::iter::once(&123)` + +error: `into_iter` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:9:16 + | +LL | assert_eq!(Some(123).into_iter().next(), Some(123)); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::once(123)` + +error: `iter_mut` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:10:16 + | +LL | assert_eq!(Some(123).iter_mut().next(), Some(&mut 123)); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::once(&mut 123)` + +error: `iter` call on a collection with only one item + --> $DIR/iter_on_single_items.rs:11:16 + | +LL | assert_eq!(Some(123).iter().next(), Some(&123)); + | ^^^^^^^^^^^^^^^^ help: try: `std::iter::once(&123)` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/manual_string_new.fixed b/tests/ui/manual_string_new.fixed new file mode 100644 index 00000000000..a376411bfbc --- /dev/null +++ b/tests/ui/manual_string_new.fixed @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::manual_string_new)] + +macro_rules! create_strings_from_macro { + // When inside a macro, nothing should warn to prevent false positives. + ($some_str:expr) => { + let _: String = $some_str.into(); + let _ = $some_str.to_string(); + }; +} + +fn main() { + // Method calls + let _ = String::new(); + let _ = "no warning".to_string(); + + let _ = String::new(); + let _ = "no warning".to_owned(); + + let _: String = String::new(); + let _: String = "no warning".into(); + + let _: SomeOtherStruct = "no warning".into(); + let _: SomeOtherStruct = "".into(); // No warning too. We are not converting into String. + + // Calls + let _ = String::new(); + let _ = String::new(); + let _ = String::from("no warning"); + let _ = SomeOtherStruct::from("no warning"); + let _ = SomeOtherStruct::from(""); // Again: no warning. + + let _ = String::new(); + let _ = String::try_from("no warning").unwrap(); + let _ = String::try_from("no warning").expect("this should not warn"); + let _ = SomeOtherStruct::try_from("no warning").unwrap(); + let _ = SomeOtherStruct::try_from("").unwrap(); // Again: no warning. + + let _: String = String::new(); + let _: String = From::from("no warning"); + let _: SomeOtherStruct = From::from("no warning"); + let _: SomeOtherStruct = From::from(""); // Again: no warning. + + let _: String = String::new(); + let _: String = TryFrom::try_from("no warning").unwrap(); + let _: String = TryFrom::try_from("no warning").expect("this should not warn"); + let _: String = String::new(); + let _: SomeOtherStruct = TryFrom::try_from("no_warning").unwrap(); + let _: SomeOtherStruct = TryFrom::try_from("").unwrap(); // Again: no warning. + + // Macros (never warn) + create_strings_from_macro!(""); + create_strings_from_macro!("Hey"); +} + +struct SomeOtherStruct {} + +impl From<&str> for SomeOtherStruct { + fn from(_value: &str) -> Self { + Self {} + } +} diff --git a/tests/ui/manual_string_new.rs b/tests/ui/manual_string_new.rs new file mode 100644 index 00000000000..6bfc52fb1bc --- /dev/null +++ b/tests/ui/manual_string_new.rs @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::manual_string_new)] + +macro_rules! create_strings_from_macro { + // When inside a macro, nothing should warn to prevent false positives. + ($some_str:expr) => { + let _: String = $some_str.into(); + let _ = $some_str.to_string(); + }; +} + +fn main() { + // Method calls + let _ = "".to_string(); + let _ = "no warning".to_string(); + + let _ = "".to_owned(); + let _ = "no warning".to_owned(); + + let _: String = "".into(); + let _: String = "no warning".into(); + + let _: SomeOtherStruct = "no warning".into(); + let _: SomeOtherStruct = "".into(); // No warning too. We are not converting into String. + + // Calls + let _ = String::from(""); + let _ = <String>::from(""); + let _ = String::from("no warning"); + let _ = SomeOtherStruct::from("no warning"); + let _ = SomeOtherStruct::from(""); // Again: no warning. + + let _ = String::try_from("").unwrap(); + let _ = String::try_from("no warning").unwrap(); + let _ = String::try_from("no warning").expect("this should not warn"); + let _ = SomeOtherStruct::try_from("no warning").unwrap(); + let _ = SomeOtherStruct::try_from("").unwrap(); // Again: no warning. + + let _: String = From::from(""); + let _: String = From::from("no warning"); + let _: SomeOtherStruct = From::from("no warning"); + let _: SomeOtherStruct = From::from(""); // Again: no warning. + + let _: String = TryFrom::try_from("").unwrap(); + let _: String = TryFrom::try_from("no warning").unwrap(); + let _: String = TryFrom::try_from("no warning").expect("this should not warn"); + let _: String = TryFrom::try_from("").expect("this should warn"); + let _: SomeOtherStruct = TryFrom::try_from("no_warning").unwrap(); + let _: SomeOtherStruct = TryFrom::try_from("").unwrap(); // Again: no warning. + + // Macros (never warn) + create_strings_from_macro!(""); + create_strings_from_macro!("Hey"); +} + +struct SomeOtherStruct {} + +impl From<&str> for SomeOtherStruct { + fn from(_value: &str) -> Self { + Self {} + } +} diff --git a/tests/ui/manual_string_new.stderr b/tests/ui/manual_string_new.stderr new file mode 100644 index 00000000000..e5ecfc61947 --- /dev/null +++ b/tests/ui/manual_string_new.stderr @@ -0,0 +1,58 @@ +error: empty String is being created manually + --> $DIR/manual_string_new.rs:15:13 + | +LL | let _ = "".to_string(); + | ^^^^^^^^^^^^^^ help: consider using: `String::new()` + | + = note: `-D clippy::manual-string-new` implied by `-D warnings` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:18:13 + | +LL | let _ = "".to_owned(); + | ^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:21:21 + | +LL | let _: String = "".into(); + | ^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:28:13 + | +LL | let _ = String::from(""); + | ^^^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:29:13 + | +LL | let _ = <String>::from(""); + | ^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:34:13 + | +LL | let _ = String::try_from("").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:40:21 + | +LL | let _: String = From::from(""); + | ^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:45:21 + | +LL | let _: String = TryFrom::try_from("").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: empty String is being created manually + --> $DIR/manual_string_new.rs:48:21 + | +LL | let _: String = TryFrom::try_from("").expect("this should warn"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()` + +error: aborting due to 9 previous errors + diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed index 1ccbfda64b7..95ca571d07b 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_expr_like_matches_macro.fixed @@ -167,4 +167,29 @@ fn main() { _ => false, }; } + + let x = ' '; + // ignore if match block contains comment + let _line_comments = match x { + // numbers are bad! + '1' | '2' | '3' => true, + // spaces are very important to be true. + ' ' => true, + // as are dots + '.' => true, + _ => false, + }; + + let _block_comments = match x { + /* numbers are bad! + */ + '1' | '2' | '3' => true, + /* spaces are very important to be true. + */ + ' ' => true, + /* as are dots + */ + '.' => true, + _ => false, + }; } diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_expr_like_matches_macro.rs index a49991f5941..3b9c8cadadc 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_expr_like_matches_macro.rs @@ -208,4 +208,29 @@ fn main() { _ => false, }; } + + let x = ' '; + // ignore if match block contains comment + let _line_comments = match x { + // numbers are bad! + '1' | '2' | '3' => true, + // spaces are very important to be true. + ' ' => true, + // as are dots + '.' => true, + _ => false, + }; + + let _block_comments = match x { + /* numbers are bad! + */ + '1' | '2' | '3' => true, + /* spaces are very important to be true. + */ + ' ' => true, + /* as are dots + */ + '.' => true, + _ => false, + }; } diff --git a/tests/ui/multi_assignments.rs b/tests/ui/multi_assignments.rs new file mode 100644 index 00000000000..b186bf8bbdb --- /dev/null +++ b/tests/ui/multi_assignments.rs @@ -0,0 +1,9 @@ +#![warn(clippy::multi_assignments)] +fn main() { + let (mut a, mut b, mut c, mut d) = ((), (), (), ()); + a = b = c; + a = b = c = d; + a = b = { c }; + a = { b = c }; + a = (b = c); +} diff --git a/tests/ui/multi_assignments.stderr b/tests/ui/multi_assignments.stderr new file mode 100644 index 00000000000..d6c42bb698c --- /dev/null +++ b/tests/ui/multi_assignments.stderr @@ -0,0 +1,40 @@ +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:4:5 + | +LL | a = b = c; + | ^^^^^^^^^ + | + = note: `-D clippy::multi-assignments` implied by `-D warnings` + +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:5:5 + | +LL | a = b = c = d; + | ^^^^^^^^^^^^^ + +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:5:9 + | +LL | a = b = c = d; + | ^^^^^^^^^ + +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:6:5 + | +LL | a = b = { c }; + | ^^^^^^^^^^^^^ + +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:7:5 + | +LL | a = { b = c }; + | ^^^^^^^^^^^^^ + +error: assignments don't nest intuitively + --> $DIR/multi_assignments.rs:8:5 + | +LL | a = (b = c); + | ^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index bfd2725ecaa..8cf93bd2481 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![feature(lint_reasons)] +#![feature(custom_inner_attributes, lint_reasons)] #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] @@ -127,6 +127,20 @@ fn main() { 0 } } + + let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap(); + let _ = std::path::Path::new(".").join("."); + deref_target_is_x(X); + multiple_constraints([[""]]); + multiple_constraints_normalizes_to_same(X, X); + let _ = Some("").unwrap_or(""); + + only_sized(&""); // Don't lint. `Sized` is only bound + let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound + let _ = Box::new(&""); // Don't lint. Type parameter appears in return type + ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter + refs_only(&()); // Don't lint. `&T` implements trait, but `T` doesn't + multiple_constraints_normalizes_to_different(&[[""]], &[""]); // Don't lint. Projected type appears in arguments } #[allow(clippy::needless_borrowed_reference)] @@ -183,3 +197,104 @@ mod issue9160 { } } } + +#[derive(Clone, Copy)] +struct X; + +impl std::ops::Deref for X { + type Target = X; + fn deref(&self) -> &Self::Target { + self + } +} + +fn deref_target_is_x<T>(_: T) +where + T: std::ops::Deref<Target = X>, +{ +} + +fn multiple_constraints<T, U, V, X, Y>(_: T) +where + T: IntoIterator<Item = U> + IntoIterator<Item = X>, + U: IntoIterator<Item = V>, + V: AsRef<str>, + X: IntoIterator<Item = Y>, + Y: AsRef<std::ffi::OsStr>, +{ +} + +fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V) +where + T: std::ops::Deref<Target = U>, + U: std::ops::Deref<Target = V>, +{ +} + +fn only_sized<T>(_: T) {} + +fn ref_as_ref_path<T: 'static>(_: &'static T) +where + &'static T: AsRef<std::path::Path>, +{ +} + +trait RefsOnly { + type Referent; +} + +impl<T> RefsOnly for &T { + type Referent = T; +} + +fn refs_only<T, U>(_: T) +where + T: RefsOnly<Referent = U>, +{ +} + +fn multiple_constraints_normalizes_to_different<T, U, V>(_: T, _: U) +where + T: IntoIterator<Item = U>, + U: IntoIterator<Item = V>, + V: AsRef<str>, +{ +} + +// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321 +#[allow(dead_code)] +mod copyable_iterator { + #[derive(Clone, Copy)] + struct Iter; + impl Iterator for Iter { + type Item = (); + fn next(&mut self) -> Option<Self::Item> { + None + } + } + fn takes_iter(_: impl Iterator) {} + fn dont_warn(mut x: Iter) { + takes_iter(&mut x); + } + fn warn(mut x: &mut Iter) { + takes_iter(&mut x) + } +} + +mod under_msrv { + #![allow(dead_code)] + #![clippy::msrv = "1.52.0"] + + fn foo() { + let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + } +} + +mod meets_msrv { + #![allow(dead_code)] + #![clippy::msrv = "1.53.0"] + + fn foo() { + let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap(); + } +} diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index c457d8c5471..fd9b2a11df9 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -1,6 +1,6 @@ // run-rustfix -#![feature(lint_reasons)] +#![feature(custom_inner_attributes, lint_reasons)] #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] @@ -127,6 +127,20 @@ fn main() { 0 } } + + let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + let _ = std::path::Path::new(".").join(&&"."); + deref_target_is_x(&X); + multiple_constraints(&[[""]]); + multiple_constraints_normalizes_to_same(&X, X); + let _ = Some("").unwrap_or(&""); + + only_sized(&""); // Don't lint. `Sized` is only bound + let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound + let _ = Box::new(&""); // Don't lint. Type parameter appears in return type + ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter + refs_only(&()); // Don't lint. `&T` implements trait, but `T` doesn't + multiple_constraints_normalizes_to_different(&[[""]], &[""]); // Don't lint. Projected type appears in arguments } #[allow(clippy::needless_borrowed_reference)] @@ -183,3 +197,104 @@ mod issue9160 { } } } + +#[derive(Clone, Copy)] +struct X; + +impl std::ops::Deref for X { + type Target = X; + fn deref(&self) -> &Self::Target { + self + } +} + +fn deref_target_is_x<T>(_: T) +where + T: std::ops::Deref<Target = X>, +{ +} + +fn multiple_constraints<T, U, V, X, Y>(_: T) +where + T: IntoIterator<Item = U> + IntoIterator<Item = X>, + U: IntoIterator<Item = V>, + V: AsRef<str>, + X: IntoIterator<Item = Y>, + Y: AsRef<std::ffi::OsStr>, +{ +} + +fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V) +where + T: std::ops::Deref<Target = U>, + U: std::ops::Deref<Target = V>, +{ +} + +fn only_sized<T>(_: T) {} + +fn ref_as_ref_path<T: 'static>(_: &'static T) +where + &'static T: AsRef<std::path::Path>, +{ +} + +trait RefsOnly { + type Referent; +} + +impl<T> RefsOnly for &T { + type Referent = T; +} + +fn refs_only<T, U>(_: T) +where + T: RefsOnly<Referent = U>, +{ +} + +fn multiple_constraints_normalizes_to_different<T, U, V>(_: T, _: U) +where + T: IntoIterator<Item = U>, + U: IntoIterator<Item = V>, + V: AsRef<str>, +{ +} + +// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321 +#[allow(dead_code)] +mod copyable_iterator { + #[derive(Clone, Copy)] + struct Iter; + impl Iterator for Iter { + type Item = (); + fn next(&mut self) -> Option<Self::Item> { + None + } + } + fn takes_iter(_: impl Iterator) {} + fn dont_warn(mut x: Iter) { + takes_iter(&mut x); + } + fn warn(mut x: &mut Iter) { + takes_iter(&mut x) + } +} + +mod under_msrv { + #![allow(dead_code)] + #![clippy::msrv = "1.52.0"] + + fn foo() { + let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + } +} + +mod meets_msrv { + #![allow(dead_code)] + #![clippy::msrv = "1.53.0"] + + fn foo() { + let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + } +} diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index 66588689d81..5af68706d4b 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -120,17 +120,59 @@ error: this expression creates a reference which is immediately dereferenced by LL | (&&5).foo(); | ^^^^^ help: change this to: `(&5)` +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:131:51 + | +LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]` + +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:132:44 + | +LL | let _ = std::path::Path::new(".").join(&&"."); + | ^^^^^ help: change this to: `"."` + +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:133:23 + | +LL | deref_target_is_x(&X); + | ^^ help: change this to: `X` + +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:134:26 + | +LL | multiple_constraints(&[[""]]); + | ^^^^^^^ help: change this to: `[[""]]` + +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:135:45 + | +LL | multiple_constraints_normalizes_to_same(&X, X); + | ^^ help: change this to: `X` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:136:32 + | +LL | let _ = Some("").unwrap_or(&""); + | ^^^ help: change this to: `""` + error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:173:13 + --> $DIR/needless_borrow.rs:187:13 | LL | (&self.f)() | ^^^^^^^^^ help: change this to: `(self.f)` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:182:13 + --> $DIR/needless_borrow.rs:196:13 | LL | (&mut self.f)() | ^^^^^^^^^^^^^ help: change this to: `(self.f)` -error: aborting due to 22 previous errors +error: the borrowed expression implements the required traits + --> $DIR/needless_borrow.rs:298:55 + | +LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap(); + | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]` + +error: aborting due to 29 previous errors diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index 1f11d1f8d56..12a9ace1ee6 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -112,3 +112,192 @@ fn allow_test() { let v = [1].iter().collect::<Vec<_>>(); v.into_iter().collect::<HashSet<_>>(); } + +mod issue_8553 { + fn test_for() { + let vec = vec![1, 2]; + let w: Vec<usize> = vec.iter().map(|i| i * i).collect(); + + for i in 0..2 { + // Do not lint, because this method call is in the loop + w.contains(&i); + } + + for i in 0..2 { + let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); + // Do lint + y.contains(&i); + for j in 0..2 { + // Do not lint, because this method call is in the loop + z.contains(&j); + } + } + + // Do not lint, because this variable is used. + w.contains(&0); + } + + fn test_while() { + let vec = vec![1, 2]; + let x: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut n = 0; + while n > 1 { + // Do not lint, because this method call is in the loop + x.contains(&n); + n += 1; + } + + while n > 2 { + let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); + // Do lint + y.contains(&n); + n += 1; + while n > 4 { + // Do not lint, because this method call is in the loop + z.contains(&n); + n += 1; + } + } + } + + fn test_loop() { + let vec = vec![1, 2]; + let x: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut n = 0; + loop { + if n < 1 { + // Do not lint, because this method call is in the loop + x.contains(&n); + n += 1; + } else { + break; + } + } + + loop { + if n < 2 { + let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); + // Do lint + y.contains(&n); + n += 1; + loop { + if n < 4 { + // Do not lint, because this method call is in the loop + z.contains(&n); + n += 1; + } else { + break; + } + } + } else { + break; + } + } + } + + fn test_while_let() { + let vec = vec![1, 2]; + let x: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let optional = Some(0); + let mut n = 0; + while let Some(value) = optional { + if n < 1 { + // Do not lint, because this method call is in the loop + x.contains(&n); + n += 1; + } else { + break; + } + } + + while let Some(value) = optional { + let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); + if n < 2 { + // Do lint + y.contains(&n); + n += 1; + } else { + break; + } + + while let Some(value) = optional { + if n < 4 { + // Do not lint, because this method call is in the loop + z.contains(&n); + n += 1; + } else { + break; + } + } + } + } + + fn test_if_cond() { + let vec = vec![1, 2]; + let v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let w = v.iter().collect::<Vec<_>>(); + // Do lint + for _ in 0..w.len() { + todo!(); + } + } + + fn test_if_cond_false_case() { + let vec = vec![1, 2]; + let v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let w = v.iter().collect::<Vec<_>>(); + // Do not lint, because w is used. + for _ in 0..w.len() { + todo!(); + } + + w.len(); + } + + fn test_while_cond() { + let mut vec = vec![1, 2]; + let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut w = v.iter().collect::<Vec<_>>(); + // Do lint + while 1 == w.len() { + todo!(); + } + } + + fn test_while_cond_false_case() { + let mut vec = vec![1, 2]; + let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut w = v.iter().collect::<Vec<_>>(); + // Do not lint, because w is used. + while 1 == w.len() { + todo!(); + } + + w.len(); + } + + fn test_while_let_cond() { + let mut vec = vec![1, 2]; + let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut w = v.iter().collect::<Vec<_>>(); + // Do lint + while let Some(i) = Some(w.len()) { + todo!(); + } + } + + fn test_while_let_cond_false_case() { + let mut vec = vec![1, 2]; + let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); + let mut w = v.iter().collect::<Vec<_>>(); + // Do not lint, because w is used. + while let Some(i) = Some(w.len()) { + todo!(); + } + w.len(); + } +} diff --git a/tests/ui/needless_collect_indirect.stderr b/tests/ui/needless_collect_indirect.stderr index 0f5e78f9119..9f0880cc606 100644 --- a/tests/ui/needless_collect_indirect.stderr +++ b/tests/ui/needless_collect_indirect.stderr @@ -125,5 +125,122 @@ LL ~ LL ~ sample.iter().count() | -error: aborting due to 9 previous errors +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:127:59 + | +LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + | ^^^^^^^ +... +LL | y.contains(&i); + | -------------- the iterator could be used here instead + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL ~ +LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); +LL | // Do lint +LL ~ vec.iter().map(|k| k * k).any(|x| x == i); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:152:59 + | +LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + | ^^^^^^^ +... +LL | y.contains(&n); + | -------------- the iterator could be used here instead + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL ~ +LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); +LL | // Do lint +LL ~ vec.iter().map(|k| k * k).any(|x| x == n); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:181:63 + | +LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + | ^^^^^^^ +... +LL | y.contains(&n); + | -------------- the iterator could be used here instead + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL ~ +LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); +LL | // Do lint +LL ~ vec.iter().map(|k| k * k).any(|x| x == n); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:217:59 + | +LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect(); + | ^^^^^^^ +... +LL | y.contains(&n); + | -------------- the iterator could be used here instead + | +help: check if the original Iterator contains an element instead of collecting then checking + | +LL ~ +LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect(); +LL | if n < 2 { +LL | // Do lint +LL ~ vec.iter().map(|k| k * k).any(|x| x == n); + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:242:26 + | +LL | let w = v.iter().collect::<Vec<_>>(); + | ^^^^^^^ +LL | // Do lint +LL | for _ in 0..w.len() { + | ------- the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL ~ +LL | // Do lint +LL ~ for _ in 0..v.iter().count() { + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:264:30 + | +LL | let mut w = v.iter().collect::<Vec<_>>(); + | ^^^^^^^ +LL | // Do lint +LL | while 1 == w.len() { + | ------- the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL ~ +LL | // Do lint +LL ~ while 1 == v.iter().count() { + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:286:30 + | +LL | let mut w = v.iter().collect::<Vec<_>>(); + | ^^^^^^^ +LL | // Do lint +LL | while let Some(i) = Some(w.len()) { + | ------- the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL ~ +LL | // Do lint +LL ~ while let Some(i) = Some(v.iter().count()) { + | + +error: aborting due to 16 previous errors diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index 0c9178fb85e..7e47406798c 100644 --- a/tests/ui/needless_match.fixed +++ b/tests/ui/needless_match.fixed @@ -207,4 +207,43 @@ impl Tr for Result<i32, i32> { } } +mod issue9084 { + fn wildcard_if() { + let mut some_bool = true; + let e = Some(1); + + // should lint + let _ = e; + + // should lint + let _ = e; + + // should not lint + let _ = match e { + _ if some_bool => e, + _ => Some(2), + }; + + // should not lint + let _ = match e { + Some(i) => Some(i + 1), + _ if some_bool => e, + _ => e, + }; + + // should not lint (guard has side effects) + let _ = match e { + Some(i) => Some(i), + _ if { + some_bool = false; + some_bool + } => + { + e + }, + _ => e, + }; + } +} + fn main() {} diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs index f66f01d7cca..809c694bf40 100644 --- a/tests/ui/needless_match.rs +++ b/tests/ui/needless_match.rs @@ -244,4 +244,50 @@ impl Tr for Result<i32, i32> { } } +mod issue9084 { + fn wildcard_if() { + let mut some_bool = true; + let e = Some(1); + + // should lint + let _ = match e { + _ if some_bool => e, + _ => e, + }; + + // should lint + let _ = match e { + Some(i) => Some(i), + _ if some_bool => e, + _ => e, + }; + + // should not lint + let _ = match e { + _ if some_bool => e, + _ => Some(2), + }; + + // should not lint + let _ = match e { + Some(i) => Some(i + 1), + _ if some_bool => e, + _ => e, + }; + + // should not lint (guard has side effects) + let _ = match e { + Some(i) => Some(i), + _ if { + some_bool = false; + some_bool + } => + { + e + }, + _ => e, + }; + } +} + fn main() {} diff --git a/tests/ui/needless_match.stderr b/tests/ui/needless_match.stderr index 5bc79800a1a..28e78441c25 100644 --- a/tests/ui/needless_match.stderr +++ b/tests/ui/needless_match.stderr @@ -109,5 +109,26 @@ LL | | Complex::D(E::VariantB(ea, eb), b) => Complex::D(E::VariantB( LL | | }; | |_________^ help: replace it with: `ce` -error: aborting due to 11 previous errors +error: this match expression is unnecessary + --> $DIR/needless_match.rs:253:17 + | +LL | let _ = match e { + | _________________^ +LL | | _ if some_bool => e, +LL | | _ => e, +LL | | }; + | |_________^ help: replace it with: `e` + +error: this match expression is unnecessary + --> $DIR/needless_match.rs:259:17 + | +LL | let _ = match e { + | _________________^ +LL | | Some(i) => Some(i), +LL | | _ if some_bool => e, +LL | | _ => e, +LL | | }; + | |_________^ help: replace it with: `e` + +error: aborting due to 13 previous errors diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 0bc0d0011ef..87c8fc03b3c 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -228,13 +228,9 @@ fn needless_return_macro() -> String { format!("Hello {}", "world!") } -fn check_expect() -> bool { - if true { - // no error! - return true; - } - #[expect(clippy::needless_return)] - return true; +fn issue_9361() -> i32 { + #[allow(clippy::integer_arithmetic)] + return 1 + 2; } fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index eb9f72e8e78..5a86e656255 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -228,13 +228,9 @@ fn needless_return_macro() -> String { return format!("Hello {}", "world!"); } -fn check_expect() -> bool { - if true { - // no error! - return true; - } - #[expect(clippy::needless_return)] - return true; +fn issue_9361() -> i32 { + #[allow(clippy::integer_arithmetic)] + return 1 + 2; } fn main() {} diff --git a/tests/ui/only_used_in_recursion.rs b/tests/ui/only_used_in_recursion.rs index 5768434f988..f71e8ead519 100644 --- a/tests/ui/only_used_in_recursion.rs +++ b/tests/ui/only_used_in_recursion.rs @@ -1,122 +1,113 @@ #![warn(clippy::only_used_in_recursion)] -fn simple(a: usize, b: usize) -> usize { - if a == 0 { 1 } else { simple(a - 1, b) } +fn _simple(x: u32) -> u32 { + x } -fn with_calc(a: usize, b: isize) -> usize { - if a == 0 { 1 } else { with_calc(a - 1, -b + 1) } +fn _simple2(x: u32) -> u32 { + _simple(x) } -fn tuple((a, b): (usize, usize)) -> usize { - if a == 0 { 1 } else { tuple((a - 1, b + 1)) } +fn _one_unused(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { _one_unused(flag - 1, a) } } -fn let_tuple(a: usize, b: usize) -> usize { - let (c, d) = (a, b); - if c == 0 { 1 } else { let_tuple(c - 1, d + 1) } +fn _two_unused(flag: u32, a: u32, b: i32) -> usize { + if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } } -fn array([a, b]: [usize; 2]) -> usize { - if a == 0 { 1 } else { array([a - 1, b + 1]) } +fn _with_calc(flag: u32, a: i64) -> usize { + if flag == 0 { + 0 + } else { + _with_calc(flag - 1, (-a + 10) * 5) + } } -fn index(a: usize, mut b: &[usize], c: usize) -> usize { - if a == 0 { 1 } else { index(a - 1, b, c + b[0]) } +// Don't lint +fn _used_with_flag(flag: u32, a: u32) -> usize { + if flag == 0 { 0 } else { _used_with_flag(flag ^ a, a - 1) } } -fn break_(a: usize, mut b: usize, mut c: usize) -> usize { - let c = loop { - b += 1; - c += 1; - if c == 10 { - break b; - } - }; - - if a == 0 { 1 } else { break_(a - 1, c, c) } +fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { + if flag == 0 { + 0 + } else { + _used_with_unused(flag - 1, -a, a + b) + } } -// this has a side effect -fn mut_ref(a: usize, b: &mut usize) -> usize { - *b = 1; - if a == 0 { 1 } else { mut_ref(a - 1, b) } +fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { + if flag == 0 { + 0 + } else { + _codependent_unused(flag - 1, a * b, a + b) + } } -fn mut_ref2(a: usize, b: &mut usize) -> usize { - let mut c = *b; - if a == 0 { 1 } else { mut_ref2(a - 1, &mut c) } -} - -fn not_primitive(a: usize, b: String) -> usize { - if a == 0 { 1 } else { not_primitive(a - 1, b) } -} - -// this doesn't have a side effect, -// but `String` is not primitive. -fn not_primitive_op(a: usize, b: String, c: &str) -> usize { - if a == 1 { 1 } else { not_primitive_op(a, b + c, c) } +fn _not_primitive(flag: u32, b: String) -> usize { + if flag == 0 { 0 } else { _not_primitive(flag - 1, b) } } struct A; impl A { - fn method(a: usize, b: usize) -> usize { - if a == 0 { 1 } else { A::method(a - 1, b - 1) } + fn _method(flag: usize, a: usize) -> usize { + if flag == 0 { 0 } else { Self::_method(flag - 1, a) } } - fn method2(&self, a: usize, b: usize) -> usize { - if a == 0 { 1 } else { self.method2(a - 1, b + 1) } + fn _method_self(&self, flag: usize, a: usize) -> usize { + if flag == 0 { 0 } else { self._method_self(flag - 1, a) } } } trait B { - fn hello(a: usize, b: usize) -> usize; - - fn hello2(&self, a: usize, b: usize) -> usize; + fn method(flag: u32, a: usize) -> usize; + fn method_self(&self, flag: u32, a: usize) -> usize; } impl B for A { - fn hello(a: usize, b: usize) -> usize { - if a == 0 { 1 } else { A::hello(a - 1, b + 1) } + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { Self::method(flag - 1, a) } } - fn hello2(&self, a: usize, b: usize) -> usize { - if a == 0 { 1 } else { self.hello2(a - 1, b + 1) } + fn method_self(&self, flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { self.method_self(flag - 1, a) } + } +} + +impl B for () { + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { a } + } + + fn method_self(&self, flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { a } + } +} + +impl B for u32 { + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { <() as B>::method(flag, a) } + } + + fn method_self(&self, flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { ().method_self(flag, a) } } } trait C { - fn hello(a: usize, b: usize) -> usize { - if a == 0 { 1 } else { Self::hello(a - 1, b + 1) } + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { Self::method(flag - 1, a) } } - fn hello2(&self, a: usize, b: usize) -> usize { - if a == 0 { 1 } else { self.hello2(a - 1, b + 1) } + fn method_self(&self, flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { self.method_self(flag - 1, a) } } } -fn ignore(a: usize, _: usize) -> usize { - if a == 1 { 1 } else { ignore(a - 1, 0) } -} - -fn ignore2(a: usize, _b: usize) -> usize { - if a == 1 { 1 } else { ignore2(a - 1, _b) } -} - -fn f1(a: u32) -> u32 { - a -} - -fn f2(a: u32) -> u32 { - f1(a) -} - -fn inner_fn(a: u32) -> u32 { - fn inner_fn(a: u32) -> u32 { - a - } - inner_fn(a) +fn _ignore(flag: usize, _a: usize) -> usize { + if flag == 0 { 0 } else { _ignore(flag - 1, _a) } } fn main() {} diff --git a/tests/ui/only_used_in_recursion.stderr b/tests/ui/only_used_in_recursion.stderr index 6fe9361bf5f..74057ddcfda 100644 --- a/tests/ui/only_used_in_recursion.stderr +++ b/tests/ui/only_used_in_recursion.stderr @@ -1,82 +1,195 @@ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:3:21 + --> $DIR/only_used_in_recursion.rs:11:27 | -LL | fn simple(a: usize, b: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _one_unused(flag: u32, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` | = note: `-D clippy::only-used-in-recursion` implied by `-D warnings` +note: parameter used here + --> $DIR/only_used_in_recursion.rs:12:53 + | +LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:7:24 + --> $DIR/only_used_in_recursion.rs:15:27 | -LL | fn with_calc(a: usize, b: isize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:16:53 + | +LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:11:14 + --> $DIR/only_used_in_recursion.rs:15:35 | -LL | fn tuple((a, b): (usize, usize)) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:16:56 + | +LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:15:24 + --> $DIR/only_used_in_recursion.rs:19:26 | -LL | fn let_tuple(a: usize, b: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _with_calc(flag: u32, a: i64) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:23:32 + | +LL | _with_calc(flag - 1, (-a + 10) * 5) + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:20:14 + --> $DIR/only_used_in_recursion.rs:32:33 | -LL | fn array([a, b]: [usize; 2]) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:36:38 + | +LL | _used_with_unused(flag - 1, -a, a + b) + | ^ ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:24:20 + --> $DIR/only_used_in_recursion.rs:32:41 | -LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize { - | ^^^^^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:36:45 + | +LL | _used_with_unused(flag - 1, -a, a + b) + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:24:37 + --> $DIR/only_used_in_recursion.rs:40:35 | -LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_c` +LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:44:39 + | +LL | _codependent_unused(flag - 1, a * b, a + b) + | ^ ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:28:21 + --> $DIR/only_used_in_recursion.rs:40:43 | -LL | fn break_(a: usize, mut b: usize, mut c: usize) -> usize { - | ^^^^^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:44:43 + | +LL | _codependent_unused(flag - 1, a * b, a + b) + | ^ ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:46:23 + --> $DIR/only_used_in_recursion.rs:48:30 | -LL | fn mut_ref2(a: usize, b: &mut usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _not_primitive(flag: u32, b: String) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:49:56 + | +LL | if flag == 0 { 0 } else { _not_primitive(flag - 1, b) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:51:28 + --> $DIR/only_used_in_recursion.rs:55:29 | -LL | fn not_primitive(a: usize, b: String) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _method(flag: usize, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:56:59 + | +LL | if flag == 0 { 0 } else { Self::_method(flag - 1, a) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:68:33 + --> $DIR/only_used_in_recursion.rs:59:22 | -LL | fn method2(&self, a: usize, b: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _method_self(&self, flag: usize, a: usize) -> usize { + | ^^^^ + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:60:35 + | +LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } + | ^^^^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:90:24 + --> $DIR/only_used_in_recursion.rs:59:41 | -LL | fn hello(a: usize, b: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn _method_self(&self, flag: usize, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:60:63 + | +LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } + | ^ error: parameter is only used in recursion - --> $DIR/only_used_in_recursion.rs:94:32 + --> $DIR/only_used_in_recursion.rs:70:26 | -LL | fn hello2(&self, a: usize, b: usize) -> usize { - | ^ help: if this is intentional, prefix with an underscore: `_b` +LL | fn method(flag: u32, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:71:58 + | +LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } + | ^ -error: aborting due to 13 previous errors +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion.rs:74:38 + | +LL | fn method_self(&self, flag: u32, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:75:62 + | +LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion.rs:100:26 + | +LL | fn method(flag: u32, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:101:58 + | +LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion.rs:104:38 + | +LL | fn method_self(&self, flag: u32, a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion.rs:105:62 + | +LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } + | ^ + +error: aborting due to 16 previous errors diff --git a/tests/ui/only_used_in_recursion2.rs b/tests/ui/only_used_in_recursion2.rs new file mode 100644 index 00000000000..45dd0553f58 --- /dev/null +++ b/tests/ui/only_used_in_recursion2.rs @@ -0,0 +1,91 @@ +#![warn(clippy::only_used_in_recursion)] + +fn _with_inner(flag: u32, a: u32, b: u32) -> usize { + fn inner(flag: u32, a: u32) -> u32 { + if flag == 0 { 0 } else { inner(flag, a) } + } + + let x = inner(flag, a); + if flag == 0 { 0 } else { _with_inner(flag, a, b + x) } +} + +fn _with_closure(a: Option<u32>, b: u32, f: impl Fn(u32, u32) -> Option<u32>) -> u32 { + if let Some(x) = a.and_then(|x| f(x, x)) { + _with_closure(Some(x), b, f) + } else { + 0 + } +} + +// Issue #8560 +trait D { + fn foo(&mut self, arg: u32) -> u32; +} + +mod m { + pub struct S(u32); + impl S { + pub fn foo(&mut self, arg: u32) -> u32 { + arg + self.0 + } + } +} + +impl D for m::S { + fn foo(&mut self, arg: u32) -> u32 { + self.foo(arg) + } +} + +// Issue #8782 +fn only_let(x: u32) { + let y = 10u32; + let _z = x * y; +} + +trait E<T: E<()>> { + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { + 0 + } else { + <T as E<()>>::method(flag - 1, a) + } + } +} + +impl E<()> for () { + fn method(flag: u32, a: usize) -> usize { + if flag == 0 { 0 } else { a } + } +} + +fn overwritten_param(flag: u32, mut a: usize) -> usize { + if flag == 0 { + return 0; + } else if flag > 5 { + a += flag as usize; + } else { + a = 5; + } + overwritten_param(flag, a) +} + +fn field_direct(flag: u32, mut a: (usize,)) -> usize { + if flag == 0 { + 0 + } else { + a.0 += 5; + field_direct(flag - 1, a) + } +} + +fn field_deref(flag: u32, a: &mut Box<(usize,)>) -> usize { + if flag == 0 { + 0 + } else { + a.0 += 5; + field_deref(flag - 1, a) + } +} + +fn main() {} diff --git a/tests/ui/only_used_in_recursion2.stderr b/tests/ui/only_used_in_recursion2.stderr new file mode 100644 index 00000000000..23f6ffd30c9 --- /dev/null +++ b/tests/ui/only_used_in_recursion2.stderr @@ -0,0 +1,63 @@ +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion2.rs:3:35 + | +LL | fn _with_inner(flag: u32, a: u32, b: u32) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | + = note: `-D clippy::only-used-in-recursion` implied by `-D warnings` +note: parameter used here + --> $DIR/only_used_in_recursion2.rs:9:52 + | +LL | if flag == 0 { 0 } else { _with_inner(flag, a, b + x) } + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion2.rs:4:25 + | +LL | fn inner(flag: u32, a: u32) -> u32 { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion2.rs:5:47 + | +LL | if flag == 0 { 0 } else { inner(flag, a) } + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion2.rs:12:34 + | +LL | fn _with_closure(a: Option<u32>, b: u32, f: impl Fn(u32, u32) -> Option<u32>) -> u32 { + | ^ help: if this is intentional, prefix it with an underscore: `_b` + | +note: parameter used here + --> $DIR/only_used_in_recursion2.rs:14:32 + | +LL | _with_closure(Some(x), b, f) + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion2.rs:62:37 + | +LL | fn overwritten_param(flag: u32, mut a: usize) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion2.rs:70:29 + | +LL | overwritten_param(flag, a) + | ^ + +error: parameter is only used in recursion + --> $DIR/only_used_in_recursion2.rs:73:32 + | +LL | fn field_direct(flag: u32, mut a: (usize,)) -> usize { + | ^ help: if this is intentional, prefix it with an underscore: `_a` + | +note: parameter used here + --> $DIR/only_used_in_recursion2.rs:78:32 + | +LL | field_direct(flag - 1, a) + | ^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index b6d5e106f05..f15ac551bb3 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -179,4 +179,13 @@ fn main() { let _ = pattern_to_vec("hello world"); let _ = complex_subpat(); + + // issue #8492 + let _ = s.map_or(1, |string| string.len()); + let _ = Some(10).map_or(5, |a| a + 1); + + let res: Result<i32, i32> = Ok(5); + let _ = res.map_or(1, |a| a + 1); + let _ = res.map_or(1, |a| a + 1); + let _ = res.map_or(5, |a| a + 1); } diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 35bae159343..9eeaea12d3b 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -208,4 +208,25 @@ fn main() { let _ = pattern_to_vec("hello world"); let _ = complex_subpat(); + + // issue #8492 + let _ = match s { + Some(string) => string.len(), + None => 1, + }; + let _ = match Some(10) { + Some(a) => a + 1, + None => 5, + }; + + let res: Result<i32, i32> = Ok(5); + let _ = match res { + Ok(a) => a + 1, + _ => 1, + }; + let _ = match res { + Err(_) => 1, + Ok(a) => a + 1, + }; + let _ = if let Ok(a) = res { a + 1 } else { 5 }; } diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index daba606004e..a5dbf6e1f22 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -206,5 +206,51 @@ LL + s.len() + x LL ~ }); | -error: aborting due to 15 previous errors +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:213:13 + | +LL | let _ = match s { + | _____________^ +LL | | Some(string) => string.len(), +LL | | None => 1, +LL | | }; + | |_____^ help: try: `s.map_or(1, |string| string.len())` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:217:13 + | +LL | let _ = match Some(10) { + | _____________^ +LL | | Some(a) => a + 1, +LL | | None => 5, +LL | | }; + | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:223:13 + | +LL | let _ = match res { + | _____________^ +LL | | Ok(a) => a + 1, +LL | | _ => 1, +LL | | }; + | |_____^ help: try: `res.map_or(1, |a| a + 1)` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:227:13 + | +LL | let _ = match res { + | _____________^ +LL | | Err(_) => 1, +LL | | Ok(a) => a + 1, +LL | | }; + | |_____^ help: try: `res.map_or(1, |a| a + 1)` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:231:13 + | +LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` + +error: aborting due to 20 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index fdb08d953ff..18ea4e55029 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -90,8 +90,8 @@ fn or_fun_call() { let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new(); btree_vec.entry(42).or_insert(vec![]); - let stringy = Some(String::from("")); - let _ = stringy.unwrap_or_else(|| "".to_owned()); + let stringy = Some(String::new()); + let _ = stringy.unwrap_or_default(); let opt = Some(1); let hello = "Hello"; diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 57ab5f03ee2..c353b41e449 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -90,8 +90,8 @@ fn or_fun_call() { let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new(); btree_vec.entry(42).or_insert(vec![]); - let stringy = Some(String::from("")); - let _ = stringy.unwrap_or("".to_owned()); + let stringy = Some(String::new()); + let _ = stringy.unwrap_or(String::new()); let opt = Some(1); let hello = "Hello"; diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 4c5938ab88b..887f23ac976 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -66,11 +66,11 @@ error: use of `unwrap_or` followed by a function call LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)` -error: use of `unwrap_or` followed by a function call +error: use of `unwrap_or` followed by a call to `new` --> $DIR/or_fun_call.rs:94:21 | -LL | let _ = stringy.unwrap_or("".to_owned()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())` +LL | let _ = stringy.unwrap_or(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:102:21 diff --git a/tests/ui/partialeq_to_none.fixed b/tests/ui/partialeq_to_none.fixed index f3e4c58d694..4644ea8f51d 100644 --- a/tests/ui/partialeq_to_none.fixed +++ b/tests/ui/partialeq_to_none.fixed @@ -26,6 +26,18 @@ fn optref() -> &'static &'static Option<()> { &&None } +pub fn macro_expansion() { + macro_rules! foo { + () => { + None::<()> + }; + } + + let _ = foobar() == foo!(); + let _ = foo!() == foobar(); + let _ = foo!() == foo!(); +} + fn main() { let x = Some(0); diff --git a/tests/ui/partialeq_to_none.rs b/tests/ui/partialeq_to_none.rs index 767b2a38bcc..61011b3a8c5 100644 --- a/tests/ui/partialeq_to_none.rs +++ b/tests/ui/partialeq_to_none.rs @@ -26,6 +26,18 @@ fn optref() -> &'static &'static Option<()> { &&None } +pub fn macro_expansion() { + macro_rules! foo { + () => { + None::<()> + }; + } + + let _ = foobar() == foo!(); + let _ = foo!() == foobar(); + let _ = foo!() == foo!(); +} + fn main() { let x = Some(0); diff --git a/tests/ui/partialeq_to_none.stderr b/tests/ui/partialeq_to_none.stderr index de15a9f7baa..d06ab7aee55 100644 --- a/tests/ui/partialeq_to_none.stderr +++ b/tests/ui/partialeq_to_none.stderr @@ -7,55 +7,55 @@ LL | if f != None { "yay" } else { "nay" } = note: `-D clippy::partialeq-to-none` implied by `-D warnings` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:32:13 + --> $DIR/partialeq_to_none.rs:44:13 | LL | let _ = x == None; | ^^^^^^^^^ help: use `Option::is_none()` instead: `x.is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:33:13 + --> $DIR/partialeq_to_none.rs:45:13 | LL | let _ = x != None; | ^^^^^^^^^ help: use `Option::is_some()` instead: `x.is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:34:13 + --> $DIR/partialeq_to_none.rs:46:13 | LL | let _ = None == x; | ^^^^^^^^^ help: use `Option::is_none()` instead: `x.is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:35:13 + --> $DIR/partialeq_to_none.rs:47:13 | LL | let _ = None != x; | ^^^^^^^^^ help: use `Option::is_some()` instead: `x.is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:37:8 + --> $DIR/partialeq_to_none.rs:49:8 | LL | if foobar() == None {} | ^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `foobar().is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:39:8 + --> $DIR/partialeq_to_none.rs:51:8 | LL | if bar().ok() != None {} | ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `bar().ok().is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:41:13 + --> $DIR/partialeq_to_none.rs:53:13 | LL | let _ = Some(1 + 2) != None; | ^^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `Some(1 + 2).is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:43:13 + --> $DIR/partialeq_to_none.rs:55:13 | LL | let _ = { Some(0) } == None; | ^^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `{ Some(0) }.is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:45:13 + --> $DIR/partialeq_to_none.rs:57:13 | LL | let _ = { | _____________^ @@ -77,31 +77,31 @@ LL ~ }.is_some(); | error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:55:13 + --> $DIR/partialeq_to_none.rs:67:13 | LL | let _ = optref() == &&None; | ^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `optref().is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:56:13 + --> $DIR/partialeq_to_none.rs:68:13 | LL | let _ = &&None != optref(); | ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `optref().is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:57:13 + --> $DIR/partialeq_to_none.rs:69:13 | LL | let _ = **optref() == None; | ^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `optref().is_none()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:58:13 + --> $DIR/partialeq_to_none.rs:70:13 | LL | let _ = &None != *optref(); | ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `optref().is_some()` error: binary comparison to literal `Option::None` - --> $DIR/partialeq_to_none.rs:61:13 + --> $DIR/partialeq_to_none.rs:73:13 | LL | let _ = None != *x; | ^^^^^^^^^^ help: use `Option::is_some()` instead: `(*x).is_some()` diff --git a/tests/ui/positional_named_format_parameters.fixed b/tests/ui/positional_named_format_parameters.fixed new file mode 100644 index 00000000000..4170e109820 --- /dev/null +++ b/tests/ui/positional_named_format_parameters.fixed @@ -0,0 +1,56 @@ +// run-rustfix +#![allow(unused_must_use)] +#![allow(named_arguments_used_positionally)] // Unstable at time of writing. +#![warn(clippy::positional_named_format_parameters)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + let hello = "Hello"; + + println!("{hello:.foo$}", foo = 2); + writeln!(v, "{hello:.foo$}", foo = 2); + + // Warnings + println!("{zero} {one:?}", zero = 0, one = 1); + println!("This is a test {zero} {one:?}", zero = 0, one = 1); + println!("Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01); + println!("Hello {one:zero$}!", zero = 5, one = 1); + println!("Hello {zero:one$}!", zero = 4, one = 1); + println!("Hello {zero:0one$}!", zero = 4, one = 1); + println!("Hello is {one:.zero$}", zero = 5, one = 0.01); + println!("Hello is {one:<6.zero$}", zero = 5, one = 0.01); + println!("{zero}, `{two:>8.one$}` has 3", zero = hello, one = 3, two = hello); + println!("Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01); + println!("Hello {world} {world}!", world = 5); + + writeln!(v, "{zero} {one:?}", zero = 0, one = 1); + writeln!(v, "This is a test {zero} {one:?}", zero = 0, one = 1); + writeln!(v, "Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01); + writeln!(v, "Hello {one:zero$}!", zero = 4, one = 1); + writeln!(v, "Hello {zero:one$}!", zero = 4, one = 1); + writeln!(v, "Hello {zero:0one$}!", zero = 4, one = 1); + writeln!(v, "Hello is {one:.zero$}", zero = 3, one = 0.01); + writeln!(v, "Hello is {one:<6.zero$}", zero = 2, one = 0.01); + writeln!(v, "{zero}, `{two:>8.one$}` has 3", zero = hello, one = 3, two = hello); + writeln!(v, "Hello {one} is {two:.zero$}", zero = 1, one = hello, two = 0.01); + writeln!(v, "Hello {world} {world}!", world = 0); + + // Tests from other files + println!("{w:w$}", w = 1); + println!("{p:.p$}", p = 1); + println!("{v}", v = 1); + println!("{v:v$}", v = 1); + println!("{v:v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{v:v$.v$}", v = 1); + println!("{w:w$}", w = 1); + println!("{p:.p$}", p = 1); + println!("{:p$.w$}", 1, w = 1, p = 1); +} diff --git a/tests/ui/positional_named_format_parameters.rs b/tests/ui/positional_named_format_parameters.rs new file mode 100644 index 00000000000..553d8494ecc --- /dev/null +++ b/tests/ui/positional_named_format_parameters.rs @@ -0,0 +1,56 @@ +// run-rustfix +#![allow(unused_must_use)] +#![allow(named_arguments_used_positionally)] // Unstable at time of writing. +#![warn(clippy::positional_named_format_parameters)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + let hello = "Hello"; + + println!("{hello:.foo$}", foo = 2); + writeln!(v, "{hello:.foo$}", foo = 2); + + // Warnings + println!("{} {1:?}", zero = 0, one = 1); + println!("This is a test { } {000001:?}", zero = 0, one = 1); + println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + println!("Hello {1:0$}!", zero = 5, one = 1); + println!("Hello {0:1$}!", zero = 4, one = 1); + println!("Hello {0:01$}!", zero = 4, one = 1); + println!("Hello is {1:.*}", zero = 5, one = 0.01); + println!("Hello is {:<6.*}", zero = 5, one = 0.01); + println!("{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + println!("Hello {world} {}!", world = 5); + + writeln!(v, "{} {1:?}", zero = 0, one = 1); + writeln!(v, "This is a test { } {000001:?}", zero = 0, one = 1); + writeln!(v, "Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + writeln!(v, "Hello {1:0$}!", zero = 4, one = 1); + writeln!(v, "Hello {0:1$}!", zero = 4, one = 1); + writeln!(v, "Hello {0:01$}!", zero = 4, one = 1); + writeln!(v, "Hello is {1:.*}", zero = 3, one = 0.01); + writeln!(v, "Hello is {:<6.*}", zero = 2, one = 0.01); + writeln!(v, "{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + writeln!(v, "Hello {1} is {2:.0$}", zero = 1, one = hello, two = 0.01); + writeln!(v, "Hello {world} {}!", world = 0); + + // Tests from other files + println!("{:w$}", w = 1); + println!("{:.p$}", p = 1); + println!("{}", v = 1); + println!("{:0$}", v = 1); + println!("{0:0$}", v = 1); + println!("{:0$.0$}", v = 1); + println!("{0:0$.0$}", v = 1); + println!("{0:0$.v$}", v = 1); + println!("{0:v$.0$}", v = 1); + println!("{v:0$.0$}", v = 1); + println!("{v:v$.0$}", v = 1); + println!("{v:0$.v$}", v = 1); + println!("{:w$}", w = 1); + println!("{:.p$}", p = 1); + println!("{:p$.w$}", 1, w = 1, p = 1); +} diff --git a/tests/ui/positional_named_format_parameters.stderr b/tests/ui/positional_named_format_parameters.stderr new file mode 100644 index 00000000000..48ddb6d67ad --- /dev/null +++ b/tests/ui/positional_named_format_parameters.stderr @@ -0,0 +1,418 @@ +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:16:16 + | +LL | println!("{} {1:?}", zero = 0, one = 1); + | ^ help: replace it with: `zero` + | + = note: `-D clippy::positional-named-format-parameters` implied by `-D warnings` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:16:19 + | +LL | println!("{} {1:?}", zero = 0, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:17:31 + | +LL | println!("This is a test { } {000001:?}", zero = 0, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:17:35 + | +LL | println!("This is a test { } {000001:?}", zero = 0, one = 1); + | ^^^^^^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:18:32 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:18:22 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `one` + +error: named parameter two is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:18:29 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `two` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:19:24 + | +LL | println!("Hello {1:0$}!", zero = 5, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:19:22 + | +LL | println!("Hello {1:0$}!", zero = 5, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:20:22 + | +LL | println!("Hello {0:1$}!", zero = 4, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:20:24 + | +LL | println!("Hello {0:1$}!", zero = 4, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:21:22 + | +LL | println!("Hello {0:01$}!", zero = 4, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:21:25 + | +LL | println!("Hello {0:01$}!", zero = 4, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:22:28 + | +LL | println!("Hello is {1:.*}", zero = 5, one = 0.01); + | ^ help: replace it with: `zero$` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:22:25 + | +LL | println!("Hello is {1:.*}", zero = 5, one = 0.01); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:23:29 + | +LL | println!("Hello is {:<6.*}", zero = 5, one = 0.01); + | ^ help: replace it with: `zero$` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:23:25 + | +LL | println!("Hello is {:<6.*}", zero = 5, one = 0.01); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:24:16 + | +LL | println!("{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:24:28 + | +LL | println!("{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + | ^ help: replace it with: `one$` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:25:32 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:25:22 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `one` + +error: named parameter two is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:25:29 + | +LL | println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `two` + +error: named parameter world is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:26:30 + | +LL | println!("Hello {world} {}!", world = 5); + | ^ help: replace it with: `world` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:28:19 + | +LL | writeln!(v, "{} {1:?}", zero = 0, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:28:22 + | +LL | writeln!(v, "{} {1:?}", zero = 0, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:29:34 + | +LL | writeln!(v, "This is a test { } {000001:?}", zero = 0, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:29:38 + | +LL | writeln!(v, "This is a test { } {000001:?}", zero = 0, one = 1); + | ^^^^^^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:30:35 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:30:25 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `one` + +error: named parameter two is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:30:32 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01); + | ^ help: replace it with: `two` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:31:27 + | +LL | writeln!(v, "Hello {1:0$}!", zero = 4, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:31:25 + | +LL | writeln!(v, "Hello {1:0$}!", zero = 4, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:32:25 + | +LL | writeln!(v, "Hello {0:1$}!", zero = 4, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:32:27 + | +LL | writeln!(v, "Hello {0:1$}!", zero = 4, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:33:25 + | +LL | writeln!(v, "Hello {0:01$}!", zero = 4, one = 1); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:33:28 + | +LL | writeln!(v, "Hello {0:01$}!", zero = 4, one = 1); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:34:31 + | +LL | writeln!(v, "Hello is {1:.*}", zero = 3, one = 0.01); + | ^ help: replace it with: `zero$` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:34:28 + | +LL | writeln!(v, "Hello is {1:.*}", zero = 3, one = 0.01); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:35:32 + | +LL | writeln!(v, "Hello is {:<6.*}", zero = 2, one = 0.01); + | ^ help: replace it with: `zero$` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:35:28 + | +LL | writeln!(v, "Hello is {:<6.*}", zero = 2, one = 0.01); + | ^ help: replace it with: `one` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:36:19 + | +LL | writeln!(v, "{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:36:31 + | +LL | writeln!(v, "{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello); + | ^ help: replace it with: `one$` + +error: named parameter zero is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:37:35 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 1, one = hello, two = 0.01); + | ^ help: replace it with: `zero` + +error: named parameter one is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:37:25 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 1, one = hello, two = 0.01); + | ^ help: replace it with: `one` + +error: named parameter two is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:37:32 + | +LL | writeln!(v, "Hello {1} is {2:.0$}", zero = 1, one = hello, two = 0.01); + | ^ help: replace it with: `two` + +error: named parameter world is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:38:33 + | +LL | writeln!(v, "Hello {world} {}!", world = 0); + | ^ help: replace it with: `world` + +error: named parameter w is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:41:16 + | +LL | println!("{:w$}", w = 1); + | ^ help: replace it with: `w` + +error: named parameter p is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:42:16 + | +LL | println!("{:.p$}", p = 1); + | ^ help: replace it with: `p` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:43:16 + | +LL | println!("{}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:44:16 + | +LL | println!("{:0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:44:17 + | +LL | println!("{:0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:45:16 + | +LL | println!("{0:0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:45:18 + | +LL | println!("{0:0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:46:16 + | +LL | println!("{:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:46:20 + | +LL | println!("{:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:46:17 + | +LL | println!("{:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:47:16 + | +LL | println!("{0:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:47:21 + | +LL | println!("{0:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:47:18 + | +LL | println!("{0:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:48:16 + | +LL | println!("{0:0$.v$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:48:18 + | +LL | println!("{0:0$.v$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:49:16 + | +LL | println!("{0:v$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:49:21 + | +LL | println!("{0:v$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:50:21 + | +LL | println!("{v:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:50:18 + | +LL | println!("{v:0$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:51:21 + | +LL | println!("{v:v$.0$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter v is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:52:18 + | +LL | println!("{v:0$.v$}", v = 1); + | ^ help: replace it with: `v` + +error: named parameter w is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:53:16 + | +LL | println!("{:w$}", w = 1); + | ^ help: replace it with: `w` + +error: named parameter p is used as a positional parameter + --> $DIR/positional_named_format_parameters.rs:54:16 + | +LL | println!("{:.p$}", p = 1); + | ^ help: replace it with: `p` + +error: aborting due to 69 previous errors + diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index c4c9c821433..57f23bd1916 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -207,4 +207,19 @@ fn option_map() -> Option<bool> { } } +pub struct PatternedError { + flag: bool, +} + +// No warning +fn pattern() -> Result<(), PatternedError> { + let res = Ok(()); + + if let Err(err @ PatternedError { flag: true }) = res { + return Err(err); + } + + res +} + fn main() {} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index cdbc7b1606f..436f027c215 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -243,4 +243,19 @@ fn option_map() -> Option<bool> { } } +pub struct PatternedError { + flag: bool, +} + +// No warning +fn pattern() -> Result<(), PatternedError> { + let res = Ok(()); + + if let Err(err @ PatternedError { flag: true }) = res { + return Err(err); + } + + res +} + fn main() {} diff --git a/tests/ui/regex.rs b/tests/ui/regex.rs index f7f3b195ccc..f0e1a8128d7 100644 --- a/tests/ui/regex.rs +++ b/tests/ui/regex.rs @@ -1,4 +1,4 @@ -#![allow(unused)] +#![allow(unused, clippy::needless_borrow)] #![warn(clippy::invalid_regex, clippy::trivial_regex)] extern crate regex; diff --git a/tests/ui/result_large_err.rs b/tests/ui/result_large_err.rs new file mode 100644 index 00000000000..78d8f76fe66 --- /dev/null +++ b/tests/ui/result_large_err.rs @@ -0,0 +1,98 @@ +#![warn(clippy::result_large_err)] + +pub fn small_err() -> Result<(), u128> { + Ok(()) +} + +pub fn large_err() -> Result<(), [u8; 512]> { + Ok(()) +} + +pub struct FullyDefinedLargeError { + _foo: u128, + _bar: [u8; 100], + _foobar: [u8; 120], +} + +impl FullyDefinedLargeError { + pub fn ret() -> Result<(), Self> { + Ok(()) + } +} + +pub fn struct_error() -> Result<(), FullyDefinedLargeError> { + Ok(()) +} + +type Fdlr<T> = std::result::Result<T, FullyDefinedLargeError>; +pub fn large_err_via_type_alias<T>(x: T) -> Fdlr<T> { + Ok(x) +} + +pub fn param_small_error<R>() -> Result<(), (R, u128)> { + Ok(()) +} + +pub fn param_large_error<R>() -> Result<(), (u128, R, FullyDefinedLargeError)> { + Ok(()) +} + +pub enum LargeErrorVariants<T> { + _Small(u8), + _Omg([u8; 512]), + _Param(T), +} + +impl LargeErrorVariants<()> { + pub fn large_enum_error() -> Result<(), Self> { + Ok(()) + } +} + +trait TraitForcesLargeError { + fn large_error() -> Result<(), [u8; 512]> { + Ok(()) + } +} + +struct TraitImpl; + +impl TraitForcesLargeError for TraitImpl { + // Should not lint + fn large_error() -> Result<(), [u8; 512]> { + Ok(()) + } +} + +pub union FullyDefinedUnionError { + _maybe: u8, + _or_even: [[u8; 16]; 32], +} + +pub fn large_union_err() -> Result<(), FullyDefinedUnionError> { + Ok(()) +} + +pub union UnionError<T: Copy> { + _maybe: T, + _or_perhaps_even: (T, [u8; 512]), +} + +pub fn param_large_union<T: Copy>() -> Result<(), UnionError<T>> { + Ok(()) +} + +pub struct ArrayError<T, U> { + _large_array: [T; 32], + _other_stuff: U, +} + +pub fn array_error_subst<U>() -> Result<(), ArrayError<i32, U>> { + Ok(()) +} + +pub fn array_error<T, U>() -> Result<(), ArrayError<(i32, T), U>> { + Ok(()) +} + +fn main() {} diff --git a/tests/ui/result_large_err.stderr b/tests/ui/result_large_err.stderr new file mode 100644 index 00000000000..0f1f39d72cb --- /dev/null +++ b/tests/ui/result_large_err.stderr @@ -0,0 +1,91 @@ +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:7:23 + | +LL | pub fn large_err() -> Result<(), [u8; 512]> { + | ^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes + | + = note: `-D clippy::result-large-err` implied by `-D warnings` + = help: try reducing the size of `[u8; 512]`, for example by boxing large elements or replacing it with `Box<[u8; 512]>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:18:21 + | +LL | pub fn ret() -> Result<(), Self> { + | ^^^^^^^^^^^^^^^^ the `Err`-variant is at least 240 bytes + | + = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:23:26 + | +LL | pub fn struct_error() -> Result<(), FullyDefinedLargeError> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 240 bytes + | + = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:28:45 + | +LL | pub fn large_err_via_type_alias<T>(x: T) -> Fdlr<T> { + | ^^^^^^^ the `Err`-variant is at least 240 bytes + | + = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:36:34 + | +LL | pub fn param_large_error<R>() -> Result<(), (u128, R, FullyDefinedLargeError)> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 256 bytes + | + = help: try reducing the size of `(u128, R, FullyDefinedLargeError)`, for example by boxing large elements or replacing it with `Box<(u128, R, FullyDefinedLargeError)>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:47:34 + | +LL | pub fn large_enum_error() -> Result<(), Self> { + | ^^^^^^^^^^^^^^^^ the `Err`-variant is at least 513 bytes + | + = help: try reducing the size of `LargeErrorVariants<()>`, for example by boxing large elements or replacing it with `Box<LargeErrorVariants<()>>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:53:25 + | +LL | fn large_error() -> Result<(), [u8; 512]> { + | ^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes + | + = help: try reducing the size of `[u8; 512]`, for example by boxing large elements or replacing it with `Box<[u8; 512]>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:72:29 + | +LL | pub fn large_union_err() -> Result<(), FullyDefinedUnionError> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes + | + = help: try reducing the size of `FullyDefinedUnionError`, for example by boxing large elements or replacing it with `Box<FullyDefinedUnionError>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:81:40 + | +LL | pub fn param_large_union<T: Copy>() -> Result<(), UnionError<T>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes + | + = help: try reducing the size of `UnionError<T>`, for example by boxing large elements or replacing it with `Box<UnionError<T>>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:90:34 + | +LL | pub fn array_error_subst<U>() -> Result<(), ArrayError<i32, U>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 128 bytes + | + = help: try reducing the size of `ArrayError<i32, U>`, for example by boxing large elements or replacing it with `Box<ArrayError<i32, U>>` + +error: the `Err`-variant returned from this function is very large + --> $DIR/result_large_err.rs:94:31 + | +LL | pub fn array_error<T, U>() -> Result<(), ArrayError<(i32, T), U>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 128 bytes + | + = help: try reducing the size of `ArrayError<(i32, T), U>`, for example by boxing large elements or replacing it with `Box<ArrayError<(i32, T), U>>` + +error: aborting due to 11 previous errors + diff --git a/tests/ui/same_item_push.rs b/tests/ui/same_item_push.rs index 99964f0de07..af01a8df71b 100644 --- a/tests/ui/same_item_push.rs +++ b/tests/ui/same_item_push.rs @@ -151,6 +151,7 @@ fn main() { // Fix #6987 let mut vec = Vec::new(); + #[allow(clippy::needless_borrow)] for _ in 0..10 { vec.push(1); vec.extend(&[2]); diff --git a/tests/ui/string_add.rs b/tests/ui/string_add.rs index 30fd17c59e5..16673c01e63 100644 --- a/tests/ui/string_add.rs +++ b/tests/ui/string_add.rs @@ -7,13 +7,13 @@ extern crate macro_rules; #[allow(clippy::string_add_assign, unused)] fn main() { // ignores assignment distinction - let mut x = "".to_owned(); + let mut x = String::new(); for _ in 1..3 { x = x + "."; } - let y = "".to_owned(); + let y = String::new(); let z = y + "..."; assert_eq!(&x, &z); diff --git a/tests/ui/string_add_assign.fixed b/tests/ui/string_add_assign.fixed index db71bab1e52..b687f43b254 100644 --- a/tests/ui/string_add_assign.fixed +++ b/tests/ui/string_add_assign.fixed @@ -4,13 +4,13 @@ #[warn(clippy::string_add_assign)] fn main() { // ignores assignment distinction - let mut x = "".to_owned(); + let mut x = String::new(); for _ in 1..3 { x += "."; } - let y = "".to_owned(); + let y = String::new(); let z = y + "..."; assert_eq!(&x, &z); diff --git a/tests/ui/string_add_assign.rs b/tests/ui/string_add_assign.rs index 644991945cb..e5dbde108fb 100644 --- a/tests/ui/string_add_assign.rs +++ b/tests/ui/string_add_assign.rs @@ -4,13 +4,13 @@ #[warn(clippy::string_add_assign)] fn main() { // ignores assignment distinction - let mut x = "".to_owned(); + let mut x = String::new(); for _ in 1..3 { x = x + "."; } - let y = "".to_owned(); + let y = String::new(); let z = y + "..."; assert_eq!(&x, &z); diff --git a/tests/ui/suspicious_to_owned.rs b/tests/ui/suspicious_to_owned.rs new file mode 100644 index 00000000000..cba21bf4a93 --- /dev/null +++ b/tests/ui/suspicious_to_owned.rs @@ -0,0 +1,62 @@ +#![warn(clippy::suspicious_to_owned)] +#![warn(clippy::implicit_clone)] +#![allow(clippy::redundant_clone)] +use std::borrow::Cow; +use std::ffi::{c_char, CStr}; + +fn main() { + let moo = "Moooo"; + let c_moo = b"Moooo\0"; + let c_moo_ptr = c_moo.as_ptr() as *const c_char; + let moos = ['M', 'o', 'o']; + let moos_vec = moos.to_vec(); + + // we expect this to be linted + let cow = Cow::Borrowed(moo); + let _ = cow.to_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos); + let _ = cow.to_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.to_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.clone(); + + // we expect this to be linted + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.to_owned(); + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.clone(); + + // we expect no lints for these + let _ = moo.to_owned(); + let _ = c_moo.to_owned(); + let _ = moos.to_owned(); + + // we expect implicit_clone lints for these + let _ = String::from(moo).to_owned(); + let _ = moos_vec.to_owned(); +} diff --git a/tests/ui/suspicious_to_owned.stderr b/tests/ui/suspicious_to_owned.stderr new file mode 100644 index 00000000000..92e1024bf1f --- /dev/null +++ b/tests/ui/suspicious_to_owned.stderr @@ -0,0 +1,42 @@ +error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned + --> $DIR/suspicious_to_owned.rs:16:13 + | +LL | let _ = cow.to_owned(); + | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + | + = note: `-D clippy::suspicious-to-owned` implied by `-D warnings` + +error: this `to_owned` call clones the std::borrow::Cow<[char; 3]> itself and does not cause the std::borrow::Cow<[char; 3]> contents to become owned + --> $DIR/suspicious_to_owned.rs:26:13 + | +LL | let _ = cow.to_owned(); + | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + +error: this `to_owned` call clones the std::borrow::Cow<std::vec::Vec<char>> itself and does not cause the std::borrow::Cow<std::vec::Vec<char>> contents to become owned + --> $DIR/suspicious_to_owned.rs:36:13 + | +LL | let _ = cow.to_owned(); + | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + +error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned + --> $DIR/suspicious_to_owned.rs:46:13 + | +LL | let _ = cow.to_owned(); + | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + +error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type + --> $DIR/suspicious_to_owned.rs:60:13 + | +LL | let _ = String::from(moo).to_owned(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::from(moo).clone()` + | + = note: `-D clippy::implicit-clone` implied by `-D warnings` + +error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type + --> $DIR/suspicious_to_owned.rs:61:13 + | +LL | let _ = moos_vec.to_owned(); + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `moos_vec.clone()` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/trait_duplication_in_bounds.fixed b/tests/ui/trait_duplication_in_bounds.fixed new file mode 100644 index 00000000000..4ce5d421782 --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds.fixed @@ -0,0 +1,112 @@ +// run-rustfix +#![deny(clippy::trait_duplication_in_bounds)] +#![allow(unused)] + +fn bad_foo<T: Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { + unimplemented!(); +} + +fn bad_bar<T, U>(arg0: T, arg1: U) +where + T: Clone + Copy, + U: Clone + Copy, +{ + unimplemented!(); +} + +fn good_bar<T: Clone + Copy, U: Clone + Copy>(arg0: T, arg1: U) { + unimplemented!(); +} + +fn good_foo<T, U>(arg0: T, arg1: U) +where + T: Clone + Copy, + U: Clone + Copy, +{ + unimplemented!(); +} + +trait GoodSelfTraitBound: Clone + Copy { + fn f(); +} + +trait GoodSelfWhereClause { + fn f() + where + Self: Clone + Copy; +} + +trait BadSelfTraitBound: Clone { + fn f(); +} + +trait BadSelfWhereClause { + fn f() + where + Self: Clone; +} + +trait GoodTraitBound<T: Clone + Copy, U: Clone + Copy> { + fn f(); +} + +trait GoodWhereClause<T, U> { + fn f() + where + T: Clone + Copy, + U: Clone + Copy; +} + +trait BadTraitBound<T: Clone + Copy, U: Clone + Copy> { + fn f(); +} + +trait BadWhereClause<T, U> { + fn f() + where + T: Clone + Copy, + U: Clone + Copy; +} + +struct GoodStructBound<T: Clone + Copy, U: Clone + Copy> { + t: T, + u: U, +} + +impl<T: Clone + Copy, U: Clone + Copy> GoodTraitBound<T, U> for GoodStructBound<T, U> { + // this should not warn + fn f() {} +} + +struct GoodStructWhereClause; + +impl<T, U> GoodTraitBound<T, U> for GoodStructWhereClause +where + T: Clone + Copy, + U: Clone + Copy, +{ + // this should not warn + fn f() {} +} + +fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {} + +trait GenericTrait<T> {} + +fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) { + unimplemented!(); +} + +fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) { + unimplemented!(); +} + +mod foo { + pub trait Clone {} +} + +fn qualified_path<T: std::clone::Clone + foo::Clone>(arg0: T) { + unimplemented!(); +} + +fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index a5751c58aab..7f2e96a22e6 100644 --- a/tests/ui/trait_duplication_in_bounds.rs +++ b/tests/ui/trait_duplication_in_bounds.rs @@ -1,212 +1,112 @@ +// run-rustfix #![deny(clippy::trait_duplication_in_bounds)] #![allow(unused)] -use std::collections::BTreeMap; -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { + unimplemented!(); +} -fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) +fn bad_bar<T, U>(arg0: T, arg1: U) where - T: Clone, - T: Default, + T: Clone + Clone + Clone + Copy, + U: Clone + Copy, { unimplemented!(); } -fn good_bar<T: Clone + Default>(arg: T) { +fn good_bar<T: Clone + Copy, U: Clone + Copy>(arg0: T, arg1: U) { unimplemented!(); } -fn good_foo<T>(arg: T) +fn good_foo<T, U>(arg0: T, arg1: U) where - T: Clone + Default, + T: Clone + Copy, + U: Clone + Copy, { unimplemented!(); } -fn good_foobar<T: Default>(arg: T) -where - T: Clone, -{ - unimplemented!(); +trait GoodSelfTraitBound: Clone + Copy { + fn f(); } -trait T: Default { +trait GoodSelfWhereClause { fn f() where - Self: Default; + Self: Clone + Copy; } -trait U: Default { +trait BadSelfTraitBound: Clone + Clone + Clone { + fn f(); +} + +trait BadSelfWhereClause { fn f() where - Self: Clone; + Self: Clone + Clone + Clone; } -trait ZZ: Default { - fn g(); - fn h(); +trait GoodTraitBound<T: Clone + Copy, U: Clone + Copy> { + fn f(); +} + +trait GoodWhereClause<T, U> { fn f() where - Self: Default + Clone; + T: Clone + Copy, + U: Clone + Copy; } -trait BadTrait: Default + Clone { +trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> { + fn f(); +} + +trait BadWhereClause<T, U> { fn f() - where - Self: Default + Clone; - fn g() - where - Self: Default; - fn h() - where - Self: Copy; -} - -#[derive(Default, Clone)] -struct Life; - -impl T for Life { - // this should not warn - fn f() {} -} - -impl U for Life { - // this should not warn - fn f() {} -} - -// should not warn -trait Iter: Iterator { - fn into_group_btreemap<K, V>(self) -> BTreeMap<K, Vec<V>> - where - Self: Iterator<Item = (K, V)> + Sized, - K: Ord + Eq, - { - unimplemented!(); - } -} - -struct Foo; - -trait FooIter: Iterator<Item = Foo> { - fn bar() - where - Self: Iterator<Item = Foo>, - { - } -} - -// This should not lint -fn impl_trait(_: impl AsRef<str>, _: impl AsRef<str>) {} - -mod repeated_where_clauses_or_trait_bounds { - fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { - unimplemented!(); - } - - fn bad_bar<T, U>(arg0: T, arg1: U) where T: Clone + Clone + Clone + Copy, - U: Clone + Copy, - { - unimplemented!(); - } + U: Clone + Copy; +} - fn good_bar<T: Clone + Copy, U: Clone + Copy>(arg0: T, arg1: U) { - unimplemented!(); - } +struct GoodStructBound<T: Clone + Copy, U: Clone + Copy> { + t: T, + u: U, +} - fn good_foo<T, U>(arg0: T, arg1: U) - where - T: Clone + Copy, - U: Clone + Copy, - { - unimplemented!(); - } +impl<T: Clone + Copy, U: Clone + Copy> GoodTraitBound<T, U> for GoodStructBound<T, U> { + // this should not warn + fn f() {} +} - trait GoodSelfTraitBound: Clone + Copy { - fn f(); - } +struct GoodStructWhereClause; - trait GoodSelfWhereClause { - fn f() - where - Self: Clone + Copy; - } +impl<T, U> GoodTraitBound<T, U> for GoodStructWhereClause +where + T: Clone + Copy, + U: Clone + Copy, +{ + // this should not warn + fn f() {} +} - trait BadSelfTraitBound: Clone + Clone + Clone { - fn f(); - } +fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {} - trait BadSelfWhereClause { - fn f() - where - Self: Clone + Clone + Clone; - } +trait GenericTrait<T> {} - trait GoodTraitBound<T: Clone + Copy, U: Clone + Copy> { - fn f(); - } +fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) { + unimplemented!(); +} - trait GoodWhereClause<T, U> { - fn f() - where - T: Clone + Copy, - U: Clone + Copy; - } +fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) { + unimplemented!(); +} - trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> { - fn f(); - } +mod foo { + pub trait Clone {} +} - trait BadWhereClause<T, U> { - fn f() - where - T: Clone + Clone + Clone + Copy, - U: Clone + Copy; - } - - struct GoodStructBound<T: Clone + Copy, U: Clone + Copy> { - t: T, - u: U, - } - - impl<T: Clone + Copy, U: Clone + Copy> GoodTraitBound<T, U> for GoodStructBound<T, U> { - // this should not warn - fn f() {} - } - - struct GoodStructWhereClause; - - impl<T, U> GoodTraitBound<T, U> for GoodStructWhereClause - where - T: Clone + Copy, - U: Clone + Copy, - { - // this should not warn - fn f() {} - } - - fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {} - - trait GenericTrait<T> {} - - // This should not warn but currently does see #8757 - fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) { - unimplemented!(); - } - - fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) { - unimplemented!(); - } - - mod foo { - pub trait Clone {} - } - - fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) { - unimplemented!(); - } +fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) { + unimplemented!(); } fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index 7ef04e52708..af800ba7888 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -1,167 +1,56 @@ -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:7:15 +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:5:15 | -LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) - | ^^^^^ +LL | fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` | note: the lint level is defined here - --> $DIR/trait_duplication_in_bounds.rs:1:9 + --> $DIR/trait_duplication_in_bounds.rs:2:9 | LL | #![deny(clippy::trait_duplication_in_bounds)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: consider removing this trait bound - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:7:23 - | -LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) - | ^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:36:15 - | -LL | Self: Default; - | ^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:50:15 - | -LL | Self: Default + Clone; - | ^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:56:15 - | -LL | Self: Default + Clone; - | ^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:56:25 - | -LL | Self: Default + Clone; - | ^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:59:15 - | -LL | Self: Default; - | ^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:94:15 - | -LL | Self: Iterator<Item = Foo>, - | ^^^^^^^^^^^^^^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:103:19 - | -LL | fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { - | ^^^^^ - | - = help: consider removing this trait bound - -error: these bounds contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:103:19 - | -LL | fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:109:12 - | -LL | T: Clone + Clone + Clone + Copy, - | ^^^^^ - | - = help: consider removing this trait bound error: these where clauses contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:109:12 + --> $DIR/trait_duplication_in_bounds.rs:11:8 + | +LL | T: Clone + Clone + Clone + Copy, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:39:26 + | +LL | trait BadSelfTraitBound: Clone + Clone + Clone { + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` + +error: these where clauses contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:46:15 + | +LL | Self: Clone + Clone + Clone; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:60:24 + | +LL | trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: these where clauses contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:67:12 | LL | T: Clone + Clone + Clone + Copy, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` error: these bounds contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:137:30 + --> $DIR/trait_duplication_in_bounds.rs:100:19 | -LL | trait BadSelfTraitBound: Clone + Clone + Clone { - | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` - -error: these where clauses contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:144:19 - | -LL | Self: Clone + Clone + Clone; - | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:158:28 - | -LL | trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> { - | ^^^^^ - | - = help: consider removing this trait bound +LL | fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `GenericTrait<u64> + GenericTrait<u32>` error: these bounds contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:158:28 + --> $DIR/trait_duplication_in_bounds.rs:108:22 | -LL | trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` +LL | fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::clone::Clone + foo::Clone` -error: these where clauses contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:165:16 - | -LL | T: Clone + Clone + Clone + Copy, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:195:24 - | -LL | fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) { - | ^^^^^^^^^^^^^^^^^ - | - = help: consider removing this trait bound - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:199:23 - | -LL | fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) { - | ^^^^^^^^^^^^^^^^^ - | - = help: consider removing this trait bound - -error: these bounds contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:199:23 - | -LL | fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `GenericTrait<u32> + GenericTrait<u64>` - -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:207:26 - | -LL | fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) { - | ^^^^^^^^^^^^^^^^^ - | - = help: consider removing this trait bound - -error: these bounds contain repeated elements - --> $DIR/trait_duplication_in_bounds.rs:207:26 - | -LL | fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + foo::Clone` - -error: aborting due to 22 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/trait_duplication_in_bounds_unfixable.rs b/tests/ui/trait_duplication_in_bounds_unfixable.rs new file mode 100644 index 00000000000..5630a0345ad --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_unfixable.rs @@ -0,0 +1,166 @@ +#![deny(clippy::trait_duplication_in_bounds)] + +use std::collections::BTreeMap; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) +where + T: Clone, + T: Default, +{ + unimplemented!(); +} + +fn good_bar<T: Clone + Default>(arg: T) { + unimplemented!(); +} + +fn good_foo<T>(arg: T) +where + T: Clone + Default, +{ + unimplemented!(); +} + +fn good_foobar<T: Default>(arg: T) +where + T: Clone, +{ + unimplemented!(); +} + +trait T: Default { + fn f() + where + Self: Default; +} + +trait U: Default { + fn f() + where + Self: Clone; +} + +trait ZZ: Default { + fn g(); + fn h(); + fn f() + where + Self: Default + Clone; +} + +trait BadTrait: Default + Clone { + fn f() + where + Self: Default + Clone; + fn g() + where + Self: Default; + fn h() + where + Self: Copy; +} + +#[derive(Default, Clone)] +struct Life; + +impl T for Life { + // this should not warn + fn f() {} +} + +impl U for Life { + // this should not warn + fn f() {} +} + +// should not warn +trait Iter: Iterator { + fn into_group_btreemap<K, V>(self) -> BTreeMap<K, Vec<V>> + where + Self: Iterator<Item = (K, V)> + Sized, + K: Ord + Eq, + { + unimplemented!(); + } +} + +struct Foo; + +trait FooIter: Iterator<Item = Foo> { + fn bar() + where + Self: Iterator<Item = Foo>, + { + } +} + +// The below should not lint and exist to guard against false positives +fn impl_trait(_: impl AsRef<str>, _: impl AsRef<str>) {} + +pub mod one { + #[derive(Clone, Debug)] + struct MultiProductIter<I> + where + I: Iterator + Clone, + I::Item: Clone, + { + _marker: I, + } + + pub struct MultiProduct<I>(Vec<MultiProductIter<I>>) + where + I: Iterator + Clone, + I::Item: Clone; + + pub fn multi_cartesian_product<H>(_: H) -> MultiProduct<<H::Item as IntoIterator>::IntoIter> + where + H: Iterator, + H::Item: IntoIterator, + <H::Item as IntoIterator>::IntoIter: Clone, + <H::Item as IntoIterator>::Item: Clone, + { + todo!() + } +} + +pub mod two { + use std::iter::Peekable; + + pub struct MergeBy<I, J, F> + where + I: Iterator, + J: Iterator<Item = I::Item>, + { + _i: Peekable<I>, + _j: Peekable<J>, + _f: F, + } + + impl<I, J, F> Clone for MergeBy<I, J, F> + where + I: Iterator, + J: Iterator<Item = I::Item>, + std::iter::Peekable<I>: Clone, + std::iter::Peekable<J>: Clone, + F: Clone, + { + fn clone(&self) -> Self { + Self { + _i: self._i.clone(), + _j: self._j.clone(), + _f: self._f.clone(), + } + } + } +} + +pub trait Trait {} + +pub fn f(_a: impl Trait, _b: impl Trait) {} + +pub trait ImplTrait<T> {} + +impl<A, B> ImplTrait<(A, B)> for Foo where Foo: ImplTrait<A> + ImplTrait<B> {} + +fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds_unfixable.stderr b/tests/ui/trait_duplication_in_bounds_unfixable.stderr new file mode 100644 index 00000000000..fbd9abb005f --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_unfixable.stderr @@ -0,0 +1,71 @@ +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds_unfixable.rs:6:15 + | +LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/trait_duplication_in_bounds_unfixable.rs:1:9 + | +LL | #![deny(clippy::trait_duplication_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider removing this trait bound + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds_unfixable.rs:6:23 + | +LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:35:15 + | +LL | Self: Default; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:49:15 + | +LL | Self: Default + Clone; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:55:15 + | +LL | Self: Default + Clone; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:55:25 + | +LL | Self: Default + Clone; + | ^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:58:15 + | +LL | Self: Default; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:93:15 + | +LL | Self: Iterator<Item = Foo>, + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: aborting due to 8 previous errors + diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index ebcaa7a84cf..5aad0b44270 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -4,6 +4,7 @@ use core::any::TypeId; use core::ffi::c_void; use core::mem::{size_of, transmute, MaybeUninit}; +use core::ptr::NonNull; fn value<T>() -> T { unimplemented!() @@ -109,6 +110,17 @@ fn main() { let _: Ty2<u32, u32> = transmute(value::<MaybeUninit<Ty2<u32, u32>>>()); // Ok let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec<u32>>()); // Ok + + let _: *const Ty2<u32, u32> = transmute(value::<*const Ty2C<Ty2<u32, u32>, u32>>()); // Ok + let _: *const Ty2C<Ty2<u32, u32>, u32> = transmute(value::<*const Ty2<u32, u32>>()); // Ok + let _: *const Ty2<u32, u32> = transmute(value::<*const Ty2C<(), Ty2<u32, u32>>>()); // Ok + let _: *const Ty2C<(), Ty2<u32, u32>> = transmute(value::<*const Ty2<u32, u32>>()); // Ok + + let _: *const Ty2<u32, u32> = transmute(value::<*const Ty2C<u32, Ty2<u32, u32>>>()); // Err + let _: *const Ty2C<u32, Ty2<u32, u32>> = transmute(value::<*const Ty2<u32, u32>>()); // Err + + let _: NonNull<u8> = transmute(value::<NonNull<(String, String)>>()); // Ok + let _: NonNull<(String, String)> = transmute(value::<NonNull<u8>>()); // Ok } } diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 28bfba6c757..e50a773290e 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -1,5 +1,5 @@ error: transmute from `Ty2<u32, i32>` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:27:33 + --> $DIR/transmute_undefined_repr.rs:28:33 | LL | let _: Ty2C<u32, i32> = transmute(value::<Ty2<u32, i32>>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | let _: Ty2C<u32, i32> = transmute(value::<Ty2<u32, i32>>()); // Lin = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings` error: transmute into `Ty2<u32, i32>` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:28:32 + --> $DIR/transmute_undefined_repr.rs:29:32 | LL | let _: Ty2<u32, i32> = transmute(value::<Ty2C<u32, i32>>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from `Ty<Ty2<u32, i32>>` to `Ty2<u32, f32>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:33:32 + --> $DIR/transmute_undefined_repr.rs:34:32 | LL | let _: Ty2<u32, f32> = transmute(value::<Ty<Ty2<u32, i32>>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | let _: Ty2<u32, f32> = transmute(value::<Ty<Ty2<u32, i32>>>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty2<u32, f32>` to `Ty<Ty2<u32, i32>>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:34:36 + --> $DIR/transmute_undefined_repr.rs:35:36 | LL | let _: Ty<Ty2<u32, i32>> = transmute(value::<Ty2<u32, f32>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -29,7 +29,7 @@ LL | let _: Ty<Ty2<u32, i32>> = transmute(value::<Ty2<u32, f32>>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty<&Ty2<u32, i32>>` to `&Ty2<u32, f32>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:39:33 + --> $DIR/transmute_undefined_repr.rs:40:33 | LL | let _: &Ty2<u32, f32> = transmute(value::<Ty<&Ty2<u32, i32>>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL | let _: &Ty2<u32, f32> = transmute(value::<Ty<&Ty2<u32, i32>>>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&Ty2<u32, f32>` to `Ty<&Ty2<u32, i32>>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:40:37 + --> $DIR/transmute_undefined_repr.rs:41:37 | LL | let _: Ty<&Ty2<u32, i32>> = transmute(value::<&Ty2<u32, f32>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | let _: Ty<&Ty2<u32, i32>> = transmute(value::<&Ty2<u32, f32>>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `std::boxed::Box<Ty2<u32, u32>>` to `&mut Ty2<u32, f32>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:57:45 + --> $DIR/transmute_undefined_repr.rs:58:45 | LL | let _: &'static mut Ty2<u32, f32> = transmute(value::<Box<Ty2<u32, u32>>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -53,15 +53,31 @@ LL | let _: &'static mut Ty2<u32, f32> = transmute(value::<Box<Ty2<u32, = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&mut Ty2<u32, f32>` to `std::boxed::Box<Ty2<u32, u32>>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:58:37 + --> $DIR/transmute_undefined_repr.rs:59:37 | LL | let _: Box<Ty2<u32, u32>> = transmute(value::<&'static mut Ty2<u32, f32>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts +error: transmute into `*const Ty2<u32, u32>` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:119:39 + | +LL | let _: *const Ty2<u32, u32> = transmute(value::<*const Ty2C<u32, Ty2<u32, u32>>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the contained type `Ty2<u32, u32>` has an undefined layout + +error: transmute from `*const Ty2<u32, u32>` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:120:50 + | +LL | let _: *const Ty2C<u32, Ty2<u32, u32>> = transmute(value::<*const Ty2<u32, u32>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the contained type `Ty2<u32, u32>` has an undefined layout + error: transmute from `std::vec::Vec<Ty2<U, i32>>` to `std::vec::Vec<Ty2<T, u32>>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:138:35 + --> $DIR/transmute_undefined_repr.rs:150:35 | LL | let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); // Err | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,12 +85,12 @@ LL | let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); / = note: two instances of the same generic type (`Vec`) may have different layouts error: transmute from `std::vec::Vec<Ty2<T, u32>>` to `std::vec::Vec<Ty2<U, i32>>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:139:35 + --> $DIR/transmute_undefined_repr.rs:151:35 | LL | let _: Vec<Ty2<U, i32>> = transmute(value::<Vec<Ty2<T, u32>>>()); // Err | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Vec`) may have different layouts -error: aborting due to 10 previous errors +error: aborting due to 12 previous errors diff --git a/tests/ui/unicode.fixed b/tests/ui/unicode.fixed index 328cda369e1..94b4723452f 100644 --- a/tests/ui/unicode.fixed +++ b/tests/ui/unicode.fixed @@ -1,4 +1,7 @@ // run-rustfix +// compile-flags: --test +#![allow(dead_code)] + #[warn(clippy::invisible_characters)] fn zero() { print!("Here >\u{200B}< is a ZWS, and \u{200B}another"); @@ -15,22 +18,43 @@ fn canon() { print!("a\u{0300}h?"); // also ok } -#[warn(clippy::non_ascii_literal)] -fn uni() { - print!("\u{dc}ben!"); - print!("\u{DC}ben!"); // this is ok -} +mod non_ascii_literal { + #![deny(clippy::non_ascii_literal)] -// issue 8013 -#[warn(clippy::non_ascii_literal)] -fn single_quote() { - const _EMPTY_BLOCK: char = '\u{25b1}'; - const _FULL_BLOCK: char = '\u{25b0}'; + fn uni() { + print!("\u{dc}ben!"); + print!("\u{DC}ben!"); // this is ok + } + + // issue 8013 + fn single_quote() { + const _EMPTY_BLOCK: char = '\u{25b1}'; + const _FULL_BLOCK: char = '\u{25b0}'; + } + + #[test] + pub fn issue_7739() { + // Ryū crate: https://github.com/dtolnay/ryu + } + + mod issue_8263 { + #![deny(clippy::non_ascii_literal)] + + // Re-allow for a single test + #[test] + #[allow(clippy::non_ascii_literal)] + fn allowed() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + + #[test] + fn denied() { + let _ = "\u{60b2}\u{3057}\u{3044}\u{304b}\u{306a}\u{3001}\u{3053}\u{3053}\u{306b}\u{65e5}\u{672c}\u{8a9e}\u{3092}\u{66f8}\u{304f}\u{3053}\u{3068}\u{306f}\u{3067}\u{304d}\u{306a}\u{3044}\u{3002}"; + } + } } fn main() { zero(); - uni(); canon(); - single_quote(); } diff --git a/tests/ui/unicode.rs b/tests/ui/unicode.rs index 7828d6bcbea..6ad0b255b94 100644 --- a/tests/ui/unicode.rs +++ b/tests/ui/unicode.rs @@ -1,4 +1,7 @@ // run-rustfix +// compile-flags: --test +#![allow(dead_code)] + #[warn(clippy::invisible_characters)] fn zero() { print!("Here >< is a ZWS, and another"); @@ -15,22 +18,43 @@ fn canon() { print!("a\u{0300}h?"); // also ok } -#[warn(clippy::non_ascii_literal)] -fn uni() { - print!("Üben!"); - print!("\u{DC}ben!"); // this is ok -} +mod non_ascii_literal { + #![deny(clippy::non_ascii_literal)] -// issue 8013 -#[warn(clippy::non_ascii_literal)] -fn single_quote() { - const _EMPTY_BLOCK: char = '▱'; - const _FULL_BLOCK: char = '▰'; + fn uni() { + print!("Üben!"); + print!("\u{DC}ben!"); // this is ok + } + + // issue 8013 + fn single_quote() { + const _EMPTY_BLOCK: char = '▱'; + const _FULL_BLOCK: char = '▰'; + } + + #[test] + pub fn issue_7739() { + // Ryū crate: https://github.com/dtolnay/ryu + } + + mod issue_8263 { + #![deny(clippy::non_ascii_literal)] + + // Re-allow for a single test + #[test] + #[allow(clippy::non_ascii_literal)] + fn allowed() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + + #[test] + fn denied() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + } } fn main() { zero(); - uni(); canon(); - single_quote(); } diff --git a/tests/ui/unicode.stderr b/tests/ui/unicode.stderr index 01d3f3c0296..ea74a81451e 100644 --- a/tests/ui/unicode.stderr +++ b/tests/ui/unicode.stderr @@ -1,5 +1,5 @@ error: invisible character detected - --> $DIR/unicode.rs:4:12 + --> $DIR/unicode.rs:7:12 | LL | print!("Here >< is a ZWS, and another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{200B}< is a ZWS, and /u{200B}another"` @@ -7,19 +7,19 @@ LL | print!("Here >< is a ZWS, and another"); = note: `-D clippy::invisible-characters` implied by `-D warnings` error: invisible character detected - --> $DIR/unicode.rs:6:12 + --> $DIR/unicode.rs:9:12 | LL | print!("Here >< is a SHY, and another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{AD}< is a SHY, and /u{AD}another"` error: invisible character detected - --> $DIR/unicode.rs:8:12 + --> $DIR/unicode.rs:11:12 | LL | print!("Here >< is a WJ, and another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{2060}< is a WJ, and /u{2060}another"` error: non-NFC Unicode sequence detected - --> $DIR/unicode.rs:14:12 + --> $DIR/unicode.rs:17:12 | LL | print!("̀àh?"); | ^^^^^ help: consider replacing the string with: `"̀àh?"` @@ -27,24 +27,40 @@ LL | print!("̀àh?"); = note: `-D clippy::unicode-not-nfc` implied by `-D warnings` error: literal non-ASCII character detected - --> $DIR/unicode.rs:20:12 + --> $DIR/unicode.rs:25:16 | -LL | print!("Üben!"); - | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` +LL | print!("Üben!"); + | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` | - = note: `-D clippy::non-ascii-literal` implied by `-D warnings` +note: the lint level is defined here + --> $DIR/unicode.rs:22:13 + | +LL | #![deny(clippy::non_ascii_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: literal non-ASCII character detected - --> $DIR/unicode.rs:27:32 + --> $DIR/unicode.rs:31:36 | -LL | const _EMPTY_BLOCK: char = '▱'; - | ^^^ help: consider replacing the string with: `'/u{25b1}'` +LL | const _EMPTY_BLOCK: char = '▱'; + | ^^^ help: consider replacing the string with: `'/u{25b1}'` error: literal non-ASCII character detected - --> $DIR/unicode.rs:28:31 + --> $DIR/unicode.rs:32:35 | -LL | const _FULL_BLOCK: char = '▰'; - | ^^^ help: consider replacing the string with: `'/u{25b0}'` +LL | const _FULL_BLOCK: char = '▰'; + | ^^^ help: consider replacing the string with: `'/u{25b0}'` -error: aborting due to 7 previous errors +error: literal non-ASCII character detected + --> $DIR/unicode.rs:52:21 + | +LL | let _ = "悲しいかな、ここに日本語を書くことはできない。"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"/u{60b2}/u{3057}/u{3044}/u{304b}/u{306a}/u{3001}/u{3053}/u{3053}/u{306b}/u{65e5}/u{672c}/u{8a9e}/u{3092}/u{66f8}/u{304f}/u{3053}/u{3068}/u{306f}/u{3067}/u{304d}/u{306a}/u{3044}/u{3002}"` + | +note: the lint level is defined here + --> $DIR/unicode.rs:41:17 + | +LL | #![deny(clippy::non_ascii_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_cast.fixed b/tests/ui/unnecessary_cast.fixed index b352b285c86..ee9f157342d 100644 --- a/tests/ui/unnecessary_cast.fixed +++ b/tests/ui/unnecessary_cast.fixed @@ -88,4 +88,13 @@ mod fixable { } type I32Alias = i32; + + fn issue_9380() { + let _: i32 = -1_i32; + let _: f32 = -(1) as f32; + let _: i64 = -1_i64; + let _: i64 = -(1.0) as i64; + + let _ = -(1 + 1) as i64; + } } diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index 6c8cc3effe8..5b70412424c 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -88,4 +88,13 @@ mod fixable { } type I32Alias = i32; + + fn issue_9380() { + let _: i32 = -(1) as i32; + let _: f32 = -(1) as f32; + let _: i64 = -(1) as i64; + let _: i64 = -(1.0) as i64; + + let _ = -(1 + 1) as i64; + } } diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index bad45f0025b..f7829ff3b0e 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -150,5 +150,17 @@ error: casting float literal to `f32` is unnecessary LL | let _ = -1.0 as f32; | ^^^^^^^^^^^ help: try: `-1.0_f32` -error: aborting due to 25 previous errors +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast.rs:93:22 + | +LL | let _: i32 = -(1) as i32; + | ^^^^^^^^^^^ help: try: `-1_i32` + +error: casting integer literal to `i64` is unnecessary + --> $DIR/unnecessary_cast.rs:95:22 + | +LL | let _: i64 = -(1) as i64; + | ^^^^^^^^^^^ help: try: `-1_i64` + +error: aborting due to 27 previous errors diff --git a/tests/ui/unnecessary_owned_empty_strings.fixed b/tests/ui/unnecessary_owned_empty_strings.fixed index f95f91329a2..40052c41039 100644 --- a/tests/ui/unnecessary_owned_empty_strings.fixed +++ b/tests/ui/unnecessary_owned_empty_strings.fixed @@ -12,6 +12,7 @@ fn main() { ref_str_argument(""); // should be linted + #[allow(clippy::manual_string_new)] ref_str_argument(""); // should not be linted diff --git a/tests/ui/unnecessary_owned_empty_strings.rs b/tests/ui/unnecessary_owned_empty_strings.rs index 0cbdc151ed9..2304dff5192 100644 --- a/tests/ui/unnecessary_owned_empty_strings.rs +++ b/tests/ui/unnecessary_owned_empty_strings.rs @@ -12,6 +12,7 @@ fn main() { ref_str_argument(&String::new()); // should be linted + #[allow(clippy::manual_string_new)] ref_str_argument(&String::from("")); // should not be linted diff --git a/tests/ui/unnecessary_owned_empty_strings.stderr b/tests/ui/unnecessary_owned_empty_strings.stderr index 46bc4597b33..1eb198a8675 100644 --- a/tests/ui/unnecessary_owned_empty_strings.stderr +++ b/tests/ui/unnecessary_owned_empty_strings.stderr @@ -7,7 +7,7 @@ LL | ref_str_argument(&String::new()); = note: `-D clippy::unnecessary-owned-empty-strings` implied by `-D warnings` error: usage of `&String::from("")` for a function expecting a `&str` argument - --> $DIR/unnecessary_owned_empty_strings.rs:15:22 + --> $DIR/unnecessary_owned_empty_strings.rs:16:22 | LL | ref_str_argument(&String::from("")); | ^^^^^^^^^^^^^^^^^ help: try: `""` diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index f4f76cd3dd4..9cd5bc73b1e 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -329,3 +329,31 @@ mod issue_8759_variant { rw.set_view(&rw.default_view().to_owned()); } } + +mod issue_9317 { + #![allow(dead_code)] + + struct Bytes {} + + impl ToString for Bytes { + fn to_string(&self) -> String { + "123".to_string() + } + } + + impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + &[1, 2, 3] + } + } + + fn consume<C: AsRef<[u8]>>(c: C) { + let _ = c; + } + + pub fn main() { + let b = Bytes {}; + // Should not lint. + consume(b.to_string()); + } +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index fe09a489ab0..7f62ba3ab5d 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -329,3 +329,31 @@ mod issue_8759_variant { rw.set_view(&rw.default_view().to_owned()); } } + +mod issue_9317 { + #![allow(dead_code)] + + struct Bytes {} + + impl ToString for Bytes { + fn to_string(&self) -> String { + "123".to_string() + } + } + + impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + &[1, 2, 3] + } + } + + fn consume<C: AsRef<[u8]>>(c: C) { + let _ = c; + } + + pub fn main() { + let b = Bytes {}; + // Should not lint. + consume(b.to_string()); + } +} diff --git a/tests/ui/unused_peekable.rs b/tests/ui/unused_peekable.rs new file mode 100644 index 00000000000..153457e3671 --- /dev/null +++ b/tests/ui/unused_peekable.rs @@ -0,0 +1,144 @@ +#![warn(clippy::unused_peekable)] +#![allow(clippy::no_effect)] + +use std::iter::Empty; +use std::iter::Peekable; + +fn main() { + invalid(); + valid(); +} + +#[allow(clippy::unused_unit)] +fn invalid() { + let peekable = std::iter::empty::<u32>().peekable(); + + // Only lint `new_local` + let old_local = std::iter::empty::<u32>().peekable(); + let new_local = old_local; + + // Behind mut ref + let mut by_mut_ref_test = std::iter::empty::<u32>().peekable(); + let by_mut_ref = &mut by_mut_ref_test; + + // Explicitly returns `Peekable` + fn returns_peekable() -> Peekable<Empty<u32>> { + std::iter::empty().peekable() + } + + let peekable_from_fn = returns_peekable(); + + // Using a method not exclusive to `Peekable` + let mut peekable_using_iterator_method = std::iter::empty::<u32>().peekable(); + peekable_using_iterator_method.next(); + + // Passed by ref to another function + fn takes_ref(_peek: &Peekable<Empty<u32>>) {} + let passed_along_ref = std::iter::empty::<u32>().peekable(); + takes_ref(&passed_along_ref); + + // `by_ref` without `peek` + let mut by_ref_test = std::iter::empty::<u32>().peekable(); + let _by_ref = by_ref_test.by_ref(); + + let mut peekable_in_for_loop = std::iter::empty::<u32>().peekable(); + for x in peekable_in_for_loop {} +} + +fn valid() { + fn takes_peekable(_peek: Peekable<Empty<u32>>) {} + + // Passed to another function + let passed_along = std::iter::empty::<u32>().peekable(); + takes_peekable(passed_along); + + // Passed to another method + struct PeekableConsumer; + impl PeekableConsumer { + fn consume(&self, _: Peekable<Empty<u32>>) {} + fn consume_mut_ref(&self, _: &mut Peekable<Empty<u32>>) {} + } + + let peekable_consumer = PeekableConsumer; + let mut passed_along_to_method = std::iter::empty::<u32>().peekable(); + peekable_consumer.consume_mut_ref(&mut passed_along_to_method); + peekable_consumer.consume(passed_along_to_method); + + // `peek` called in another block + let mut peekable_in_block = std::iter::empty::<u32>().peekable(); + { + peekable_in_block.peek(); + } + + // Check the other `Peekable` methods :) + { + let mut peekable_with_peek_mut = std::iter::empty::<u32>().peekable(); + peekable_with_peek_mut.peek_mut(); + + let mut peekable_with_next_if = std::iter::empty::<u32>().peekable(); + peekable_with_next_if.next_if(|_| true); + + let mut peekable_with_next_if_eq = std::iter::empty::<u32>().peekable(); + peekable_with_next_if_eq.next_if_eq(&3); + } + + let mut peekable_in_closure = std::iter::empty::<u32>().peekable(); + let call_peek = |p: &mut Peekable<Empty<u32>>| { + p.peek(); + }; + call_peek(&mut peekable_in_closure); + + // From a macro + macro_rules! make_me_a_peekable_please { + () => { + std::iter::empty::<u32>().peekable() + }; + } + + let _unsuspecting_macro_user = make_me_a_peekable_please!(); + + // Generic Iterator returned + fn return_an_iter() -> impl Iterator<Item = u32> { + std::iter::empty::<u32>().peekable() + } + + let _unsuspecting_user = return_an_iter(); + + // Call `peek` in a macro + macro_rules! peek_iter { + ($iter:ident) => { + $iter.peek(); + }; + } + + let mut peek_in_macro = std::iter::empty::<u32>().peekable(); + peek_iter!(peek_in_macro); + + // Behind mut ref + let mut by_mut_ref_test = std::iter::empty::<u32>().peekable(); + let by_mut_ref = &mut by_mut_ref_test; + by_mut_ref.peek(); + + // Behind ref + let mut by_ref_test = std::iter::empty::<u32>().peekable(); + let by_ref = &by_ref_test; + by_ref_test.peek(); + + // In struct + struct PeekableWrapper { + f: Peekable<Empty<u32>>, + } + + let struct_test = std::iter::empty::<u32>().peekable(); + PeekableWrapper { f: struct_test }; + + // `by_ref` before `peek` + let mut by_ref_test = std::iter::empty::<u32>().peekable(); + let peeked_val = by_ref_test.by_ref().peek(); + + // `peek` called in another block as the last expression + let mut peekable_last_expr = std::iter::empty::<u32>().peekable(); + { + peekable_last_expr.peek(); + } +} diff --git a/tests/ui/unused_peekable.stderr b/tests/ui/unused_peekable.stderr new file mode 100644 index 00000000000..d557f54179d --- /dev/null +++ b/tests/ui/unused_peekable.stderr @@ -0,0 +1,67 @@ +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:14:9 + | +LL | let peekable = std::iter::empty::<u32>().peekable(); + | ^^^^^^^^ + | + = note: `-D clippy::unused-peekable` implied by `-D warnings` + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:18:9 + | +LL | let new_local = old_local; + | ^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:22:9 + | +LL | let by_mut_ref = &mut by_mut_ref_test; + | ^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:29:9 + | +LL | let peekable_from_fn = returns_peekable(); + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:32:13 + | +LL | let mut peekable_using_iterator_method = std::iter::empty::<u32>().peekable(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:37:9 + | +LL | let passed_along_ref = std::iter::empty::<u32>().peekable(); + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:42:9 + | +LL | let _by_ref = by_ref_test.by_ref(); + | ^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:44:13 + | +LL | let mut peekable_in_for_loop = std::iter::empty::<u32>().peekable(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/unwrap.rs b/tests/ui/unwrap.rs index a4a3cd1d379..d9fd402e7cf 100644 --- a/tests/ui/unwrap.rs +++ b/tests/ui/unwrap.rs @@ -6,8 +6,9 @@ fn unwrap_option() { } fn unwrap_result() { - let res: Result<u8, ()> = Ok(0); + let res: Result<u8, u8> = Ok(0); let _ = res.unwrap(); + let _ = res.unwrap_err(); } fn main() { diff --git a/tests/ui/unwrap.stderr b/tests/ui/unwrap.stderr index 4f0858005f6..78422757819 100644 --- a/tests/ui/unwrap.stderr +++ b/tests/ui/unwrap.stderr @@ -15,5 +15,13 @@ LL | let _ = res.unwrap(); | = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message -error: aborting due to 2 previous errors +error: used `unwrap_err()` on `a Result` value + --> $DIR/unwrap.rs:11:13 + | +LL | let _ = res.unwrap_err(); + | ^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `Ok` case gracefully, consider using `expect_err()` to provide a better panic message + +error: aborting due to 3 previous errors diff --git a/tests/ui/unwrap_expect_used.rs b/tests/ui/unwrap_expect_used.rs index 0d4a0504a6e..9f27fef8249 100644 --- a/tests/ui/unwrap_expect_used.rs +++ b/tests/ui/unwrap_expect_used.rs @@ -1,10 +1,35 @@ #![warn(clippy::unwrap_used, clippy::expect_used)] +trait OptionExt { + type Item; + + fn unwrap_err(self) -> Self::Item; + + fn expect_err(self, msg: &str) -> Self::Item; +} + +impl<T> OptionExt for Option<T> { + type Item = T; + fn unwrap_err(self) -> T { + panic!(); + } + + fn expect_err(self, msg: &str) -> T { + panic!(); + } +} + fn main() { Some(3).unwrap(); Some(3).expect("Hello world!"); + // Don't trigger on unwrap_err on an option + Some(3).unwrap_err(); + Some(3).expect_err("Hellow none!"); + let a: Result<i32, i32> = Ok(3); a.unwrap(); a.expect("Hello world!"); + a.unwrap_err(); + a.expect_err("Hello error!"); } diff --git a/tests/ui/unwrap_expect_used.stderr b/tests/ui/unwrap_expect_used.stderr index f54bfd617c4..1a19459b2c1 100644 --- a/tests/ui/unwrap_expect_used.stderr +++ b/tests/ui/unwrap_expect_used.stderr @@ -1,5 +1,5 @@ error: used `unwrap()` on `an Option` value - --> $DIR/unwrap_expect_used.rs:4:5 + --> $DIR/unwrap_expect_used.rs:23:5 | LL | Some(3).unwrap(); | ^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | Some(3).unwrap(); = help: if this value is `None`, it will panic error: used `expect()` on `an Option` value - --> $DIR/unwrap_expect_used.rs:5:5 + --> $DIR/unwrap_expect_used.rs:24:5 | LL | Some(3).expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | Some(3).expect("Hello world!"); = help: if this value is `None`, it will panic error: used `unwrap()` on `a Result` value - --> $DIR/unwrap_expect_used.rs:8:5 + --> $DIR/unwrap_expect_used.rs:31:5 | LL | a.unwrap(); | ^^^^^^^^^^ @@ -25,12 +25,28 @@ LL | a.unwrap(); = help: if this value is an `Err`, it will panic error: used `expect()` on `a Result` value - --> $DIR/unwrap_expect_used.rs:9:5 + --> $DIR/unwrap_expect_used.rs:32:5 | LL | a.expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: if this value is an `Err`, it will panic -error: aborting due to 4 previous errors +error: used `unwrap_err()` on `a Result` value + --> $DIR/unwrap_expect_used.rs:33:5 + | +LL | a.unwrap_err(); + | ^^^^^^^^^^^^^^ + | + = help: if this value is an `Ok`, it will panic + +error: used `expect_err()` on `a Result` value + --> $DIR/unwrap_expect_used.rs:34:5 + | +LL | a.expect_err("Hello error!"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if this value is an `Ok`, it will panic + +error: aborting due to 6 previous errors diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs index 39f54c27bee..4acf5b5fa2d 100644 --- a/tests/ui/useless_conversion_try.rs +++ b/tests/ui/useless_conversion_try.rs @@ -29,10 +29,10 @@ fn main() { let _ = String::try_from("foo".to_string()).unwrap(); let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); let _: String = format!("Hello {}", "world").try_into().unwrap(); - let _: String = "".to_owned().try_into().unwrap(); + let _: String = String::new().try_into().unwrap(); let _: String = match String::from("_").try_into() { Ok(a) => a, - Err(_) => "".into(), + Err(_) => String::new(), }; // FIXME this is a false negative #[allow(clippy::cmp_owned)] diff --git a/tests/ui/useless_conversion_try.stderr b/tests/ui/useless_conversion_try.stderr index b691c13f7db..12e74d61471 100644 --- a/tests/ui/useless_conversion_try.stderr +++ b/tests/ui/useless_conversion_try.stderr @@ -62,7 +62,7 @@ LL | let _: String = format!("Hello {}", "world").try_into().unwrap(); error: useless conversion to the same type: `std::string::String` --> $DIR/useless_conversion_try.rs:32:21 | -LL | let _: String = "".to_owned().try_into().unwrap(); +LL | let _: String = String::new().try_into().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider removing `.try_into()` diff --git a/tests/ui/vec_resize_to_zero.rs b/tests/ui/vec_resize_to_zero.rs index 7ed27439ec6..a8307e741cf 100644 --- a/tests/ui/vec_resize_to_zero.rs +++ b/tests/ui/vec_resize_to_zero.rs @@ -1,15 +1,19 @@ #![warn(clippy::vec_resize_to_zero)] fn main() { + let mut v = vec![1, 2, 3, 4, 5]; + // applicable here - vec![1, 2, 3, 4, 5].resize(0, 5); + v.resize(0, 5); // not applicable - vec![1, 2, 3, 4, 5].resize(2, 5); + v.resize(2, 5); + + let mut v = vec!["foo", "bar", "baz"]; // applicable here, but only implemented for integer literals for now - vec!["foo", "bar", "baz"].resize(0, "bar"); + v.resize(0, "bar"); // not applicable - vec!["foo", "bar", "baz"].resize(2, "bar") + v.resize(2, "bar") } diff --git a/tests/ui/vec_resize_to_zero.stderr b/tests/ui/vec_resize_to_zero.stderr index feb846298c6..7428cf62d6c 100644 --- a/tests/ui/vec_resize_to_zero.stderr +++ b/tests/ui/vec_resize_to_zero.stderr @@ -1,10 +1,10 @@ error: emptying a vector with `resize` - --> $DIR/vec_resize_to_zero.rs:5:5 + --> $DIR/vec_resize_to_zero.rs:7:5 | -LL | vec![1, 2, 3, 4, 5].resize(0, 5); - | ^^^^^^^^^^^^^^^^^^^^------------ - | | - | help: ...or you can empty the vector with: `clear()` +LL | v.resize(0, 5); + | ^^------------ + | | + | help: ...or you can empty the vector with: `clear()` | = note: `-D clippy::vec-resize-to-zero` implied by `-D warnings` = help: the arguments may be inverted... diff --git a/tests/ui/verbose_file_reads.rs b/tests/ui/verbose_file_reads.rs index e0065e05ade..df267e9872a 100644 --- a/tests/ui/verbose_file_reads.rs +++ b/tests/ui/verbose_file_reads.rs @@ -18,7 +18,7 @@ fn main() -> std::io::Result<()> { s.read_to_end(); s.read_to_string(); // Should catch this - let mut f = File::open(&path)?; + let mut f = File::open(path)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; // ...and this diff --git a/tests/workspace.rs b/tests/workspace.rs index e13efb3e016..95325e06037 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -20,8 +20,8 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .current_dir(&cwd) .env("CARGO_TARGET_DIR", &target_dir) .arg("clean") - .args(&["-p", "subcrate"]) - .args(&["-p", "path_dep"]) + .args(["-p", "subcrate"]) + .args(["-p", "path_dep"]) .output() .unwrap(); @@ -32,11 +32,11 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--no-deps") .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir - .args(&["--cfg", r#"feature="primary_package_test""#]) + .args(["--cfg", r#"feature="primary_package_test""#]) .output() .unwrap(); println!("status: {}", output.status); @@ -52,10 +52,10 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir - .args(&["--cfg", r#"feature="primary_package_test""#]) + .args(["--cfg", r#"feature="primary_package_test""#]) .output() .unwrap(); println!("status: {}", output.status); @@ -79,7 +79,7 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir .output()