]> git.lizzy.rs Git - rust.git/commitdiff
Auto merge of #8487 - dswij:8478, r=giraffate
authorbors <bors@rust-lang.org>
Mon, 28 Mar 2022 00:25:45 +0000 (00:25 +0000)
committerbors <bors@rust-lang.org>
Mon, 28 Mar 2022 00:25:45 +0000 (00:25 +0000)
[`map_identity`] checks for needless `map_err`

Closes #8478

changelog: [`map_identity`] checks for needless `map_err`

198 files changed:
.github/ISSUE_TEMPLATE/bug_report.yml
.github/ISSUE_TEMPLATE/ice.yml
.github/workflows/clippy.yml
.github/workflows/clippy_bors.yml
.github/workflows/clippy_dev.yml
CHANGELOG.md
Cargo.toml
clippy_dev/Cargo.toml
clippy_lints/Cargo.toml
clippy_lints/src/attrs.rs
clippy_lints/src/await_holding_invalid.rs
clippy_lints/src/case_sensitive_file_extension_comparisons.rs
clippy_lints/src/casts/cast_enum_constructor.rs [new file with mode: 0644]
clippy_lints/src/casts/cast_possible_truncation.rs
clippy_lints/src/casts/cast_slice_different_sizes.rs [new file with mode: 0644]
clippy_lints/src/casts/mod.rs
clippy_lints/src/casts/utils.rs
clippy_lints/src/collapsible_if.rs
clippy_lints/src/copies.rs
clippy_lints/src/dbg_macro.rs
clippy_lints/src/default.rs
clippy_lints/src/default_numeric_fallback.rs
clippy_lints/src/derivable_impls.rs
clippy_lints/src/derive.rs
clippy_lints/src/doc.rs
clippy_lints/src/empty_enum.rs
clippy_lints/src/enum_clike.rs
clippy_lints/src/eq_op.rs
clippy_lints/src/eta_reduction.rs
clippy_lints/src/fallible_impl_from.rs
clippy_lints/src/float_equality_without_abs.rs
clippy_lints/src/format.rs
clippy_lints/src/functions/must_use.rs
clippy_lints/src/identity_op.rs
clippy_lints/src/implicit_hasher.rs
clippy_lints/src/inconsistent_struct_constructor.rs
clippy_lints/src/inline_fn_without_body.rs
clippy_lints/src/large_enum_variant.rs
clippy_lints/src/len_zero.rs
clippy_lints/src/lib.register_all.rs
clippy_lints/src/lib.register_complexity.rs
clippy_lints/src/lib.register_correctness.rs
clippy_lints/src/lib.register_internal.rs
clippy_lints/src/lib.register_lints.rs
clippy_lints/src/lib.register_nursery.rs
clippy_lints/src/lib.register_pedantic.rs
clippy_lints/src/lib.register_perf.rs
clippy_lints/src/lib.register_restriction.rs
clippy_lints/src/lib.register_style.rs
clippy_lints/src/lib.register_suspicious.rs
clippy_lints/src/lib.rs
clippy_lints/src/loops/manual_memcpy.rs
clippy_lints/src/loops/missing_spin_loop.rs [new file with mode: 0644]
clippy_lints/src/loops/mod.rs
clippy_lints/src/matches/match_same_arms.rs
clippy_lints/src/matches/match_wild_enum.rs
clippy_lints/src/matches/mod.rs
clippy_lints/src/matches/needless_match.rs [new file with mode: 0644]
clippy_lints/src/methods/bind_instead_of_map.rs
clippy_lints/src/methods/cloned_instead_of_copied.rs
clippy_lints/src/methods/filter_map.rs
clippy_lints/src/methods/implicit_clone.rs
clippy_lints/src/methods/inefficient_to_string.rs
clippy_lints/src/methods/iter_with_drain.rs [new file with mode: 0644]
clippy_lints/src/methods/map_flatten.rs
clippy_lints/src/methods/mod.rs
clippy_lints/src/methods/option_map_or_none.rs
clippy_lints/src/methods/or_then_unwrap.rs [new file with mode: 0644]
clippy_lints/src/methods/str_splitn.rs
clippy_lints/src/methods/unnecessary_filter_map.rs
clippy_lints/src/methods/unnecessary_join.rs [new file with mode: 0644]
clippy_lints/src/methods/unnecessary_lazy_eval.rs
clippy_lints/src/methods/unnecessary_to_owned.rs
clippy_lints/src/missing_const_for_fn.rs
clippy_lints/src/mut_key.rs
clippy_lints/src/needless_pass_by_value.rs
clippy_lints/src/needless_update.rs
clippy_lints/src/new_without_default.rs
clippy_lints/src/non_copy_const.rs
clippy_lints/src/non_send_fields_in_send_ty.rs
clippy_lints/src/only_used_in_recursion.rs [new file with mode: 0644]
clippy_lints/src/ptr.rs
clippy_lints/src/question_mark.rs
clippy_lints/src/redundant_clone.rs
clippy_lints/src/self_named_constructors.rs
clippy_lints/src/single_component_path_imports.rs
clippy_lints/src/suspicious_operation_groupings.rs
clippy_lints/src/trailing_empty_array.rs
clippy_lints/src/trait_bounds.rs
clippy_lints/src/transmute/mod.rs
clippy_lints/src/transmute/transmute_undefined_repr.rs
clippy_lints/src/transmute/unsound_collection_transmute.rs
clippy_lints/src/try_err.rs
clippy_lints/src/unnecessary_sort_by.rs
clippy_lints/src/unnecessary_wraps.rs
clippy_lints/src/unwrap_in_result.rs
clippy_lints/src/upper_case_acronyms.rs
clippy_lints/src/use_self.rs
clippy_lints/src/utils/inspector.rs
clippy_lints/src/utils/internal_lints.rs
clippy_lints/src/utils/internal_lints/metadata_collector.rs
clippy_lints/src/write.rs
clippy_utils/src/ast_utils.rs
clippy_utils/src/consts.rs
clippy_utils/src/diagnostics.rs
clippy_utils/src/eager_or_lazy.rs
clippy_utils/src/hir_utils.rs
clippy_utils/src/lib.rs
clippy_utils/src/msrvs.rs
clippy_utils/src/paths.rs
clippy_utils/src/qualify_min_const_fn.rs
clippy_utils/src/sugg.rs
clippy_utils/src/ty.rs
rust-toolchain
tests/compile-test.rs
tests/lint_message_convention.rs
tests/ui-internal/invalid_msrv_attr_impl.fixed [new file with mode: 0644]
tests/ui-internal/invalid_msrv_attr_impl.rs [new file with mode: 0644]
tests/ui-internal/invalid_msrv_attr_impl.stderr [new file with mode: 0644]
tests/ui/allow_attributes_without_reason.rs [new file with mode: 0644]
tests/ui/allow_attributes_without_reason.stderr [new file with mode: 0644]
tests/ui/cast_enum_constructor.rs [new file with mode: 0644]
tests/ui/cast_enum_constructor.stderr [new file with mode: 0644]
tests/ui/cast_slice_different_sizes.rs [new file with mode: 0644]
tests/ui/cast_slice_different_sizes.stderr [new file with mode: 0644]
tests/ui/crashes/ice-6250.stderr
tests/ui/crashes/ice-6251.stderr
tests/ui/crate_level_checks/no_std_main_recursion.rs
tests/ui/dbg_macro.rs
tests/ui/dbg_macro.stderr
tests/ui/extend_with_drain.fixed
tests/ui/extend_with_drain.rs
tests/ui/identity_op.rs
tests/ui/identity_op.stderr
tests/ui/iter_with_drain.fixed [new file with mode: 0644]
tests/ui/iter_with_drain.rs [new file with mode: 0644]
tests/ui/iter_with_drain.stderr [new file with mode: 0644]
tests/ui/macro_use_imports.fixed
tests/ui/macro_use_imports.stderr
tests/ui/manual_map_option.fixed
tests/ui/manual_map_option.rs
tests/ui/manual_memcpy/with_loop_counters.rs
tests/ui/map_flatten.fixed [deleted file]
tests/ui/map_flatten.rs
tests/ui/map_flatten.stderr
tests/ui/map_flatten_fixable.fixed [new file with mode: 0644]
tests/ui/map_flatten_fixable.rs [new file with mode: 0644]
tests/ui/map_flatten_fixable.stderr [new file with mode: 0644]
tests/ui/match_same_arms.stderr
tests/ui/match_same_arms2.rs
tests/ui/match_same_arms2.stderr
tests/ui/missing_const_for_fn/could_be_const.rs
tests/ui/missing_const_for_fn/could_be_const.stderr
tests/ui/missing_spin_loop.fixed [new file with mode: 0644]
tests/ui/missing_spin_loop.rs [new file with mode: 0644]
tests/ui/missing_spin_loop.stderr [new file with mode: 0644]
tests/ui/missing_spin_loop_no_std.fixed [new file with mode: 0644]
tests/ui/missing_spin_loop_no_std.rs [new file with mode: 0644]
tests/ui/missing_spin_loop_no_std.stderr [new file with mode: 0644]
tests/ui/modulo_arithmetic_integral_const.rs
tests/ui/modulo_arithmetic_integral_const.stderr
tests/ui/modulo_one.stderr
tests/ui/needless_match.fixed [new file with mode: 0644]
tests/ui/needless_match.rs [new file with mode: 0644]
tests/ui/needless_match.stderr [new file with mode: 0644]
tests/ui/only_used_in_recursion.rs [new file with mode: 0644]
tests/ui/only_used_in_recursion.stderr [new file with mode: 0644]
tests/ui/or_then_unwrap.fixed [new file with mode: 0644]
tests/ui/or_then_unwrap.rs [new file with mode: 0644]
tests/ui/or_then_unwrap.stderr [new file with mode: 0644]
tests/ui/ptr_arg.rs
tests/ui/range_plus_minus_one.fixed
tests/ui/range_plus_minus_one.rs
tests/ui/single_component_path_imports_macro.fixed [deleted file]
tests/ui/single_component_path_imports_macro.rs
tests/ui/single_component_path_imports_macro.stderr [deleted file]
tests/ui/transmute_undefined_repr.rs
tests/ui/transmute_undefined_repr.stderr
tests/ui/transmutes_expressible_as_ptr_casts.fixed
tests/ui/transmutes_expressible_as_ptr_casts.rs
tests/ui/transmutes_expressible_as_ptr_casts.stderr
tests/ui/unnecessary_find_map.rs [new file with mode: 0644]
tests/ui/unnecessary_find_map.stderr [new file with mode: 0644]
tests/ui/unnecessary_join.fixed [new file with mode: 0644]
tests/ui/unnecessary_join.rs [new file with mode: 0644]
tests/ui/unnecessary_join.stderr [new file with mode: 0644]
tests/ui/unnecessary_lazy_eval.fixed
tests/ui/unnecessary_lazy_eval.rs
tests/ui/unnecessary_lazy_eval.stderr
tests/ui/unnecessary_lazy_eval_unfixable.stderr
tests/ui/unnecessary_to_owned.fixed
tests/ui/unnecessary_to_owned.rs
tests/ui/unnecessary_to_owned.stderr
tests/ui/use_self.fixed
tests/ui/use_self.rs
tests/ui/use_self.stderr
tests/workspace.rs
util/gh-pages/index.html

index 68877efc9e12fbf57ce19eee38312f43738dd54e..b6f70a7f18300227ce4cf88379d2231813c98f1b 100644 (file)
@@ -18,7 +18,7 @@ body:
     id: reproducer
     attributes:
       label: Reproducer
-      description: Please provide the code and steps to repoduce the bug
+      description: Please provide the code and steps to reproduce the bug
       value: |
         I tried this code:
 
index 2a5b8b3c8914163382f63d964f5d14d9cdb3dbe6..81bd9c5e032780eb306c9e5c245939f2aa1a4850 100644 (file)
@@ -10,7 +10,7 @@ body:
     attributes:
       label: Summary
       description: |
-        If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occured.
+        If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occurred.
 
         [mve]: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
     validations:
index 116ae031bb719d7b8a6b62223308a01289c09e1a..cd83bc9642b60c73ee1529c492512b993f251fab 100644 (file)
@@ -74,10 +74,3 @@ jobs:
       run: bash .github/driver.sh
       env:
         OS: ${{ runner.os }}
-
-    - name: Test cargo dev new lint
-      run: |
-        cargo dev new_lint --name new_early_pass --pass early
-        cargo dev new_lint --name new_late_pass --pass late
-        cargo check
-        git reset --hard HEAD
index 989667037c1cb4e23a0dcb0e2802023258510e4f..f571485e6d389befdf909d2f9ab5fa7bf25db69a 100644 (file)
@@ -143,13 +143,6 @@ jobs:
       env:
         OS: ${{ runner.os }}
 
-    - name: Test cargo dev new lint
-      run: |
-        cargo dev new_lint --name new_early_pass --pass early
-        cargo dev new_lint --name new_late_pass --pass late
-        cargo check
-        git reset --hard HEAD
-
   integration_build:
     needs: changelog
     runs-on: ubuntu-latest
index fe8bce00fa82e162ebcffca2944482dcefe1ffe9..5dfab1d2ebc402495477e11fd70daa13372d38ce 100644 (file)
@@ -36,6 +36,13 @@ jobs:
     - name: Test fmt
       run: cargo dev fmt --check
 
+    - name: Test cargo dev new lint
+      run: |
+        cargo dev new_lint --name new_early_pass --pass early
+        cargo dev new_lint --name new_late_pass --pass late
+        cargo check
+        git reset --hard HEAD
+
   # These jobs doesn't actually test anything, but they're only used to tell
   # bors the build completed, as there is no practical way to detect when a
   # workflow is successful listening to webhooks only.
index 35983b7fb506f7799cd2f6e6549061bf5b64e868..fd288285532ea00b9bc7950bb64a136118f8979d 100644 (file)
@@ -6,11 +6,143 @@ document.
 
 ## Unreleased / In Rust Nightly
 
-[0eff589...master](https://github.com/rust-lang/rust-clippy/compare/0eff589...master)
+[57b3c4b...master](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...master)
 
 ## Rust 1.59 (beta)
 
-Current beta, release 2022-02-24
+Current beta, release 2022-04-07
+
+[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
+
+### New Lints
+
+* [`single_char_lifetime_names`]
+  [#8236](https://github.com/rust-lang/rust-clippy/pull/8236)
+* [`iter_overeager_cloned`]
+  [#8203](https://github.com/rust-lang/rust-clippy/pull/8203)
+* [`transmute_undefined_repr`]
+  [#8398](https://github.com/rust-lang/rust-clippy/pull/8398)
+* [`default_union_representation`]
+  [#8289](https://github.com/rust-lang/rust-clippy/pull/8289)
+* [`manual_bits`]
+  [#8213](https://github.com/rust-lang/rust-clippy/pull/8213)
+* [`borrow_as_ptr`]
+  [#8210](https://github.com/rust-lang/rust-clippy/pull/8210)
+
+### Moves and Deprecations
+
+* Moved [`disallowed_methods`] and [`disallowed_types`] to `style` (now warn-by-default)
+  [#8261](https://github.com/rust-lang/rust-clippy/pull/8261)
+* Rename `ref_in_deref` to [`needless_borrow`]
+  [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* Moved [`mutex_atomic`] to `nursery` (now allow-by-default)
+  [#8260](https://github.com/rust-lang/rust-clippy/pull/8260)
+
+### Enhancements
+
+* [`ptr_arg`]: Now takes the argument usage into account and lints for mutable references
+  [#8271](https://github.com/rust-lang/rust-clippy/pull/8271)
+* [`unused_io_amount`]: Now supports async read and write traits
+  [#8179](https://github.com/rust-lang/rust-clippy/pull/8179)
+* [`while_let_on_iterator`]: Improved detection to catch more cases
+  [#8221](https://github.com/rust-lang/rust-clippy/pull/8221)
+* [`trait_duplication_in_bounds`]: Now covers trait functions with `Self` bounds
+  [#8252](https://github.com/rust-lang/rust-clippy/pull/8252)
+* [`unwrap_used`]: Now works for `.get(i).unwrap()` and `.get_mut(i).unwrap()`
+  [#8372](https://github.com/rust-lang/rust-clippy/pull/8372)
+* [`map_clone`]: The suggestion takes `msrv` into account
+  [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`manual_bits`] and [`borrow_as_ptr`]: Now track the `clippy::msrv` attribute
+  [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`disallowed_methods`]: Now works for methods on primitive types
+  [#8112](https://github.com/rust-lang/rust-clippy/pull/8112)
+* [`not_unsafe_ptr_arg_deref`]: Now works for type aliases
+  [#8273](https://github.com/rust-lang/rust-clippy/pull/8273)
+* [`needless_question_mark`]: Now works for async functions
+  [#8311](https://github.com/rust-lang/rust-clippy/pull/8311)
+* [`iter_not_returning_iterator`]: Now handles type projections
+  [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`wrong_self_convention`]: Now detects wrong `self` references in more cases
+  [#8208](https://github.com/rust-lang/rust-clippy/pull/8208)
+* [`single_match`]: Now works for `match` statements with tuples
+  [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+
+### False Positive Fixes
+
+* [`erasing_op`]: No longer triggers if the output type changes
+  [#8204](https://github.com/rust-lang/rust-clippy/pull/8204)
+* [`if_same_then_else`]: No longer triggers for `if let` statements
+  [#8297](https://github.com/rust-lang/rust-clippy/pull/8297)
+* [`manual_memcpy`]: No longer lints on `VecDeque`
+  [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`trait_duplication_in_bounds`]: Now takes path segments into account
+  [#8315](https://github.com/rust-lang/rust-clippy/pull/8315)
+* [`deref_addrof`]: No longer lints when the dereference or borrow occurs in different a context
+  [#8268](https://github.com/rust-lang/rust-clippy/pull/8268)
+* [`type_repetition_in_bounds`]: Now checks for full equality to prevent false positives
+  [#8224](https://github.com/rust-lang/rust-clippy/pull/8224)
+* [`ptr_arg`]: No longer lint for mutable references in traits
+  [#8369](https://github.com/rust-lang/rust-clippy/pull/8369)
+* [`implicit_clone`]: No longer lints for double references
+  [#8231](https://github.com/rust-lang/rust-clippy/pull/8231)
+* [`needless_lifetimes`]: No longer lints lifetimes for explicit `self` types
+  [#8278](https://github.com/rust-lang/rust-clippy/pull/8278)
+* [`op_ref`]: No longer lints in `BinOp` impl if that can cause recursion
+  [#8298](https://github.com/rust-lang/rust-clippy/pull/8298)
+* [`enum_variant_names`]: No longer triggers for empty variant names
+  [#8329](https://github.com/rust-lang/rust-clippy/pull/8329)
+* [`redundant_closure`]: No longer lints for `Arc<T>` or `Rc<T>`
+  [#8193](https://github.com/rust-lang/rust-clippy/pull/8193)
+* [`iter_not_returning_iterator`]: No longer lints on trait implementations but therefore on trait definitions
+  [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`single_match`]: No longer lints on exhaustive enum patterns without a wildcard
+  [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+* [`manual_swap`]: No longer lints on cases that involve automatic dereferences
+  [#8220](https://github.com/rust-lang/rust-clippy/pull/8220)
+* [`useless_format`]: Now works for implicit named arguments
+  [#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
+  [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* [`chars_next_cmp`]: Correctly excapes the suggestion
+  [#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
+* [`explicit_write`]: Add suggestions for `write!`s with format arguments
+  [#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
+* [`manual_memcpy`]: Suggests `copy_from_slice` when applicable
+  [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`or_fun_call`]: Improved suggestion display for long arguments
+  [#8292](https://github.com/rust-lang/rust-clippy/pull/8292)
+* [`unnecessary_cast`]: Now correctly includes the sign
+  [#8350](https://github.com/rust-lang/rust-clippy/pull/8350)
+* [`cmp_owned`]: No longer flips the comparison order
+  [#8299](https://github.com/rust-lang/rust-clippy/pull/8299)
+* [`explicit_counter_loop`]: Now correctly suggests `iter()` on references
+  [#8382](https://github.com/rust-lang/rust-clippy/pull/8382)
+
+### ICE Fixes
+
+* [`manual_split_once`]
+  [#8250](https://github.com/rust-lang/rust-clippy/pull/8250)
+
+### Documentation Improvements
+
+* [`map_flatten`]: Add documentation for the `Option` type
+  [#8354](https://github.com/rust-lang/rust-clippy/pull/8354)
+* Document that Clippy's driver might use a different code generation than rustc
+  [#8037](https://github.com/rust-lang/rust-clippy/pull/8037)
+* Clippy's lint list will now automatically focus the search box
+  [#8343](https://github.com/rust-lang/rust-clippy/pull/8343)
+
+### Others
+
+* Clippy now warns if we find multiple Clippy config files exist
+  [#8326](https://github.com/rust-lang/rust-clippy/pull/8326)
+
+## Rust 1.59
+
+Current stable, release 2022-02-24
 
 [e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
 
@@ -174,7 +306,7 @@ Current beta, release 2022-02-24
 
 ## Rust 1.58
 
-Current stable, released 2022-01-13
+Released 2022-01-13
 
 [00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
 
@@ -3042,6 +3174,7 @@ Released 2018-09-13
 <!-- lint disable no-unused-definitions -->
 <!-- begin autogenerated links to lint list -->
 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
+[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
 [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
 [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
 [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
@@ -3068,6 +3201,7 @@ Released 2018-09-13
 [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
 [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
 [`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
+[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
 [`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
 [`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
 [`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
@@ -3076,6 +3210,7 @@ Released 2018-09-13
 [`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
 [`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
 [`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
@@ -3225,6 +3360,7 @@ Released 2018-09-13
 [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
 [`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
 [`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
 [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
 [`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
@@ -3297,6 +3433,7 @@ Released 2018-09-13
 [`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
 [`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
 [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
+[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
 [`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
 [`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
 [`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
@@ -3327,6 +3464,7 @@ Released 2018-09-13
 [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
 [`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
 [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
+[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
 [`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
 [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
 [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
@@ -3351,6 +3489,7 @@ Released 2018-09-13
 [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
 [`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
 [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
+[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
 [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
 [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
 [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
@@ -3360,6 +3499,7 @@ Released 2018-09-13
 [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
 [`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
 [`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
+[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
 [`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
 [`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
 [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
@@ -3498,7 +3638,9 @@ Released 2018-09-13
 [`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
 [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
 [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
+[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
 [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
+[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
 [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
 [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
 [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
index 5cc5530f874dd63e275f54d8832bf33ffd003e44..123af23881b623ffd7ba83b8f1a259db3fa49b40 100644 (file)
@@ -43,14 +43,14 @@ rustc-workspace-hack = "1.0"
 clippy_utils = { path = "clippy_utils" }
 derive-new = "0.5"
 if_chain = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
 quote = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 syn = { version = "1.0", features = ["full"] }
 futures = "0.3"
 parking_lot = "0.11.2"
 tokio = { version = "1", features = ["io-util"] }
-num_cpus = "1.13"
+rustc-semver = "1.1"
 
 [build-dependencies]
 rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
index d350d9a001827e899b183fc19bcf5952eb43bb29..d133e8cddabc729b96ec2d2617401a771883660a 100644 (file)
@@ -7,7 +7,7 @@ edition = "2021"
 bytecount = "0.6"
 clap = "2.33"
 indoc = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
 opener = "0.5"
 regex = "1.5"
 shell-escape = "0.1"
index 40d7dd702628f77c47475290ad312c3e5b0160d1..66e61660d313aa150e1fcbb9106fce4d83bc7ffe 100644 (file)
@@ -12,7 +12,7 @@ edition = "2021"
 cargo_metadata = "0.14"
 clippy_utils = { path = "../clippy_utils" }
 if_chain = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
 pulldown-cmark = { version = "0.9", default-features = false }
 quine-mc_cluskey = "0.2"
 regex-syntax = "0.6"
index a58d12ddd6b4372278e4569126f2233d81183e28..d0b03c0a9c276e644e89c6bfa22499cfdce7e052 100644 (file)
     "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for attributes that allow lints without a reason.
+    ///
+    /// (This requires the `lint_reasons` feature)
+    ///
+    /// ### Why is this bad?
+    /// Allowing a lint should always have a reason. This reason should be documented to
+    /// ensure that others understand the reasoning
+    ///
+    /// ### Example
+    /// Bad:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint)]
+    /// ```
+    ///
+    /// Good:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
+    restriction,
+    "ensures that all `allow` and `expect` attributes have a reason"
+}
+
 declare_lint_pass!(Attributes => [
+    ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
     DEPRECATED_SEMVER,
     USELESS_ATTRIBUTE,
@@ -269,6 +300,9 @@ fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
                 if is_lint_level(ident.name) {
                     check_clippy_lint_names(cx, ident.name, items);
                 }
+                if matches!(ident.name, sym::allow | sym::expect) {
+                    check_lint_reason(cx, ident.name, items, attr);
+                }
                 if items.is_empty() || !attr.has_name(sym::deprecated) {
                     return;
                 }
@@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
     }
 }
 
+fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
+    // Check for the feature
+    if !cx.tcx.sess.features_untracked().lint_reasons {
+        return;
+    }
+
+    // Check if the reason is present
+    if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
+        && let MetaItemKind::NameValue(_) = &item.kind
+        && item.path == sym::reason
+    {
+        return;
+    }
+
+    span_lint_and_help(
+        cx,
+        ALLOW_ATTRIBUTES_WITHOUT_REASON,
+        attr.span,
+        &format!("`{}` attribute without specifying a reason", name.as_str()),
+        None,
+        "try adding a reason at the end with `, reason = \"..\"`",
+    );
+}
+
 fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
     if let ItemKind::Fn(_, _, eid) = item.kind {
         is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
@@ -659,5 +717,5 @@ fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
 }
 
 fn is_lint_level(symbol: Symbol) -> bool {
-    matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
+    matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
 }
index f0979840ff8d8ab9d55d0a1426b5905ee10d23ec..4592ca7274888dd97b13552ea01ac9c75dd8bf50 100644 (file)
@@ -149,7 +149,7 @@ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
 fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
     for ty_cause in ty_causes {
         if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
-            if is_mutex_guard(cx, adt.did) {
+            if is_mutex_guard(cx, adt.did()) {
                 span_lint_and_then(
                     cx,
                     AWAIT_HOLDING_LOCK,
@@ -167,7 +167,7 @@ fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorType
                     },
                 );
             }
-            if is_refcell_ref(cx, adt.did) {
+            if is_refcell_ref(cx, adt.did()) {
                 span_lint_and_then(
                     cx,
                     AWAIT_HOLDING_REFCELL_REF,
index 7637666d059ef319be896210e2e7e0c6cb0feca5..df780747a0c759d5857bb924db6c99a66ddbf00f 100644 (file)
@@ -1,6 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_help;
 use if_chain::if_chain;
 use rustc_ast::ast::LitKind;
+use rustc_data_structures::intern::Interned;
 use rustc_hir::{Expr, ExprKind, PathSegment};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty;
@@ -55,7 +56,7 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &
                 ty::Str => {
                     return Some(span);
                 },
-                ty::Adt(&ty::AdtDef { did, .. }, _) => {
+                ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) => {
                     if ctx.tcx.is_diagnostic_item(sym::String, did) {
                         return Some(span);
                     }
diff --git a/clippy_lints/src/casts/cast_enum_constructor.rs b/clippy_lints/src/casts/cast_enum_constructor.rs
new file mode 100644 (file)
index 0000000..1973692
--- /dev/null
@@ -0,0 +1,21 @@
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CAST_ENUM_CONSTRUCTOR;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>) {
+    if matches!(cast_from.kind(), ty::FnDef(..))
+        && let ExprKind::Path(path) = &cast_expr.kind
+        && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = cx.qpath_res(path, cast_expr.hir_id)
+    {
+        span_lint(
+            cx,
+            CAST_ENUM_CONSTRUCTOR,
+            expr.span,
+            "cast of an enum tuple constructor to an integer",
+        );
+    }
+}
index 9b189ea1ef8fb102396b7cefa76829bcff426ee5..421bd6f53f71b4a78f37788f2a16b61301c76a44 100644 (file)
@@ -116,15 +116,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
                 && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
             {
                 let i = def.variant_index_with_ctor_id(id);
-                let variant = &def.variants[i];
-                let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, def, i));
+                let variant = def.variant(i);
+                let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
                 (nbits, Some(variant))
             } else {
-                (utils::enum_ty_to_nbits(def, cx.tcx), None)
+                (utils::enum_ty_to_nbits(*def, cx.tcx), None)
             };
             let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
 
-            let cast_from_ptr_size = def.repr.int.map_or(true, |ty| {
+            let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
                 matches!(
                     ty,
                     IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
diff --git a/clippy_lints/src/casts/cast_slice_different_sizes.rs b/clippy_lints/src/casts/cast_slice_different_sizes.rs
new file mode 100644 (file)
index 0000000..3608c16
--- /dev/null
@@ -0,0 +1,117 @@
+use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt};
+use if_chain::if_chain;
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, Node};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::CAST_SLICE_DIFFERENT_SIZES;
+
+fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    let map = cx.tcx.hir();
+    if_chain! {
+        if let Some(parent_id) = map.find_parent_node(expr.hir_id);
+        if let Some(parent) = map.find(parent_id);
+        then {
+            let expr = match parent {
+                Node::Block(block) => {
+                    if let Some(parent_expr) = block.expr {
+                        parent_expr
+                    } else {
+                        return false;
+                    }
+                },
+                Node::Expr(expr) => expr,
+                _ => return false,
+            };
+
+            matches!(expr.kind, ExprKind::Cast(..))
+        } else {
+            false
+        }
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
+    // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
+    if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
+        return;
+    }
+
+    // if this cast is the child of another cast expression then don't emit something for it, the full
+    // chain will be analyzed
+    if is_child_of_cast(cx, expr) {
+        return;
+    }
+
+    if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) {
+        if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) {
+            let from_size = from_layout.size.bytes();
+            let to_size = to_layout.size.bytes();
+            if from_size != to_size && from_size != 0 && to_size != 0 {
+                span_lint_and_then(
+                    cx,
+                    CAST_SLICE_DIFFERENT_SIZES,
+                    expr.span,
+                    &format!(
+                        "casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
+                        from_slice_ty, from_size, to_slice_ty, to_size,
+                    ),
+                    |diag| {
+                        let cast_expr = match expr.kind {
+                            ExprKind::Cast(cast_expr, ..) => cast_expr,
+                            _ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"),
+                        };
+                        let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap();
+
+                        let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl {
+                            Mutability::Mut => ("_mut", "mut"),
+                            Mutability::Not => ("", "const"),
+                        };
+                        let sugg = format!(
+                            "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)"
+                        );
+
+                        diag.span_suggestion(
+                            expr.span,
+                            &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
+                            sugg,
+                            rustc_errors::Applicability::HasPlaceholders,
+                        );
+                    },
+                );
+            }
+        }
+    }
+}
+
+/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
+/// the type is one of those slices
+fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
+    match ty.kind() {
+        ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
+            ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
+/// Returns None if the expr is not a Cast
+fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
+    if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
+        let cast_to = cx.typeck_results().expr_ty(expr);
+        let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
+        if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
+            Some((inner_from_ty, to_slice_ty))
+        } else {
+            let cast_from = cx.typeck_results().expr_ty(cast_expr);
+            let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
+            Some((from_slice_ty, to_slice_ty))
+        }
+    } else {
+        None
+    }
+}
index f2077c569c04121a1232d7d3b483dc927f2069e2..be59145afa0032a3cd7c8882c770dd48f4a53288 100644 (file)
@@ -1,3 +1,4 @@
+mod cast_enum_constructor;
 mod cast_lossless;
 mod cast_possible_truncation;
 mod cast_possible_wrap;
@@ -5,6 +6,7 @@
 mod cast_ptr_alignment;
 mod cast_ref_to_mut;
 mod cast_sign_loss;
+mod cast_slice_different_sizes;
 mod char_lit_as_u8;
 mod fn_to_numeric_cast;
 mod fn_to_numeric_cast_any;
     "casts from an enum type to an integral type which will truncate the value"
 }
 
+declare_clippy_lint! {
+    /// Checks for `as` casts between raw pointers to slices with differently sized elements.
+    ///
+    /// ### Why is this bad?
+    /// The produced raw pointer to a slice does not update its length metadata. The produced
+    /// pointer will point to a different number of bytes than the original pointer because the
+    /// length metadata of a raw slice pointer is in elements rather than bytes.
+    /// Producing a slice reference from the raw pointer will either create a slice with
+    /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
+    ///
+    /// ### Example
+    /// // Missing data
+    /// ```rust
+    /// let a = [1_i32, 2, 3, 4];
+    /// let p = &a as *const [i32] as *const [u8];
+    /// unsafe {
+    ///     println!("{:?}", &*p);
+    /// }
+    /// ```
+    /// // Undefined Behavior (note: also potential alignment issues)
+    /// ```rust
+    /// let a = [1_u8, 2, 3, 4];
+    /// let p = &a as *const [u8] as *const [u32];
+    /// unsafe {
+    ///     println!("{:?}", &*p);
+    /// }
+    /// ```
+    /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
+    /// ```rust
+    /// let a = [1_i32, 2, 3, 4];
+    /// let old_ptr = &a as *const [i32];
+    /// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
+    /// // The length comes from the known length of 4 i32s times the 4 bytes per i32
+    /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
+    /// unsafe {
+    ///     println!("{:?}", &*new_ptr);
+    /// }
+    /// ```
+    #[clippy::version = "1.60.0"]
+    pub CAST_SLICE_DIFFERENT_SIZES,
+    correctness,
+    "casting using `as` between raw pointers to slices of types with different sizes"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts from an enum tuple constructor to an integer.
+    ///
+    /// ### Why is this bad?
+    /// The cast is easily confused with casting a c-like enum value to an integer.
+    ///
+    /// ### Example
+    /// ```rust
+    /// enum E { X(i32) };
+    /// let _ = E::X as usize;
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub CAST_ENUM_CONSTRUCTOR,
+    suspicious,
+    "casts from an enum tuple constructor to an integer"
+}
+
 pub struct Casts {
     msrv: Option<RustcVersion>,
 }
@@ -428,6 +492,7 @@ pub fn new(msrv: Option<RustcVersion>) -> Self {
     CAST_LOSSLESS,
     CAST_REF_TO_MUT,
     CAST_PTR_ALIGNMENT,
+    CAST_SLICE_DIFFERENT_SIZES,
     UNNECESSARY_CAST,
     FN_TO_NUMERIC_CAST_ANY,
     FN_TO_NUMERIC_CAST,
@@ -435,6 +500,7 @@ pub fn new(msrv: Option<RustcVersion>) -> Self {
     CHAR_LIT_AS_U8,
     PTR_AS_PTR,
     CAST_ENUM_TRUNCATION,
+    CAST_ENUM_CONSTRUCTOR
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -472,12 +538,15 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                     cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
                 }
                 cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
+                cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
             }
         }
 
         cast_ref_to_mut::check(cx, expr);
         cast_ptr_alignment::check(cx, expr);
         char_lit_as_u8::check(cx, expr);
+        ptr_as_ptr::check(cx, expr, &self.msrv);
+        cast_slice_different_sizes::check(cx, expr, &self.msrv);
     }
 
     extract_msrv_attr!(LateContext);
index bbed766c47a8526f45c9ec90fd34b3b6b57b18e9..5a4f20f099060c7f911c3cf4e5e10937b59547f4 100644 (file)
@@ -34,10 +34,10 @@ pub(super) fn enum_value_nbits(value: EnumValue) -> u64 {
     .into()
 }
 
-pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
+pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 {
     let mut explicit = 0i128;
     let (start, end) = adt
-        .variants
+        .variants()
         .iter()
         .fold((0, i128::MIN), |(start, end), variant| match variant.discr {
             VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
index f03f34e5a4b381e0fc238a252dcafb5d914bf2f4..eae2723a7dac1f60ea6e1282a8385e8ae07166ec 100644 (file)
@@ -42,7 +42,7 @@
     ///
     /// Should be written:
     ///
-    /// ```rust.ignore
+    /// ```rust,ignore
     /// if x && y {
     ///     â€¦
     /// }
@@ -76,7 +76,7 @@
     ///
     /// Should be written:
     ///
-    /// ```rust.ignore
+    /// ```rust,ignore
     /// if x {
     ///     â€¦
     /// } else if y {
index 8b79f1600aeb0ab7542ea6d3286b68d8d1159f45..e6a0162fd02728d5c205996b2a34c0500756dba6 100644 (file)
@@ -6,7 +6,7 @@
 };
 use if_chain::if_chain;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{Applicability, Diagnostic};
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{Block, Expr, ExprKind, HirId};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
     /// Duplicate code is less maintainable.
     ///
     /// ### Known problems
-    /// * The lint doesn't check if the moved expressions modify values that are beeing used in
+    /// * The lint doesn't check if the moved expressions modify values that are being used in
     ///   the if condition. The suggestion can in that case modify the behavior of the program.
     ///   See [rust-clippy#7452](https://github.com/rust-lang/rust-clippy/issues/7452)
     ///
@@ -489,7 +489,7 @@ fn emit_branches_sharing_code_lint(
         add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit();
     }
 
-    let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
+    let add_optional_msgs = |diag: &mut Diagnostic| {
         if add_expr_note {
             diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
         }
index df1a4128af35957aa8cb5bf8fa02d37b905611cd..a0e5d30263316b88921d4b9671991106b4b4f510 100644 (file)
@@ -1,4 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_in_test_function;
 use clippy_utils::macros::root_macro_call_first_node;
 use clippy_utils::source::snippet_with_applicability;
 use rustc_errors::Applicability;
@@ -35,6 +36,10 @@ impl LateLintPass<'_> for DbgMacro {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
         if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
+            // we make an exception for test code
+            if is_in_test_function(cx.tcx, expr.hir_id) {
+                return;
+            }
             let mut applicability = Applicability::MachineApplicable;
             let suggestion = match expr.peel_drop_temps().kind {
                 // dbg!()
index 06e6bf986c2a9458e6417ec0ec9fe2991bed7626..f7e4bc24321c5a8ac58461d41ef60683a3bc9102 100644 (file)
@@ -96,7 +96,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             then {
                 // TODO: Work out a way to put "whatever the imported way of referencing
                 // this type in this file" rather than a fully-qualified type.
-                let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
+                let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did()));
                 span_lint_and_sugg(
                     cx,
                     DEFAULT_TRAIT_ACCESS,
@@ -137,7 +137,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
                 if let Some(adt) = binding_type.ty_adt_def();
                 if adt.is_struct();
                 let variant = adt.non_enum_variant();
-                if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
+                if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
                 let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
                 if variant
                     .fields
@@ -216,7 +216,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
                     if let ty::Adt(adt_def, substs) = binding_type.kind();
                     if !substs.is_empty();
                     then {
-                        let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
+                        let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
                         let generic_args = substs.iter().collect::<Vec<_>>();
                         let tys_str = generic_args
                             .iter()
index b80d55dd192a10201dc0fa67c4c49fd7cbdbdbea..f3996e5b44d74c6ad52f87a8938393b718cd4185 100644 (file)
@@ -148,7 +148,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
                 if_chain! {
                     if let Some(adt_def) = ty.ty_adt_def();
                     if adt_def.is_struct();
-                    if let Some(variant) = adt_def.variants.iter().next();
+                    if let Some(variant) = adt_def.variants().iter().next();
                     then {
                         let fields_def = &variant.fields;
 
index eccb18982f30da8a74e4f8a8626a94ef7c5e3189..14098340745b10b9a9654c0ccbd2d8684f5d8b3d 100644 (file)
@@ -103,7 +103,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
                     _ => false,
                 };
                 if should_emit {
-                    let path_string = cx.tcx.def_path_str(adt_def.did);
+                    let path_string = cx.tcx.def_path_str(adt_def.did());
                     span_lint_and_help(
                         cx,
                         DERIVABLE_IMPLS,
index 7277e4080c5c0763ffa132a28a48dc4fd394486d..557e101494e3acd87c9188336c3198cdb89fd639 100644 (file)
@@ -315,7 +315,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
             let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(&copy_id).map_or(false, |impls| {
                 impls
                     .iter()
-                    .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
+                    .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
             });
             if !has_copy_impl {
                 return;
@@ -357,10 +357,10 @@ fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
         if let Some(trait_def_id) = trait_ref.trait_def_id();
         if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
         if let ty::Adt(def, _) = ty.kind();
-        if let Some(local_def_id) = def.did.as_local();
+        if let Some(local_def_id) = def.did().as_local();
         let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
         if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
-        if cx.tcx.inherent_impls(def.did)
+        if cx.tcx.inherent_impls(def.did())
             .iter()
             .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
             .any(|imp| has_unsafe(cx, imp));
index a00361e6062ad4ab1faee890838dd36a9f54328a..703aa458f44e5a074ac0cd3a1cc33b1573444223 100644 (file)
@@ -628,9 +628,7 @@ fn has_needless_main(code: String, edition: Edition) -> bool {
                 let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
                     Ok(p) => p,
                     Err(errs) => {
-                        for mut err in errs {
-                            err.cancel();
-                        }
+                        drop(errs);
                         return false;
                     },
                 };
@@ -639,12 +637,6 @@ fn has_needless_main(code: String, edition: Edition) -> bool {
                 loop {
                     match parser.parse_item(ForceCollect::No) {
                         Ok(Some(item)) => match &item.kind {
-                            // Tests with one of these items are ignored
-                            ItemKind::Static(..)
-                            | ItemKind::Const(..)
-                            | ItemKind::ExternCrate(..)
-                            | ItemKind::ForeignMod(..) => return false,
-                            // We found a main function ...
                             ItemKind::Fn(box Fn {
                                 sig, body: Some(block), ..
                             }) if item.ident.name == sym::main => {
@@ -663,12 +655,17 @@ fn has_needless_main(code: String, edition: Edition) -> bool {
                                     return false;
                                 }
                             },
-                            // Another function was found; this case is ignored too
-                            ItemKind::Fn(..) => return false,
+                            // Tests with one of these items are ignored
+                            ItemKind::Static(..)
+                            | ItemKind::Const(..)
+                            | ItemKind::ExternCrate(..)
+                            | ItemKind::ForeignMod(..)
+                            // Another function was found; this case is ignored
+                            | ItemKind::Fn(..) => return false,
                             _ => {},
                         },
                         Ok(None) => break,
-                        Err(mut e) => {
+                        Err(e) => {
                             e.cancel();
                             return false;
                         },
index af9e65e636135d8fb186f7390bb89d8047e46230..b5d6b3c7524baad8f64bea9069054624c990707e 100644 (file)
@@ -52,7 +52,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
         if let ItemKind::Enum(..) = item.kind {
             let ty = cx.tcx.type_of(item.def_id);
             let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
-            if adt.variants.is_empty() {
+            if adt.variants().is_empty() {
                 span_lint_and_help(
                     cx,
                     EMPTY_ENUM,
index 3b6661c817be7677fe01941e096af0e4f786c1ac..e2a5430da08c8c16bcfd1fdccbae12defb886b37 100644 (file)
@@ -55,7 +55,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
                     if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
                         if let ty::Adt(adt, _) = ty.kind() {
                             if adt.is_enum() {
-                                ty = adt.repr.discr_type().to_ty(cx.tcx);
+                                ty = adt.repr().discr_type().to_ty(cx.tcx);
                             }
                         }
                         match ty.kind() {
index 6490231fed8a7195ee6c1b9cd62ed1554d43cac0..51c811b304cae663f71853125f579b5ea2e25576 100644 (file)
@@ -306,7 +306,7 @@ fn in_impl<'tcx>(
 fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
     if_chain! {
         if let ty::Adt(adt_def, _) = middle_ty.kind();
-        if let Some(local_did) = adt_def.did.as_local();
+        if let Some(local_did) = adt_def.did().as_local();
         let item = cx.tcx.hir().expect_item(local_did);
         let middle_ty_id = item.def_id.to_def_id();
         if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
index d23c0c225e192e84fc4ad27b0e6a5d3ac613713d..845863bd209c6ef4cc9696b68341bf9fee537437 100644 (file)
@@ -224,7 +224,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
         ty::ImplContainer(def_id) => {
             let ty = cx.tcx.type_of(def_id);
             match ty.kind() {
-                ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did),
+                ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
                 _ => ty.to_string(),
             }
         },
index 574678b5542111d4c3132b825139c56356254e25..088d9996516e83683fdb9ffc160b6967be5da19f 100644 (file)
@@ -86,9 +86,9 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
 
             // check for `unwrap`
             if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
-                let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
-                if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
-                    || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+                let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+                if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+                    || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
                 {
                     self.result.push(expr.span);
                 }
index ca8886228de264a042c24c89e2f9688d4c15bb9f..98aee7592ae80a496bde4da3e3f47327a7a547b3 100644 (file)
@@ -20,7 +20,7 @@
     ///
     /// ### Known problems
     /// If the user can ensure that b is larger than a, the `.abs()` is
-    /// technically unneccessary. However, it will make the code more robust and doesn't have any
+    /// technically unnecessary. However, it will make the code more robust and doesn't have any
     /// large performance implications. If the abs call was deliberately left out for performance
     /// reasons, it is probably better to state this explicitly in the code, which then can be done
     /// with an allow.
@@ -69,7 +69,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
 
         if_chain! {
 
-            // left hand side is a substraction
+            // left hand side is a subtraction
             if let ExprKind::Binary(
                 Spanned {
                     node: BinOpKind::Sub,
@@ -84,7 +84,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
             if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
 
-            // values of the substractions on the left hand side are of the type float
+            // values of the subtractions on the left hand side are of the type float
             let t_val_l = cx.typeck_results().expr_ty(val_l);
             let t_val_r = cx.typeck_results().expr_ty(val_r);
             if let ty::Float(_) = t_val_l.kind();
index 395c920c9974c0a525772f0e5ae88c108458b40e..64c41b565878b36772fe2d2e5d17882c2f867314 100644 (file)
@@ -72,7 +72,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             if_chain! {
                 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::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
                     ty::Str => true,
                     _ => false,
                 };
index ea9b68d1a40e3982a01dfa16b56541b940691cd3..0709580c8adfdd2ad66a15af13efcf76ebc98d94 100644 (file)
@@ -189,8 +189,8 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m
         // primitive types are never mutable
         ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
         ty::Adt(adt, substs) => {
-            tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
-                || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
+            tys.insert(adt.did()) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
+                || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did(), path))
                     && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
         },
         ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),
index f824f20ca40a017b28d725fcc452fc50f3209e55..4d6bef89bea7f09cbbfa8f618b4d26b871e371b6 100644 (file)
@@ -5,7 +5,7 @@
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::source_map::Span;
 
-use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::{clip, unsext};
 
@@ -54,6 +54,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
                     check(cx, left, -1, e.span, right.span);
                     check(cx, right, -1, e.span, left.span);
                 },
+                BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
                 _ => (),
             }
         }
@@ -70,6 +71,18 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_
             && constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
 }
 
+fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
+    let lhs_const = constant_full_int(cx, cx.typeck_results(), left);
+    let rhs_const = constant_full_int(cx, cx.typeck_results(), right);
+    if match (lhs_const, rhs_const) {
+        (Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
+        (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
+        _ => return,
+    } {
+        span_ineffective_operation(cx, span, arg);
+    }
+}
+
 fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
     if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
         let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
@@ -83,15 +96,19 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
             1 => v == 1,
             _ => unreachable!(),
         } {
-            span_lint(
-                cx,
-                IDENTITY_OP,
-                span,
-                &format!(
-                    "the operation is ineffective. Consider reducing it to `{}`",
-                    snippet(cx, arg, "..")
-                ),
-            );
+            span_ineffective_operation(cx, span, arg);
         }
     }
 }
+
+fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span) {
+    span_lint(
+        cx,
+        IDENTITY_OP,
+        span,
+        &format!(
+            "the operation is ineffective. Consider reducing it to `{}`",
+            snippet(cx, arg, "..")
+        ),
+    );
+}
index 5e4cde553b52e954a0bf7e30f2eed0ef0531e21c..d5430a8c91750784b0033ccb226c9d352c632b60 100644 (file)
@@ -1,7 +1,7 @@
 use std::borrow::Cow;
 use std::collections::BTreeMap;
 
-use rustc_errors::DiagnosticBuilder;
+use rustc_errors::Diagnostic;
 use rustc_hir as hir;
 use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor};
 use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
@@ -68,7 +68,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
 
         fn suggestion<'tcx>(
             cx: &LateContext<'tcx>,
-            diag: &mut DiagnosticBuilder<'_>,
+            diag: &mut Diagnostic,
             generics_span: Span,
             generics_suggestion_span: Span,
             target: &ImplicitHasherType<'_>,
index 3d44a669d8f0507ed1870a6fe81a4bea12aa38d7..c8ec2f45137072539afbba571cb87541eb34bff7 100644 (file)
@@ -71,7 +71,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
             let ty = cx.typeck_results().expr_ty(expr);
             if let Some(adt_def) = ty.ty_adt_def();
             if adt_def.is_struct();
-            if let Some(variant) = adt_def.variants.iter().next();
+            if let Some(variant) = adt_def.variants().iter().next();
             if fields.iter().all(|f| f.is_shorthand);
             then {
                 let mut def_order_map = FxHashMap::default();
index df69d3dcc51603fa18e14d8177667c359f3c179a..dd7177e0131ca5d3ef2ff523a6d68a1b023ccf26 100644 (file)
@@ -1,7 +1,7 @@
 //! checks for `#[inline]` on trait methods without bodies
 
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::sugg::DiagnosticBuilderExt;
+use clippy_utils::sugg::DiagnosticExt;
 use rustc_ast::ast::Attribute;
 use rustc_errors::Applicability;
 use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
index d1dc6b775c567ed467214db9d66683f9d506a7b9..0f3889a2936188a8ffdb83aea483ce7504727785 100644 (file)
@@ -81,11 +81,11 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
         if let ItemKind::Enum(ref def, _) = item.kind {
             let ty = cx.tcx.type_of(item.def_id);
             let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
-            if adt.variants.len() <= 1 {
+            if adt.variants().len() <= 1 {
                 return;
             }
             let mut variants_size: Vec<VariantInfo> = Vec::new();
-            for (i, variant) in adt.variants.iter().enumerate() {
+            for (i, variant) in adt.variants().iter().enumerate() {
                 let mut fields_size = Vec::new();
                 for (i, f) in variant.fields.iter().enumerate() {
                     let ty = cx.tcx.type_of(f.did);
index 35d10d53112ec6c3591555833dcace76f3c9ed6c..dabbb8375f0a6ff97b030379624a2d3861cd8e7b 100644 (file)
@@ -248,13 +248,13 @@ enum LenOutput<'tcx> {
 fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
     match *sig.output().kind() {
         ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
-        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) => {
-            subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did))
+        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
+            subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did()))
         },
-        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did) => subs
+        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => subs
             .type_at(0)
             .is_integral()
-            .then(|| LenOutput::Result(adt.did, subs.type_at(1))),
+            .then(|| LenOutput::Result(adt.did(), subs.type_at(1))),
         _ => None,
     }
 }
@@ -263,8 +263,8 @@ impl LenOutput<'_> {
     fn matches_is_empty_output(self, ty: Ty<'_>) -> bool {
         match (self, ty.kind()) {
             (_, &ty::Bool) => true,
-            (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(),
-            (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => {
+            (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
+            (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did() => {
                 subs.type_at(0).is_bool() && subs.type_at(1) == err_ty
             },
             _ => false,
@@ -488,7 +488,7 @@ fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
                 .any(|item| is_is_empty(cx, item))
         }),
         ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
-        ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
+        ty::Adt(id, _) => has_is_empty_impl(cx, id.did()),
         ty::Array(..) | ty::Slice(..) | ty::Str => true,
         _ => false,
     }
index f6d467941e3ef80dd629caba969e28cfdf208b16..132a466267626e51fd6b0444e0e48763db3880d5 100644 (file)
     LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
     LintId::of(booleans::LOGIC_BUG),
     LintId::of(booleans::NONMINIMAL_BOOL),
+    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::CHAR_LIT_AS_U8),
     LintId::of(casts::FN_TO_NUMERIC_CAST),
     LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
     LintId::of(loops::ITER_NEXT_LOOP),
     LintId::of(loops::MANUAL_FLATTEN),
     LintId::of(loops::MANUAL_MEMCPY),
+    LintId::of(loops::MISSING_SPIN_LOOP),
     LintId::of(loops::MUT_RANGE_BOUND),
     LintId::of(loops::NEEDLESS_COLLECT),
     LintId::of(loops::NEEDLESS_RANGE_LOOP),
     LintId::of(matches::MATCH_OVERLAPPING_ARM),
     LintId::of(matches::MATCH_REF_PATS),
     LintId::of(matches::MATCH_SINGLE_BINDING),
+    LintId::of(matches::NEEDLESS_MATCH),
     LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
     LintId::of(matches::SINGLE_MATCH),
     LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
     LintId::of(methods::OPTION_FILTER_MAP),
     LintId::of(methods::OPTION_MAP_OR_NONE),
     LintId::of(methods::OR_FUN_CALL),
+    LintId::of(methods::OR_THEN_UNWRAP),
     LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
     LintId::of(methods::SEARCH_IS_SOME),
     LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
     LintId::of(methods::SUSPICIOUS_SPLITN),
     LintId::of(methods::UNINIT_ASSUMED_INIT),
     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_TO_OWNED),
     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(only_used_in_recursion::ONLY_USED_IN_RECURSION),
     LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
     LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
     LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
     LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
     LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
     LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
-    LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
     LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
     LintId::of(transmute::WRONG_TRANSMUTE),
     LintId::of(transmuting_null::TRANSMUTING_NULL),
-    LintId::of(try_err::TRY_ERR),
     LintId::of(types::BORROWED_BOX),
     LintId::of(types::BOX_COLLECTION),
     LintId::of(types::REDUNDANT_ALLOCATION),
index bd5ff613447cddf521113dfccbfe71d863fc1776..a2ce69065f94d47685a1c47f1fa8deb01ecbc05b 100644 (file)
@@ -30,6 +30,7 @@
     LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
     LintId::of(matches::MATCH_AS_REF),
     LintId::of(matches::MATCH_SINGLE_BINDING),
+    LintId::of(matches::NEEDLESS_MATCH),
     LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
     LintId::of(methods::BIND_INSTEAD_OF_MAP),
     LintId::of(methods::CLONE_ON_COPY),
     LintId::of(methods::NEEDLESS_SPLITN),
     LintId::of(methods::OPTION_AS_REF_DEREF),
     LintId::of(methods::OPTION_FILTER_MAP),
+    LintId::of(methods::OR_THEN_UNWRAP),
     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::USELESS_ASREF),
     LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
     LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
@@ -63,6 +66,7 @@
     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(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
     LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
     LintId::of(precedence::PRECEDENCE),
index 35b1e644a8a71d63bd9afa2375a0d089cec14bb0..df63f84463dba27345e320748c0b8117dff9d840 100644 (file)
@@ -13,6 +13,7 @@
     LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
     LintId::of(booleans::LOGIC_BUG),
     LintId::of(casts::CAST_REF_TO_MUT),
+    LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
     LintId::of(copies::IFS_SAME_COND),
     LintId::of(copies::IF_SAME_THEN_ELSE),
     LintId::of(derive::DERIVE_HASH_XOR_EQ),
@@ -58,7 +59,6 @@
     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::TRANSMUTE_UNDEFINED_REPR),
     LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
     LintId::of(transmute::WRONG_TRANSMUTE),
     LintId::of(transmuting_null::TRANSMUTING_NULL),
index 7d4c7d2adb5b9e8063e5bdc2307ff8f33d23f42c..4778f4fdfa76c365a40d4d1a325516d86ff8ef6c 100644 (file)
@@ -14,6 +14,7 @@
     LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
     LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
     LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
+    LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
     LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
     LintId::of(utils::internal_lints::PRODUCE_ICE),
     LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
index 686482671b482ae698579d25f42f7edb9893731a..21f1ef562b5a31482c01c3a56af620d9450e2ad4 100644 (file)
@@ -26,6 +26,8 @@
     #[cfg(feature = "internal")]
     utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
     #[cfg(feature = "internal")]
+    utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
+    #[cfg(feature = "internal")]
     utils::internal_lints::OUTER_EXPN_EXPN_DATA,
     #[cfg(feature = "internal")]
     utils::internal_lints::PRODUCE_ICE,
@@ -42,6 +44,7 @@
     assign_ops::ASSIGN_OP_PATTERN,
     assign_ops::MISREFACTORED_ASSIGN_OP,
     async_yields_async::ASYNC_YIELDS_ASYNC,
+    attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
     attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
     attrs::DEPRECATED_CFG_ATTR,
     attrs::DEPRECATED_SEMVER,
@@ -67,6 +70,7 @@
     cargo::REDUNDANT_FEATURE_NAMES,
     cargo::WILDCARD_DEPENDENCIES,
     case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+    casts::CAST_ENUM_CONSTRUCTOR,
     casts::CAST_ENUM_TRUNCATION,
     casts::CAST_LOSSLESS,
     casts::CAST_POSSIBLE_TRUNCATION,
@@ -75,6 +79,7 @@
     casts::CAST_PTR_ALIGNMENT,
     casts::CAST_REF_TO_MUT,
     casts::CAST_SIGN_LOSS,
+    casts::CAST_SLICE_DIFFERENT_SIZES,
     casts::CHAR_LIT_AS_U8,
     casts::FN_TO_NUMERIC_CAST,
     casts::FN_TO_NUMERIC_CAST_ANY,
     loops::ITER_NEXT_LOOP,
     loops::MANUAL_FLATTEN,
     loops::MANUAL_MEMCPY,
+    loops::MISSING_SPIN_LOOP,
     loops::MUT_RANGE_BOUND,
     loops::NEEDLESS_COLLECT,
     loops::NEEDLESS_RANGE_LOOP,
     matches::MATCH_SINGLE_BINDING,
     matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
     matches::MATCH_WILD_ERR_ARM,
+    matches::NEEDLESS_MATCH,
     matches::REDUNDANT_PATTERN_MATCHING,
     matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
     matches::SINGLE_MATCH,
     methods::ITER_NTH_ZERO,
     methods::ITER_OVEREAGER_CLONED,
     methods::ITER_SKIP_NEXT,
+    methods::ITER_WITH_DRAIN,
     methods::MANUAL_FILTER_MAP,
     methods::MANUAL_FIND_MAP,
     methods::MANUAL_SATURATING_ARITHMETIC,
     methods::OPTION_FILTER_MAP,
     methods::OPTION_MAP_OR_NONE,
     methods::OR_FUN_CALL,
+    methods::OR_THEN_UNWRAP,
     methods::RESULT_MAP_OR_INTO_OPTION,
     methods::SEARCH_IS_SOME,
     methods::SHOULD_IMPLEMENT_TRAIT,
     methods::SUSPICIOUS_SPLITN,
     methods::UNINIT_ASSUMED_INIT,
     methods::UNNECESSARY_FILTER_MAP,
+    methods::UNNECESSARY_FIND_MAP,
     methods::UNNECESSARY_FOLD,
+    methods::UNNECESSARY_JOIN,
     methods::UNNECESSARY_LAZY_EVALUATIONS,
     methods::UNNECESSARY_TO_OWNED,
     methods::UNWRAP_OR_ELSE_DEFAULT,
     non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
     nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
     octal_escapes::OCTAL_ESCAPES,
+    only_used_in_recursion::ONLY_USED_IN_RECURSION,
     open_options::NONSENSICAL_OPEN_OPTIONS,
     option_env_unwrap::OPTION_ENV_UNWRAP,
     option_if_let_else::OPTION_IF_LET_ELSE,
index a7353790100267681b11ae92b234125b48571dfe..c2fc67afba517f4447d33df8bfc5d504015f6c77 100644 (file)
@@ -13,6 +13,7 @@
     LintId::of(future_not_send::FUTURE_NOT_SEND),
     LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
     LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
+    LintId::of(methods::ITER_WITH_DRAIN),
     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),
@@ -26,6 +27,7 @@
     LintId::of(strings::STRING_LIT_AS_BYTES),
     LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
     LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
+    LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
     LintId::of(transmute::USELESS_TRANSMUTE),
     LintId::of(use_self::USE_SELF),
 ])
index 00d305131810df7418ae29eb519c660cbd954746..eb6534cb8cae761433a0a979ff932f78d063f5a7 100644 (file)
@@ -63,6 +63,7 @@
     LintId::of(methods::IMPLICIT_CLONE),
     LintId::of(methods::INEFFICIENT_TO_STRING),
     LintId::of(methods::MAP_UNWRAP_OR),
+    LintId::of(methods::UNNECESSARY_JOIN),
     LintId::of(misc::FLOAT_CMP),
     LintId::of(misc::USED_UNDERSCORE_BINDING),
     LintId::of(mut_mut::MUT_MUT),
index c44ef124bfa0ea3bcb1130283606736cc08ebbef..f2f5c988d8f9056b557e05ac61e43287b01ef2d6 100644 (file)
@@ -10,6 +10,7 @@
     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(methods::EXPECT_FUN_CALL),
     LintId::of(methods::EXTEND_WITH_DRAIN),
index f89f35b885c15a377c772e103254c0b0884aeebf..6ab139b2fb67b551a80d88a6cf17638111539f01 100644 (file)
@@ -8,6 +8,7 @@
     LintId::of(as_conversions::AS_CONVERSIONS),
     LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
     LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
+    LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
     LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
     LintId::of(create_dir::CREATE_DIR),
     LintId::of(dbg_macro::DBG_MACRO),
@@ -61,6 +62,7 @@
     LintId::of(strings::STRING_SLICE),
     LintId::of(strings::STRING_TO_STRING),
     LintId::of(strings::STR_TO_STRING),
+    LintId::of(try_err::TRY_ERR),
     LintId::of(types::RC_BUFFER),
     LintId::of(types::RC_MUTEX),
     LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
index 05211476ff2300df79103d25e801f600f2cb692b..dcf399cf9562f3d7c814b8b04b75b2299d336fab 100644 (file)
     LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
     LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
     LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
-    LintId::of(try_err::TRY_ERR),
     LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
     LintId::of(unused_unit::UNUSED_UNIT),
     LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
index 465baa8258174e59f14d91b941dbe4bae33d3936..fa3a88e1368ce5cde3ff9aee911e993d4231c604 100644 (file)
@@ -7,6 +7,7 @@
     LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
     LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
     LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+    LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
     LintId::of(casts::CAST_ENUM_TRUNCATION),
     LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
     LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
index 230e2b2ccdfb597e8fe293ce666587f746ad22fb..f2a07999144482e48231ba3e4bf19edbf4d71d77 100644 (file)
@@ -23,6 +23,7 @@
 
 // FIXME: switch to something more ergonomic here, once available.
 // (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_arena;
 extern crate rustc_ast;
 extern crate rustc_ast_pretty;
 extern crate rustc_attr;
@@ -318,6 +319,7 @@ macro_rules! declare_clippy_lint {
 mod non_send_fields_in_send_ty;
 mod nonstandard_macro_braces;
 mod octal_escapes;
+mod only_used_in_recursion;
 mod open_options;
 mod option_env_unwrap;
 mod option_if_let_else;
@@ -505,6 +507,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
         store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
         store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
+        store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
     }
 
     store.register_late_pass(|| Box::new(utils::author::Author));
@@ -856,6 +859,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     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_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
     store.register_late_pass(|| Box::new(dbg_macro::DbgMacro));
     let cargo_ignore_publish = conf.cargo_ignore_publish;
     store.register_late_pass(move || {
index f6ef87264c0a6f3b8f2a70cf3f3e3a44164c3f85..b31015d195b52007211e0bf97b04b39f9c19733e 100644 (file)
@@ -334,7 +334,7 @@ struct Start<'hir> {
 
 fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     match ty.kind() {
-        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did) => Some(subs.type_at(0)),
+        ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did()) => Some(subs.type_at(0)),
         ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, *subty),
         ty::Slice(ty) | ty::Array(ty, _) => Some(*ty),
         _ => None,
diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs
new file mode 100644 (file)
index 0000000..0696afa
--- /dev/null
@@ -0,0 +1,56 @@
+use super::MISSING_SPIN_LOOP;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_no_std_crate;
+use rustc_errors::Applicability;
+use rustc_hir::{Block, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+    match &cond.kind {
+        ExprKind::Block(
+            Block {
+                stmts: [],
+                expr: Some(e),
+                ..
+            },
+            _,
+        )
+        | ExprKind::Unary(_, e) => unpack_cond(e),
+        ExprKind::Binary(_, l, r) => {
+            let l = unpack_cond(l);
+            if let ExprKind::MethodCall(..) = l.kind {
+                l
+            } else {
+                unpack_cond(r)
+            }
+        },
+        _ => cond,
+    }
+}
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
+    if_chain! {
+        if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
+        if let ExprKind::MethodCall(method, [callee, ..], _) = unpack_cond(cond).kind;
+        if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
+        if let ty::Adt(def, _substs) = cx.typeck_results().expr_ty(callee).kind();
+        if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
+        then {
+            span_lint_and_sugg(
+                cx,
+                MISSING_SPIN_LOOP,
+                body.span,
+                "busy-waiting loop should at least have a spin loop hint",
+                "try this",
+                (if is_no_std_crate(cx) {
+                    "{ core::hint::spin_loop() }"
+                } else {
+                    "{ std::hint::spin_loop() }"
+                }).into(),
+                Applicability::MachineApplicable
+            );
+        }
+    }
+}
index 5bc32acf56eccd1a22a64fb19011b5946b68e045..f029067d36715f91cc7a33097e8b799dec51be7c 100644 (file)
@@ -7,6 +7,7 @@
 mod iter_next_loop;
 mod manual_flatten;
 mod manual_memcpy;
+mod missing_spin_loop;
 mod mut_range_bound;
 mod needless_collect;
 mod needless_range_loop;
     "for loops over `Option`s or `Result`s with a single expression can be simplified"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Check for empty spin loops
+    ///
+    /// ### Why is this bad?
+    /// The loop body should have something like `thread::park()` or at least
+    /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve
+    /// energy. Perhaps even better use an actual lock, if possible.
+    ///
+    /// ### Known problems
+    /// This lint doesn't currently trigger on `while let` or
+    /// `loop { match .. { .. } }` loops, which would be considered idiomatic in
+    /// combination with e.g. `AtomicBool::compare_exchange_weak`.
+    ///
+    /// ### Example
+    ///
+    /// ```ignore
+    /// use core::sync::atomic::{AtomicBool, Ordering};
+    /// let b = AtomicBool::new(true);
+    /// // give a ref to `b` to another thread,wait for it to become false
+    /// while b.load(Ordering::Acquire) {};
+    /// ```
+    /// Use instead:
+    /// ```rust,no_run
+    ///# use core::sync::atomic::{AtomicBool, Ordering};
+    ///# let b = AtomicBool::new(true);
+    /// while b.load(Ordering::Acquire) {
+    ///     std::hint::spin_loop()
+    /// }
+    /// ```
+    #[clippy::version = "1.59.0"]
+    pub MISSING_SPIN_LOOP,
+    perf,
+    "An empty busy waiting loop"
+}
+
 declare_lint_pass!(Loops => [
     MANUAL_MEMCPY,
     MANUAL_FLATTEN,
     WHILE_IMMUTABLE_CONDITION,
     SAME_ITEM_PUSH,
     SINGLE_ELEMENT_LOOP,
+    MISSING_SPIN_LOOP,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -628,6 +666,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
 
         if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
             while_immutable_condition::check(cx, condition, body);
+            missing_spin_loop::check(cx, condition, body);
         }
 
         needless_collect::check(expr, cx);
index d11dda57e6fd94c2676a4ef1a0fc2d95bc4d8feb..b8591fe0db0ad31a3e94fc030f69310a3f137f7f 100644 (file)
@@ -1,19 +1,66 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet;
 use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
-use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdSet, Pat, PatKind};
+use core::cmp::Ordering;
+use core::iter;
+use core::slice;
+use rustc_arena::DroplessArena;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd};
 use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Symbol;
 use std::collections::hash_map::Entry;
 
 use super::MATCH_SAME_ARMS;
 
-pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
     let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
         let mut h = SpanlessHash::new(cx);
         h.hash_expr(arm.body);
         h.finish()
     };
 
+    let arena = DroplessArena::default();
+    let normalized_pats: Vec<_> = arms
+        .iter()
+        .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat))
+        .collect();
+
+    // The furthast forwards a pattern can move without semantic changes
+    let forwards_blocking_idxs: Vec<_> = normalized_pats
+        .iter()
+        .enumerate()
+        .map(|(i, pat)| {
+            normalized_pats[i + 1..]
+                .iter()
+                .enumerate()
+                .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
+                .unwrap_or(normalized_pats.len())
+        })
+        .collect();
+
+    // The furthast backwards a pattern can move without semantic changes
+    let backwards_blocking_idxs: Vec<_> = normalized_pats
+        .iter()
+        .enumerate()
+        .map(|(i, pat)| {
+            normalized_pats[..i]
+                .iter()
+                .enumerate()
+                .rev()
+                .zip(forwards_blocking_idxs[..i].iter().copied().rev())
+                .skip_while(|&(_, forward_block)| forward_block > i)
+                .find_map(|((j, other), forward_block)| {
+                    (forward_block == i || pat.has_overlapping_values(other)).then(|| j)
+                })
+                .unwrap_or(0)
+        })
+        .collect();
+
     let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
         let min_index = usize::min(lindex, rindex);
         let max_index = usize::max(lindex, rindex);
@@ -42,53 +89,316 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
             }
         };
         // Arms with a guard are ignored, those can’t always be merged together
-        // This is also the case for arms in-between each there is an arm with a guard
-        (min_index..=max_index).all(|index| arms[index].guard.is_none())
-            && SpanlessEq::new(cx)
-                .expr_fallback(eq_fallback)
-                .eq_expr(lhs.body, rhs.body)
-            // these checks could be removed to allow unused bindings
-            && bindings_eq(lhs.pat, local_map.keys().copied().collect())
-            && bindings_eq(rhs.pat, local_map.values().copied().collect())
+        // If both arms overlap with an arm in between then these can't be merged either.
+        !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
+                && lhs.guard.is_none()
+                && rhs.guard.is_none()
+                && SpanlessEq::new(cx)
+                    .expr_fallback(eq_fallback)
+                    .eq_expr(lhs.body, rhs.body)
+                // these checks could be removed to allow unused bindings
+                && bindings_eq(lhs.pat, local_map.keys().copied().collect())
+                && bindings_eq(rhs.pat, local_map.values().copied().collect())
     };
 
     let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
-    for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) {
-        span_lint_and_then(
-            cx,
-            MATCH_SAME_ARMS,
-            j.body.span,
-            "this `match` has identical arm bodies",
-            |diag| {
-                diag.span_note(i.body.span, "same as this");
-
-                // Note: this does not use `span_suggestion` on purpose:
-                // there is no clean way
-                // to remove the other arm. Building a span and suggest to replace it to ""
-                // makes an even more confusing error message. Also in order not to make up a
-                // span for the whole pattern, the suggestion is only shown when there is only
-                // one pattern. The user should know about `|` if they are already using it…
-
-                let lhs = snippet(cx, i.pat.span, "<pat1>");
-                let rhs = snippet(cx, j.pat.span, "<pat2>");
-
-                if let PatKind::Wild = j.pat.kind {
-                    // if the last arm is _, then i could be integrated into _
-                    // note that i.pat cannot be _, because that would mean that we're
-                    // hiding all the subsequent arms, and rust won't compile
-                    diag.span_note(
-                        i.body.span,
-                        &format!(
-                            "`{}` has the same arm body as the `_` wildcard, consider removing it",
-                            lhs
-                        ),
-                    );
+    for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
+        if matches!(arm2.pat.kind, PatKind::Wild) {
+            span_lint_and_then(
+                cx,
+                MATCH_SAME_ARMS,
+                arm1.span,
+                "this match arm has an identical body to the `_` wildcard arm",
+                |diag| {
+                    diag.span_suggestion(
+                        arm1.span,
+                        "try removing the arm",
+                        String::new(),
+                        Applicability::MaybeIncorrect,
+                    )
+                    .help("or try changing either arm body")
+                    .span_note(arm2.span, "`_` wildcard arm here");
+                },
+            );
+        } else {
+            let back_block = backwards_blocking_idxs[j];
+            let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
+                (arm1, arm2)
+            } else {
+                (arm2, arm1)
+            };
+
+            span_lint_and_then(
+                cx,
+                MATCH_SAME_ARMS,
+                keep_arm.span,
+                "this match arm has an identical body to another arm",
+                |diag| {
+                    let move_pat_snip = snippet(cx, move_arm.pat.span, "<pat2>");
+                    let keep_pat_snip = snippet(cx, keep_arm.pat.span, "<pat1>");
+
+                    diag.span_suggestion(
+                        keep_arm.pat.span,
+                        "try merging the arm patterns",
+                        format!("{} | {}", keep_pat_snip, move_pat_snip),
+                        Applicability::MaybeIncorrect,
+                    )
+                    .help("or try changing either arm body")
+                    .span_note(move_arm.span, "other arm here");
+                },
+            );
+        }
+    }
+}
+
+#[derive(Clone, Copy)]
+enum NormalizedPat<'a> {
+    Wild,
+    Struct(Option<DefId>, &'a [(Symbol, Self)]),
+    Tuple(Option<DefId>, &'a [Self]),
+    Or(&'a [Self]),
+    Path(Option<DefId>),
+    LitStr(Symbol),
+    LitBytes(&'a [u8]),
+    LitInt(u128),
+    LitBool(bool),
+    Range(PatRange),
+    /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise
+    /// the first value contains everything before the `..` wildcard pattern, and the second value
+    /// contains everything afterwards. Note that either side, or both sides, may contain zero
+    /// patterns.
+    Slice(&'a [Self], Option<&'a [Self]>),
+}
+
+#[derive(Clone, Copy)]
+struct PatRange {
+    start: u128,
+    end: u128,
+    bounds: RangeEnd,
+}
+impl PatRange {
+    fn contains(&self, x: u128) -> bool {
+        x >= self.start
+            && match self.bounds {
+                RangeEnd::Included => x <= self.end,
+                RangeEnd::Excluded => x < self.end,
+            }
+    }
+
+    fn overlaps(&self, other: &Self) -> bool {
+        // Note: Empty ranges are impossible, so this is correct even though it would return true if an
+        // empty exclusive range were to reside within an inclusive range.
+        (match self.bounds {
+            RangeEnd::Included => self.end >= other.start,
+            RangeEnd::Excluded => self.end > other.start,
+        } && match other.bounds {
+            RangeEnd::Included => self.start <= other.end,
+            RangeEnd::Excluded => self.start < other.end,
+        })
+    }
+}
+
+/// Iterates over the pairs of fields with matching names.
+fn iter_matching_struct_fields<'a>(
+    left: &'a [(Symbol, NormalizedPat<'a>)],
+    right: &'a [(Symbol, NormalizedPat<'a>)],
+) -> impl Iterator<Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>)> + 'a {
+    struct Iter<'a>(
+        slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+        slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+    );
+    impl<'a> Iterator for Iter<'a> {
+        type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>);
+        fn next(&mut self) -> Option<Self::Item> {
+            // Note: all the fields in each slice are sorted by symbol value.
+            let mut left = self.0.next()?;
+            let mut right = self.1.next()?;
+            loop {
+                match left.0.cmp(&right.0) {
+                    Ordering::Equal => return Some((&left.1, &right.1)),
+                    Ordering::Less => left = self.0.next()?,
+                    Ordering::Greater => right = self.1.next()?,
+                }
+            }
+        }
+    }
+    Iter(left.iter(), right.iter())
+}
+
+#[allow(clippy::similar_names)]
+impl<'a> NormalizedPat<'a> {
+    #[allow(clippy::too_many_lines)]
+    fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
+        match pat.kind {
+            PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
+            PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => {
+                Self::from_pat(cx, arena, pat)
+            },
+            PatKind::Struct(ref path, fields, _) => {
+                let fields =
+                    arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat))));
+                fields.sort_by_key(|&(name, _)| name);
+                Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields)
+            },
+            PatKind::TupleStruct(ref path, pats, wild_idx) => {
+                let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() {
+                    Some(x) => x,
+                    None => return Self::Wild,
+                };
+                let (var_id, variant) = if adt.is_enum() {
+                    match cx.qpath_res(path, pat.hir_id).opt_def_id() {
+                        Some(x) => (Some(x), adt.variant_with_ctor_id(x)),
+                        None => return Self::Wild,
+                    }
                 } else {
-                    diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,))
-                        .help("...or consider changing the match arm bodies");
+                    (None, adt.non_enum_variant())
+                };
+                let (front, back) = match wild_idx {
+                    Some(i) => pats.split_at(i),
+                    None => (pats, [].as_slice()),
+                };
+                let pats = arena.alloc_from_iter(
+                    front
+                        .iter()
+                        .map(|pat| Self::from_pat(cx, arena, pat))
+                        .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len()))
+                        .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+                );
+                Self::Tuple(var_id, pats)
+            },
+            PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+            PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()),
+            PatKind::Tuple(pats, wild_idx) => {
+                let field_count = match cx.typeck_results().pat_ty(pat).kind() {
+                    ty::Tuple(subs) => subs.len(),
+                    _ => return Self::Wild,
+                };
+                let (front, back) = match wild_idx {
+                    Some(i) => pats.split_at(i),
+                    None => (pats, [].as_slice()),
+                };
+                let pats = arena.alloc_from_iter(
+                    front
+                        .iter()
+                        .map(|pat| Self::from_pat(cx, arena, pat))
+                        .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len()))
+                        .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+                );
+                Self::Tuple(None, pats)
+            },
+            PatKind::Lit(e) => match &e.kind {
+                // TODO: Handle negative integers. They're currently treated as a wild match.
+                ExprKind::Lit(lit) => match lit.node {
+                    LitKind::Str(sym, _) => Self::LitStr(sym),
+                    LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
+                    LitKind::Byte(val) => Self::LitInt(val.into()),
+                    LitKind::Char(val) => Self::LitInt(val.into()),
+                    LitKind::Int(val, _) => Self::LitInt(val),
+                    LitKind::Bool(val) => Self::LitBool(val),
+                    LitKind::Float(..) | LitKind::Err(_) => Self::Wild,
+                },
+                _ => Self::Wild,
+            },
+            PatKind::Range(start, end, bounds) => {
+                // TODO: Handle negative integers. They're currently treated as a wild match.
+                let start = match start {
+                    None => 0,
+                    Some(e) => match &e.kind {
+                        ExprKind::Lit(lit) => match lit.node {
+                            LitKind::Int(val, _) => val,
+                            LitKind::Char(val) => val.into(),
+                            LitKind::Byte(val) => val.into(),
+                            _ => return Self::Wild,
+                        },
+                        _ => return Self::Wild,
+                    },
+                };
+                let (end, bounds) = match end {
+                    None => (u128::MAX, RangeEnd::Included),
+                    Some(e) => match &e.kind {
+                        ExprKind::Lit(lit) => match lit.node {
+                            LitKind::Int(val, _) => (val, bounds),
+                            LitKind::Char(val) => (val.into(), bounds),
+                            LitKind::Byte(val) => (val.into(), bounds),
+                            _ => return Self::Wild,
+                        },
+                        _ => return Self::Wild,
+                    },
+                };
+                Self::Range(PatRange { start, end, bounds })
+            },
+            PatKind::Slice(front, wild_pat, back) => Self::Slice(
+                arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+                wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+            ),
+        }
+    }
+
+    /// Checks if two patterns overlap in the values they can match assuming they are for the same
+    /// type.
+    fn has_overlapping_values(&self, other: &Self) -> bool {
+        match (*self, *other) {
+            (Self::Wild, _) | (_, Self::Wild) => true,
+            (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => {
+                pats.iter().any(|pat| pat.has_overlapping_values(other))
+            },
+            (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => {
+                if lpath != rpath {
+                    return false;
+                }
+                iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+            },
+            (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => {
+                if lpath != rpath {
+                    return false;
                 }
+                lpats
+                    .iter()
+                    .zip(rpats.iter())
+                    .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+            },
+            (Self::Path(x), Self::Path(y)) => x == y,
+            (Self::LitStr(x), Self::LitStr(y)) => x == y,
+            (Self::LitBytes(x), Self::LitBytes(y)) => x == y,
+            (Self::LitInt(x), Self::LitInt(y)) => x == y,
+            (Self::LitBool(x), Self::LitBool(y)) => x == y,
+            (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
+            (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
+            (Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
+                lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
             },
-        );
+            (Self::Slice(pats, None), Self::Slice(front, Some(back)))
+            | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
+                // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
+                // then the minium length required will be greater than the length of `pats`.
+                if pats.len() < front.len() + back.len() {
+                    return false;
+                }
+                pats[..front.len()]
+                    .iter()
+                    .zip(front.iter())
+                    .chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
+                    .all(|(x, y)| x.has_overlapping_values(y))
+            },
+            (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
+                .iter()
+                .zip(rfront.iter())
+                .chain(lback.iter().rev().zip(rback.iter().rev()))
+                .all(|(x, y)| x.has_overlapping_values(y)),
+
+            // Enums can mix unit variants with tuple/struct variants. These can never overlap.
+            (Self::Path(_), Self::Tuple(..) | Self::Struct(..))
+            | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false,
+
+            // Tuples can be matched like a struct.
+            (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => {
+                // TODO: check fields here.
+                x == y
+            },
+
+            // TODO: Lit* with Path, Range with Path, LitBytes with Slice
+            _ => true,
+        }
     }
 }
 
index 3515286d5b4af8e45596abfc7dd98ce51a1b2521..93bf0dc62e076707e4228e784a016a96b80c2f61 100644 (file)
@@ -45,8 +45,8 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
 
     // Accumulate the variants which should be put in place of the wildcard because they're not
     // already covered.
-    let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x));
-    let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect();
+    let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x));
+    let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect();
 
     let mut path_prefix = CommonPrefixSearcher::None;
     for arm in arms {
@@ -118,7 +118,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
                 }
                 s
             } else {
-                let mut s = cx.tcx.def_path_str(adt_def.did);
+                let mut s = cx.tcx.def_path_str(adt_def.did());
                 s.push_str("::");
                 s
             },
index 92179eb6f0e60e5f7c8a90bdd1e6daa7b54d742c..ff85623acf49b85eabdd4c4eb4e399e9537ceefa 100644 (file)
@@ -16,6 +16,7 @@
 mod match_single_binding;
 mod match_wild_enum;
 mod match_wild_err_arm;
+mod needless_match;
 mod overlapping_arms;
 mod redundant_pattern_match;
 mod rest_pat_in_fully_bound_struct;
     "`match` with identical arm bodies"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
+    /// when function signatures are the same.
+    ///
+    /// ### Why is this bad?
+    /// This `match` block does nothing and might not be what the coder intended.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// fn foo() -> Result<(), i32> {
+    ///     match result {
+    ///         Ok(val) => Ok(val),
+    ///         Err(err) => Err(err),
+    ///     }
+    /// }
+    ///
+    /// fn bar() -> Option<i32> {
+    ///     if let Some(val) = option {
+    ///         Some(val)
+    ///     } else {
+    ///         None
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// Could be replaced as
+    ///
+    /// ```rust,ignore
+    /// fn foo() -> Result<(), i32> {
+    ///     result
+    /// }
+    ///
+    /// fn bar() -> Option<i32> {
+    ///     option
+    /// }
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub NEEDLESS_MATCH,
+    complexity,
+    "`match` or match-like `if let` that are unnecessary"
+}
+
 #[derive(Default)]
 pub struct Matches {
     msrv: Option<RustcVersion>,
@@ -599,6 +643,7 @@ pub fn new(msrv: Option<RustcVersion>) -> Self {
     REDUNDANT_PATTERN_MATCHING,
     MATCH_LIKE_MATCHES_MACRO,
     MATCH_SAME_ARMS,
+    NEEDLESS_MATCH,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Matches {
@@ -622,6 +667,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                     overlapping_arms::check(cx, ex, arms);
                     match_wild_enum::check(cx, ex, arms);
                     match_as_ref::check(cx, ex, arms, expr);
+                    needless_match::check_match(cx, ex, arms);
 
                     if self.infallible_destructuring_match_linted {
                         self.infallible_destructuring_match_linted = false;
@@ -640,6 +686,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                 match_like_matches::check(cx, expr);
             }
             redundant_pattern_match::check(cx, expr);
+            needless_match::check(cx, expr);
         }
     }
 
diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs
new file mode 100644 (file)
index 0000000..76131d3
--- /dev/null
@@ -0,0 +1,197 @@
+use super::NEEDLESS_MATCH;
+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::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+    // This is for avoiding collision with `match_single_binding`.
+    if arms.len() < 2 {
+        return;
+    }
+
+    for arm in arms {
+        if let PatKind::Wild = arm.pat.kind {
+            let ret_expr = strip_return(arm.body);
+            if !eq_expr_value(cx, ex, ret_expr) {
+                return;
+            }
+        } else if !pat_same_as_expr(arm.pat, arm.body) {
+            return;
+        }
+    }
+
+    if let Some(match_expr) = get_parent_expr(cx, ex) {
+        let mut applicability = Applicability::MachineApplicable;
+        span_lint_and_sugg(
+            cx,
+            NEEDLESS_MATCH,
+            match_expr.span,
+            "this match expression is unnecessary",
+            "replace it with",
+            snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
+            applicability,
+        );
+    }
+}
+
+/// Check for nop `if let` expression that assembled as unnecessary match
+///
+/// ```rust,ignore
+/// if let Some(a) = option {
+///     Some(a)
+/// } else {
+///     None
+/// }
+/// ```
+/// OR
+/// ```rust,ignore
+/// if let SomeEnum::A = some_enum {
+///     SomeEnum::A
+/// } else if let SomeEnum::B = some_enum {
+///     SomeEnum::B
+/// } else {
+///     some_enum
+/// }
+/// ```
+pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
+    if_chain! {
+        if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
+        if !is_else_clause(cx.tcx, ex);
+        if check_if_let(cx, if_let);
+        then {
+            let mut applicability = Applicability::MachineApplicable;
+            span_lint_and_sugg(
+                cx,
+                NEEDLESS_MATCH,
+                ex.span,
+                "this if-let expression is unnecessary",
+                "replace it with",
+                snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
+                applicability,
+            );
+        }
+    }
+}
+
+fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
+    if let Some(if_else) = if_let.if_else {
+        if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
+            return false;
+        }
+
+        // Recurrsively check for each `else if let` phrase,
+        if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
+            return check_if_let(cx, nested_if_let);
+        }
+
+        if matches!(if_else.kind, ExprKind::Block(..)) {
+            let else_expr = peel_blocks_with_stmt(if_else);
+            let ret = strip_return(else_expr);
+            let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
+            if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
+                if let ExprKind::Path(ref qpath) = ret.kind {
+                    return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
+                }
+            } else {
+                return eq_expr_value(cx, if_let.let_expr, ret);
+            }
+            return true;
+        }
+    }
+    false
+}
+
+/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
+fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+    if let ExprKind::Ret(Some(ret)) = expr.kind {
+        ret
+    } else {
+        expr
+    }
+}
+
+fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+    let expr = strip_return(expr);
+    match (&pat.kind, &expr.kind) {
+        // Example: `Some(val) => Some(val)`
+        (
+            PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
+            ExprKind::Call(call_expr, [first_param, ..]),
+        ) => {
+            if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
+                if has_identical_segments(path.segments, call_path.segments)
+                    && has_same_non_ref_symbol(first_pat, first_param)
+                {
+                    return true;
+                }
+            }
+        },
+        // Example: `val => val`, or `ref val => *val`
+        (PatKind::Binding(annot, _, pat_ident, _), _) => {
+            let new_expr = if let (
+                BindingAnnotation::Ref | BindingAnnotation::RefMut,
+                ExprKind::Unary(UnOp::Deref, operand_expr),
+            ) = (annot, &expr.kind)
+            {
+                operand_expr
+            } else {
+                expr
+            };
+
+            if let ExprKind::Path(QPath::Resolved(
+                _,
+                Path {
+                    segments: [first_seg, ..],
+                    ..
+                },
+            )) = new_expr.kind
+            {
+                return pat_ident.name == first_seg.ident.name;
+            }
+        },
+        // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
+        (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
+            return has_identical_segments(p_path.segments, e_path.segments);
+        },
+        // Example: `5 => 5`
+        (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
+            if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
+                return pat_spanned.node == expr_spanned.node;
+            }
+        },
+        _ => {},
+    }
+
+    false
+}
+
+fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
+    if left_segs.len() != right_segs.len() {
+        return false;
+    }
+    for i in 0..left_segs.len() {
+        if left_segs[i].ident.name != right_segs[i].ident.name {
+            return false;
+        }
+    }
+    true
+}
+
+fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+    if_chain! {
+        if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
+        if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
+        if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
+        then {
+            return pat_ident.name == first_seg.ident.name;
+        }
+    }
+
+    false
+}
index ce958b8ac9f5991a82de380f4ce71575ac60381c..eec232e6d0989dd731c44b47902780d8f66a6076 100644 (file)
@@ -145,7 +145,7 @@ fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg:
         if_chain! {
             if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
             if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
-            if Some(adt.did) == cx.tcx.parent(vid);
+            if Some(adt.did()) == cx.tcx.parent(vid);
             then {} else { return false; }
         }
 
index 67a585edc2550615aa610d2c958f0a2591582c14..6d30bb5a278bb42ca9606aceed73bee80b46c5c1 100644 (file)
@@ -15,7 +15,7 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span,
     let inner_ty = match recv_ty.kind() {
         // `Option<T>` -> `T`
         ty::Adt(adt, subst)
-            if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && meets_msrv(msrv, &msrvs::OPTION_COPIED) =>
+            if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, &msrvs::OPTION_COPIED) =>
         {
             subst.type_at(0)
         },
index 30c68186b3aef1468033f63161c9c5a7f1d1d724..558cb6bd64e7275a04e2ba49e27acb706eef8049 100644 (file)
@@ -119,9 +119,9 @@ pub(super) fn check<'tcx>(
             if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
             if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind;
             if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
-            if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did) {
+            if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
                 Some(false)
-            } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did) {
+            } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
                 Some(true)
             } else {
                 None
index 7a255baffd745aa7855fb9a78be1f0762bbb0080..6e64e7f62220704fa6759a79d690428a38f63664 100644 (file)
@@ -17,7 +17,7 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv
         let return_type = cx.typeck_results().expr_ty(expr);
         let input_type = cx.typeck_results().expr_ty(recv);
         let (input_type, ref_count) = peel_mid_ty_refs(input_type);
-        if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did));
+        if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did()));
         if return_type == input_type;
         then {
             let mut app = Applicability::MachineApplicable;
index c0f66feb48ae8c067ef1b006fb1e075cfda25bc4..06ead144afa24b459a406639721ea06fdf7a7f1a 100644 (file)
@@ -60,7 +60,7 @@ fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
     }
 
     if let ty::Adt(adt, substs) = ty.kind() {
-        match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str()
+        match_def_path(cx, adt.did(), &paths::COW) && substs.type_at(1).is_str()
     } else {
         false
     }
diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs
new file mode 100644 (file)
index 0000000..958c377
--- /dev/null
@@ -0,0 +1,72 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_integer_const;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{
+    higher::{self, Range},
+    SpanlessEq,
+};
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::LateContext;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::Span;
+
+use super::ITER_WITH_DRAIN;
+
+const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
+    let ty = cx.typeck_results().expr_ty(recv).peel_refs();
+    if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
+        // Refuse to emit `into_iter` suggestion on draining struct fields due
+        // to the strong possibility of processing unmovable field.
+        if let ExprKind::Field(..) = recv.kind {
+            return;
+        }
+
+        if let Some(range) = higher::Range::hir(arg) {
+            let left_full = match range {
+                Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
+                Range { start: None, .. } => true,
+                _ => false,
+            };
+            let full = left_full
+                && match range {
+                    Range {
+                        end: Some(end),
+                        limits: RangeLimits::HalfOpen,
+                        ..
+                    } => {
+                        // `x.drain(..x.len())` call
+                        if_chain! {
+                            if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
+                            if len_path.ident.name == sym::len && len_args.len() == 1;
+                            if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
+                            if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
+                            if SpanlessEq::new(cx).eq_path(drain_path, len_path);
+                            then { true }
+                            else { false }
+                        }
+                    },
+                    Range {
+                        end: None,
+                        limits: RangeLimits::HalfOpen,
+                        ..
+                    } => true,
+                    _ => false,
+                };
+            if full {
+                span_lint_and_sugg(
+                    cx,
+                    ITER_WITH_DRAIN,
+                    span.with_hi(expr.span.hi()),
+                    &format!("`drain(..)` used on a `{}`", drained_type),
+                    "try this",
+                    "into_iter()".to_string(),
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+    }
+}
index 6782f64f2ca41f78a2128587741524dbd8cb2caa..f447940ea3b5aaa84a2845c7ff8bbffc1e08fc1b 100644 (file)
@@ -1,83 +1,73 @@
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_sugg_for_edges;
 use clippy_utils::is_trait_method;
-use clippy_utils::source::snippet;
+use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::is_type_diagnostic_item;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::Expr;
 use rustc_lint::LateContext;
 use rustc_middle::ty;
-use rustc_span::symbol::sym;
+use rustc_span::{symbol::sym, Span};
 
 use super::MAP_FLATTEN;
 
 /// lint use of `map().flatten()` for `Iterators` and 'Options'
-pub(super) fn check<'tcx>(
-    cx: &LateContext<'tcx>,
-    expr: &'tcx hir::Expr<'_>,
-    recv: &'tcx hir::Expr<'_>,
-    map_arg: &'tcx hir::Expr<'_>,
-) {
-    // lint if caller of `.map().flatten()` is an Iterator
-    if is_trait_method(cx, expr, sym::Iterator) {
-        let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
-        let is_map_to_option = match map_closure_ty.kind() {
-            ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
-                let map_closure_sig = match map_closure_ty.kind() {
-                    ty::Closure(_, substs) => substs.as_closure().sig(),
-                    _ => map_closure_ty.fn_sig(cx.tcx),
-                };
-                let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
-                is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
-            },
-            _ => false,
-        };
-
-        let method_to_use = if is_map_to_option {
-            // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
-            "filter_map"
-        } else {
-            // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
-            "flat_map"
-        };
-        let func_snippet = snippet(cx, map_arg.span, "..");
-        let hint = format!(".{0}({1})", method_to_use, func_snippet);
-        span_lint_and_sugg(
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
+    if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
+        let mut applicability = Applicability::MachineApplicable;
+        let help_msgs = [
+            &format!("try replacing `map` with `{}`", method_to_use),
+            "and remove the `.flatten()`",
+        ];
+        let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
+        span_lint_and_sugg_for_edges(
             cx,
             MAP_FLATTEN,
-            expr.span.with_lo(recv.span.hi()),
-            "called `map(..).flatten()` on an `Iterator`",
-            &format!("try using `{}` instead", method_to_use),
-            hint,
-            Applicability::MachineApplicable,
+            expr.span.with_lo(map_span.lo()),
+            &format!("called `map(..).flatten()` on `{}`", caller_ty_name),
+            &help_msgs,
+            format!("{}({})", method_to_use, closure_snippet),
+            applicability,
         );
     }
+}
 
-    // lint if caller of `.map().flatten()` is an Option or Result
-    let caller_type = match cx.typeck_results().expr_ty(recv).kind() {
-        ty::Adt(adt, _) => {
-            if cx.tcx.is_diagnostic_item(sym::Option, adt.did) {
-                "Option"
-            } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did) {
-                "Result"
-            } else {
-                return;
+fn try_get_caller_ty_name_and_method_name(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    caller_expr: &Expr<'_>,
+    map_arg: &Expr<'_>,
+) -> Option<(&'static str, &'static str)> {
+    if is_trait_method(cx, expr, sym::Iterator) {
+        if is_map_to_option(cx, map_arg) {
+            // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
+            Some(("Iterator", "filter_map"))
+        } else {
+            // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
+            Some(("Iterator", "flat_map"))
+        }
+    } else {
+        if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
+            if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+                return Some(("Option", "and_then"));
+            } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+                return Some(("Result", "and_then"));
             }
-        },
-        _ => {
-            return;
-        },
-    };
+        }
+        None
+    }
+}
 
-    let func_snippet = snippet(cx, map_arg.span, "..");
-    let hint = format!(".and_then({})", func_snippet);
-    let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type);
-    span_lint_and_sugg(
-        cx,
-        MAP_FLATTEN,
-        expr.span.with_lo(recv.span.hi()),
-        &lint_info,
-        "try using `and_then` instead",
-        hint,
-        Applicability::MachineApplicable,
-    );
+fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
+    let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
+    match map_closure_ty.kind() {
+        ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
+            let map_closure_sig = match map_closure_ty.kind() {
+                ty::Closure(_, substs) => substs.as_closure().sig(),
+                _ => map_closure_ty.fn_sig(cx.tcx),
+            };
+            let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
+            is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
+        },
+        _ => false,
+    }
 }
index fd00ac7380de88071ebeb0479747e8694b5baaa2..021337280d1397dc0019d909e2925a1339e57d01 100644 (file)
@@ -32,6 +32,7 @@
 mod iter_nth_zero;
 mod iter_overeager_cloned;
 mod iter_skip_next;
+mod iter_with_drain;
 mod iterator_step_by_zero;
 mod manual_saturating_arithmetic;
 mod manual_str_repeat;
@@ -44,6 +45,7 @@
 mod option_map_or_none;
 mod option_map_unwrap_or;
 mod or_fun_call;
+mod or_then_unwrap;
 mod search_is_some;
 mod single_char_add_str;
 mod single_char_insert_string;
@@ -58,6 +60,7 @@
 mod unnecessary_filter_map;
 mod unnecessary_fold;
 mod unnecessary_iter_cloned;
+mod unnecessary_join;
 mod unnecessary_lazy_eval;
 mod unnecessary_to_owned;
 mod unwrap_or_else_default;
     "using any `*or` method with a function call, which suggests `*or_else`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `.or(…).unwrap()` calls to Options and Results.
+    ///
+    /// ### Why is this bad?
+    /// You should use `.unwrap_or(…)` instead for clarity.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # let fallback = "fallback";
+    /// // Result
+    /// # type Error = &'static str;
+    /// # let result: Result<&str, Error> = Err("error");
+    /// let value = result.or::<Error>(Ok(fallback)).unwrap();
+    ///
+    /// // Option
+    /// # let option: Option<&str> = None;
+    /// let value = option.or(Some(fallback)).unwrap();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # let fallback = "fallback";
+    /// // Result
+    /// # let result: Result<&str, &str> = Err("error");
+    /// let value = result.unwrap_or(fallback);
+    ///
+    /// // Option
+    /// # let option: Option<&str> = None;
+    /// let value = option.unwrap_or(fallback);
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub OR_THEN_UNWRAP,
+    complexity,
+    "checks for `.or(…).unwrap()` calls to Options and Results."
+}
+
 declare_clippy_lint! {
     /// ### What it does
     /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
     "using `.skip(x).next()` on an iterator"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
+    ///
+    /// ### Why is this bad?
+    /// `.into_iter()` is simpler with better performance.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # use std::collections::HashSet;
+    /// let mut foo = vec![0, 1, 2, 3];
+    /// let bar: HashSet<usize> = foo.drain(..).collect();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # use std::collections::HashSet;
+    /// let foo = vec![0, 1, 2, 3];
+    /// let bar: HashSet<usize> = foo.into_iter().collect();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub ITER_WITH_DRAIN,
+    nursery,
+    "replace `.drain(..)` with `.into_iter()`"
+}
+
 declare_clippy_lint! {
     /// ### What it does
     /// Checks for use of `.get().unwrap()` (or
 
 declare_clippy_lint! {
     /// ### What it does
-    /// Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+    /// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
     /// More specifically it checks if the closure provided is only performing one of the
     /// filter or map operations and suggests the appropriate option.
     ///
     "using `filter_map` when a more succinct alternative exists"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `find_map` calls that could be replaced by `find` or `map`. More
+    /// specifically it checks if the closure provided is only performing one of the
+    /// find or map operations and suggests the appropriate option.
+    ///
+    /// ### Why is this bad?
+    /// Complexity. The intent is also clearer if only a single
+    /// operation is being performed.
+    ///
+    /// ### Example
+    /// ```rust
+    /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
+    ///
+    /// // As there is no transformation of the argument this could be written as:
+    /// let _ = (0..3).find(|&x| x > 2);
+    /// ```
+    ///
+    /// ```rust
+    /// let _ = (0..4).find_map(|x| Some(x + 1));
+    ///
+    /// // As there is no conditional check on the argument this could be written as:
+    /// let _ = (0..4).map(|x| x + 1).next();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub UNNECESSARY_FIND_MAP,
+    complexity,
+    "using `find_map` when a more succinct alternative exists"
+}
+
 declare_clippy_lint! {
     /// ### What it does
     /// Checks for `into_iter` calls on references which should be replaced by `iter`
     "unnecessary calls to `to_owned`-like functions"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
+    ///
+    /// ### Why is this bad?
+    /// `.collect::<String>()` is more concise and usually more performant
+    ///
+    /// ### Example
+    /// ```rust
+    /// let vector = vec!["hello",  "world"];
+    /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
+    /// println!("{}", output);
+    /// ```
+    /// The correct use would be:
+    /// ```rust
+    /// let vector = vec!["hello",  "world"];
+    /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+    /// println!("{}", output);
+    /// ```
+    /// ### Known problems
+    /// While `.collect::<String>()` is more performant in most cases, there are cases where
+    /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+    /// will prevent loop unrolling and will result in a negative performance impact.
+    #[clippy::version = "1.61.0"]
+    pub UNNECESSARY_JOIN,
+    pedantic,
+    "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Option<RustcVersion>,
@@ -1983,6 +2106,7 @@ pub fn new(avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>) -> Sel
     OPTION_MAP_OR_NONE,
     BIND_INSTEAD_OF_MAP,
     OR_FUN_CALL,
+    OR_THEN_UNWRAP,
     EXPECT_FUN_CALL,
     CHARS_NEXT_CMP,
     CHARS_LAST_CMP,
@@ -2017,9 +2141,11 @@ pub fn new(avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>) -> Sel
     GET_UNWRAP,
     STRING_EXTEND_CHARS,
     ITER_CLONED_COLLECT,
+    ITER_WITH_DRAIN,
     USELESS_ASREF,
     UNNECESSARY_FOLD,
     UNNECESSARY_FILTER_MAP,
+    UNNECESSARY_FIND_MAP,
     INTO_ITER_ON_REF,
     SUSPICIOUS_MAP,
     UNINIT_ASSUMED_INIT,
@@ -2038,6 +2164,7 @@ pub fn new(avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>) -> Sel
     MANUAL_SPLIT_ONCE,
     NEEDLESS_SPLITN,
     UNNECESSARY_TO_OWNED,
+    UNNECESSARY_JOIN,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -2296,6 +2423,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                 Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
                 _ => {},
             },
+            ("drain", [arg]) => {
+                iter_with_drain::check(cx, expr, recv, span, arg);
+            },
             ("expect", [_]) => match method_call(recv) {
                 Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
                 _ => expect_used::check(cx, expr, recv),
@@ -2305,15 +2435,18 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                 extend_with_drain::check(cx, expr, recv, arg);
             },
             ("filter_map", [arg]) => {
-                unnecessary_filter_map::check(cx, expr, arg);
+                unnecessary_filter_map::check(cx, expr, arg, name);
                 filter_map_identity::check(cx, expr, arg, span);
             },
+            ("find_map", [arg]) => {
+                unnecessary_filter_map::check(cx, expr, arg, name);
+            },
             ("flat_map", [arg]) => {
                 flat_map_identity::check(cx, expr, arg, span);
                 flat_map_option::check(cx, expr, arg, span);
             },
             (name @ "flatten", args @ []) => match method_call(recv) {
-                Some(("map", [recv, map_arg], _)) => map_flatten::check(cx, expr, recv, map_arg),
+                Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
                 Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
                 _ => {},
             },
@@ -2327,6 +2460,11 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
             ("is_file", []) => filetype_is_file::check(cx, expr, recv),
             ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
             ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+            ("join", [join_arg]) => {
+                if let Some(("collect", _, span)) = method_call(recv) {
+                    unnecessary_join::check(cx, expr, recv, join_arg, span);
+                }
+            },
             ("last", args @ []) | ("skip", args @ [_]) => {
                 if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
                     if let ("cloned", []) = (name2, args2) {
@@ -2410,6 +2548,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                     Some(("get_mut", [recv, get_arg], _)) => {
                         get_unwrap::check(cx, expr, recv, get_arg, true);
                     },
+                    Some(("or", [recv, or_arg], or_span)) => {
+                        or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+                    },
                     _ => {},
                 }
                 unwrap_used::check(cx, expr, recv);
index bdf8cea120739f63b1092dc88cafc352cfb0f741..2a5ab6e625c111ae0bec3d204db34d7f5c6653f0 100644 (file)
 
 // The expression inside a closure may or may not have surrounding braces
 // which causes problems when generating a suggestion.
-fn reduce_unit_expression<'a>(
-    cx: &LateContext<'_>,
-    expr: &'a hir::Expr<'_>,
-) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
+fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
     match expr.kind {
         hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
         hir::ExprKind::Block(block, _) => {
@@ -25,7 +22,7 @@ fn reduce_unit_expression<'a>(
                 (&[], Some(inner_expr)) => {
                     // If block only contains an expression,
                     // reduce `|x| { x + 1 }` to `|x| x + 1`
-                    reduce_unit_expression(cx, inner_expr)
+                    reduce_unit_expression(inner_expr)
                 },
                 _ => None,
             }
@@ -77,7 +74,7 @@ pub(super) fn check<'tcx>(
         if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
             let arg_snippet = snippet(cx, span, "..");
             let body = cx.tcx.hir().body(id);
-                if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
+                if let Some((func, [arg_char])) = reduce_unit_expression(&body.value);
                 if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
                 if Some(id) == cx.tcx.lang_items().option_some_variant();
                 then {
diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs
new file mode 100644 (file)
index 0000000..be5768c
--- /dev/null
@@ -0,0 +1,68 @@
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor};
+use rustc_errors::Applicability;
+use rustc_hir::{lang_items::LangItem, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::OR_THEN_UNWRAP;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    unwrap_expr: &Expr<'_>,
+    recv: &'tcx Expr<'tcx>,
+    or_arg: &'tcx Expr<'_>,
+    or_span: Span,
+) {
+    let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
+    let title;
+    let or_arg_content: Span;
+
+    if is_type_diagnostic_item(cx, ty, sym::Option) {
+        title = "found `.or(Some(…)).unwrap()`";
+        if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
+            or_arg_content = content;
+        } else {
+            return;
+        }
+    } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+        title = "found `.or(Ok(…)).unwrap()`";
+        if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
+            or_arg_content = content;
+        } else {
+            return;
+        }
+    } else {
+        // Someone has implemented a struct with .or(...).unwrap() chaining,
+        // but it's not an Option or a Result, so bail
+        return;
+    }
+
+    let mut applicability = Applicability::MachineApplicable;
+    let suggestion = format!(
+        "unwrap_or({})",
+        snippet_with_applicability(cx, or_arg_content, "..", &mut applicability)
+    );
+
+    span_lint_and_sugg(
+        cx,
+        OR_THEN_UNWRAP,
+        unwrap_expr.span.with_lo(or_span.lo()),
+        title,
+        "try this",
+        suggestion,
+        applicability,
+    );
+}
+
+fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
+    if let ExprKind::Call(some_expr, [arg]) = expr.kind
+        && let ExprKind::Path(qpath) = &some_expr.kind
+        && is_lang_ctor(cx, qpath, item)
+    {
+        Some(arg.span)
+    } else {
+        None
+    }
+}
index 926c25b4b40a5c7aa2bd7c5003384ed5951fa857..8125930b3046144053a7ab4bf8e4063e2ef42675 100644 (file)
@@ -152,7 +152,7 @@ fn parse_iter_usage<'tcx>(
                     return if_chain! {
                         if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
                         if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
-                        if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did);
+                        if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
                         if let ty::Tuple(subs) = subs.type_at(0).kind();
                         if subs.len() == 2;
                         then {
index 108e143f33737d631231061bf56b6ba53c83898d..2fda254ca98e99ddf1820a790842ab0af2163a16 100644 (file)
@@ -11,8 +11,9 @@
 use rustc_span::sym;
 
 use super::UNNECESSARY_FILTER_MAP;
+use super::UNNECESSARY_FIND_MAP;
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
     if !is_trait_method(cx, expr, sym::Iterator) {
         return;
     }
@@ -33,11 +34,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
 
         let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
         let sugg = if !found_filtering {
-            "map"
+            if name == "filter_map" { "map" } else { "map(..).next()" }
         } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
             match cx.typeck_results().expr_ty(&body.value).kind() {
-                ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => {
-                    "filter"
+                ty::Adt(adt, subst)
+                    if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
+                {
+                    if name == "filter_map" { "filter" } else { "find" }
                 },
                 _ => return,
             }
@@ -46,9 +49,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
         };
         span_lint(
             cx,
-            UNNECESSARY_FILTER_MAP,
+            if name == "filter_map" {
+                UNNECESSARY_FILTER_MAP
+            } else {
+                UNNECESSARY_FIND_MAP
+            },
             expr.span,
-            &format!("this `.filter_map` can be written more simply using `.{}`", sugg),
+            &format!("this `.{}` can be written more simply using `.{}`", name, sugg),
         );
     }
 }
diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs
new file mode 100644 (file)
index 0000000..973b8a7
--- /dev/null
@@ -0,0 +1,41 @@
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{Ref, Slice};
+use rustc_span::{sym, Span};
+
+use super::UNNECESSARY_JOIN;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'tcx>,
+    join_self_arg: &'tcx Expr<'tcx>,
+    join_arg: &'tcx Expr<'tcx>,
+    span: Span,
+) {
+    let applicability = Applicability::MachineApplicable;
+    let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg);
+    if_chain! {
+        // the turbofish for collect is ::<Vec<String>>
+        if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind();
+        if let Slice(slice) = ref_type.kind();
+        if is_type_diagnostic_item(cx, *slice, sym::String);
+        // the argument for join is ""
+        if let ExprKind::Lit(spanned) = &join_arg.kind;
+        if let LitKind::Str(symbol, _) = spanned.node;
+        if symbol.is_empty();
+        then {
+            span_lint_and_sugg(
+                cx,
+                UNNECESSARY_JOIN,
+                span.with_hi(expr.span.hi()),
+                r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
+                "try using",
+                "collect::<String>()".to_owned(),
+                applicability,
+            );
+        }
+    }
+}
index 1e2765263c87ddb0ed920c8da8a71b74394ee0b3..2369be708129403f242fcdbfd55204d1295cf47b 100644 (file)
@@ -1,4 +1,4 @@
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet;
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::{eager_or_lazy, usage};
@@ -48,20 +48,19 @@ pub(super) fn check<'tcx>(
                     Applicability::MaybeIncorrect
                 };
 
-                span_lint_and_sugg(
-                    cx,
-                    UNNECESSARY_LAZY_EVALUATIONS,
-                    expr.span,
-                    msg,
-                    &format!("use `{}` instead", simplify_using),
-                    format!(
-                        "{0}.{1}({2})",
-                        snippet(cx, recv.span, ".."),
-                        simplify_using,
-                        snippet(cx, body_expr.span, ".."),
-                    ),
-                    applicability,
-                );
+                // This is a duplicate of what's happening in clippy_lints::methods::method_call,
+                // which isn't ideal, We want to get the method call span,
+                // but prefer to avoid changing the signature of the function itself.
+                if let hir::ExprKind::MethodCall(_, _, span) = expr.kind {
+                    span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| {
+                        diag.span_suggestion(
+                            span,
+                            &format!("use `{}(..)` instead", simplify_using),
+                            format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")),
+                            applicability,
+                        );
+                    });
+                }
             }
         }
     }
index 7916fb8e3b45ca2739f6efc35a6c079bfd864ed7..1555758fc4ad825b6b013a7bb1014544123f6804 100644 (file)
@@ -2,7 +2,9 @@
 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::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
+use clippy_utils::ty::{
+    contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
+};
 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
 use rustc_errors::Applicability;
 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
@@ -114,7 +116,12 @@ fn check_addr_of_expr(
                     parent.span,
                     &format!("unnecessary use of `{}`", method_name),
                     "use",
-                    format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs),
+                    format!(
+                        "{:&>width$}{}",
+                        "",
+                        receiver_snippet,
+                        width = n_target_refs - n_receiver_refs
+                    ),
                     Applicability::MachineApplicable,
                 );
                 return true;
@@ -182,20 +189,10 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name:
         if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
         then {
-            if unnecessary_iter_cloned::check_for_loop_iter(
-                cx,
-                parent,
-                method_name,
-                receiver,
-                true,
-            ) {
+            if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
                 return true;
             }
-            let cloned_or_copied = if is_copy(cx, item_ty) {
-                "copied"
-            } else {
-                "cloned"
-            };
+            let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" };
             // The next suggestion may be incorrect because the removal of the `to_owned`-like
             // function could cause the iterator to hold a reference to a resource that is used
             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
@@ -243,10 +240,11 @@ fn check_other_call_arg<'tcx>(
         if if trait_predicate.def_id() == deref_trait_id {
             if let [projection_predicate] = projection_predicates[..] {
                 let normalized_ty =
-                    cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
+                    cx.tcx
+                        .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
                 implements_trait(cx, receiver_ty, deref_trait_id, &[])
-                    && get_associated_type(cx, receiver_ty, deref_trait_id,
-                    "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
+                    && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
+                        .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
             } else {
                 false
             }
@@ -254,7 +252,7 @@ fn check_other_call_arg<'tcx>(
             let composed_substs = compose_substs(
                 cx,
                 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
-                call_substs
+                call_substs,
             );
             implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
         } else {
@@ -264,6 +262,12 @@ fn check_other_call_arg<'tcx>(
         // `Target = T`.
         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
         let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
+        // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
+        // `T` must not be instantiated with a reference
+        // (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);
         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
         then {
             span_lint_and_sugg(
@@ -339,11 +343,7 @@ fn get_input_traits_and_projections<'tcx>(
                 if let Some(arg) = substs.iter().next();
                 if let GenericArgKind::Type(arg_ty) = arg.unpack();
                 if arg_ty == input;
-                then {
-                    true
-                } else {
-                    false
-                }
+                then { true } else { false }
             }
         };
         match predicate.kind().skip_binder() {
index bad9e0be82e61e026a25b715d2b20e50d18c960a..ecc9acf4445d03681bfc1a0e28ce1257abf99974 100644 (file)
@@ -3,6 +3,7 @@
 use clippy_utils::ty::has_drop;
 use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method};
 use rustc_hir as hir;
+use rustc_hir::def_id::CRATE_DEF_ID;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
 use rustc_lint::{LateContext, LateLintPass};
@@ -131,6 +132,18 @@ fn check_fn(
             FnKind::Closure => return,
         }
 
+        // Const fns are not allowed as methods in a trait.
+        {
+            let parent = cx.tcx.hir().get_parent_item(hir_id);
+            if parent != CRATE_DEF_ID {
+                if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent) {
+                    if let hir::ItemKind::Trait(..) = &item.kind {
+                        return;
+                    }
+                }
+            }
+        }
+
         let mir = cx.tcx.optimized_mir(def_id);
 
         if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
index ce9ca15430e421cd16455c52b80508ebda522b59..cba54e14212d0d18949213c00db27d684eea1237 100644 (file)
@@ -125,7 +125,7 @@ fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
     if let Adt(def, substs) = ty.kind() {
         let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
             .iter()
-            .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
+            .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
         if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
             span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
         }
@@ -159,8 +159,8 @@ fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Sp
                 sym::Arc,
             ]
             .iter()
-            .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
-            let is_box = Some(def.did) == cx.tcx.lang_items().owned_box();
+            .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
+            let is_box = Some(def.did()) == cx.tcx.lang_items().owned_box();
             if is_std_collection || is_box {
                 // The type is mutable if any of its type parameters are
                 substs.types().any(|ty| is_interior_mutable_type(cx, ty, span))
index ebd4fb0bf51ccb4390a4fa96fdf7e9c5c42f1549..5eb7b0f0521e0a81be4e76f1e035bbdda1332698 100644 (file)
@@ -6,7 +6,7 @@
 use if_chain::if_chain;
 use rustc_ast::ast::Attribute;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{Applicability, Diagnostic};
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind};
 use rustc_hir::{HirIdMap, HirIdSet};
@@ -196,10 +196,15 @@ fn check_fn(
                     }
 
                     // Dereference suggestion
-                    let sugg = |diag: &mut DiagnosticBuilder<'_>| {
+                    let sugg = |diag: &mut Diagnostic| {
                         if let ty::Adt(def, ..) = ty.kind() {
-                            if let Some(span) = cx.tcx.hir().span_if_local(def.did) {
-                                if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() {
+                            if let Some(span) = cx.tcx.hir().span_if_local(def.did()) {
+                                if can_type_implement_copy(
+                                    cx.tcx,
+                                    cx.param_env,
+                                    ty,
+                                    traits::ObligationCause::dummy_with_span(span),
+                                ).is_ok() {
                                     diag.span_help(span, "consider marking this type as `Copy`");
                                 }
                             }
index ed315efaa2fa7c546de3a7fabce42dc2997a80f5..c87c174ef732cb71824640bd7932ee874402c0b4 100644 (file)
@@ -54,7 +54,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             let ty = cx.typeck_results().expr_ty(expr);
             if let ty::Adt(def, _) = ty.kind() {
                 if fields.len() == def.non_enum_variant().fields.len()
-                    && !def.variants[0_usize.into()].is_field_list_non_exhaustive()
+                    && !def.variant(0_usize.into()).is_field_list_non_exhaustive()
                 {
                     span_lint(
                         cx,
index b40425bb6355c7386c0b800a2605cd2d4fbf7ca3..9419056be14325a39b7912c51dfbbdcbe9604fc2 100644 (file)
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::return_ty;
 use clippy_utils::source::snippet;
-use clippy_utils::sugg::DiagnosticBuilderExt;
+use clippy_utils::sugg::DiagnosticExt;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
@@ -113,7 +113,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
                                     let mut impls = HirIdSet::default();
                                     cx.tcx.for_each_impl(default_trait_id, |d| {
                                         if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
-                                            if let Some(local_def_id) = ty_def.did.as_local() {
+                                            if let Some(local_def_id) = ty_def.did().as_local() {
                                                 impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
                                             }
                                         }
@@ -126,7 +126,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
                                 if_chain! {
                                     if let Some(ref impling_types) = self.impling_types;
                                     if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
-                                    if let Some(self_local_did) = self_def.did.as_local();
+                                    if let Some(self_local_did) = self_def.did().as_local();
                                     let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
                                     if impling_types.contains(&self_id);
                                     then {
index 3ba99403f06d005645138f6511fd9e1c3d5418e9..8db41ba6ee296f399877221d2c24b77d6c0e6e06 100644 (file)
@@ -140,7 +140,7 @@ fn inner<'tcx>(cx: &LateContext<'tcx>, val: Const<'tcx>) -> bool {
         match val.ty().kind() {
             // the fact that we have to dig into every structs to search enums
             // leads us to the point checking `UnsafeCell` directly is the only option.
-            ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true,
+            ty::Adt(ty_def, ..) if Some(ty_def.did()) == cx.tcx.lang_items().unsafe_cell_type() => true,
             ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => {
                 let val = cx.tcx.destructure_const(cx.param_env.and(val));
                 val.fields.iter().any(|field| inner(cx, *field))
index 5168ca67b6abb86c135264f6bb7b273474e2db20..ddef7352de8891075d5a38ee8a6ae0f286e8d720 100644 (file)
@@ -96,7 +96,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
                 let mut non_send_fields = Vec::new();
 
                 let hir_map = cx.tcx.hir();
-                for variant in &adt_def.variants {
+                for variant in adt_def.variants() {
                     for field in &variant.fields {
                         if_chain! {
                             if let Some(field_hir_id) = field
@@ -233,7 +233,7 @@ fn contains_pointer_like<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> b
                     return true;
                 },
                 ty::Adt(adt_def, _) => {
-                    if match_def_path(cx, adt_def.did, &paths::PTR_NON_NULL) {
+                    if match_def_path(cx, adt_def.did(), &paths::PTR_NON_NULL) {
                         return true;
                     }
                 },
diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs
new file mode 100644 (file)
index 0000000..8e61f23
--- /dev/null
@@ -0,0 +1,668 @@
+use std::collections::VecDeque;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use itertools::{izip, Itertools};
+use rustc_ast::{walk_list, Label, Mutability};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+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, FnKind, Visitor};
+use rustc_hir::{
+    Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
+    QPath, Stmt, StmtKind, TyKind, UnOp,
+};
+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_span::Span;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for arguments that are only used in recursion with no side-effects.
+    ///
+    /// ### Why is this bad?
+    /// It could contain a useless calculation and can make function simpler.
+    ///
+    /// The arguments can be involved in calculations and assignments but as long as
+    /// the calculations have no side-effects (function calls or mutating dereference)
+    /// and the assigned variables are also only in recursion, it is useless.
+    ///
+    /// ### Known problems
+    /// In some cases, this would not catch all useless arguments.
+    ///
+    /// ```rust
+    /// fn foo(a: usize, b: usize) -> usize {
+    ///     let f = |x| x + 1;
+    ///
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         foo(a - 1, f(b))
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// For example, the argument `b` is only used in recursion, but the lint would not catch it.
+    ///
+    /// List of some examples that can not be caught:
+    /// - binary operation of non-primitive types
+    /// - closure usage
+    /// - some `break` relative operations
+    /// - struct pattern binding
+    ///
+    /// Also, when you recurse the function name with path segments, it is not possible to detect.
+    ///
+    /// ### Example
+    /// ```rust
+    /// fn f(a: usize, b: usize) -> usize {
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         f(a - 1, b + 1)
+    ///     }
+    /// }
+    /// # fn main() {
+    /// #     print!("{}", f(1, 1));
+    /// # }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// fn f(a: usize) -> usize {
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         f(a - 1)
+    ///     }
+    /// }
+    /// # fn main() {
+    /// #     print!("{}", f(1));
+    /// # }
+    /// ```
+    #[clippy::version = "1.60.0"]
+    pub ONLY_USED_IN_RECURSION,
+    complexity,
+    "arguments that is only used in recursion can be removed"
+}
+declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
+
+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 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,
+                ty_ctx: cx.tcx,
+            };
+
+            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 {
+    match ty.kind() {
+        ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
+        ty::Ref(_, t, _) => is_primitive(*t),
+        _ => false,
+    }
+}
+
+pub fn is_array(ty: Ty<'_>) -> bool {
+    match ty.kind() {
+        ty::Array(..) | ty::Slice(..) => true,
+        ty::Ref(_, t, _) => is_array(*t),
+        _ => false,
+    }
+}
+
+/// 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>,
+    ty_ctx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
+    fn visit_block(&mut self, b: &'tcx Block<'tcx>) {
+        b.stmts.iter().for_each(|stmt| {
+            self.visit_stmt(stmt);
+            self.ret_vars.clear();
+        });
+        walk_list!(self, visit_expr, b.expr);
+    }
+
+    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);
+                self.ret_vars.clear();
+            },
+            StmtKind::Item(i) => {
+                let item = self.ty_ctx.hir().item(i);
+                self.visit_item(item);
+                self.ret_vars.clear();
+            },
+            StmtKind::Expr(e) | StmtKind::Semi(e) => {
+                self.visit_expr(e);
+                self.ret_vars.clear();
+            },
+            StmtKind::Local(_) => {},
+        }
+    }
+
+    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+        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(_, _, body_id, _, _) => {
+                let body = self.ty_ctx.hir().body(body_id);
+                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);
+                }
+            },
+            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
+            .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(_, 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
+            })
+            .collect();
+        self.ret_vars.append(&mut expr_vars);
+    }
+
+    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);
+            }
+        }
+
+        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;
+        }
+    }
+}
index 2c328195f24e1af1a6cc792f960d568e09b3b837..ba1997e70e1314ee631fdf87832121c6372250f8 100644 (file)
@@ -405,13 +405,13 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                 // Check that the name as typed matches the actual name of the type.
                 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
                 if let [.., name] = path.segments;
-                if cx.tcx.item_name(adt.did) == name.ident.name;
+                if cx.tcx.item_name(adt.did()) == name.ident.name;
 
                 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
                 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
 
                 then {
-                    let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
+                    let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did()) {
                         Some(sym::Vec) => (
                             [("clone", ".to_owned()")].as_slice(),
                             DerefTy::Slice(
@@ -436,7 +436,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                             DerefTy::Path,
                             None,
                         ),
-                        Some(sym::Cow) => {
+                        Some(sym::Cow) if mutability == Mutability::Not => {
                             let ty_name = name.args
                                 .and_then(|args| {
                                     args.args.iter().find_map(|a| match a {
@@ -462,7 +462,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                     return Some(PtrArg {
                         idx: i,
                         span: hir_ty.span,
-                        ty_did: adt.did,
+                        ty_did: adt.did(),
                         ty_name: name.ident.name,
                         method_renames,
                         ref_prefix: RefPrefix {
@@ -570,7 +570,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
                             .map(|sig| sig.input(i).skip_binder().peel_refs())
                             .map_or(true, |ty| match *ty.kind() {
                                 ty::Param(_) => true,
-                                ty::Adt(def, _) => def.did == args.ty_did,
+                                ty::Adt(def, _) => def.did() == args.ty_did,
                                 _ => false,
                             })
                         {
@@ -607,7 +607,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
                             // If the types match check for methods which exist on both types. e.g. `Vec::len` and
                             // `slice::len`
                             ty::Adt(def, _)
-                                if def.did == args.ty_did
+                                if def.did() == args.ty_did
                                     && (i != 0
                                         || self.cx.tcx.trait_of_item(id).is_some()
                                         || !args.deref_assoc_items.map_or(false, |(id, items)| {
index 6f634ded5fef700ffcc8a08ce79fe92be7156f34..899568a933f7afe196b890a5f81e146aca01ec2a 100644 (file)
@@ -123,7 +123,7 @@ fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'
     }
 
     fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
-        Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
+        Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr)
     }
 
     fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
@@ -156,9 +156,9 @@ fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool
         }
     }
 
-    fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
+    fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
         match peel_blocks_with_stmt(expr).kind {
-            ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr),
+            ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr),
             ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
             _ => false,
         }
index b3988973256c4c16cad3c8306c6e43cf4916b062..1507c75ff612314236bb2b2ed5be935a9fa9088e 100644 (file)
@@ -3,7 +3,7 @@
 use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
 use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
 use if_chain::if_chain;
-use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation};
+use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{def_id, Body, FnDecl, HirId};
@@ -512,7 +512,7 @@ fn call_return_effect(
 /// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
 /// possible borrowers of `a`.
 struct PossibleBorrowerVisitor<'a, 'tcx> {
-    possible_borrower: TransitiveRelation<mir::Local>,
+    possible_borrower: TransitiveRelation,
     body: &'a mir::Body<'tcx>,
     cx: &'a LateContext<'tcx>,
     possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
@@ -543,18 +543,10 @@ fn into_map(
                 continue;
             }
 
-            let borrowers = self.possible_borrower.reachable_from(&row);
+            let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
+            borrowers.remove(mir::Local::from_usize(0));
             if !borrowers.is_empty() {
-                let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
-                for &c in borrowers {
-                    if c != mir::Local::from_usize(0) {
-                        bs.insert(c);
-                    }
-                }
-
-                if !bs.is_empty() {
-                    map.insert(row, bs);
-                }
+                map.insert(row, borrowers);
             }
         }
 
@@ -644,7 +636,7 @@ fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Loca
 /// For exampel, `_1 = &mut _2` generate _1: {_2,...}
 /// Known Problems: not sure all borrowed are tracked
 struct PossibleOriginVisitor<'a, 'tcx> {
-    possible_origin: TransitiveRelation<mir::Local>,
+    possible_origin: TransitiveRelation,
     body: &'a mir::Body<'tcx>,
 }
 
@@ -663,18 +655,10 @@ fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<
                 continue;
             }
 
-            let borrowers = self.possible_origin.reachable_from(&row);
+            let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
+            borrowers.remove(mir::Local::from_usize(0));
             if !borrowers.is_empty() {
-                let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
-                for &c in borrowers {
-                    if c != mir::Local::from_usize(0) {
-                        bs.insert(c);
-                    }
-                }
-
-                if !bs.is_empty() {
-                    map.insert(row, bs);
-                }
+                map.insert(row, borrowers);
             }
         }
         map
@@ -766,3 +750,28 @@ fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
         self.maybe_live.contains(local)
     }
 }
+
+#[derive(Default)]
+struct TransitiveRelation {
+    relations: FxHashMap<mir::Local, Vec<mir::Local>>,
+}
+impl TransitiveRelation {
+    fn add(&mut self, a: mir::Local, b: mir::Local) {
+        self.relations.entry(a).or_default().push(b);
+    }
+
+    fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
+        let mut seen = HybridBitSet::new_empty(domain_size);
+        let mut stack = vec![a];
+        while let Some(u) = stack.pop() {
+            if let Some(edges) = self.relations.get(&u) {
+                for &v in edges {
+                    if seen.insert(v) {
+                        stack.push(v);
+                    }
+                }
+            }
+        }
+        seen
+    }
+}
index 123d0ad0457d1f1d5c2dff1f13c3f5eee0f6eb28..d07c26d7c8975da63108f5ec5e9af7efd2ab8ebd 100644 (file)
@@ -72,7 +72,7 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<
 
         if_chain! {
             if let Some(self_def) = self_ty.ty_adt_def();
-            if let Some(self_local_did) = self_def.did.as_local();
+            if let Some(self_local_did) = self_def.did().as_local();
             let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
             if let Some(Node::Item(x)) = cx.tcx.hir().find(self_id);
             let type_name = x.ident.name.as_str().to_lowercase();
index 961cdb317e76cd0abd0c26a240567533869f72c5..66b79513032f6e8871e52ed543dafc9eb8585b5e 100644 (file)
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
-use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind};
+use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -76,14 +76,13 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) {
         );
     }
 
-    for single_use in &single_use_usages {
-        if !imports_reused_with_self.contains(&single_use.0) {
-            let can_suggest = single_use.2;
+    for (name, span, can_suggest) in single_use_usages {
+        if !imports_reused_with_self.contains(&name) {
             if can_suggest {
                 span_lint_and_sugg(
                     cx,
                     SINGLE_COMPONENT_PATH_IMPORTS,
-                    single_use.1,
+                    span,
                     "this import is redundant",
                     "remove it entirely",
                     String::new(),
@@ -93,7 +92,7 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) {
                 span_lint_and_help(
                     cx,
                     SINGLE_COMPONENT_PATH_IMPORTS,
-                    single_use.1,
+                    span,
                     "this import is redundant",
                     None,
                     "remove this import",
@@ -124,14 +123,11 @@ fn track_uses(
         ItemKind::Use(use_tree) => {
             let segments = &use_tree.prefix.segments;
 
-            let should_report =
-                |name: &Symbol| !macros.contains(name) || matches!(item.vis.kind, VisibilityKind::Inherited);
-
             // keep track of `use some_module;` usages
             if segments.len() == 1 {
                 if let UseTreeKind::Simple(None, _, _) = use_tree.kind {
                     let name = segments[0].ident.name;
-                    if should_report(&name) {
+                    if !macros.contains(&name) {
                         single_use_usages.push((name, item.span, true));
                     }
                 }
@@ -146,7 +142,7 @@ fn track_uses(
                         if segments.len() == 1 {
                             if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
                                 let name = segments[0].ident.name;
-                                if should_report(&name) {
+                                if !macros.contains(&name) {
                                     single_use_usages.push((name, tree.0.span, false));
                                 }
                             }
index c3d984fb5ae10b3d57314fedc1f4940dacf75e8e..b5dd27ff80de4052ea5960c26d63cb64e6f76610 100644 (file)
@@ -290,7 +290,7 @@ fn ident_swap_sugg(
             // used instead, in these cases.
             *applicability = Applicability::MaybeIncorrect;
 
-            // We arbitraily choose one side to suggest changing,
+            // We arbitrarily choose one side to suggest changing,
             // since we don't have a better guess. If the user
             // ends up duplicating a clause, the `logic_bug` lint
             // should catch it.
@@ -374,19 +374,19 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
 }
 
 fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
-    append_opt_vecs(chained_binops(kind), if_statment_binops(kind))
+    append_opt_vecs(chained_binops(kind), if_statement_binops(kind))
 }
 
-fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
     match kind {
         ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
-        ExprKind::Paren(ref e) => if_statment_binops(&e.kind),
+        ExprKind::Paren(ref e) => if_statement_binops(&e.kind),
         ExprKind::Block(ref block, _) => {
             let mut output = None;
             for stmt in &block.stmts {
                 match stmt.kind {
                     StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
-                        output = append_opt_vecs(output, if_statment_binops(&e.kind));
+                        output = append_opt_vecs(output, if_statement_binops(&e.kind));
                     },
                     _ => {},
                 }
@@ -399,9 +399,9 @@ fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
 
 fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
     match (target_opt, source_opt) {
-        (Some(mut target), Some(mut source)) => {
+        (Some(mut target), Some(source)) => {
             target.reserve(source.len());
-            for op in source.drain(..) {
+            for op in source {
                 target.push(op);
             }
             Some(target)
@@ -436,9 +436,9 @@ fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Exp
             chained_binops_helper(left_left, left_right),
             chained_binops_helper(right_left, right_right),
         ) {
-            (Some(mut left_ops), Some(mut right_ops)) => {
+            (Some(mut left_ops), Some(right_ops)) => {
                 left_ops.reserve(right_ops.len());
-                for op in right_ops.drain(..) {
+                for op in right_ops {
                     left_ops.push(op);
                 }
                 Some(left_ops)
index c9b2ce476e89d0d5cb8408a7b4f0978a3593c5fb..58cc057a39ed94001c8417fc75ff09a48d39f8c3 100644 (file)
@@ -10,7 +10,7 @@
     /// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
     ///
     /// ### Why is this bad?
-    /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
+    /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjunction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
     ///
     /// ### Example
     /// ```rust
index be9d538c36267cf8fd3243dc9283b5724f18f64c..43e0132a7ec7b1a5f48397d9760e367a1e7a7f93 100644 (file)
@@ -46,7 +46,7 @@
     ///
     /// ### Why is this bad?
     /// Duplicate bounds makes the code
-    /// less readable than specifing them only once.
+    /// less readable than specifying them only once.
     ///
     /// ### Example
     /// ```rust
index 22a8c53a5852e805476cfc2603b446fabb5e496c..02569bd3a476e50e3b0ea759ad9930712847f481 100644 (file)
     /// ### Why is this bad?
     /// The results of such a transmute are not defined.
     ///
+    /// ### Known problems
+    /// This lint has had multiple problems in the past and was moved to `nursery`. See issue
+    /// [#8496](https://github.com/rust-lang/rust-clippy/issues/8496) for more details.
+    ///
     /// ### Example
     /// ```rust
     /// struct Foo<T>(u32, T);
     /// ```
     #[clippy::version = "1.60.0"]
     pub TRANSMUTE_UNDEFINED_REPR,
-    correctness,
+    nursery,
     "transmute to or from a type with an undefined representation"
 }
 
@@ -411,7 +415,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
                 // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
                 let const_context = in_constant(cx, e.hir_id);
 
-                let from_ty = cx.typeck_results().expr_ty(arg);
+                let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
+                // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them.
                 let to_ty = cx.typeck_results().expr_ty(e);
 
                 // If useless_transmute is triggered, the other lints can be skipped.
index 05eadab3e6ccdcc500f881b62668bbc1a53a2a08..f5e21267a8976e4aff843095c42782569b9c73cd 100644 (file)
@@ -3,8 +3,8 @@
 use clippy_utils::ty::is_c_void;
 use rustc_hir::Expr;
 use rustc_lint::LateContext;
-use rustc_middle::ty::subst::Subst;
-use rustc_middle::ty::{self, Ty, TypeAndMut};
+use rustc_middle::ty::subst::{Subst, SubstsRef};
+use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
 use rustc_span::Span;
 
 #[allow(clippy::too_many_lines)]
@@ -23,7 +23,8 @@ pub(super) fn check<'tcx>(
                 unsized_ty,
                 to_ty: to_sub_ty,
             } => match reduce_ty(cx, to_sub_ty) {
-                ReducedTy::IntArray | ReducedTy::TypeErasure => break,
+                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;
@@ -48,7 +49,8 @@ pub(super) fn check<'tcx>(
                 unsized_ty,
                 from_ty: from_sub_ty,
             } => match reduce_ty(cx, from_sub_ty) {
-                ReducedTy::IntArray | ReducedTy::TypeErasure => break,
+                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;
@@ -123,9 +125,19 @@ pub(super) fn check<'tcx>(
                 from_ty: from_sub_ty,
                 to_ty: to_sub_ty,
             } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
-                (ReducedTy::IntArray | ReducedTy::TypeErasure, _)
-                | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false,
+                (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))
+                        = (from_ty.kind(), to_ty.kind())
+                        && from_def == to_def
+                    {
+                        if same_except_params(from_subs, to_subs) {
+                            return false;
+                        }
+                        Some(from_def.did())
+                    } else {
+                        None
+                    };
                     span_lint_and_then(
                         cx,
                         TRANSMUTE_UNDEFINED_REPR,
@@ -135,21 +147,17 @@ pub(super) fn check<'tcx>(
                             from_ty_orig, to_ty_orig
                         ),
                         |diag| {
-                            if_chain! {
-                                if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def());
-                                if from_def == to_def;
-                                then {
-                                    diag.note(&format!(
-                                        "two instances of the same generic type (`{}`) may have different layouts",
-                                        cx.tcx.item_name(from_def.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));
-                                    }
+                            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));
                                 }
                             }
                         },
@@ -196,10 +204,13 @@ pub(super) fn check<'tcx>(
                     continue;
                 },
                 (
-                    ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
-                    ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
+                    ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
+                    ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
                 )
-                | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break,
+                | (
+                    ReducedTy::UnorderedFields(_) | ReducedTy::Param,
+                    ReducedTy::UnorderedFields(_) | ReducedTy::Param,
+                ) => break,
             },
         }
     }
@@ -263,9 +274,8 @@ enum ReducedTy<'tcx> {
     UnorderedFields(Ty<'tcx>),
     /// The type is a reference to the contained type.
     Ref(Ty<'tcx>),
-    /// The type is an array of a primitive integer type. These can be used as storage for a value
-    /// of another type.
-    IntArray,
+    /// The type is a generic parameter.
+    Param,
     /// Any other type.
     Other(Ty<'tcx>),
 }
@@ -275,17 +285,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::IntArray,
+            ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure,
             ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
                 ty = sub_ty;
                 continue;
             },
             ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure,
             ty::Tuple(args) => {
-                let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else {
+                let mut iter = args.iter();
+                let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
                     return ReducedTy::OrderedFields(ty);
                 };
-                if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) {
+                if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
                     ty = sized_ty;
                     continue;
                 }
@@ -304,18 +315,21 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx>
                     ty = sized_ty;
                     continue;
                 }
-                if def.repr.inhibit_struct_field_reordering_opt() {
+                if def.repr().inhibit_struct_field_reordering_opt() {
                     ReducedTy::OrderedFields(ty)
                 } else {
                     ReducedTy::UnorderedFields(ty)
                 }
             },
-            ty::Adt(def, _) if def.is_enum() && (def.variants.is_empty() || is_c_void(cx, ty)) => {
+            ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
                 ReducedTy::TypeErasure
             },
+            // 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,
             _ => ReducedTy::Other(ty),
         };
     }
@@ -326,9 +340,33 @@ fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
         if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
         if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
         then {
-            layout.layout.size.bytes() == 0
+            layout.layout.size().bytes() == 0
         } else {
             false
         }
     }
 }
+
+fn is_size_pair(ty: Ty<'_>) -> bool {
+    if let ty::Tuple(tys) = *ty.kind()
+        && let [ty1, ty2] = &**tys
+    {
+        matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+            && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+    } else {
+        false
+    }
+}
+
+fn same_except_params(subs1: SubstsRef<'_>, subs2: SubstsRef<'_>) -> bool {
+    // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
+    // `Array<6>`
+    for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
+        match (ty1.kind(), ty2.kind()) {
+            (ty::Param(_), _) | (_, ty::Param(_)) => (),
+            (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (),
+            _ => return false,
+        }
+    }
+    true
+}
index 2d67401a15f2d65b96cacdbaa0f92e69c6ae7fce..831b0d450d20a7f933a6bd380dc370db37710a9e 100644 (file)
 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
     match (&from_ty.kind(), &to_ty.kind()) {
         (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
-            if from_adt.did != to_adt.did {
+            if from_adt.did() != to_adt.did() {
                 return false;
             }
             if !matches!(
-                cx.tcx.get_diagnostic_name(to_adt.did),
+                cx.tcx.get_diagnostic_name(to_adt.did()),
                 Some(
                     sym::BTreeMap
                         | sym::BTreeSet
index 4da32c52e750a06e47e1c68a840a83df0c429b8d..e108f7be12e6a69c7835db14c506239fdc9e33fa 100644 (file)
@@ -43,7 +43,7 @@
     /// ```
     #[clippy::version = "1.38.0"]
     pub TRY_ERR,
-    style,
+    restriction,
     "return errors explicitly rather than hiding them behind a `?`"
 }
 
@@ -151,11 +151,11 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
 fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     if_chain! {
         if let ty::Adt(def, subst) = ty.kind();
-        if match_def_path(cx, def.did, &paths::POLL);
+        if match_def_path(cx, def.did(), &paths::POLL);
         let ready_ty = subst.type_at(0);
 
         if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
-        if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did);
+        if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did());
         then {
             Some(ready_subst.type_at(1))
         } else {
@@ -168,15 +168,15 @@ fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<
 fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     if_chain! {
         if let ty::Adt(def, subst) = ty.kind();
-        if match_def_path(cx, def.did, &paths::POLL);
+        if match_def_path(cx, def.did(), &paths::POLL);
         let ready_ty = subst.type_at(0);
 
         if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
-        if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did);
+        if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did());
         let some_ty = ready_subst.type_at(0);
 
         if let ty::Adt(some_def, some_subst) = some_ty.kind();
-        if cx.tcx.is_diagnostic_item(sym::Result, some_def.did);
+        if cx.tcx.is_diagnostic_item(sym::Result, some_def.did());
         then {
             Some(some_subst.type_at(1))
         } else {
index 075b5e43cc742ea867ea69b0efbad830dad9b3ae..d371cafb16b13e017d8cdf7b160193753543636c 100644 (file)
@@ -67,59 +67,51 @@ struct SortByKeyDetection {
 
 /// Detect if the two expressions are mirrored (identical, except one
 /// contains a and the other replaces it with b)
-fn mirrored_exprs(
-    cx: &LateContext<'_>,
-    a_expr: &Expr<'_>,
-    a_ident: &Ident,
-    b_expr: &Expr<'_>,
-    b_ident: &Ident,
-) -> bool {
+fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool {
     match (&a_expr.kind, &b_expr.kind) {
         // Two boxes with mirrored contents
         (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => {
-            mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
+            mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
         },
         // Two arrays with mirrored contents
         (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => {
-            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // The two exprs are function calls.
         // Check to see that the function itself and its arguments are mirrored
         (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => {
-            mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
-                && iter::zip(*left_args, *right_args)
-                    .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
+                && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // The two exprs are method calls.
         // Check to see that the function is the same and the arguments are mirrored
         // This is enough because the receiver of the method is listed in the arguments
         (ExprKind::MethodCall(left_segment, left_args, _), ExprKind::MethodCall(right_segment, right_args, _)) => {
             left_segment.ident == right_segment.ident
-                && iter::zip(*left_args, *right_args)
-                    .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+                && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // Two tuples with mirrored contents
         (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => {
-            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // Two binary ops, which are the same operation and which have mirrored arguments
         (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => {
             left_op.node == right_op.node
-                && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident)
-                && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident)
+                && mirrored_exprs(left_left, a_ident, right_left, b_ident)
+                && mirrored_exprs(left_right, a_ident, right_right, b_ident)
         },
         // Two unary ops, which are the same operation and which have the same argument
         (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => {
-            left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
+            left_op == right_op && mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
         },
         // The two exprs are literals of some kind
         (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node,
-        (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(cx, left, a_ident, right, b_ident),
+        (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, a_ident, right, b_ident),
         (ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => {
-            mirrored_exprs(cx, left_block, a_ident, right_block, b_ident)
+            mirrored_exprs(left_block, a_ident, right_block, b_ident)
         },
         (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => {
-            left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident)
+            left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, right_ident)
         },
         // Two paths: either one is a and the other is b, or they're identical to each other
         (
@@ -151,11 +143,9 @@ fn mirrored_exprs(
         (
             ExprKind::AddrOf(left_kind, Mutability::Not, left_expr),
             ExprKind::AddrOf(right_kind, Mutability::Not, right_expr),
-        ) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident),
-        (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => {
-            mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident)
-        },
-        (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident),
+        ) => left_kind == right_kind && mirrored_exprs(left_expr, a_ident, right_expr, b_ident),
+        (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => mirrored_exprs(a_expr, a_ident, right_expr, b_ident),
+        (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(left_expr, a_ident, b_expr, b_ident),
         _ => false,
     }
 }
@@ -176,14 +166,13 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
         if method_path.ident.name == sym::cmp;
         then {
             let (closure_body, closure_arg, reverse) = if mirrored_exprs(
-                cx,
                 left_expr,
                 left_ident,
                 right_expr,
                 right_ident
             ) {
                 (Sugg::hir(cx, left_expr, "..").to_string(), left_ident.name.to_string(), false)
-            } else if mirrored_exprs(cx, left_expr, right_ident, right_expr, left_ident) {
+            } else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) {
                 (Sugg::hir(cx, left_expr, "..").to_string(), right_ident.name.to_string(), true)
             } else {
                 return None;
index 1728533f18b858caeb6b9814f37ff2dcdbbc5e78..f4f5a4336a39ec7ad4ae0800207e429f986f1f4d 100644 (file)
@@ -102,9 +102,9 @@ fn check_fn(
 
         // Get the wrapper and inner types, if can't, abort.
         let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
-            if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did) {
+            if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) {
                 ("Option", OptionSome, subst.type_at(0))
-            } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did) {
+            } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) {
                 ("Result", ResultOk, subst.type_at(0))
             } else {
                 return;
index 2c13f1049b59955a2e81ebb7b42dc1f5317c1fe3..b32be238cd55a178129cd428ba5015059d3c0b60 100644 (file)
@@ -83,9 +83,9 @@ impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
         // check for `expect`
         if let Some(arglists) = method_chain_args(expr, &["expect"]) {
-            let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
-            if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
-                || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+            let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+            if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+                || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
             {
                 self.result.push(expr.span);
             }
@@ -93,9 +93,9 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
 
         // check for `unwrap`
         if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
-            let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
-            if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
-                || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+            let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+            if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+                || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
             {
                 self.result.push(expr.span);
             }
index 7286d0a7bf99b890b2bd0f90aa58e754c6e427ab..02bf09ed5068c152710bc2c32153f2241ab1649b 100644 (file)
@@ -114,7 +114,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
                 check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
             },
             ItemKind::Enum(ref enumdef, _) => {
-                // check enum variants seperately because again we only want to lint on private enums and
+                // check enum variants separately because again we only want to lint on private enums and
                 // the fn check_variant does not know about the vis of the enum of its variants
                 enumdef
                     .variants
index 80164c59ba74c3e570d82fb46985d25f6e47255a..09d671e11184d6e2e456909b3326af74b6ce3c04 100644 (file)
@@ -9,7 +9,8 @@
     def::{CtorOf, DefKind, Res},
     def_id::LocalDefId,
     intravisit::{walk_inf, walk_ty, Visitor},
-    Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Path, QPath, TyKind,
+    Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
+    TyKind,
 };
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_semver::RustcVersion;
@@ -252,6 +253,22 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         }
     }
 
+    fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
+        if_chain! {
+            if !pat.span.from_expansion();
+            if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+            if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+            if let PatKind::Path(QPath::Resolved(_, path)) = pat.kind;
+            if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _));
+            if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id);
+            if let [first, ..] = path.segments;
+            if let Some(hir_id) = first.hir_id;
+            then {
+                span_lint(cx, cx.tcx.hir().span(hir_id));
+            }
+        }
+    }
+
     extract_msrv_attr!(LateContext);
 }
 
index 8691148313702e4657a2137b664289b4b1925cb8..dc48ea3f4f99d77612cfcb3a4e48fab6dabf8f5a 100644 (file)
@@ -373,7 +373,7 @@ fn print_item(cx: &LateContext<'_>, item: &hir::Item<'_>) {
             let item_ty = cx.tcx.type_of(did);
             println!("function of type {:#?}", item_ty);
         },
-        hir::ItemKind::Macro(ref macro_def) => {
+        hir::ItemKind::Macro(ref macro_def, _) => {
             if macro_def.macro_rules {
                 println!("macro introduced by `macro_rules!`");
             } else {
index 4433d5f5bf1463046f27e0feafc039fcc10dd8ea..b3b241392fed5d172d7f2649236feeb0318d352d 100644 (file)
@@ -25,7 +25,7 @@
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::mir::interpret::ConstValue;
-use rustc_middle::ty;
+use rustc_middle::ty::{self, subst::GenericArgKind};
 use rustc_semver::RustcVersion;
 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::Spanned;
     "found clippy lint without `clippy::version` attribute"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
+    ///
+    pub MISSING_MSRV_ATTR_IMPL,
+    internal,
+    "checking if all necessary steps were taken when adding a MSRV to a lint"
+}
+
 declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
 
 impl EarlyLintPass for ClippyLintsInternal {
@@ -1314,3 +1323,46 @@ fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: S
         span.parent(),
     )
 }
+
+declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
+
+impl LateLintPass<'_> for MsrvAttrImpl {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if_chain! {
+            if let hir::ItemKind::Impl(hir::Impl {
+                of_trait: Some(lint_pass_trait_ref),
+                self_ty,
+                items,
+                ..
+            }) = &item.kind;
+            if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
+            let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
+            if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
+            let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
+            if let ty::Adt(self_ty_def, _) = self_ty.kind();
+            if self_ty_def.is_struct();
+            if self_ty_def.all_fields().any(|f| {
+                cx.tcx
+                    .type_of(f.did)
+                    .walk()
+                    .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
+                    .any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
+            });
+            if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
+            then {
+                let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
+                let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
+                let span = cx.sess().source_map().span_through_char(item.span, '{');
+                span_lint_and_sugg(
+                    cx,
+                    MISSING_MSRV_ATTR_IMPL,
+                    span,
+                    &format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
+                    &format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
+                    format!("{}\n    extract_msrv_attr!({context});", snippet(cx, span, "..")),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+    }
+}
index 56633490eaa1a7d51d1c4744eaba273226ccaa09..b3fad6ce7b65137e8a0a6a8e0c0c6fd2210c9c88 100644 (file)
@@ -85,7 +85,7 @@ macro_rules! CONFIGURATION_VALUE_TEMPLATE {
     };
 }
 
-const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
+const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [
     &["clippy_utils", "diagnostics", "span_lint"],
     &["clippy_utils", "diagnostics", "span_lint_and_help"],
     &["clippy_utils", "diagnostics", "span_lint_and_note"],
@@ -93,6 +93,7 @@ macro_rules! CONFIGURATION_VALUE_TEMPLATE {
     &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
     &["clippy_utils", "diagnostics", "span_lint_and_then"],
     &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
+    &["clippy_utils", "diagnostics", "span_lint_and_sugg_for_edges"],
 ];
 const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
     ("span_suggestion", false),
@@ -473,7 +474,7 @@ fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
     /// ```
     fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
         if let Some(args) = match_lint_emission(cx, expr) {
-            let mut emission_info = extract_emission_info(cx, args);
+            let emission_info = extract_emission_info(cx, args);
             if emission_info.is_empty() {
                 // See:
                 // - src/misc.rs:734:9
@@ -483,7 +484,7 @@ fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
                 return;
             }
 
-            for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
+            for (lint_name, applicability, is_multi_part) in emission_info {
                 let app_info = self.applicability_info.entry(lint_name).or_default();
                 app_info.applicability = applicability;
                 app_info.is_multi_part_suggestion = is_multi_part;
@@ -693,7 +694,7 @@ fn extract_emission_info<'hir>(
     }
 
     lints
-        .drain(..)
+        .into_iter()
         .map(|lint_name| (lint_name, applicability, multi_part))
         .collect()
 }
index 1fa6301ebd73dfcc04f1e555fda3b93ab03204ad..f3d818cc3485dd0c617da2a3591418a2b39b032a 100644 (file)
@@ -7,7 +7,7 @@
 use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
 use rustc_ast::token::{self, LitKind};
 use rustc_ast::tokenstream::TokenStream;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, DiagnosticBuilder};
 use rustc_lexer::unescape::{self, EscapeError};
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_parse::parser;
@@ -534,7 +534,7 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
             match parser
                 .parse_expr()
                 .map(rustc_ast::ptr::P::into_inner)
-                .map_err(|mut e| e.cancel())
+                .map_err(DiagnosticBuilder::cancel)
             {
                 // write!(e, ...)
                 Ok(p) if parser.eat(&token::Comma) => Some(p),
@@ -563,7 +563,7 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
             }
 
             let comma_span = parser.prev_token.span;
-            let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
+            let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) {
                 expr
             } else {
                 return (Some(fmtstr), None);
@@ -581,14 +581,19 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
             };
 
             let replacement: String = match lit.token.kind {
-                LitKind::Integer | LitKind::Float | LitKind::Err => continue,
                 LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
                     lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
                 },
                 LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
                     lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
                 },
-                LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
+                LitKind::StrRaw(_)
+                | LitKind::Str
+                | LitKind::ByteStrRaw(_)
+                | LitKind::ByteStr
+                | LitKind::Integer
+                | LitKind::Float
+                | LitKind::Err => continue,
                 LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() {
                     "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
                     "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
index 3f4043ad052a034279ec979ea0e67590dde0a19d..3fce4987679ad595b4479511cccf95a77ba32ffd 100644 (file)
@@ -285,12 +285,14 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
                 generics: lg,
                 bounds: lb,
                 ty: lt,
+                ..
             }),
             TyAlias(box ast::TyAlias {
                 defaultness: rd,
                 generics: rg,
                 bounds: rb,
                 ty: rt,
+                ..
             }),
         ) => {
             eq_defaultness(*ld, *rd)
@@ -388,12 +390,14 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
                 generics: lg,
                 bounds: lb,
                 ty: lt,
+                ..
             }),
             TyAlias(box ast::TyAlias {
                 defaultness: rd,
                 generics: rg,
                 bounds: rb,
                 ty: rt,
+                ..
             }),
         ) => {
             eq_defaultness(*ld, *rd)
@@ -432,12 +436,14 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
                 generics: lg,
                 bounds: lb,
                 ty: lt,
+                ..
             }),
             TyAlias(box ast::TyAlias {
                 defaultness: rd,
                 generics: rg,
                 bounds: rb,
                 ty: rt,
+                ..
             }),
         ) => {
             eq_defaultness(*ld, *rd)
index d40583c47dd7074a8c069c0656652fdcf4652509..1d6f7acab139bf8c7b2f31cb2da8ad0f59564ae7 100644 (file)
@@ -593,7 +593,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
         ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty().kind() {
             ty::Ref(_, tam, _) => match tam.kind() {
                 ty::Str => String::from_utf8(
-                    data.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
+                    data.inner()
+                        .inspect_with_uninit_and_ptr_outside_interpreter(start..end)
                         .to_owned(),
                 )
                 .ok()
@@ -606,6 +607,7 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
             ty::Array(sub_type, len) => match sub_type.kind() {
                 ty::Float(FloatTy::F32) => match miri_to_const(*len) {
                     Some(Constant::Int(len)) => alloc
+                        .inner()
                         .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
                         .to_owned()
                         .chunks(4)
@@ -620,6 +622,7 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
                 },
                 ty::Float(FloatTy::F64) => match miri_to_const(*len) {
                     Some(Constant::Int(len)) => alloc
+                        .inner()
                         .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
                         .to_owned()
                         .chunks(8)
index ca222c3d669956d4b97eb5e4bd9f46ead9aa0d1d..625a53899df94d69e0dbf8db91b9856ea677e69d 100644 (file)
@@ -8,13 +8,13 @@
 //! Thank you!
 //! ~The `INTERNAL_METADATA_COLLECTOR` lint
 
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic};
 use rustc_hir::HirId;
 use rustc_lint::{LateContext, Lint, LintContext};
 use rustc_span::source_map::{MultiSpan, Span};
 use std::env;
 
-fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
+fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
     if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
         if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
             diag.help(&format!(
@@ -145,7 +145,7 @@ pub fn span_lint_and_then<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str
 where
     C: LintContext,
     S: Into<MultiSpan>,
-    F: FnOnce(&mut DiagnosticBuilder<'_>),
+    F: FnOnce(&mut Diagnostic),
 {
     cx.struct_span_lint(lint, sp, |diag| {
         let mut diag = diag.build(msg);
@@ -169,7 +169,7 @@ pub fn span_lint_hir_and_then(
     hir_id: HirId,
     sp: impl Into<MultiSpan>,
     msg: &str,
-    f: impl FnOnce(&mut DiagnosticBuilder<'_>),
+    f: impl FnOnce(&mut Diagnostic),
 ) {
     cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
         let mut diag = diag.build(msg);
@@ -213,13 +213,97 @@ pub fn span_lint_and_sugg<'a, T: LintContext>(
     });
 }
 
+/// Like [`span_lint_and_sugg`] with a focus on the edges. The output will either
+/// emit single span or multispan suggestion depending on the number of its lines.
+///
+/// If the given suggestion string has more lines than the maximum display length defined by
+/// [`MAX_SUGGESTION_HIGHLIGHT_LINES`][`rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES`],
+/// this function will split the suggestion and span to showcase the change for the top and
+/// bottom edge of the code. For normal suggestions, in one display window, the help message
+/// will be combined with a colon.
+///
+/// Multipart suggestions like the one being created here currently cannot be
+/// applied by rustfix (See [rustfix#141](https://github.com/rust-lang/rustfix/issues/141)).
+/// Testing rustfix with this lint emission function might require a file with
+/// suggestions that can be fixed and those that can't. See
+/// [clippy#8520](https://github.com/rust-lang/rust-clippy/pull/8520/files) for
+/// an example and of this.
+///
+/// # Example for a long suggestion
+///
+/// ```text
+/// error: called `map(..).flatten()` on `Option`
+///   --> $DIR/map_flatten.rs:8:10
+///    |
+/// LL |           .map(|x| {
+///    |  __________^
+/// LL | |             if x <= 5 {
+/// LL | |                 Some(x)
+/// LL | |             } else {
+/// ...  |
+/// LL | |         })
+/// LL | |         .flatten();
+///    | |__________________^
+///    |
+///   = note: `-D clippy::map-flatten` implied by `-D warnings`
+/// help: try replacing `map` with `and_then`
+///    |
+/// LL ~         .and_then(|x| {
+/// LL +             if x <= 5 {
+/// LL +                 Some(x)
+///    |
+/// help: and remove the `.flatten()`
+///    |
+/// LL +                 None
+/// LL +             }
+/// LL ~         });
+///    |
+/// ```
+pub fn span_lint_and_sugg_for_edges(
+    cx: &LateContext<'_>,
+    lint: &'static Lint,
+    sp: Span,
+    msg: &str,
+    helps: &[&str; 2],
+    sugg: String,
+    applicability: Applicability,
+) {
+    span_lint_and_then(cx, lint, sp, msg, |diag| {
+        let sugg_lines_count = sugg.lines().count();
+        if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES {
+            let sm = cx.sess().source_map();
+            if let (Ok(line_upper), Ok(line_bottom)) = (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi())) {
+                let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2;
+                let span_upper = sm.span_until_char(sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]), '\n');
+                let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]);
+
+                let sugg_lines_vec = sugg.lines().collect::<Vec<&str>>();
+                let sugg_upper = sugg_lines_vec[..split_idx].join("\n");
+                let sugg_bottom = sugg_lines_vec[sugg_lines_count - split_idx..].join("\n");
+
+                diag.span_suggestion(span_upper, helps[0], sugg_upper, applicability);
+                diag.span_suggestion(span_bottom, helps[1], sugg_bottom, applicability);
+
+                return;
+            }
+        }
+        diag.span_suggestion_with_style(
+            sp,
+            &helps.join(", "),
+            sugg,
+            applicability,
+            rustc_errors::SuggestionStyle::ShowAlways,
+        );
+    });
+}
+
 /// Create a suggestion made from several `span â†’ replacement`.
 ///
 /// Note: in the JSON format (used by `compiletest_rs`), the help message will
 /// appear once per
 /// replacement. In human-readable format though, it only appears once before
 /// the whole suggestion.
-pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
+pub fn multispan_sugg<I>(diag: &mut Diagnostic, help_msg: &str, sugg: I)
 where
     I: IntoIterator<Item = (Span, String)>,
 {
@@ -232,7 +316,7 @@ pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg:
 /// multiple spans. This is tracked in issue [rustfix#141](https://github.com/rust-lang/rustfix/issues/141).
 /// Suggestions with multiple spans will be silently ignored.
 pub fn multispan_sugg_with_applicability<I>(
-    diag: &mut DiagnosticBuilder<'_>,
+    diag: &mut Diagnostic,
     help_msg: &str,
     applicability: Applicability,
     sugg: I,
index eb9efec3f1612a8f7af66888ba3b3534e726f400..a6ef6d79fc023f33a83896292a35dec6db694103 100644 (file)
@@ -73,7 +73,7 @@ fn fn_eagerness<'tcx>(
         // than marker traits.
         // Due to the limited operations on these types functions should be fairly cheap.
         if def
-            .variants
+            .variants()
             .iter()
             .flat_map(|v| v.fields.iter())
             .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
index 9654895060f85bde17984d509fb4fc2f0906adf4..00594f4d42addb594875349a2e6e2e031e24423a 100644 (file)
@@ -74,6 +74,10 @@ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
         self.inter_expr().eq_expr(left, right)
     }
 
+    pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+        self.inter_expr().eq_path(left, right)
+    }
+
     pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
         self.inter_expr().eq_path_segment(left, right)
     }
@@ -362,7 +366,7 @@ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
         }
     }
 
-    fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+    pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
         match (left.res, right.res) {
             (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
             (Res::Local(_), _) | (_, Res::Local(_)) => false,
index 397783e309e85b135e5c1d4c25b25e86e1b34219..cd20abd94ed256811487649df709158ec74f4a01 100644 (file)
@@ -268,7 +268,7 @@ pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str])
 pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
     if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
         if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
-            return cx.tcx.is_diagnostic_item(diag_item, adt.did);
+            return cx.tcx.is_diagnostic_item(diag_item, adt.did());
         }
     }
     false
@@ -489,7 +489,8 @@ fn find_primitive(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
     fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
         tcx.crates(())
             .iter()
-            .find(|&&num| tcx.crate_name(num).as_str() == name)
+            .copied()
+            .find(|&num| tcx.crate_name(num).as_str() == name)
             .map(CrateNum::as_def_id)
     }
 
@@ -656,7 +657,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
                 if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
                     return std_types_symbols
                         .iter()
-                        .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
+                        .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did()));
                 }
             }
         }
index a5b409ad96bbbd3125a50005e77430b93339b7df..fce93153d96ec1841eae99b5ec3b584e22c2e261 100644 (file)
@@ -20,7 +20,7 @@ macro_rules! msrv_aliases {
     1,46,0 { CONST_IF_MATCH }
     1,45,0 { STR_STRIP_PREFIX }
     1,43,0 { LOG2_10, LOG10_2 }
-    1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
+    1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
     1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
     1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
     1,38,0 { POINTER_CAST }
index 2778e30388e263ae9aca921617cf023cb69ef2bf..6f56f8d51365a9407e2a36ce6205ac6a1719c789 100644 (file)
@@ -32,6 +32,8 @@
 pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
 #[cfg(feature = "internal")]
 pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+#[cfg(feature = "internal")]
+pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
 pub const EXIT: [&str; 3] = ["std", "process", "exit"];
 pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
 pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
@@ -67,6 +69,8 @@
 #[cfg(feature = "internal")]
 pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
 #[cfg(feature = "internal")]
+pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
+#[cfg(feature = "internal")]
 pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
 pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
 pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
 pub const RESULT: [&str; 3] = ["core", "result", "Result"];
 pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
 pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+#[cfg(feature = "internal")]
+pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
 pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
 pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
 pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
index c039fec955db9d2641fc86e43ce772da69f04021..891531951c1a03c0f6f5a3947544a04297151fcf 100644 (file)
@@ -32,32 +32,12 @@ pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv:
                 | ty::PredicateKind::Projection(_)
                 | ty::PredicateKind::ConstEvaluatable(..)
                 | ty::PredicateKind::ConstEquate(..)
+                | ty::PredicateKind::Trait(..)
                 | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
                 ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
                 ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
                 ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
                 ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate),
-                ty::PredicateKind::Trait(pred) => {
-                    if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
-                        continue;
-                    }
-                    match pred.self_ty().kind() {
-                        ty::Param(ref p) => {
-                            let generics = tcx.generics_of(current);
-                            let def = generics.type_param(p, tcx);
-                            let span = tcx.def_span(def.def_id);
-                            return Err((
-                                span,
-                                "trait bounds other than `Sized` \
-                                 on const fn parameters are unstable"
-                                    .into(),
-                            ));
-                        },
-                        // other kinds of bounds are either tautologies
-                        // or cause errors in other passes
-                        _ => continue,
-                    }
-                },
             }
         }
         match predicates.parent {
index fa63ddff253cfd7afa686a48c2e4af3d82512079..1fc9979f3dd7de45db66a7dd5eb6fd8a48506841 100644 (file)
@@ -673,8 +673,8 @@ fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
         })
 }
 
-/// Convenience extension trait for `DiagnosticBuilder`.
-pub trait DiagnosticBuilderExt<T: LintContext> {
+/// Convenience extension trait for `Diagnostic`.
+pub trait DiagnosticExt<T: LintContext> {
     /// Suggests to add an attribute to an item.
     ///
     /// Correctly handles indentation of the attribute and item.
@@ -721,7 +721,7 @@ fn suggest_item_with_attr<D: Display + ?Sized>(
     fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
 }
 
-impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
+impl<T: LintContext> DiagnosticExt<T> for rustc_errors::Diagnostic {
     fn suggest_item_with_attr<D: Display + ?Sized>(
         &mut self,
         cx: &T,
@@ -808,7 +808,7 @@ pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'
             closure_arg_is_type_annotated_double_ref,
             next_pos: closure.span.lo(),
             suggestion_start: String::new(),
-            applicability: Applicability::MaybeIncorrect,
+            applicability: Applicability::MachineApplicable,
         };
 
         let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
index 0646d1524a767310ee26d879649a16a441d8ee5e..e3fc76f4e1a26b16754cb1318df3745a8b1989a0 100644 (file)
@@ -51,7 +51,7 @@ pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool {
 
 /// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
 /// constructor.
-pub fn contains_adt_constructor(ty: Ty<'_>, adt: &AdtDef) -> bool {
+pub fn contains_adt_constructor(ty: Ty<'_>, adt: AdtDef<'_>) -> bool {
     ty.walk().any(|inner| match inner.unpack() {
         GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
         GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
@@ -112,7 +112,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
     let def_id = match ty_to_check.kind() {
         ty::Array(..) => return Some(sym::array),
         ty::Slice(..) => return Some(sym::slice),
-        ty::Adt(adt, _) => adt.did,
+        ty::Adt(adt, _) => adt.did(),
         _ => return None,
     };
 
@@ -164,7 +164,7 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
 // Returns whether the type has #[must_use] attribute
 pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did)).is_some(),
+        ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did())).is_some(),
         ty::Foreign(ref did) => must_use_attr(cx.tcx.get_attrs(*did)).is_some(),
         ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
             // for the Array case we don't need to care for the len == 0 case
@@ -220,7 +220,7 @@ fn is_normalizable_helper<'tcx>(
         let cause = rustc_middle::traits::ObligationCause::dummy();
         if infcx.at(&cause, param_env).normalize(ty).is_ok() {
             match ty.kind() {
-                ty::Adt(def, substs) => def.variants.iter().all(|variant| {
+                ty::Adt(def, substs) => def.variants().iter().all(|variant| {
                     variant
                         .fields
                         .iter()
@@ -264,7 +264,7 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
 pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
     match ty.kind() {
         ty::Ref(_, ref_ty, _) => match ref_ty.kind() {
-            ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+            ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
             _ => false,
         },
         _ => false,
@@ -284,7 +284,7 @@ pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_ite
 /// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
 pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+        ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
         _ => false,
     }
 }
@@ -294,7 +294,11 @@ pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symb
 /// Returns `false` if the `LangItem` is not defined.
 pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).map_or(false, |li| li == adt.did),
+        ty::Adt(adt, _) => cx
+            .tcx
+            .lang_items()
+            .require(lang_item)
+            .map_or(false, |li| li == adt.did()),
         _ => false,
     }
 }
@@ -310,7 +314,7 @@ pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
 /// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
 pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
+        ty::Adt(adt, _) => match_def_path(cx, adt.did(), path),
         _ => false,
     }
 }
@@ -398,7 +402,7 @@ pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
     match *ty.kind() {
         ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
         ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
-        ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did),
+        ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()),
         _ => false,
     }
 }
@@ -562,11 +566,11 @@ pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue>
 }
 
 /// Gets the value of the given variant.
-pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) -> EnumValue {
-    let variant = &adt.variants[i];
+pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: AdtDef<'_>, i: VariantIdx) -> EnumValue {
+    let variant = &adt.variant(i);
     match variant.discr {
         VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(),
-        VariantDiscr::Relative(x) => match adt.variants[(i.as_usize() - x as usize).into()].discr {
+        VariantDiscr::Relative(x) => match adt.variant((i.as_usize() - x as usize).into()).discr {
             VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x,
             VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()),
         },
@@ -577,7 +581,7 @@ pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) -
 /// platform specific `libc::<platform>::c_void` types in libc.
 pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
     if let ty::Adt(adt, _) = ty.kind()
-        && let &[krate, .., name] = &*cx.get_def_path(adt.did)
+        && let &[krate, .., name] = &*cx.get_def_path(adt.did())
         && let sym::libc | sym::core | sym::std = krate
         && name.as_str() == "c_void"
     {
index 4d2c57619912f024722c427fb5ca9c4fa000be01..5befb856a023470c0e211f281807f65a6fa17fa3 100644 (file)
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "nightly-2022-02-24"
+channel = "nightly-2022-03-24"
 components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
index 6bc74bc1e9aada60c2755bc51415ee8edb986854..c9710e3db8e8d264e956dc8cb8e2eb04f07911dd 100644 (file)
@@ -34,6 +34,7 @@
     "syn",
     "tokio",
     "parking_lot",
+    "rustc_semver",
 ];
 
 // Test dependencies may need an `extern crate` here to ensure that they show up
@@ -53,6 +54,8 @@
 #[allow(unused_extern_crates)]
 extern crate quote;
 #[allow(unused_extern_crates)]
+extern crate rustc_semver;
+#[allow(unused_extern_crates)]
 extern crate syn;
 #[allow(unused_extern_crates)]
 extern crate tokio;
@@ -165,7 +168,11 @@ fn run_ui() {
     let _threads = VarGuard::set(
         "RUST_TEST_THREADS",
         // if RUST_TEST_THREADS is set, adhere to it, otherwise override it
-        env::var("RUST_TEST_THREADS").unwrap_or_else(|_| num_cpus::get().to_string()),
+        env::var("RUST_TEST_THREADS").unwrap_or_else(|_| {
+            std::thread::available_parallelism()
+                .map_or(1, std::num::NonZeroUsize::get)
+                .to_string()
+        }),
     );
     compiletest::run_tests(&config);
 }
index b4d94dc983fec11fecc2efc20e8dbf006193ae07..dc82ba891fb1f098d2c1b1099c5a0453ec67b311 100644 (file)
@@ -16,7 +16,7 @@ impl Message {
     fn new(path: PathBuf) -> Self {
         let content: String = std::fs::read_to_string(&path).unwrap();
         // we don't want the first letter after "error: ", "help: " ... to be capitalized
-        // also no puncutation (except for "?" ?) at the end of a line
+        // also no punctuation (except for "?" ?) at the end of a line
         let regex_set: RegexSet = RegexSet::new(&[
             r"error: [A-Z]",
             r"help: [A-Z]",
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.fixed b/tests/ui-internal/invalid_msrv_attr_impl.fixed
new file mode 100644 (file)
index 0000000..900a8ff
--- /dev/null
@@ -0,0 +1,40 @@
+// run-rustfix
+
+#![deny(clippy::internal)]
+#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use clippy_utils::extract_msrv_attr;
+use rustc_hir::Expr;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+
+declare_lint! {
+    pub TEST_LINT,
+    Warn,
+    ""
+}
+
+struct Pass {
+    msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(Pass => [TEST_LINT]);
+
+impl LateLintPass<'_> for Pass {
+    extract_msrv_attr!(LateContext);
+    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
+}
+
+impl EarlyLintPass for Pass {
+    extract_msrv_attr!(EarlyContext);
+    fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
+}
+
+fn main() {}
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.rs b/tests/ui-internal/invalid_msrv_attr_impl.rs
new file mode 100644 (file)
index 0000000..4bc8164
--- /dev/null
@@ -0,0 +1,38 @@
+// run-rustfix
+
+#![deny(clippy::internal)]
+#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use clippy_utils::extract_msrv_attr;
+use rustc_hir::Expr;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+
+declare_lint! {
+    pub TEST_LINT,
+    Warn,
+    ""
+}
+
+struct Pass {
+    msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(Pass => [TEST_LINT]);
+
+impl LateLintPass<'_> for Pass {
+    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
+}
+
+impl EarlyLintPass for Pass {
+    fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
+}
+
+fn main() {}
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.stderr b/tests/ui-internal/invalid_msrv_attr_impl.stderr
new file mode 100644 (file)
index 0000000..ddc06f0
--- /dev/null
@@ -0,0 +1,32 @@
+error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
+  --> $DIR/invalid_msrv_attr_impl.rs:30:1
+   |
+LL | impl LateLintPass<'_> for Pass {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/invalid_msrv_attr_impl.rs:3:9
+   |
+LL | #![deny(clippy::internal)]
+   |         ^^^^^^^^^^^^^^^^
+   = note: `#[deny(clippy::missing_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]`
+help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation
+   |
+LL + impl LateLintPass<'_> for Pass {
+LL +     extract_msrv_attr!(LateContext);
+   |
+
+error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
+  --> $DIR/invalid_msrv_attr_impl.rs:34:1
+   |
+LL | impl EarlyLintPass for Pass {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation
+   |
+LL + impl EarlyLintPass for Pass {
+LL +     extract_msrv_attr!(EarlyContext);
+   |
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/allow_attributes_without_reason.rs b/tests/ui/allow_attributes_without_reason.rs
new file mode 100644 (file)
index 0000000..1a0d4e8
--- /dev/null
@@ -0,0 +1,14 @@
+#![feature(lint_reasons)]
+#![deny(clippy::allow_attributes_without_reason)]
+
+// These should trigger the lint
+#[allow(dead_code)]
+#[allow(dead_code, deprecated)]
+// These should be fine
+#[allow(dead_code, reason = "This should be allowed")]
+#[warn(dyn_drop, reason = "Warnings can also have reasons")]
+#[warn(deref_nullptr)]
+#[deny(deref_nullptr)]
+#[forbid(deref_nullptr)]
+
+fn main() {}
diff --git a/tests/ui/allow_attributes_without_reason.stderr b/tests/ui/allow_attributes_without_reason.stderr
new file mode 100644 (file)
index 0000000..cd040a1
--- /dev/null
@@ -0,0 +1,23 @@
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:5:1
+   |
+LL | #[allow(dead_code)]
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/allow_attributes_without_reason.rs:2:9
+   |
+LL | #![deny(clippy::allow_attributes_without_reason)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:6:1
+   |
+LL | #[allow(dead_code, deprecated)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/cast_enum_constructor.rs b/tests/ui/cast_enum_constructor.rs
new file mode 100644 (file)
index 0000000..0193454
--- /dev/null
@@ -0,0 +1,17 @@
+#![warn(clippy::cast_enum_constructor)]
+#![allow(clippy::fn_to_numeric_cast)]
+
+fn main() {
+    enum Foo {
+        Y(u32),
+    }
+
+    enum Bar {
+        X,
+    }
+
+    let _ = Foo::Y as usize;
+    let _ = Foo::Y as isize;
+    let _ = Foo::Y as fn(u32) -> Foo;
+    let _ = Bar::X as usize;
+}
diff --git a/tests/ui/cast_enum_constructor.stderr b/tests/ui/cast_enum_constructor.stderr
new file mode 100644 (file)
index 0000000..710909d
--- /dev/null
@@ -0,0 +1,16 @@
+error: cast of an enum tuple constructor to an integer
+  --> $DIR/cast_enum_constructor.rs:13:13
+   |
+LL |     let _ = Foo::Y as usize;
+   |             ^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::cast-enum-constructor` implied by `-D warnings`
+
+error: cast of an enum tuple constructor to an integer
+  --> $DIR/cast_enum_constructor.rs:14:13
+   |
+LL |     let _ = Foo::Y as isize;
+   |             ^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/cast_slice_different_sizes.rs b/tests/ui/cast_slice_different_sizes.rs
new file mode 100644 (file)
index 0000000..cfe1cca
--- /dev/null
@@ -0,0 +1,39 @@
+fn main() {
+    let x: [i32; 3] = [1_i32, 2, 3];
+    let r_x = &x;
+    // Check casting through multiple bindings
+    // Because it's separate, it does not check the cast back to something of the same size
+    let a = r_x as *const [i32];
+    let b = a as *const [u8];
+    let c = b as *const [u32];
+
+    // loses data
+    let loss = r_x as *const [i32] as *const [u8];
+
+    // Cast back to same size but different type loses no data, just type conversion
+    // This is weird code but there's no reason for this lint specifically to fire *twice* on it
+    let restore = r_x as *const [i32] as *const [u8] as *const [u32];
+
+    // Check casting through blocks is detected
+    let loss_block_1 = { r_x as *const [i32] } as *const [u8];
+    let loss_block_2 = {
+        let _ = ();
+        r_x as *const [i32]
+    } as *const [u8];
+
+    // Check that resores of the same size are detected through blocks
+    let restore_block_1 = { r_x as *const [i32] } as *const [u8] as *const [u32];
+    let restore_block_2 = { ({ r_x as *const [i32] }) as *const [u8] } as *const [u32];
+    let restore_block_3 = {
+        let _ = ();
+        ({
+            let _ = ();
+            r_x as *const [i32]
+        }) as *const [u8]
+    } as *const [u32];
+
+    // Check that the result of a long chain of casts is detected
+    let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
+    let long_chain_restore =
+        r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8] as *const [u32];
+}
diff --git a/tests/ui/cast_slice_different_sizes.stderr b/tests/ui/cast_slice_different_sizes.stderr
new file mode 100644 (file)
index 0000000..a37cec7
--- /dev/null
@@ -0,0 +1,52 @@
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:7:13
+   |
+LL |     let b = a as *const [u8];
+   |             ^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(a as *const u8, ..)`
+   |
+   = note: `#[deny(clippy::cast_slice_different_sizes)]` on by default
+
+error: casting between raw pointers to `[u8]` (element size 1) and `[u32]` (element size 4) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:8:13
+   |
+LL |     let c = b as *const [u32];
+   |             ^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(b as *const u32, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:11:16
+   |
+LL |     let loss = r_x as *const [i32] as *const [u8];
+   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const u8, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:18:24
+   |
+LL |     let loss_block_1 = { r_x as *const [i32] } as *const [u8];
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts({ r_x as *const [i32] } as *const u8, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:19:24
+   |
+LL |       let loss_block_2 = {
+   |  ________________________^
+LL | |         let _ = ();
+LL | |         r_x as *const [i32]
+LL | |     } as *const [u8];
+   | |____________________^
+   |
+help: replace with `ptr::slice_from_raw_parts`
+   |
+LL ~     let loss_block_2 = core::ptr::slice_from_raw_parts({
+LL +         let _ = ();
+LL +         r_x as *const [i32]
+LL ~     } as *const u8, ..);
+   |
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:36:27
+   |
+LL |     let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const u8, ..)`
+
+error: aborting due to 6 previous errors
+
index 7ffbd7a64b341fdb9cf27bddd90b50a49edec5d9..878897c410cf5e5c7014b75199df8ba15c964006 100644 (file)
@@ -1,14 +1,8 @@
 error[E0601]: `main` function not found in crate `ice_6250`
-  --> $DIR/ice-6250.rs:4:1
+  --> $DIR/ice-6250.rs:16:2
    |
-LL | / pub struct Cache {
-LL | |     data: Vec<i32>,
-LL | | }
-LL | |
-...  |
-LL | |     }
-LL | | }
-   | |_^ consider adding a `main` function to `$DIR/ice-6250.rs`
+LL | }
+   |  ^ consider adding a `main` function to `$DIR/ice-6250.rs`
 
 error[E0308]: mismatched types
   --> $DIR/ice-6250.rs:12:14
index 14c71e884b6ea9779cc867b6104d156e6821bf2c..77a3c2ba4ad09ba0286e9dc34a19e1b847e83805 100644 (file)
@@ -1,10 +1,8 @@
 error[E0601]: `main` function not found in crate `ice_6251`
-  --> $DIR/ice-6251.rs:4:1
+  --> $DIR/ice-6251.rs:6:2
    |
-LL | / fn bug<T>() -> impl Iterator<Item = [(); { |x: [u8]| x }]> {
-LL | |     std::iter::empty()
-LL | | }
-   | |_^ consider adding a `main` function to `$DIR/ice-6251.rs`
+LL | }
+   |  ^ consider adding a `main` function to `$DIR/ice-6251.rs`
 
 error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
   --> $DIR/ice-6251.rs:4:45
index be4348e2bb71c96912d90bd4b558c79232eaa0a6..4a5c597dda51fa3304afafe4174630eb62655db2 100644 (file)
 
 #[warn(clippy::main_recursion)]
 #[start]
-fn main(argc: isize, argv: *const *const u8) -> isize {
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
     let x = N.load(Ordering::Relaxed);
     N.store(x + 1, Ordering::Relaxed);
 
     if x < 3 {
-        main(argc, argv);
+        main(_argc, _argv);
     }
 
     0
index 9b03c9b47832f60e1626b6477d404ec8fab87157..baf01174b6764b8663d97593018db1549480e9ea 100644 (file)
@@ -1,3 +1,4 @@
+// compile-flags: --test
 #![warn(clippy::dbg_macro)]
 
 fn foo(n: u32) -> u32 {
@@ -40,3 +41,8 @@ fn foo<'b>(&self) {
         dbg!(2);
     });
 }
+
+#[test]
+pub fn issue8481() {
+    dbg!(1);
+}
index 8ee1b328720d919f2e9629bae91bd8ae5ca8e4bc..a3e7a7162e51dff7516e79451764518e33e6f6bf 100644 (file)
@@ -1,5 +1,5 @@
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:4:22
+  --> $DIR/dbg_macro.rs:5:22
    |
 LL |     if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
    |                      ^^^^^^^^^^^^^^^^^^^^^^
@@ -11,7 +11,7 @@ LL |     if let Some(n) = n.checked_sub(4) { n } else { n }
    |                      ~~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:8:8
+  --> $DIR/dbg_macro.rs:9:8
    |
 LL |     if dbg!(n <= 1) {
    |        ^^^^^^^^^^^^
@@ -22,7 +22,7 @@ LL |     if n <= 1 {
    |        ~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:9:9
+  --> $DIR/dbg_macro.rs:10:9
    |
 LL |         dbg!(1)
    |         ^^^^^^^
@@ -33,7 +33,7 @@ LL |         1
    |
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:11:9
+  --> $DIR/dbg_macro.rs:12:9
    |
 LL |         dbg!(n * factorial(n - 1))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -44,7 +44,7 @@ LL |         n * factorial(n - 1)
    |
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:16:5
+  --> $DIR/dbg_macro.rs:17:5
    |
 LL |     dbg!(42);
    |     ^^^^^^^^
@@ -55,7 +55,7 @@ LL |     42;
    |     ~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:17:5
+  --> $DIR/dbg_macro.rs:18:5
    |
 LL |     dbg!(dbg!(dbg!(42)));
    |     ^^^^^^^^^^^^^^^^^^^^
@@ -66,7 +66,7 @@ LL |     dbg!(dbg!(42));
    |     ~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:18:14
+  --> $DIR/dbg_macro.rs:19:14
    |
 LL |     foo(3) + dbg!(factorial(4));
    |              ^^^^^^^^^^^^^^^^^^
@@ -77,7 +77,7 @@ LL |     foo(3) + factorial(4);
    |              ~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:19:5
+  --> $DIR/dbg_macro.rs:20:5
    |
 LL |     dbg!(1, 2, dbg!(3, 4));
    |     ^^^^^^^^^^^^^^^^^^^^^^
@@ -88,7 +88,7 @@ LL |     (1, 2, dbg!(3, 4));
    |     ~~~~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:20:5
+  --> $DIR/dbg_macro.rs:21:5
    |
 LL |     dbg!(1, 2, 3, 4, 5);
    |     ^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL |     (1, 2, 3, 4, 5);
    |     ~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:40:9
+  --> $DIR/dbg_macro.rs:41:9
    |
 LL |         dbg!(2);
    |         ^^^^^^^
index e863870e7d61b01ae9dfe0992264b7707a2d0507..71ebad24c16ea0b622f9d02a044c4f17ce5fbd21 100644 (file)
@@ -1,11 +1,11 @@
 // run-rustfix
 #![warn(clippy::extend_with_drain)]
+#![allow(clippy::iter_with_drain)]
 use std::collections::BinaryHeap;
 fn main() {
     //gets linted
     let mut vec1 = vec![0u8; 1024];
     let mut vec2: std::vec::Vec<u8> = Vec::new();
-
     vec2.append(&mut vec1);
 
     let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
 
     vec11.append(&mut return_vector());
 
-    //won't get linted it dosen't move the entire content of a vec into another
+    //won't get linted it doesn't move the entire content of a vec into another
     let mut test1 = vec![0u8, 10];
     let mut test2: std::vec::Vec<u8> = Vec::new();
 
index dcb36b5951cb28285a2b973b5aa19b692a009d9c..e9f011abb0e837f301fd44682fb7fccb7c0d02a1 100644 (file)
@@ -1,11 +1,11 @@
 // run-rustfix
 #![warn(clippy::extend_with_drain)]
+#![allow(clippy::iter_with_drain)]
 use std::collections::BinaryHeap;
 fn main() {
     //gets linted
     let mut vec1 = vec![0u8; 1024];
     let mut vec2: std::vec::Vec<u8> = Vec::new();
-
     vec2.extend(vec1.drain(..));
 
     let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
 
     vec11.extend(return_vector().drain(..));
 
-    //won't get linted it dosen't move the entire content of a vec into another
+    //won't get linted it doesn't move the entire content of a vec into another
     let mut test1 = vec![0u8, 10];
     let mut test2: std::vec::Vec<u8> = Vec::new();
 
index 12bbda71f434813ca78c1e499bb3d70e82106490..edc3fe1aec13a556fa0c8c2121a1639553c57952 100644 (file)
@@ -66,4 +66,13 @@ fn main() {
     let b = a << 0; // no error: non-integer
 
     1 * Meter; // no error: non-integer
+
+    2 % 3;
+    -2 % 3;
+    2 % -3 + x;
+    -2 % -3 + x;
+    x + 1 % 3;
+    (x + 1) % 3; // no error
+    4 % 3; // no error
+    4 % -3; // no error
 }
index 0103cf5457e81a8a313dc1b78506497679903647..706f01a3dd6c403b245ca8568cdb624def8f943a 100644 (file)
@@ -78,5 +78,35 @@ error: the operation is ineffective. Consider reducing it to `x`
 LL |     x >> &0;
    |     ^^^^^^^
 
-error: aborting due to 13 previous errors
+error: the operation is ineffective. Consider reducing it to `2`
+  --> $DIR/identity_op.rs:70:5
+   |
+LL |     2 % 3;
+   |     ^^^^^
+
+error: the operation is ineffective. Consider reducing it to `-2`
+  --> $DIR/identity_op.rs:71:5
+   |
+LL |     -2 % 3;
+   |     ^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `2`
+  --> $DIR/identity_op.rs:72:5
+   |
+LL |     2 % -3 + x;
+   |     ^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `-2`
+  --> $DIR/identity_op.rs:73:5
+   |
+LL |     -2 % -3 + x;
+   |     ^^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `1`
+  --> $DIR/identity_op.rs:74:9
+   |
+LL |     x + 1 % 3;
+   |         ^^^^^
+
+error: aborting due to 18 previous errors
 
diff --git a/tests/ui/iter_with_drain.fixed b/tests/ui/iter_with_drain.fixed
new file mode 100644 (file)
index 0000000..aea4dba
--- /dev/null
@@ -0,0 +1,56 @@
+// run-rustfix
+// will emits unused mut warnings after fixing
+#![allow(unused_mut)]
+// will emits needless collect warnings after fixing
+#![allow(clippy::needless_collect)]
+#![warn(clippy::iter_with_drain)]
+use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
+
+fn full() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.into_iter().collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.into_iter().collect();
+    let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn closed() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.into_iter().collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.into_iter().collect();
+    let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn should_not_help() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(1..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
+    let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+
+    let mut b = vec!["aaa".to_string(), "bbb".to_string()];
+    let _: Vec<_> = b.drain(0..a.len()).collect();
+}
+
+#[derive(Default)]
+struct Bomb {
+    fire: Vec<u8>,
+}
+
+fn should_not_help_0(bomb: &mut Bomb) {
+    let _: Vec<u8> = bomb.fire.drain(..).collect();
+}
+
+fn main() {
+    full();
+    closed();
+    should_not_help();
+    should_not_help_0(&mut Bomb::default());
+}
diff --git a/tests/ui/iter_with_drain.rs b/tests/ui/iter_with_drain.rs
new file mode 100644 (file)
index 0000000..271878c
--- /dev/null
@@ -0,0 +1,56 @@
+// run-rustfix
+// will emits unused mut warnings after fixing
+#![allow(unused_mut)]
+// will emits needless collect warnings after fixing
+#![allow(clippy::needless_collect)]
+#![warn(clippy::iter_with_drain)]
+use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
+
+fn full() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..).collect();
+    let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn closed() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(0..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len()).collect();
+    let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn should_not_help() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(1..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
+    let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+
+    let mut b = vec!["aaa".to_string(), "bbb".to_string()];
+    let _: Vec<_> = b.drain(0..a.len()).collect();
+}
+
+#[derive(Default)]
+struct Bomb {
+    fire: Vec<u8>,
+}
+
+fn should_not_help_0(bomb: &mut Bomb) {
+    let _: Vec<u8> = bomb.fire.drain(..).collect();
+}
+
+fn main() {
+    full();
+    closed();
+    should_not_help();
+    should_not_help_0(&mut Bomb::default());
+}
diff --git a/tests/ui/iter_with_drain.stderr b/tests/ui/iter_with_drain.stderr
new file mode 100644 (file)
index 0000000..aa39443
--- /dev/null
@@ -0,0 +1,40 @@
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:11:34
+   |
+LL |     let mut a: BinaryHeap<_> = a.drain(..).collect();
+   |                                  ^^^^^^^^^ help: try this: `into_iter()`
+   |
+   = note: `-D clippy::iter-with-drain` implied by `-D warnings`
+
+error: `drain(..)` used on a `VecDeque`
+  --> $DIR/iter_with_drain.rs:14:27
+   |
+LL |     let mut a: Vec<_> = a.drain(..).collect();
+   |                           ^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:15:34
+   |
+LL |     let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
+   |                                  ^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:21:34
+   |
+LL |     let mut a: BinaryHeap<_> = a.drain(0..).collect();
+   |                                  ^^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `VecDeque`
+  --> $DIR/iter_with_drain.rs:24:27
+   |
+LL |     let mut a: Vec<_> = a.drain(..a.len()).collect();
+   |                           ^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:25:34
+   |
+LL |     let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
+   |                                  ^^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
+
+error: aborting due to 6 previous errors
+
index 306ea50258da00ad0d133616dc85c1a0a4549450..a83c8ba0b6428d8345b26c65c6086e67463d1242 100644 (file)
@@ -15,7 +15,7 @@ extern crate macro_use_helper as mac;
 extern crate proc_macro_derive as mini_mac;
 
 mod a {
-    use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};
+    use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};
     use mac;
     use mini_mac::ClippyMiniMacroTest;
     use mini_mac;
index f8c86c8d9179f8af918b684eb9294c18ea4a7008..9028a636e7f7a5d064e8c8b3417b979cd6a54eba 100644 (file)
@@ -2,7 +2,7 @@ error: `macro_use` attributes are no longer needed in the Rust 2018 edition
   --> $DIR/macro_use_imports.rs:18:5
    |
 LL |     #[macro_use]
-   |     ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};`
+   |     ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};`
    |
    = note: `-D clippy::macro-use-imports` implied by `-D warnings`
 
index 294d79abc0459fe481a9125f07b1fd8faf67ac69..fc6a7abca0ebda5ea92eabdbdf406efbd0e8d59e 100644 (file)
@@ -148,6 +148,7 @@ fn main() {
 
     // #7077
     let s = &String::new();
+    #[allow(clippy::needless_match)]
     let _: Option<&str> = match Some(s) {
         Some(s) => Some(s),
         None => None,
index d11bf5ecb825ae28a1ae76ba6f1594199e37dfc8..16508270f64d9f015fc39d89c4e8f05ea99b0ae7 100644 (file)
@@ -214,6 +214,7 @@ const fn f4() {
 
     // #7077
     let s = &String::new();
+    #[allow(clippy::needless_match)]
     let _: Option<&str> = match Some(s) {
         Some(s) => Some(s),
         None => None,
index ba388a05a2859e7a05dab97158ce872be225266c..c826b082adff1312a0139571d87a85b74f02655b 100644 (file)
@@ -59,7 +59,7 @@ pub fn manual_copy_with_counters(src: &[i32], dst: &mut [i32], dst2: &mut [i32])
     }
 
     // make sure parentheses are added properly to bitwise operators, which have lower precedence than
-    // arithmetric ones
+    // arithmetic ones
     let mut count = 0 << 1;
     for i in 0..1 << 1 {
         dst[count] = src[i + 2];
diff --git a/tests/ui/map_flatten.fixed b/tests/ui/map_flatten.fixed
deleted file mode 100644 (file)
index fec3a95..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-// run-rustfix
-
-#![warn(clippy::all, clippy::pedantic)]
-#![allow(clippy::let_underscore_drop)]
-#![allow(clippy::missing_docs_in_private_items)]
-#![allow(clippy::map_identity)]
-#![allow(clippy::redundant_closure)]
-#![allow(clippy::unnecessary_wraps)]
-#![feature(result_flattening)]
-
-fn main() {
-    // mapping to Option on Iterator
-    fn option_id(x: i8) -> Option<i8> {
-        Some(x)
-    }
-    let option_id_ref: fn(i8) -> Option<i8> = option_id;
-    let option_id_closure = |x| Some(x);
-    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
-
-    // mapping to Iterator on Iterator
-    let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
-
-    // mapping to Option on Option
-    let _: Option<_> = (Some(Some(1))).and_then(|x| x);
-
-    // mapping to Result on Result
-    let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
-}
index aa1f76e335af0dad3a395921b3e25e6fbf82b6a4..7d47ee09dc1acfdac5e5932713fff966412fab88 100644 (file)
@@ -1,31 +1,55 @@
-// run-rustfix
-
-#![warn(clippy::all, clippy::pedantic)]
-#![allow(clippy::let_underscore_drop)]
-#![allow(clippy::missing_docs_in_private_items)]
-#![allow(clippy::map_identity)]
-#![allow(clippy::redundant_closure)]
-#![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::map_flatten)]
 #![feature(result_flattening)]
 
-fn main() {
-    // mapping to Option on Iterator
-    fn option_id(x: i8) -> Option<i8> {
-        Some(x)
-    }
-    let option_id_ref: fn(i8) -> Option<i8> = option_id;
-    let option_id_closure = |x| Some(x);
-    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
-    let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+// issue #8506, multi-line
+#[rustfmt::skip]
+fn long_span() {
+    let _: Option<i32> = Some(1)
+        .map(|x| {
+            if x <= 5 {
+                Some(x)
+            } else {
+                None
+            }
+        })
+        .flatten();
 
-    // mapping to Iterator on Iterator
-    let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+    let _: Result<i32, i32> = Ok(1)
+        .map(|x| {
+            if x == 1 {
+                Ok(x)
+            } else {
+                Err(0)
+            }
+        })
+        .flatten();
 
-    // mapping to Option on Option
-    let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+    let result: Result<i32, i32> = Ok(2);
+    fn do_something() { }
+    let _: Result<i32, i32> = result
+        .map(|res| {
+            if res > 0 {
+                do_something();
+                Ok(res)
+            } else {
+                Err(0)
+            }
+        })
+        .flatten();
+        
+    let _: Vec<_> = vec![5_i8; 6]
+        .into_iter()
+        .map(|some_value| {
+            if some_value > 3 {
+                Some(some_value)
+            } else {
+                None
+            }
+        })
+        .flatten()
+        .collect();
+}
 
-    // mapping to Result on Result
-    let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+fn main() {
+    long_span();
 }
index bcd2047e6faa3d3683b11f396e6a818855c94d21..c9c60df838f67bf07bd70cc38fb0f3b238a7d44e 100644 (file)
-error: called `map(..).flatten()` on an `Iterator`
-  --> $DIR/map_flatten.rs:18:46
+error: called `map(..).flatten()` on `Option`
+  --> $DIR/map_flatten.rs:8:10
    |
-LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)`
+LL |           .map(|x| {
+   |  __________^
+LL | |             if x <= 5 {
+LL | |                 Some(x)
+LL | |             } else {
+...  |
+LL | |         })
+LL | |         .flatten();
+   | |__________________^
    |
    = note: `-D clippy::map-flatten` implied by `-D warnings`
-
-error: called `map(..).flatten()` on an `Iterator`
-  --> $DIR/map_flatten.rs:19:46
+help: try replacing `map` with `and_then`
    |
-LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)`
-
-error: called `map(..).flatten()` on an `Iterator`
-  --> $DIR/map_flatten.rs:20:46
+LL ~         .and_then(|x| {
+LL +             if x <= 5 {
+LL +                 Some(x)
    |
-LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)`
-
-error: called `map(..).flatten()` on an `Iterator`
-  --> $DIR/map_flatten.rs:21:46
+help: and remove the `.flatten()`
+   |
+LL +                 None
+LL +             }
+LL ~         });
    |
-LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))`
 
-error: called `map(..).flatten()` on an `Iterator`
-  --> $DIR/map_flatten.rs:24:46
+error: called `map(..).flatten()` on `Result`
+  --> $DIR/map_flatten.rs:18:10
+   |
+LL |           .map(|x| {
+   |  __________^
+LL | |             if x == 1 {
+LL | |                 Ok(x)
+LL | |             } else {
+...  |
+LL | |         })
+LL | |         .flatten();
+   | |__________________^
+   |
+help: try replacing `map` with `and_then`
+   |
+LL ~         .and_then(|x| {
+LL +             if x == 1 {
+LL +                 Ok(x)
+   |
+help: and remove the `.flatten()`
+   |
+LL +                 Err(0)
+LL +             }
+LL ~         });
    |
-LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)`
 
-error: called `map(..).flatten()` on an `Option`
-  --> $DIR/map_flatten.rs:27:39
+error: called `map(..).flatten()` on `Result`
+  --> $DIR/map_flatten.rs:30:10
+   |
+LL |           .map(|res| {
+   |  __________^
+LL | |             if res > 0 {
+LL | |                 do_something();
+LL | |                 Ok(res)
+...  |
+LL | |         })
+LL | |         .flatten();
+   | |__________________^
+   |
+help: try replacing `map` with `and_then`
+   |
+LL ~         .and_then(|res| {
+LL +             if res > 0 {
+LL +                 do_something();
+   |
+help: and remove the `.flatten()`
+   |
+LL +                 Err(0)
+LL +             }
+LL ~         });
    |
-LL |     let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
-   |                                       ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
 
-error: called `map(..).flatten()` on an `Result`
-  --> $DIR/map_flatten.rs:30:41
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten.rs:42:10
+   |
+LL |           .map(|some_value| {
+   |  __________^
+LL | |             if some_value > 3 {
+LL | |                 Some(some_value)
+LL | |             } else {
+...  |
+LL | |         })
+LL | |         .flatten()
+   | |__________________^
+   |
+help: try replacing `map` with `filter_map`
+   |
+LL ~         .filter_map(|some_value| {
+LL +             if some_value > 3 {
+LL +                 Some(some_value)
+   |
+help: and remove the `.flatten()`
+   |
+LL +                 None
+LL +             }
+LL +         })
    |
-LL |     let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
-   |                                         ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
 
-error: aborting due to 7 previous errors
+error: aborting due to 4 previous errors
 
diff --git a/tests/ui/map_flatten_fixable.fixed b/tests/ui/map_flatten_fixable.fixed
new file mode 100644 (file)
index 0000000..fec3a95
--- /dev/null
@@ -0,0 +1,31 @@
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::let_underscore_drop)]
+#![allow(clippy::missing_docs_in_private_items)]
+#![allow(clippy::map_identity)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::unnecessary_wraps)]
+#![feature(result_flattening)]
+
+fn main() {
+    // mapping to Option on Iterator
+    fn option_id(x: i8) -> Option<i8> {
+        Some(x)
+    }
+    let option_id_ref: fn(i8) -> Option<i8> = option_id;
+    let option_id_closure = |x| Some(x);
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
+
+    // mapping to Iterator on Iterator
+    let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+
+    // mapping to Option on Option
+    let _: Option<_> = (Some(Some(1))).and_then(|x| x);
+
+    // mapping to Result on Result
+    let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
+}
diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs
new file mode 100644 (file)
index 0000000..aa1f76e
--- /dev/null
@@ -0,0 +1,31 @@
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::let_underscore_drop)]
+#![allow(clippy::missing_docs_in_private_items)]
+#![allow(clippy::map_identity)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::unnecessary_wraps)]
+#![feature(result_flattening)]
+
+fn main() {
+    // mapping to Option on Iterator
+    fn option_id(x: i8) -> Option<i8> {
+        Some(x)
+    }
+    let option_id_ref: fn(i8) -> Option<i8> = option_id;
+    let option_id_closure = |x| Some(x);
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+
+    // mapping to Iterator on Iterator
+    let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+
+    // mapping to Option on Option
+    let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+
+    // mapping to Result on Result
+    let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+}
diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr
new file mode 100644 (file)
index 0000000..c91c738
--- /dev/null
@@ -0,0 +1,80 @@
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten_fixable.rs:18:47
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::map-flatten` implied by `-D warnings`
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
+   |                                               ~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten_fixable.rs:19:47
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
+   |                                               ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten_fixable.rs:20:47
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
+   |                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten_fixable.rs:21:47
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
+   |                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+  --> $DIR/map_flatten_fixable.rs:24:47
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+   |                                               ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `flat_map`, and remove the `.flatten()`
+   |
+LL |     let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+   |                                               ~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Option`
+  --> $DIR/map_flatten_fixable.rs:27:40
+   |
+LL |     let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+   |                                        ^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `and_then`, and remove the `.flatten()`
+   |
+LL |     let _: Option<_> = (Some(Some(1))).and_then(|x| x);
+   |                                        ~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Result`
+  --> $DIR/map_flatten_fixable.rs:30:42
+   |
+LL |     let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+   |                                          ^^^^^^^^^^^^^^^^^^^^
+   |
+help: try replacing `map` with `and_then`, and remove the `.flatten()`
+   |
+LL |     let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
+   |                                          ~~~~~~~~~~~~~~~
+
+error: aborting due to 7 previous errors
+
index 7752a8a6ff2b4094a3089d3295d77b4d2a44f597..b6d04263b37a3e7e7327425fe8d2d802b8f913fc 100644 (file)
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:13:14
+error: this match arm has an identical body to the `_` wildcard arm
+  --> $DIR/match_same_arms.rs:11:9
    |
-LL |         _ => 0, //~ ERROR match arms have same body
-   |              ^
+LL |         Abc::A => 0,
+   |         ^^^^^^^^^^^ help: try removing the arm
    |
    = note: `-D clippy::match-same-arms` implied by `-D warnings`
-note: same as this
-  --> $DIR/match_same_arms.rs:11:19
+   = help: or try changing either arm body
+note: `_` wildcard arm here
+  --> $DIR/match_same_arms.rs:13:9
    |
-LL |         Abc::A => 0,
-   |                   ^
-note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it
-  --> $DIR/match_same_arms.rs:11:19
-   |
-LL |         Abc::A => 0,
-   |                   ^
+LL |         _ => 0, //~ ERROR match arms have same body
+   |         ^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:18:20
-   |
-LL |         (.., 3) => 42, //~ ERROR match arms have same body
-   |                    ^^
-   |
-note: same as this
-  --> $DIR/match_same_arms.rs:17:23
-   |
-LL |         (1, .., 3) => 42,
-   |                       ^^
-help: consider refactoring into `(1, .., 3) | (.., 3)`
+error: this match arm has an identical body to another arm
   --> $DIR/match_same_arms.rs:17:9
    |
 LL |         (1, .., 3) => 42,
-   |         ^^^^^^^^^^
-   = help: ...or consider changing the match arm bodies
+   |         ----------^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `(1, .., 3) | (.., 3)`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms.rs:18:9
+   |
+LL |         (.., 3) => 42, //~ ERROR match arms have same body
+   |         ^^^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:24:15
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms.rs:24:9
    |
 LL |         51 => 1, //~ ERROR match arms have same body
-   |               ^
+   |         --^^^^^
+   |         |
+   |         help: try merging the arm patterns: `51 | 42`
    |
-note: same as this
-  --> $DIR/match_same_arms.rs:23:15
-   |
-LL |         42 => 1,
-   |               ^
-help: consider refactoring into `42 | 51`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms.rs:23:9
    |
 LL |         42 => 1,
-   |         ^^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:26:15
-   |
-LL |         52 => 2, //~ ERROR match arms have same body
-   |               ^
-   |
-note: same as this
-  --> $DIR/match_same_arms.rs:25:15
-   |
-LL |         41 => 2,
-   |               ^
-help: consider refactoring into `41 | 52`
+error: this match arm has an identical body to another arm
   --> $DIR/match_same_arms.rs:25:9
    |
 LL |         41 => 2,
-   |         ^^
-   = help: ...or consider changing the match arm bodies
+   |         --^^^^^
+   |         |
+   |         help: try merging the arm patterns: `41 | 52`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms.rs:26:9
+   |
+LL |         52 => 2, //~ ERROR match arms have same body
+   |         ^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:32:14
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms.rs:32:9
    |
 LL |         2 => 2, //~ ERROR 2nd matched arms have same body
-   |              ^
-   |
-note: same as this
-  --> $DIR/match_same_arms.rs:31:14
+   |         -^^^^^
+   |         |
+   |         help: try merging the arm patterns: `2 | 1`
    |
-LL |         1 => 2,
-   |              ^
-help: consider refactoring into `1 | 2`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms.rs:31:9
    |
 LL |         1 => 2,
-   |         ^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:33:14
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms.rs:33:9
    |
 LL |         3 => 2, //~ ERROR 3rd matched arms have same body
-   |              ^
-   |
-note: same as this
-  --> $DIR/match_same_arms.rs:31:14
+   |         -^^^^^
+   |         |
+   |         help: try merging the arm patterns: `3 | 1`
    |
-LL |         1 => 2,
-   |              ^
-help: consider refactoring into `1 | 3`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms.rs:31:9
    |
 LL |         1 => 2,
-   |         ^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms.rs:50:55
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms.rs:32:9
    |
-LL |                 CommandInfo::External { name, .. } => name.to_string(),
-   |                                                       ^^^^^^^^^^^^^^^^
+LL |         2 => 2, //~ ERROR 2nd matched arms have same body
+   |         -^^^^^
+   |         |
+   |         help: try merging the arm patterns: `2 | 3`
    |
-note: same as this
-  --> $DIR/match_same_arms.rs:49:54
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms.rs:33:9
    |
-LL |                 CommandInfo::BuiltIn { name, .. } => name.to_string(),
-   |                                                      ^^^^^^^^^^^^^^^^
-help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }`
+LL |         3 => 2, //~ ERROR 3rd matched arms have same body
+   |         ^^^^^^
+
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms.rs:50:17
+   |
+LL |                 CommandInfo::External { name, .. } => name.to_string(),
+   |                 ----------------------------------^^^^^^^^^^^^^^^^^^^^
+   |                 |
+   |                 help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }`
+   |
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms.rs:49:17
    |
 LL |                 CommandInfo::BuiltIn { name, .. } => name.to_string(),
-   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   = help: ...or consider changing the match arm bodies
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 7 previous errors
+error: aborting due to 8 previous errors
 
index 67e1d518483c2cf6b61ca152b368506619cc9008..dbfeb4379d513f7d910ef64346eadf197571ad11 100644 (file)
@@ -174,4 +174,57 @@ fn main() {
         Some(2) => 2,
         _ => 1,
     };
+
+    enum Foo {
+        X(u32),
+        Y(u32),
+        Z(u32),
+    }
+
+    // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between.
+    let _ = match Foo::X(0) {
+        Foo::X(0) => 1,
+        Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2,
+        Foo::Z(_) => 1,
+        _ => 0,
+    };
+
+    // Suggest moving `Foo::Z(_)` up.
+    let _ = match Foo::X(0) {
+        Foo::X(0) => 1,
+        Foo::X(_) | Foo::Y(_) => 2,
+        Foo::Z(_) => 1,
+        _ => 0,
+    };
+
+    // Suggest moving `Foo::X(0)` down.
+    let _ = match Foo::X(0) {
+        Foo::X(0) => 1,
+        Foo::Y(_) | Foo::Z(0) => 2,
+        Foo::Z(_) => 1,
+        _ => 0,
+    };
+
+    // Don't lint.
+    let _ = match 0 {
+        -2 => 1,
+        -5..=50 => 2,
+        -150..=88 => 1,
+        _ => 3,
+    };
+
+    struct Bar {
+        x: u32,
+        y: u32,
+        z: u32,
+    }
+
+    // Lint.
+    let _ = match None {
+        Some(Bar { x: 0, y: 5, .. }) => 1,
+        Some(Bar { y: 10, z: 0, .. }) => 2,
+        None => 50,
+        Some(Bar { y: 0, x: 5, .. }) => 1,
+        _ => 200,
+    };
 }
index e1ed12f9370877b8b0d59411ce345529cf64b84e..14a672ba2fec1d0a5098cb1720566f8639632b1b 100644 (file)
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:20:14
+error: this match arm has an identical body to the `_` wildcard arm
+  --> $DIR/match_same_arms2.rs:11:9
    |
-LL |           _ => {
-   |  ______________^
-LL | |             //~ ERROR match arms have same body
+LL | /         42 => {
 LL | |             foo();
 LL | |             let mut a = 42 + [23].len() as i32;
+LL | |             if true {
 ...  |
 LL | |             a
 LL | |         },
-   | |_________^
+   | |_________^ help: try removing the arm
    |
    = note: `-D clippy::match-same-arms` implied by `-D warnings`
-note: same as this
-  --> $DIR/match_same_arms2.rs:11:15
+   = help: or try changing either arm body
+note: `_` wildcard arm here
+  --> $DIR/match_same_arms2.rs:20:9
    |
-LL |           42 => {
-   |  _______________^
-LL | |             foo();
-LL | |             let mut a = 42 + [23].len() as i32;
-LL | |             if true {
-...  |
-LL | |             a
-LL | |         },
-   | |_________^
-note: `42` has the same arm body as the `_` wildcard, consider removing it
-  --> $DIR/match_same_arms2.rs:11:15
-   |
-LL |           42 => {
-   |  _______________^
+LL | /         _ => {
+LL | |             //~ ERROR match arms have same body
 LL | |             foo();
 LL | |             let mut a = 42 + [23].len() as i32;
-LL | |             if true {
 ...  |
 LL | |             a
 LL | |         },
    | |_________^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:34:15
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:34:9
    |
 LL |         51 => foo(), //~ ERROR match arms have same body
-   |               ^^^^^
+   |         --^^^^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `51 | 42`
    |
-note: same as this
-  --> $DIR/match_same_arms2.rs:33:15
-   |
-LL |         42 => foo(),
-   |               ^^^^^
-help: consider refactoring into `42 | 51`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms2.rs:33:9
    |
 LL |         42 => foo(),
-   |         ^^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:40:17
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:40:9
    |
 LL |         None => 24, //~ ERROR match arms have same body
-   |                 ^^
+   |         ----^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `None | Some(_)`
    |
-note: same as this
-  --> $DIR/match_same_arms2.rs:39:20
-   |
-LL |         Some(_) => 24,
-   |                    ^^
-help: consider refactoring into `Some(_) | None`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms2.rs:39:9
    |
 LL |         Some(_) => 24,
-   |         ^^^^^^^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:62:28
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:62:9
    |
 LL |         (None, Some(a)) => bar(a), //~ ERROR match arms have same body
-   |                            ^^^^^^
-   |
-note: same as this
-  --> $DIR/match_same_arms2.rs:61:28
+   |         ---------------^^^^^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
    |
-LL |         (Some(a), None) => bar(a),
-   |                            ^^^^^^
-help: consider refactoring into `(Some(a), None) | (None, Some(a))`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms2.rs:61:9
    |
 LL |         (Some(a), None) => bar(a),
-   |         ^^^^^^^^^^^^^^^
-   = help: ...or consider changing the match arm bodies
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:68:26
-   |
-LL |         (.., Some(a)) => bar(a), //~ ERROR match arms have same body
-   |                          ^^^^^^
-   |
-note: same as this
-  --> $DIR/match_same_arms2.rs:67:26
-   |
-LL |         (Some(a), ..) => bar(a),
-   |                          ^^^^^^
-help: consider refactoring into `(Some(a), ..) | (.., Some(a))`
+error: this match arm has an identical body to another arm
   --> $DIR/match_same_arms2.rs:67:9
    |
 LL |         (Some(a), ..) => bar(a),
-   |         ^^^^^^^^^^^^^
-   = help: ...or consider changing the match arm bodies
-
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:102:29
-   |
-LL |         (Ok(_), Some(x)) => println!("ok {}", x),
-   |                             ^^^^^^^^^^^^^^^^^^^^
+   |         -------------^^^^^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))`
    |
-note: same as this
-  --> $DIR/match_same_arms2.rs:101:29
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:68:9
    |
-LL |         (Ok(x), Some(_)) => println!("ok {}", x),
-   |                             ^^^^^^^^^^^^^^^^^^^^
-help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))`
+LL |         (.., Some(a)) => bar(a), //~ ERROR match arms have same body
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
   --> $DIR/match_same_arms2.rs:101:9
    |
 LL |         (Ok(x), Some(_)) => println!("ok {}", x),
-   |         ^^^^^^^^^^^^^^^^
-   = help: ...or consider changing the match arm bodies
-   = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
+   |         ----------------^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:102:9
+   |
+LL |         (Ok(_), Some(x)) => println!("ok {}", x),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:117:18
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:117:9
    |
 LL |         Ok(_) => println!("ok"),
-   |                  ^^^^^^^^^^^^^^
-   |
-note: same as this
-  --> $DIR/match_same_arms2.rs:116:18
+   |         -----^^^^^^^^^^^^^^^^^^
+   |         |
+   |         help: try merging the arm patterns: `Ok(_) | Ok(3)`
    |
-LL |         Ok(3) => println!("ok"),
-   |                  ^^^^^^^^^^^^^^
-help: consider refactoring into `Ok(3) | Ok(_)`
+   = help: or try changing either arm body
+note: other arm here
   --> $DIR/match_same_arms2.rs:116:9
    |
 LL |         Ok(3) => println!("ok"),
-   |         ^^^^^
-   = help: ...or consider changing the match arm bodies
-   = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
 
-error: this `match` has identical arm bodies
-  --> $DIR/match_same_arms2.rs:144:14
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:144:9
    |
 LL |           1 => {
-   |  ______________^
+   |           ^ help: try merging the arm patterns: `1 | 0`
+   |  _________|
+   | |
 LL | |             empty!(0);
 LL | |         },
    | |_________^
    |
-note: same as this
-  --> $DIR/match_same_arms2.rs:141:14
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:141:9
    |
-LL |           0 => {
-   |  ______________^
+LL | /         0 => {
 LL | |             empty!(0);
 LL | |         },
    | |_________^
-help: consider refactoring into `0 | 1`
-  --> $DIR/match_same_arms2.rs:141:9
-   |
-LL |         0 => {
-   |         ^
-   = help: ...or consider changing the match arm bodies
 
 error: match expression looks like `matches!` macro
   --> $DIR/match_same_arms2.rs:162:16
@@ -184,5 +147,50 @@ LL | |     };
    |
    = note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
 
-error: aborting due to 9 previous errors
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:194:9
+   |
+LL |         Foo::X(0) => 1,
+   |         ---------^^^^^
+   |         |
+   |         help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:196:9
+   |
+LL |         Foo::Z(_) => 1,
+   |         ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:204:9
+   |
+LL |         Foo::Z(_) => 1,
+   |         ---------^^^^^
+   |         |
+   |         help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:202:9
+   |
+LL |         Foo::X(0) => 1,
+   |         ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
+  --> $DIR/match_same_arms2.rs:227:9
+   |
+LL |         Some(Bar { y: 0, x: 5, .. }) => 1,
+   |         ----------------------------^^^^^
+   |         |
+   |         help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })`
+   |
+   = help: or try changing either arm body
+note: other arm here
+  --> $DIR/match_same_arms2.rs:224:9
+   |
+LL |         Some(Bar { x: 0, y: 5, .. }) => 1,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 12 previous errors
 
index baa7eec05462ebad8baea0762ed76b51ae7dfa2d..88f6935d224aec502122cc5ebf5e06d8b70a0a7f 100644 (file)
@@ -49,8 +49,6 @@ fn sub(x: u32) -> usize {
     unsafe { transmute(&x) }
 }
 
-// NOTE: This is currently not yet allowed to be const
-// Once implemented, Clippy should be able to suggest this as const, too.
 fn generic_arr<T: Copy>(t: [T; 1]) -> T {
     t[0]
 }
index b89cc6451bb592c5f3f6fbbab1daa194953d5101..3eb52b6827475d1b52f189cb0f876c68e6dc8191 100644 (file)
@@ -58,7 +58,15 @@ LL | | }
    | |_^
 
 error: this could be a `const fn`
-  --> $DIR/could_be_const.rs:67:9
+  --> $DIR/could_be_const.rs:52:1
+   |
+LL | / fn generic_arr<T: Copy>(t: [T; 1]) -> T {
+LL | |     t[0]
+LL | | }
+   | |_^
+
+error: this could be a `const fn`
+  --> $DIR/could_be_const.rs:65:9
    |
 LL | /         pub fn b(self, a: &A) -> B {
 LL | |             B
@@ -66,12 +74,12 @@ LL | |         }
    | |_________^
 
 error: this could be a `const fn`
-  --> $DIR/could_be_const.rs:77:5
+  --> $DIR/could_be_const.rs:75:5
    |
 LL | /     fn const_fn_stabilized_before_msrv(byte: u8) {
 LL | |         byte.is_ascii_digit();
 LL | |     }
    | |_____^
 
-error: aborting due to 9 previous errors
+error: aborting due to 10 previous errors
 
diff --git a/tests/ui/missing_spin_loop.fixed b/tests/ui/missing_spin_loop.fixed
new file mode 100644 (file)
index 0000000..aa89e04
--- /dev/null
@@ -0,0 +1,28 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![allow(clippy::bool_comparison)]
+#![allow(unused_braces)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+fn main() {
+    let b = AtomicBool::new(true);
+    // Those should lint
+    while b.load(Ordering::Acquire) { std::hint::spin_loop() }
+
+    while !b.load(Ordering::SeqCst) { std::hint::spin_loop() }
+
+    while b.load(Ordering::Acquire) == false { std::hint::spin_loop() }
+
+    while { true == b.load(Ordering::Acquire) } { std::hint::spin_loop() }
+
+    while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) { std::hint::spin_loop() }
+
+    while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { std::hint::spin_loop() }
+
+    // This is OK, as the body is not empty
+    while b.load(Ordering::Acquire) {
+        std::hint::spin_loop()
+    }
+    // TODO: also match on loop+match or while let
+}
diff --git a/tests/ui/missing_spin_loop.rs b/tests/ui/missing_spin_loop.rs
new file mode 100644 (file)
index 0000000..88745e4
--- /dev/null
@@ -0,0 +1,28 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![allow(clippy::bool_comparison)]
+#![allow(unused_braces)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+fn main() {
+    let b = AtomicBool::new(true);
+    // Those should lint
+    while b.load(Ordering::Acquire) {}
+
+    while !b.load(Ordering::SeqCst) {}
+
+    while b.load(Ordering::Acquire) == false {}
+
+    while { true == b.load(Ordering::Acquire) } {}
+
+    while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
+
+    while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
+
+    // This is OK, as the body is not empty
+    while b.load(Ordering::Acquire) {
+        std::hint::spin_loop()
+    }
+    // TODO: also match on loop+match or while let
+}
diff --git a/tests/ui/missing_spin_loop.stderr b/tests/ui/missing_spin_loop.stderr
new file mode 100644 (file)
index 0000000..485da00
--- /dev/null
@@ -0,0 +1,40 @@
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:11:37
+   |
+LL |     while b.load(Ordering::Acquire) {}
+   |                                     ^^ help: try this: `{ std::hint::spin_loop() }`
+   |
+   = note: `-D clippy::missing-spin-loop` implied by `-D warnings`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:13:37
+   |
+LL |     while !b.load(Ordering::SeqCst) {}
+   |                                     ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:15:46
+   |
+LL |     while b.load(Ordering::Acquire) == false {}
+   |                                              ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:17:49
+   |
+LL |     while { true == b.load(Ordering::Acquire) } {}
+   |                                                 ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:19:93
+   |
+LL |     while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
+   |                                                                                             ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:21:94
+   |
+LL |     while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
+   |                                                                                              ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: aborting due to 6 previous errors
+
diff --git a/tests/ui/missing_spin_loop_no_std.fixed b/tests/ui/missing_spin_loop_no_std.fixed
new file mode 100644 (file)
index 0000000..bb4b479
--- /dev/null
@@ -0,0 +1,23 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![feature(lang_items, start, libc)]
+#![no_std]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+#[start]
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    // This should trigger the lint
+    let b = AtomicBool::new(true);
+    // This should lint with `core::hint::spin_loop()`
+    while b.load(Ordering::Acquire) { core::hint::spin_loop() }
+    0
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+extern "C" fn eh_personality() {}
diff --git a/tests/ui/missing_spin_loop_no_std.rs b/tests/ui/missing_spin_loop_no_std.rs
new file mode 100644 (file)
index 0000000..a19bc72
--- /dev/null
@@ -0,0 +1,23 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![feature(lang_items, start, libc)]
+#![no_std]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+#[start]
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    // This should trigger the lint
+    let b = AtomicBool::new(true);
+    // This should lint with `core::hint::spin_loop()`
+    while b.load(Ordering::Acquire) {}
+    0
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+extern "C" fn eh_personality() {}
diff --git a/tests/ui/missing_spin_loop_no_std.stderr b/tests/ui/missing_spin_loop_no_std.stderr
new file mode 100644 (file)
index 0000000..2b3b687
--- /dev/null
@@ -0,0 +1,10 @@
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop_no_std.rs:13:37
+   |
+LL |     while b.load(Ordering::Acquire) {}
+   |                                     ^^ help: try this: `{ core::hint::spin_loop() }`
+   |
+   = note: `-D clippy::missing-spin-loop` implied by `-D warnings`
+
+error: aborting due to previous error
+
index 047a29fa1e327201d12847ff71452f51cea4ec9d..3ebe46bc5be7c5b8b4108f4477b70663a5734b38 100644 (file)
@@ -1,5 +1,10 @@
 #![warn(clippy::modulo_arithmetic)]
-#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::modulo_one)]
+#![allow(
+    clippy::no_effect,
+    clippy::unnecessary_operation,
+    clippy::modulo_one,
+    clippy::identity_op
+)]
 
 fn main() {
     // Lint when both sides are const and of the opposite sign
index 64335f35f0f82d997a2b4e1e14f52fa9af68df28..11b5f77461ba2e93b94779543f77b8770ffbf440 100644 (file)
@@ -1,5 +1,5 @@
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:6:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:11:5
    |
 LL |     -1 % 2;
    |     ^^^^^^
@@ -9,7 +9,7 @@ LL |     -1 % 2;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:7:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:12:5
    |
 LL |     1 % -2;
    |     ^^^^^^
@@ -18,7 +18,7 @@ LL |     1 % -2;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 3`
-  --> $DIR/modulo_arithmetic_integral_const.rs:8:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:13:5
    |
 LL |     (1 - 2) % (1 + 2);
    |     ^^^^^^^^^^^^^^^^^
@@ -27,7 +27,7 @@ LL |     (1 - 2) % (1 + 2);
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `3 % -1`
-  --> $DIR/modulo_arithmetic_integral_const.rs:9:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:14:5
    |
 LL |     (1 + 2) % (1 - 2);
    |     ^^^^^^^^^^^^^^^^^
@@ -36,7 +36,7 @@ LL |     (1 + 2) % (1 - 2);
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-35 % 300000`
-  --> $DIR/modulo_arithmetic_integral_const.rs:10:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:15:5
    |
 LL |     35 * (7 - 4 * 2) % (-500 * -600);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -45,7 +45,7 @@ LL |     35 * (7 - 4 * 2) % (-500 * -600);
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:12:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:17:5
    |
 LL |     -1i8 % 2i8;
    |     ^^^^^^^^^^
@@ -54,7 +54,7 @@ LL |     -1i8 % 2i8;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:13:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:18:5
    |
 LL |     1i8 % -2i8;
    |     ^^^^^^^^^^
@@ -63,7 +63,7 @@ LL |     1i8 % -2i8;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:14:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:19:5
    |
 LL |     -1i16 % 2i16;
    |     ^^^^^^^^^^^^
@@ -72,7 +72,7 @@ LL |     -1i16 % 2i16;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:15:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:20:5
    |
 LL |     1i16 % -2i16;
    |     ^^^^^^^^^^^^
@@ -81,7 +81,7 @@ LL |     1i16 % -2i16;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:16:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:21:5
    |
 LL |     -1i32 % 2i32;
    |     ^^^^^^^^^^^^
@@ -90,7 +90,7 @@ LL |     -1i32 % 2i32;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:17:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:22:5
    |
 LL |     1i32 % -2i32;
    |     ^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL |     1i32 % -2i32;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:18:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:23:5
    |
 LL |     -1i64 % 2i64;
    |     ^^^^^^^^^^^^
@@ -108,7 +108,7 @@ LL |     -1i64 % 2i64;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:19:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:24:5
    |
 LL |     1i64 % -2i64;
    |     ^^^^^^^^^^^^
@@ -117,7 +117,7 @@ LL |     1i64 % -2i64;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:20:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:25:5
    |
 LL |     -1i128 % 2i128;
    |     ^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL |     -1i128 % 2i128;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:21:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:26:5
    |
 LL |     1i128 % -2i128;
    |     ^^^^^^^^^^^^^^
@@ -135,7 +135,7 @@ LL |     1i128 % -2i128;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `-1 % 2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:22:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:27:5
    |
 LL |     -1isize % 2isize;
    |     ^^^^^^^^^^^^^^^^
@@ -144,7 +144,7 @@ LL |     -1isize % 2isize;
    = note: or consider using `rem_euclid` or similar function
 
 error: you are using modulo operator on constants with different signs: `1 % -2`
-  --> $DIR/modulo_arithmetic_integral_const.rs:23:5
+  --> $DIR/modulo_arithmetic_integral_const.rs:28:5
    |
 LL |     1isize % -2isize;
    |     ^^^^^^^^^^^^^^^^
index 34f762bbb923648996cb56af20e84dba18b4fb47..03f460897fce2f5467b88f89b9efa5ffcda0bcd1 100644 (file)
@@ -1,18 +1,18 @@
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
   --> $DIR/modulo_one.rs:11:5
    |
 LL |     i32::MIN % (-1); // also caught by rustc
    |     ^^^^^^^^^^^^^^^ attempt to compute the remainder of `i32::MIN % -1_i32`, which would overflow
    |
-   = note: `#[deny(arithmetic_overflow)]` on by default
+   = note: `#[deny(unconditional_panic)]` on by default
 
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
   --> $DIR/modulo_one.rs:21:5
    |
 LL |     INT_MIN % NEG_ONE; // also caught by rustc
    |     ^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow
 
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
   --> $DIR/modulo_one.rs:22:5
    |
 LL |     INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc
diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed
new file mode 100644 (file)
index 0000000..ece18ad
--- /dev/null
@@ -0,0 +1,83 @@
+// run-rustfix
+#![warn(clippy::needless_match)]
+#![allow(clippy::manual_map)]
+#![allow(dead_code)]
+
+#[derive(Clone, Copy)]
+enum Choice {
+    A,
+    B,
+    C,
+    D,
+}
+
+#[allow(unused_mut)]
+fn useless_match() {
+    let mut i = 10;
+    let _: i32 = i;
+    let _: i32 = i;
+    let mut _i_mut = i;
+
+    let s = "test";
+    let _: &str = s;
+}
+
+fn custom_type_match(se: Choice) {
+    let _: Choice = se;
+    // Don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        _ => Choice::C,
+    };
+    // Mingled, don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::B,
+        Choice::B => Choice::C,
+        Choice::C => Choice::D,
+        Choice::D => Choice::A,
+    };
+}
+
+fn option_match(x: Option<i32>) {
+    let _: Option<i32> = x;
+    // Don't trigger, this is the case for manual_map_option
+    let _: Option<i32> = match x {
+        Some(a) => Some(-a),
+        None => None,
+    };
+}
+
+fn func_ret_err<T>(err: T) -> Result<i32, T> {
+    Err(err)
+}
+
+fn result_match() {
+    let _: Result<i32, i32> = Ok(1);
+    let _: Result<i32, i32> = func_ret_err(0_i32);
+}
+
+fn if_let_option() -> Option<i32> {
+    Some(1)
+}
+
+fn if_let_result(x: Result<(), i32>) {
+    let _: Result<(), i32> = x;
+    let _: Result<(), i32> = x;
+    // Input type mismatch, don't trigger
+    let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
+}
+
+fn if_let_custom_enum(x: Choice) {
+    let _: Choice = x;
+    // Don't trigger
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if true {
+        Choice::B
+    } else {
+        x
+    };
+}
+
+fn main() {}
diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs
new file mode 100644 (file)
index 0000000..36649de
--- /dev/null
@@ -0,0 +1,122 @@
+// run-rustfix
+#![warn(clippy::needless_match)]
+#![allow(clippy::manual_map)]
+#![allow(dead_code)]
+
+#[derive(Clone, Copy)]
+enum Choice {
+    A,
+    B,
+    C,
+    D,
+}
+
+#[allow(unused_mut)]
+fn useless_match() {
+    let mut i = 10;
+    let _: i32 = match i {
+        0 => 0,
+        1 => 1,
+        2 => 2,
+        _ => i,
+    };
+    let _: i32 = match i {
+        0 => 0,
+        1 => 1,
+        ref i => *i,
+    };
+    let mut _i_mut = match i {
+        0 => 0,
+        1 => 1,
+        ref mut i => *i,
+    };
+
+    let s = "test";
+    let _: &str = match s {
+        "a" => "a",
+        "b" => "b",
+        s => s,
+    };
+}
+
+fn custom_type_match(se: Choice) {
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        Choice::C => Choice::C,
+        Choice::D => Choice::D,
+    };
+    // Don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        _ => Choice::C,
+    };
+    // Mingled, don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::B,
+        Choice::B => Choice::C,
+        Choice::C => Choice::D,
+        Choice::D => Choice::A,
+    };
+}
+
+fn option_match(x: Option<i32>) {
+    let _: Option<i32> = match x {
+        Some(a) => Some(a),
+        None => None,
+    };
+    // Don't trigger, this is the case for manual_map_option
+    let _: Option<i32> = match x {
+        Some(a) => Some(-a),
+        None => None,
+    };
+}
+
+fn func_ret_err<T>(err: T) -> Result<i32, T> {
+    Err(err)
+}
+
+fn result_match() {
+    let _: Result<i32, i32> = match Ok(1) {
+        Ok(a) => Ok(a),
+        Err(err) => Err(err),
+    };
+    let _: Result<i32, i32> = match func_ret_err(0_i32) {
+        Err(err) => Err(err),
+        Ok(a) => Ok(a),
+    };
+}
+
+fn if_let_option() -> Option<i32> {
+    if let Some(a) = Some(1) { Some(a) } else { None }
+}
+
+fn if_let_result(x: Result<(), i32>) {
+    let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
+    let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
+    // Input type mismatch, don't trigger
+    let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
+}
+
+fn if_let_custom_enum(x: Choice) {
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if let Choice::B = x {
+        Choice::B
+    } else if let Choice::C = x {
+        Choice::C
+    } else {
+        x
+    };
+    // Don't trigger
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if true {
+        Choice::B
+    } else {
+        x
+    };
+}
+
+fn main() {}
diff --git a/tests/ui/needless_match.stderr b/tests/ui/needless_match.stderr
new file mode 100644 (file)
index 0000000..ad15254
--- /dev/null
@@ -0,0 +1,122 @@
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:17:18
+   |
+LL |       let _: i32 = match i {
+   |  __________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         2 => 2,
+LL | |         _ => i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+   |
+   = note: `-D clippy::needless-match` implied by `-D warnings`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:23:18
+   |
+LL |       let _: i32 = match i {
+   |  __________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         ref i => *i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:28:22
+   |
+LL |       let mut _i_mut = match i {
+   |  ______________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         ref mut i => *i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:35:19
+   |
+LL |       let _: &str = match s {
+   |  ___________________^
+LL | |         "a" => "a",
+LL | |         "b" => "b",
+LL | |         s => s,
+LL | |     };
+   | |_____^ help: replace it with: `s`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:43:21
+   |
+LL |       let _: Choice = match se {
+   |  _____________________^
+LL | |         Choice::A => Choice::A,
+LL | |         Choice::B => Choice::B,
+LL | |         Choice::C => Choice::C,
+LL | |         Choice::D => Choice::D,
+LL | |     };
+   | |_____^ help: replace it with: `se`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:65:26
+   |
+LL |       let _: Option<i32> = match x {
+   |  __________________________^
+LL | |         Some(a) => Some(a),
+LL | |         None => None,
+LL | |     };
+   | |_____^ help: replace it with: `x`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:81:31
+   |
+LL |       let _: Result<i32, i32> = match Ok(1) {
+   |  _______________________________^
+LL | |         Ok(a) => Ok(a),
+LL | |         Err(err) => Err(err),
+LL | |     };
+   | |_____^ help: replace it with: `Ok(1)`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:85:31
+   |
+LL |       let _: Result<i32, i32> = match func_ret_err(0_i32) {
+   |  _______________________________^
+LL | |         Err(err) => Err(err),
+LL | |         Ok(a) => Ok(a),
+LL | |     };
+   | |_____^ help: replace it with: `func_ret_err(0_i32)`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:92:5
+   |
+LL |     if let Some(a) = Some(1) { Some(a) } else { None }
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:96:30
+   |
+LL |     let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
+   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:97:30
+   |
+LL |     let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
+   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:103:21
+   |
+LL |       let _: Choice = if let Choice::A = x {
+   |  _____________________^
+LL | |         Choice::A
+LL | |     } else if let Choice::B = x {
+LL | |         Choice::B
+...  |
+LL | |         x
+LL | |     };
+   | |_____^ help: replace it with: `x`
+
+error: aborting due to 12 previous errors
+
diff --git a/tests/ui/only_used_in_recursion.rs b/tests/ui/only_used_in_recursion.rs
new file mode 100644 (file)
index 0000000..5768434
--- /dev/null
@@ -0,0 +1,122 @@
+#![warn(clippy::only_used_in_recursion)]
+
+fn simple(a: usize, b: usize) -> usize {
+    if a == 0 { 1 } else { simple(a - 1, b) }
+}
+
+fn with_calc(a: usize, b: isize) -> usize {
+    if a == 0 { 1 } else { with_calc(a - 1, -b + 1) }
+}
+
+fn tuple((a, b): (usize, usize)) -> usize {
+    if a == 0 { 1 } else { tuple((a - 1, b + 1)) }
+}
+
+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 array([a, b]: [usize; 2]) -> usize {
+    if a == 0 { 1 } else { array([a - 1, b + 1]) }
+}
+
+fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+    if a == 0 { 1 } else { index(a - 1, b, c + b[0]) }
+}
+
+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) }
+}
+
+// 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 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) }
+}
+
+struct A;
+
+impl A {
+    fn method(a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { A::method(a - 1, b - 1) }
+    }
+
+    fn method2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.method2(a - 1, b + 1) }
+    }
+}
+
+trait B {
+    fn hello(a: usize, b: usize) -> usize;
+
+    fn hello2(&self, a: usize, b: 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 hello2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
+    }
+}
+
+trait C {
+    fn hello(a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { Self::hello(a - 1, b + 1) }
+    }
+
+    fn hello2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
+    }
+}
+
+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 main() {}
diff --git a/tests/ui/only_used_in_recursion.stderr b/tests/ui/only_used_in_recursion.stderr
new file mode 100644 (file)
index 0000000..6fe9361
--- /dev/null
@@ -0,0 +1,82 @@
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:3:21
+   |
+LL | fn simple(a: usize, b: usize) -> usize {
+   |                     ^ help: if this is intentional, prefix with an underscore: `_b`
+   |
+   = note: `-D clippy::only-used-in-recursion` implied by `-D warnings`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:7:24
+   |
+LL | fn with_calc(a: usize, b: isize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:11:14
+   |
+LL | fn tuple((a, b): (usize, usize)) -> usize {
+   |              ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:15:24
+   |
+LL | fn let_tuple(a: usize, b: usize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:20:14
+   |
+LL | fn array([a, b]: [usize; 2]) -> usize {
+   |              ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:24:20
+   |
+LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+   |                    ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:24:37
+   |
+LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+   |                                     ^ help: if this is intentional, prefix with an underscore: `_c`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:28:21
+   |
+LL | fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
+   |                     ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:46:23
+   |
+LL | fn mut_ref2(a: usize, b: &mut usize) -> usize {
+   |                       ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:51:28
+   |
+LL | fn not_primitive(a: usize, b: String) -> usize {
+   |                            ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:68:33
+   |
+LL |     fn method2(&self, a: usize, b: usize) -> usize {
+   |                                 ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:90:24
+   |
+LL |     fn hello(a: usize, b: usize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:94:32
+   |
+LL |     fn hello2(&self, a: usize, b: usize) -> usize {
+   |                                ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: aborting due to 13 previous errors
+
diff --git a/tests/ui/or_then_unwrap.fixed b/tests/ui/or_then_unwrap.fixed
new file mode 100644 (file)
index 0000000..27d4b79
--- /dev/null
@@ -0,0 +1,52 @@
+// run-rustfix
+
+#![warn(clippy::or_then_unwrap)]
+#![allow(clippy::map_identity)]
+
+struct SomeStruct {}
+impl SomeStruct {
+    fn or(self, _: Option<Self>) -> Self {
+        self
+    }
+    fn unwrap(&self) {}
+}
+
+struct SomeOtherStruct {}
+impl SomeOtherStruct {
+    fn or(self) -> Self {
+        self
+    }
+    fn unwrap(&self) {}
+}
+
+fn main() {
+    let option: Option<&str> = None;
+    let _ = option.unwrap_or("fallback"); // should trigger lint
+
+    let result: Result<&str, &str> = Err("Error");
+    let _ = result.unwrap_or("fallback"); // should trigger lint
+
+    // as part of a method chain
+    let option: Option<&str> = None;
+    let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint
+
+    // Not Option/Result
+    let instance = SomeStruct {};
+    let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
+
+    // or takes no argument
+    let instance = SomeOtherStruct {};
+    let _ = instance.or().unwrap(); // should not trigger lint and should not panic
+
+    // None in or
+    let option: Option<&str> = None;
+    let _ = option.or(None).unwrap(); // should not trigger lint
+
+    // Not Err in or
+    let result: Result<&str, &str> = Err("Error");
+    let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
+
+    // other function between
+    let option: Option<&str> = None;
+    let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
+}
diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs
new file mode 100644 (file)
index 0000000..0dab5ae
--- /dev/null
@@ -0,0 +1,52 @@
+// run-rustfix
+
+#![warn(clippy::or_then_unwrap)]
+#![allow(clippy::map_identity)]
+
+struct SomeStruct {}
+impl SomeStruct {
+    fn or(self, _: Option<Self>) -> Self {
+        self
+    }
+    fn unwrap(&self) {}
+}
+
+struct SomeOtherStruct {}
+impl SomeOtherStruct {
+    fn or(self) -> Self {
+        self
+    }
+    fn unwrap(&self) {}
+}
+
+fn main() {
+    let option: Option<&str> = None;
+    let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
+
+    let result: Result<&str, &str> = Err("Error");
+    let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
+
+    // as part of a method chain
+    let option: Option<&str> = None;
+    let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
+
+    // Not Option/Result
+    let instance = SomeStruct {};
+    let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
+
+    // or takes no argument
+    let instance = SomeOtherStruct {};
+    let _ = instance.or().unwrap(); // should not trigger lint and should not panic
+
+    // None in or
+    let option: Option<&str> = None;
+    let _ = option.or(None).unwrap(); // should not trigger lint
+
+    // Not Err in or
+    let result: Result<&str, &str> = Err("Error");
+    let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
+
+    // other function between
+    let option: Option<&str> = None;
+    let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
+}
diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr
new file mode 100644 (file)
index 0000000..da88154
--- /dev/null
@@ -0,0 +1,22 @@
+error: found `.or(Some(…)).unwrap()`
+  --> $DIR/or_then_unwrap.rs:24:20
+   |
+LL |     let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+   |
+   = note: `-D clippy::or-then-unwrap` implied by `-D warnings`
+
+error: found `.or(Ok(…)).unwrap()`
+  --> $DIR/or_then_unwrap.rs:27:20
+   |
+LL |     let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+
+error: found `.or(Some(…)).unwrap()`
+  --> $DIR/or_then_unwrap.rs:31:31
+   |
+LL |     let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
+   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+
+error: aborting due to 3 previous errors
+
index 97990fedd51f3e851b065f396233205e3bb75da9..03dd938a2339e57cda8eca5088c8b06a3c5d03ab 100644 (file)
@@ -194,3 +194,10 @@ fn two_vecs(a: &mut Vec<u32>, b: &mut Vec<u32>) {
     a.push(0);
     b.push(1);
 }
+
+// Issue #8495
+fn cow_conditional_to_mut(a: &mut Cow<str>) {
+    if a.is_empty() {
+        a.to_mut().push_str("foo");
+    }
+}
index 19b253b0fe2c6b94c1c43c9cfd4e736b438967b5..40d7791df281a67f7700871529ed5d0aa96e8b16 100644 (file)
@@ -1,7 +1,7 @@
 // run-rustfix
 
 #![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
 fn f() -> usize {
     42
 }
index 7d034117547caf87990d1c824b99b872471eefe8..a8ddd9b5f751b36ccb2fb065bd291ee0b2956328 100644 (file)
@@ -1,7 +1,7 @@
 // run-rustfix
 
 #![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
 fn f() -> usize {
     42
 }
diff --git a/tests/ui/single_component_path_imports_macro.fixed b/tests/ui/single_component_path_imports_macro.fixed
deleted file mode 100644 (file)
index e43f5d3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// run-rustfix
-#![warn(clippy::single_component_path_imports)]
-#![allow(unused_imports)]
-
-// #7106: use statements exporting a macro within a crate should not trigger lint
-
-macro_rules! m1 {
-    () => {};
-}
-pub(crate) use m1; // ok
-
-macro_rules! m2 {
-    () => {};
-}
- // fail
-
-fn main() {
-    m1!();
-    m2!();
-}
index 3c65ca3054c6919eb859317608a43151ea71eb5c..fda294a61546b5c6fa2befd83ef8281b92bba9ea 100644 (file)
@@ -1,8 +1,8 @@
-// run-rustfix
 #![warn(clippy::single_component_path_imports)]
 #![allow(unused_imports)]
 
 // #7106: use statements exporting a macro within a crate should not trigger lint
+// #7923: normal `use` statements of macros should also not trigger the lint
 
 macro_rules! m1 {
     () => {};
@@ -12,7 +12,7 @@ macro_rules! m1 {
 macro_rules! m2 {
     () => {};
 }
-use m2; // fail
+use m2; // ok
 
 fn main() {
     m1!();
diff --git a/tests/ui/single_component_path_imports_macro.stderr b/tests/ui/single_component_path_imports_macro.stderr
deleted file mode 100644 (file)
index 37d5176..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-error: this import is redundant
-  --> $DIR/single_component_path_imports_macro.rs:15:1
-   |
-LL | use m2; // fail
-   | ^^^^^^^ help: remove it entirely
-   |
-   = note: `-D clippy::single-component-path-imports` implied by `-D warnings`
-
-error: aborting due to previous error
-
index b163d6056343d5b34e2e57f520c083a629db24ed..b06ed4a917376f63775d02dd60f3d4afc4d2ac75 100644 (file)
@@ -1,8 +1,9 @@
 #![warn(clippy::transmute_undefined_repr)]
 #![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)]
 
+use core::any::TypeId;
 use core::ffi::c_void;
-use core::mem::{size_of, transmute};
+use core::mem::{size_of, transmute, MaybeUninit};
 
 fn value<T>() -> T {
     unimplemented!()
@@ -87,5 +88,57 @@ struct Erase2(
 
         let _: *const [u8] = transmute(value::<Box<[u8]>>()); // Ok
         let _: Box<[u8]> = transmute(value::<*mut [u8]>()); // Ok
+
+        let _: Ty2<u32, u32> = transmute(value::<(Ty2<u32, u32>,)>()); // Ok
+        let _: (Ty2<u32, u32>,) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+        let _: Ty2<u32, u32> = transmute(value::<(Ty2<u32, u32>, ())>()); // Ok
+        let _: (Ty2<u32, u32>, ()) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+        let _: Ty2<u32, u32> = transmute(value::<((), Ty2<u32, u32>)>()); // Ok
+        let _: ((), Ty2<u32, u32>) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+        let _: (usize, usize) = transmute(value::<&[u8]>()); // Ok
+        let _: &[u8] = transmute(value::<(usize, usize)>()); // Ok
+
+        trait Trait {}
+        let _: (isize, isize) = transmute(value::<&dyn Trait>()); // Ok
+        let _: &dyn Trait = transmute(value::<(isize, isize)>()); // Ok
+
+        let _: MaybeUninit<Ty2<u32, u32>> = transmute(value::<Ty2<u32, u32>>()); // Ok
+        let _: Ty2<u32, u32> = transmute(value::<MaybeUninit<Ty2<u32, u32>>>()); // Ok
+
+        let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec<u32>>()); // Ok
+    }
+}
+
+fn _with_generics<T: 'static, U: 'static>() {
+    if TypeId::of::<T>() != TypeId::of::<u32>() || TypeId::of::<T>() != TypeId::of::<U>() {
+        return;
+    }
+    unsafe {
+        let _: &u32 = transmute(value::<&T>()); // Ok
+        let _: &T = transmute(value::<&u32>()); // Ok
+
+        let _: Vec<U> = transmute(value::<Vec<T>>()); // Ok
+        let _: Vec<T> = transmute(value::<Vec<U>>()); // Ok
+
+        let _: Ty<&u32> = transmute(value::<&T>()); // Ok
+        let _: Ty<&T> = transmute(value::<&u32>()); // Ok
+
+        let _: Vec<u32> = transmute(value::<Vec<T>>()); // Ok
+        let _: Vec<T> = transmute(value::<Vec<u32>>()); // Ok
+
+        let _: &Ty2<u32, u32> = transmute(value::<&Ty2<T, U>>()); // Ok
+        let _: &Ty2<T, U> = transmute(value::<&Ty2<u32, u32>>()); // Ok
+
+        let _: Vec<Vec<u32>> = transmute(value::<Vec<Vec<T>>>()); // Ok
+        let _: Vec<Vec<T>> = transmute(value::<Vec<Vec<u32>>>()); // Ok
+
+        let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); // Err
+        let _: Vec<Ty2<U, i32>> = transmute(value::<Vec<Ty2<T, u32>>>()); // Err
+
+        let _: *const u32 = transmute(value::<Box<T>>()); // Ok
+        let _: Box<T> = transmute(value::<*const u32>()); // Ok
     }
 }
index 42d544fc954c5b31034bb0dc2f0ace0f16ee1444..28bfba6c7571dc249ad3c97ae5c31982a4e07f9a 100644 (file)
@@ -1,5 +1,5 @@
 error: transmute from `Ty2<u32, i32>` which has an undefined layout
-  --> $DIR/transmute_undefined_repr.rs:26:33
+  --> $DIR/transmute_undefined_repr.rs:27: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:27:32
+  --> $DIR/transmute_undefined_repr.rs:28: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:32:32
+  --> $DIR/transmute_undefined_repr.rs:33: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:33:36
+  --> $DIR/transmute_undefined_repr.rs:34: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:38:33
+  --> $DIR/transmute_undefined_repr.rs:39: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:39:37
+  --> $DIR/transmute_undefined_repr.rs:40: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:56:45
+  --> $DIR/transmute_undefined_repr.rs:57:45
    |
 LL |         let _: &'static mut Ty2<u32, f32> = transmute(value::<Box<Ty2<u32, u32>>>()); // Lint, different Ty2 instances
    |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -53,12 +53,28 @@ 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:57:37
+  --> $DIR/transmute_undefined_repr.rs:58: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: aborting due to 8 previous errors
+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
+   |
+LL |         let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); // Err
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = 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
+   |
+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
 
index 9ae0ed0b13f1e779a8ee08e82d3a9b4d1505a065..b425cdd6cbfd441d45b3cacce9a3f2c60daa5b7e 100644 (file)
@@ -25,8 +25,8 @@ fn main() {
     let slice_ptr = &[0, 1, 2, 3] as *const [i32];
 
     // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
-    let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u16] };
-    let _ptr_to_unsized = slice_ptr as *const [u16];
+    let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] };
+    let _ptr_to_unsized = slice_ptr as *const [u32];
     // TODO: We could try testing vtable casts here too, but maybe
     // we should wait until std::raw::TraitObject is stabilized?
 
index 985cf9a075d312899d88409f78cb93b40a81529e..8fd57c5965279d96766ee8748195cca562277399 100644 (file)
@@ -25,8 +25,8 @@ fn main() {
     let slice_ptr = &[0, 1, 2, 3] as *const [i32];
 
     // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
-    let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
-    let _ptr_to_unsized = slice_ptr as *const [u16];
+    let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
+    let _ptr_to_unsized = slice_ptr as *const [u32];
     // TODO: We could try testing vtable casts here too, but maybe
     // we should wait until std::raw::TraitObject is stabilized?
 
index e8496a325d6dc670178d789d13e73e60bb302848..de9418c8d1adc4af6ab0e578ef3697b2a95a1c72 100644 (file)
@@ -17,8 +17,8 @@ LL |     let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr
 error: transmute from a pointer to a pointer
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:28:46
    |
-LL |     let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u16]`
+LL |     let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u32]`
 
 error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:34:50
@@ -34,13 +34,13 @@ error: transmute from a reference to a pointer
 LL |     let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) };
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]`
 
-error: transmute from `fn(usize) -> u8 {main::foo}` to `*const usize` which could be expressed as a pointer cast instead
+error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:48:41
    |
 LL |     let _usize_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, *const usize>(foo) };
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize`
 
-error: transmute from `fn(usize) -> u8 {main::foo}` to `usize` which could be expressed as a pointer cast instead
+error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:52:49
    |
 LL |     let _usize_from_fn_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, usize>(foo) };
diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs
new file mode 100644 (file)
index 0000000..a523908
--- /dev/null
@@ -0,0 +1,23 @@
+#![allow(dead_code)]
+
+fn main() {
+    let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
+    let _ = (0..4).find_map(|x| {
+        if x > 1 {
+            return Some(x);
+        };
+        None
+    });
+    let _ = (0..4).find_map(|x| match x {
+        0 | 1 => None,
+        _ => Some(x),
+    });
+
+    let _ = (0..4).find_map(|x| Some(x + 1));
+
+    let _ = (0..4).find_map(i32::checked_abs);
+}
+
+fn find_map_none_changes_item_type() -> Option<bool> {
+    "".chars().find_map(|_| None)
+}
diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr
new file mode 100644 (file)
index 0000000..fb33c12
--- /dev/null
@@ -0,0 +1,38 @@
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:4:13
+   |
+LL |     let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::unnecessary-find-map` implied by `-D warnings`
+
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:5:13
+   |
+LL |       let _ = (0..4).find_map(|x| {
+   |  _____________^
+LL | |         if x > 1 {
+LL | |             return Some(x);
+LL | |         };
+LL | |         None
+LL | |     });
+   | |______^
+
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:11:13
+   |
+LL |       let _ = (0..4).find_map(|x| match x {
+   |  _____________^
+LL | |         0 | 1 => None,
+LL | |         _ => Some(x),
+LL | |     });
+   | |______^
+
+error: this `.find_map` can be written more simply using `.map(..).next()`
+  --> $DIR/unnecessary_find_map.rs:16:13
+   |
+LL |     let _ = (0..4).find_map(|x| Some(x + 1));
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/unnecessary_join.fixed b/tests/ui/unnecessary_join.fixed
new file mode 100644 (file)
index 0000000..7e12c6a
--- /dev/null
@@ -0,0 +1,35 @@
+// run-rustfix
+
+#![warn(clippy::unnecessary_join)]
+
+fn main() {
+    // should be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<String>();
+    println!("{}", output);
+
+    // should be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<String>();
+    println!("{}", output);
+
+    // should not be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<Vec<String>>()
+        .join("\n");
+    println!("{}", output);
+
+    // should not be linted
+    let vector = vec!["hello", "world"];
+    let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+    println!("{}", output);
+}
diff --git a/tests/ui/unnecessary_join.rs b/tests/ui/unnecessary_join.rs
new file mode 100644 (file)
index 0000000..0a21656
--- /dev/null
@@ -0,0 +1,37 @@
+// run-rustfix
+
+#![warn(clippy::unnecessary_join)]
+
+fn main() {
+    // should be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<Vec<String>>()
+        .join("");
+    println!("{}", output);
+
+    // should be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<Vec<_>>()
+        .join("");
+    println!("{}", output);
+
+    // should not be linted
+    let vector = vec!["hello", "world"];
+    let output = vector
+        .iter()
+        .map(|item| item.to_uppercase())
+        .collect::<Vec<String>>()
+        .join("\n");
+    println!("{}", output);
+
+    // should not be linted
+    let vector = vec!["hello", "world"];
+    let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+    println!("{}", output);
+}
diff --git a/tests/ui/unnecessary_join.stderr b/tests/ui/unnecessary_join.stderr
new file mode 100644 (file)
index 0000000..0b14b14
--- /dev/null
@@ -0,0 +1,20 @@
+error: called `.collect<Vec<String>>().join("")` on an iterator
+  --> $DIR/unnecessary_join.rs:11:10
+   |
+LL |           .collect::<Vec<String>>()
+   |  __________^
+LL | |         .join("");
+   | |_________________^ help: try using: `collect::<String>()`
+   |
+   = note: `-D clippy::unnecessary-join` implied by `-D warnings`
+
+error: called `.collect<Vec<String>>().join("")` on an iterator
+  --> $DIR/unnecessary_join.rs:20:10
+   |
+LL |           .collect::<Vec<_>>()
+   |  __________^
+LL | |         .join("");
+   | |_________________^ help: try using: `collect::<String>()`
+
+error: aborting due to 2 previous errors
+
index 4ba2a0a5dbcc1d5dd07beca432d67f708e11e4b8..65fcdc43061bfe8557441050c3cc73c54121ccb5 100644 (file)
@@ -115,6 +115,14 @@ fn main() {
     let _: Result<usize, usize> = res.or(Ok(2));
     let _: Result<usize, usize> = res.or(Ok(astronomers_pi));
     let _: Result<usize, usize> = res.or(Ok(ext_str.some_field));
+    let _: Result<usize, usize> = res.
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        or(Ok(ext_str.some_field));
 
     // neither bind_instead_of_map nor unnecessary_lazy_eval applies here
     let _: Result<usize, usize> = res.and_then(|x| Err(x));
index 466915217e42e9dc94cf983efe4d2b70b29fed0c..206080ed69ada8dd7735ed9b6551cd7b12dc672b 100644 (file)
@@ -115,6 +115,14 @@ fn main() {
     let _: Result<usize, usize> = res.or_else(|_| Ok(2));
     let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
     let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+    let _: Result<usize, usize> = res.
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        // some lines
+        or_else(|_| Ok(ext_str.some_field));
 
     // neither bind_instead_of_map nor unnecessary_lazy_eval applies here
     let _: Result<usize, usize> = res.and_then(|x| Err(x));
index cc94bd5cd9e16bcc03ab9b8d765384e19ea3089a..7e4dd7730e71530b79aa78b6f41937a13c90b143 100644 (file)
@@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:35:13
    |
 LL |     let _ = opt.unwrap_or_else(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(2)`
+   |             ^^^^--------------------
+   |                 |
+   |                 help: use `unwrap_or(..)` instead: `unwrap_or(2)`
    |
    = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
 
@@ -10,187 +12,264 @@ error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:36:13
    |
 LL |     let _ = opt.unwrap_or_else(|| astronomers_pi);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)`
+   |             ^^^^---------------------------------
+   |                 |
+   |                 help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:37:13
    |
 LL |     let _ = opt.unwrap_or_else(|| ext_str.some_field);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)`
+   |             ^^^^-------------------------------------
+   |                 |
+   |                 help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:39:13
    |
 LL |     let _ = opt.and_then(|_| ext_opt);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `opt.and(ext_opt)`
+   |             ^^^^---------------------
+   |                 |
+   |                 help: use `and(..)` instead: `and(ext_opt)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:40:13
    |
 LL |     let _ = opt.or_else(|| ext_opt);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(ext_opt)`
+   |             ^^^^-------------------
+   |                 |
+   |                 help: use `or(..)` instead: `or(ext_opt)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:41:13
    |
 LL |     let _ = opt.or_else(|| None);
-   |             ^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(None)`
+   |             ^^^^----------------
+   |                 |
+   |                 help: use `or(..)` instead: `or(None)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:42:13
    |
 LL |     let _ = opt.get_or_insert_with(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `opt.get_or_insert(2)`
+   |             ^^^^------------------------
+   |                 |
+   |                 help: use `get_or_insert(..)` instead: `get_or_insert(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:43:13
    |
 LL |     let _ = opt.ok_or_else(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `opt.ok_or(2)`
+   |             ^^^^----------------
+   |                 |
+   |                 help: use `ok_or(..)` instead: `ok_or(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:44:13
    |
 LL |     let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `nested_tuple_opt.unwrap_or(Some((1, 2)))`
+   |             ^^^^^^^^^^^^^^^^^-------------------------------
+   |                              |
+   |                              help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:47:13
    |
 LL |     let _ = Some(10).unwrap_or_else(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Some(10).unwrap_or(2)`
+   |             ^^^^^^^^^--------------------
+   |                      |
+   |                      help: use `unwrap_or(..)` instead: `unwrap_or(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:48:13
    |
 LL |     let _ = Some(10).and_then(|_| ext_opt);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `Some(10).and(ext_opt)`
+   |             ^^^^^^^^^---------------------
+   |                      |
+   |                      help: use `and(..)` instead: `and(ext_opt)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:49:28
    |
 LL |     let _: Option<usize> = None.or_else(|| ext_opt);
-   |                            ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(ext_opt)`
+   |                            ^^^^^-------------------
+   |                                 |
+   |                                 help: use `or(..)` instead: `or(ext_opt)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:50:13
    |
 LL |     let _ = None.get_or_insert_with(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `None.get_or_insert(2)`
+   |             ^^^^^------------------------
+   |                  |
+   |                  help: use `get_or_insert(..)` instead: `get_or_insert(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:51:35
    |
 LL |     let _: Result<usize, usize> = None.ok_or_else(|| 2);
-   |                                   ^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `None.ok_or(2)`
+   |                                   ^^^^^----------------
+   |                                        |
+   |                                        help: use `ok_or(..)` instead: `ok_or(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:52:28
    |
 LL |     let _: Option<usize> = None.or_else(|| None);
-   |                            ^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(None)`
+   |                            ^^^^^----------------
+   |                                 |
+   |                                 help: use `or(..)` instead: `or(None)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:55:13
    |
 LL |     let _ = deep.0.unwrap_or_else(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `deep.0.unwrap_or(2)`
+   |             ^^^^^^^--------------------
+   |                    |
+   |                    help: use `unwrap_or(..)` instead: `unwrap_or(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:56:13
    |
 LL |     let _ = deep.0.and_then(|_| ext_opt);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `deep.0.and(ext_opt)`
+   |             ^^^^^^^---------------------
+   |                    |
+   |                    help: use `and(..)` instead: `and(ext_opt)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:57:13
    |
 LL |     let _ = deep.0.or_else(|| None);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(None)`
+   |             ^^^^^^^----------------
+   |                    |
+   |                    help: use `or(..)` instead: `or(None)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:58:13
    |
 LL |     let _ = deep.0.get_or_insert_with(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `deep.0.get_or_insert(2)`
+   |             ^^^^^^^------------------------
+   |                    |
+   |                    help: use `get_or_insert(..)` instead: `get_or_insert(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:59:13
    |
 LL |     let _ = deep.0.ok_or_else(|| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `deep.0.ok_or(2)`
+   |             ^^^^^^^----------------
+   |                    |
+   |                    help: use `ok_or(..)` instead: `ok_or(2)`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:79:28
    |
 LL |     let _: Option<usize> = None.or_else(|| Some(3));
-   |                            ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(Some(3))`
+   |                            ^^^^^-------------------
+   |                                 |
+   |                                 help: use `or(..)` instead: `or(Some(3))`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:80:13
    |
 LL |     let _ = deep.0.or_else(|| Some(3));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(Some(3))`
+   |             ^^^^^^^-------------------
+   |                    |
+   |                    help: use `or(..)` instead: `or(Some(3))`
 
 error: unnecessary closure used to substitute value for `Option::None`
   --> $DIR/unnecessary_lazy_eval.rs:81:13
    |
 LL |     let _ = opt.or_else(|| Some(3));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(Some(3))`
+   |             ^^^^-------------------
+   |                 |
+   |                 help: use `or(..)` instead: `or(Some(3))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:87:13
    |
 LL |     let _ = res2.unwrap_or_else(|_| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(2)`
+   |             ^^^^^---------------------
+   |                  |
+   |                  help: use `unwrap_or(..)` instead: `unwrap_or(2)`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:88:13
    |
 LL |     let _ = res2.unwrap_or_else(|_| astronomers_pi);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)`
+   |             ^^^^^----------------------------------
+   |                  |
+   |                  help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:89:13
    |
 LL |     let _ = res2.unwrap_or_else(|_| ext_str.some_field);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)`
+   |             ^^^^^--------------------------------------
+   |                  |
+   |                  help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:111:35
    |
 LL |     let _: Result<usize, usize> = res.and_then(|_| Err(2));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(2))`
+   |                                   ^^^^--------------------
+   |                                       |
+   |                                       help: use `and(..)` instead: `and(Err(2))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:112:35
    |
 LL |     let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(astronomers_pi))`
+   |                                   ^^^^---------------------------------
+   |                                       |
+   |                                       help: use `and(..)` instead: `and(Err(astronomers_pi))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:113:35
    |
 LL |     let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(ext_str.some_field))`
+   |                                   ^^^^-------------------------------------
+   |                                       |
+   |                                       help: use `and(..)` instead: `and(Err(ext_str.some_field))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:115:35
    |
 LL |     let _: Result<usize, usize> = res.or_else(|_| Ok(2));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(2))`
+   |                                   ^^^^------------------
+   |                                       |
+   |                                       help: use `or(..)` instead: `or(Ok(2))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:116:35
    |
 LL |     let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(astronomers_pi))`
+   |                                   ^^^^-------------------------------
+   |                                       |
+   |                                       help: use `or(..)` instead: `or(Ok(astronomers_pi))`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval.rs:117:35
    |
 LL |     let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(ext_str.some_field))`
+   |                                   ^^^^-----------------------------------
+   |                                       |
+   |                                       help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
 
-error: aborting due to 32 previous errors
+error: unnecessary closure used to substitute value for `Result::Err`
+  --> $DIR/unnecessary_lazy_eval.rs:118:35
+   |
+LL |       let _: Result<usize, usize> = res.
+   |  ___________________________________^
+LL | |         // some lines
+LL | |         // some lines
+LL | |         // some lines
+...  |
+LL | |         // some lines
+LL | |         or_else(|_| Ok(ext_str.some_field));
+   | |_________----------------------------------^
+   |           |
+   |           help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
+
+error: aborting due to 33 previous errors
 
index 75674b0a9d20ae128c8f26f427ef381364d8b779..20acab6e844f86bb3b885956f9cecbd3d181d15e 100644 (file)
@@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval_unfixable.rs:12:13
    |
 LL |     let _ = Ok(1).unwrap_or_else(|()| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+   |             ^^^^^^----------------------
+   |                   |
+   |                   help: use `unwrap_or(..)` instead: `unwrap_or(2)`
    |
    = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
 
@@ -10,13 +12,17 @@ error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval_unfixable.rs:16:13
    |
 LL |     let _ = Ok(1).unwrap_or_else(|e::E| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+   |             ^^^^^^------------------------
+   |                   |
+   |                   help: use `unwrap_or(..)` instead: `unwrap_or(2)`
 
 error: unnecessary closure used to substitute value for `Result::Err`
   --> $DIR/unnecessary_lazy_eval_unfixable.rs:17:13
    |
 LL |     let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+   |             ^^^^^^-------------------------------------
+   |                   |
+   |                   help: use `unwrap_or(..)` instead: `unwrap_or(2)`
 
 error: aborting due to 3 previous errors
 
index 720138db137726cbf6c1844a6ba2d63b8aa0e7c6..38ba41ac54ecb2ca7814f135f1525b44d66a825a 100644 (file)
@@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result<std::path::PathBuf, std::io::E
 }
 
 fn require_string(_: &String) {}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+    #![allow(dead_code)]
+
+    struct Opaque<P>(P);
+
+    pub trait Abstracted {}
+
+    impl<P> Abstracted for Opaque<P> {}
+
+    fn build<P>(p: P) -> Opaque<P>
+    where
+        P: AsRef<str>,
+    {
+        Opaque(p)
+    }
+
+    // Should not lint.
+    fn test_str(s: &str) -> Box<dyn Abstracted> {
+        Box::new(build(s.to_string()))
+    }
+
+    // Should not lint.
+    fn test_x(x: super::X) -> Box<dyn Abstracted> {
+        Box::new(build(x))
+    }
+
+    #[derive(Clone, Copy)]
+    struct Y(&'static str);
+
+    impl AsRef<str> for Y {
+        fn as_ref(&self) -> &str {
+            self.0
+        }
+    }
+
+    impl ToString for Y {
+        fn to_string(&self) -> String {
+            self.0.to_string()
+        }
+    }
+
+    // Should lint because Y is copy.
+    fn test_y(y: Y) -> Box<dyn Abstracted> {
+        Box::new(build(y))
+    }
+}
index 60b2e718f5d499aefb887461737c0ab0ea42d68e..15fb7ee83e3d100a50cfa13dfd54cf12afcd7cd0 100644 (file)
@@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result<std::path::PathBuf, std::io::E
 }
 
 fn require_string(_: &String) {}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+    #![allow(dead_code)]
+
+    struct Opaque<P>(P);
+
+    pub trait Abstracted {}
+
+    impl<P> Abstracted for Opaque<P> {}
+
+    fn build<P>(p: P) -> Opaque<P>
+    where
+        P: AsRef<str>,
+    {
+        Opaque(p)
+    }
+
+    // Should not lint.
+    fn test_str(s: &str) -> Box<dyn Abstracted> {
+        Box::new(build(s.to_string()))
+    }
+
+    // Should not lint.
+    fn test_x(x: super::X) -> Box<dyn Abstracted> {
+        Box::new(build(x))
+    }
+
+    #[derive(Clone, Copy)]
+    struct Y(&'static str);
+
+    impl AsRef<str> for Y {
+        fn as_ref(&self) -> &str {
+            self.0
+        }
+    }
+
+    impl ToString for Y {
+        fn to_string(&self) -> String {
+            self.0.to_string()
+        }
+    }
+
+    // Should lint because Y is copy.
+    fn test_y(y: Y) -> Box<dyn Abstracted> {
+        Box::new(build(y.to_string()))
+    }
+}
index 1dfc65e22e2bc7c9d023389b9ba02ef74fd14f47..c53ce32be775706f91d4ce47b2e4b5bd4220abd9 100644 (file)
@@ -491,5 +491,11 @@ LL -         let path = match get_file_path(&t) {
 LL +         let path = match get_file_path(t) {
    | 
 
-error: aborting due to 76 previous errors
+error: unnecessary use of `to_string`
+  --> $DIR/unnecessary_to_owned.rs:260:24
+   |
+LL |         Box::new(build(y.to_string()))
+   |                        ^^^^^^^^^^^^^ help: use: `y`
+
+error: aborting due to 77 previous errors
 
index 4e33e343ce0e9c414b42abf6d5e73d6b488392c6..9d216f56ae60c66b3a22df7158c40a893f424e08 100644 (file)
@@ -2,7 +2,7 @@
 // aux-build:proc_macro_derive.rs
 
 #![warn(clippy::use_self)]
-#![allow(dead_code)]
+#![allow(dead_code, unreachable_code)]
 #![allow(
     clippy::should_implement_trait,
     clippy::upper_case_acronyms,
@@ -519,3 +519,26 @@ mod self_is_ty_param {
         }
     }
 }
+
+mod use_self_in_pat {
+    enum Foo {
+        Bar,
+        Baz,
+    }
+
+    impl Foo {
+        fn do_stuff(self) {
+            match self {
+                Self::Bar => unimplemented!(),
+                Self::Baz => unimplemented!(),
+            }
+            match Some(1) {
+                Some(_) => unimplemented!(),
+                None => unimplemented!(),
+            }
+            if let Self::Bar = self {
+                unimplemented!()
+            }
+        }
+    }
+}
index 7b621ff9bcabf5be14250e8740311b04516047b8..5f604fe25e416d93e2fafbf4883b92be539ca0ec 100644 (file)
@@ -2,7 +2,7 @@
 // aux-build:proc_macro_derive.rs
 
 #![warn(clippy::use_self)]
-#![allow(dead_code)]
+#![allow(dead_code, unreachable_code)]
 #![allow(
     clippy::should_implement_trait,
     clippy::upper_case_acronyms,
@@ -519,3 +519,26 @@ fn test() {
         }
     }
 }
+
+mod use_self_in_pat {
+    enum Foo {
+        Bar,
+        Baz,
+    }
+
+    impl Foo {
+        fn do_stuff(self) {
+            match self {
+                Foo::Bar => unimplemented!(),
+                Foo::Baz => unimplemented!(),
+            }
+            match Some(1) {
+                Some(_) => unimplemented!(),
+                None => unimplemented!(),
+            }
+            if let Foo::Bar = self {
+                unimplemented!()
+            }
+        }
+    }
+}
index ecb78b3f9721b0b575a4a153e08e9833f63917b5..34d98618253a6ddcff0ab9e603896326f3a07d92 100644 (file)
@@ -168,5 +168,23 @@ error: unnecessary structure name repetition
 LL |             S2::new()
    |             ^^ help: use the applicable keyword: `Self`
 
-error: aborting due to 28 previous errors
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:532:17
+   |
+LL |                 Foo::Bar => unimplemented!(),
+   |                 ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:533:17
+   |
+LL |                 Foo::Baz => unimplemented!(),
+   |                 ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:539:20
+   |
+LL |             if let Foo::Bar = self {
+   |                    ^^^ help: use the applicable keyword: `Self`
+
+error: aborting due to 31 previous errors
 
index 677b4a4d569994598398bb9dc0b1a13f54ab1b4b..e13efb3e0164b638f96b05861b818fdc70b23ed0 100644 (file)
@@ -93,7 +93,7 @@ fn test_no_deps_ignores_path_deps_in_workspaces() {
         output
     };
 
-    // Trigger a sucessful build, so Cargo would like to cache the build result.
+    // Trigger a successful build, so Cargo would like to cache the build result.
     successful_build();
 
     // Make sure there's no spurious rebuild when nothing changes.
index 83a200ca3c4f4d112999c1bf3ddfd9d40ebd6299..97c974003c62fe7ebf6881db8494db8f092314d7 100644 (file)
@@ -25,7 +25,56 @@ Otherwise, have a great day =^.^=
         blockquote { font-size: 1em; }
         [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }
 
-        .form-inline .checkbox { margin-right: 0.6em }
+        .dropdown-menu {
+            color: var(--fg);
+            background: var(--theme-popup-bg);
+            border: 1px solid var(--theme-popup-border);
+        }
+
+        .dropdown-menu .divider {
+            background-color: var(--theme-popup-border);
+        }
+
+        .dropdown-menu .checkbox {
+            display: block;
+            white-space: nowrap;
+            margin: 0;
+        }
+        .dropdown-menu .checkbox label {
+            padding: 3px 20px;
+            width: 100%;
+        }
+
+        .dropdown-menu .checkbox input {
+            position: relative;
+            margin: 0 0.5rem 0;
+            padding: 0;
+        }
+
+        .dropdown-menu .checkbox:hover {
+            background-color: var(--theme-hover);
+        }
+
+        div.panel div.panel-body button.dropdown-toggle {
+            background: var(--searchbar-bg);
+            color: var(--searchbar-fg);
+            border-color: var(--theme-popup-border);
+        }
+
+        div.panel div.panel-body button.dropdown-toggle:hover {
+            box-shadow: 0 0 3px var(--searchbar-shadow-color);
+        }
+
+        div.panel div.panel-body .open button.dropdown-toggle {
+            background: var(--searchbar-bg);
+            color: var(--searchbar-fg);
+            border-color: var(--theme-popup-border);
+            filter: brightness(90%);
+        }
+
+        .dropdown-toggle .badge {
+            background-color: #777;
+        }
 
         .panel-heading { cursor: pointer; }
 
@@ -38,6 +87,16 @@ Otherwise, have a great day =^.^=
         .panel .panel-title-name .anchor { display: none; }
         .panel:hover .panel-title-name .anchor { display: inline;}
 
+        .search-control {
+            margin-top: 15px;
+        }
+
+        @media (min-width: 992px) {
+            .search-control {
+                margin-top: 0;
+            }
+        }
+
         .label {
             padding-top: 0.3em;
             padding-bottom: 0.3em;
@@ -143,13 +202,17 @@ Otherwise, have a great day =^.^=
             --inline-code-bg: #191f26;
         }
 
+        .theme-dropdown {
+            position: absolute;
+            margin: 0.7em;
+            z-index: 10;
+        }
+
         /* Applying the mdBook theme */
         .theme-icon {
-            position: absolute;
             text-align: center;
             width: 2em;
             height: 2em;
-            margin: 0.7em;
             line-height: 2em;
             border: solid 1px var(--icons);
             border-radius: 5px;
@@ -160,23 +223,28 @@ Otherwise, have a great day =^.^=
             background: var(--theme-hover);
         }
         .theme-choice {
-            position: absolute;
-            margin-top: calc(2em + 0.7em);
-            margin-left: 0.7em;
+            display: none;
             list-style: none;
             border: 1px solid var(--theme-popup-border);
             border-radius: 5px;
             color: var(--fg);
             background: var(--theme-popup-bg);
             padding: 0 0;
+            overflow: hidden;
         }
+
+        .theme-dropdown.open .theme-choice {
+            display: block;
+        }
+
         .theme-choice > li {
             padding: 5px 10px;
             font-size: 0.8em;
             user-select: none;
             cursor: pointer;
         }
-        .theme-choice > li:hover {
+
+        .theme-choice>li:hover {
             background: var(--theme-hover);
         }
 
@@ -240,17 +308,15 @@ Otherwise, have a great day =^.^=
 
     </style>
 </head>
-<body>
-    <div id="theme-icon" class="theme-icon">&#128396;</div>
-    <ul id="theme-menu" class="theme-choice" style="display: none;">
-        <li id="light">Light</li>
-        <li id="rust">Rust</li>
-        <li id="coal">Coal</li>
-        <li id="navy">Navy</li>
-        <li id="ayu">Ayu</li>
-    </ul>
-
-    <div class="container" ng-app="clippy" ng-controller="lintList">
+<body ng-app="clippy" ng-controller="lintList">
+    <div theme-dropdown class="theme-dropdown">
+        <div id="theme-icon" class="theme-icon">&#128396;</div>
+        <ul id="theme-menu" class="theme-choice">
+            <li id="{{id}}" ng-repeat="(id, name) in themes" ng-click="selectTheme(id)">{{name}}</li>
+        </ul>
+    </div>
+
+    <div class="container">
         <div class="page-header">
             <h1>Clippy Lints</h1>
         </div>
@@ -271,38 +337,62 @@ Otherwise, have a great day =^.^=
             </div>
 
             <div class="panel panel-default" ng-show="data">
-                <div class="panel-body row filter-panel">
-                    <div class="col-md-6 form-inline">
-                        <div class="form-group form-group-lg">
-                            <p class="h4">
-                                Lint levels
-                                <a href="https://doc.rust-lang.org/rustc/lints/levels.html">(?)</a>
-                            </p>
-                            <div class="checkbox" ng-repeat="(level, enabled) in levels">
-                                <label class="text-capitalize">
-                                    <input type="checkbox" ng-model="levels[level]" />
-                                    {{level}}
-                                </label>
-                            </div>
+                <div class="panel-body row">
+                    <div class="col-12 col-md-4">
+                        <div class="btn-group" filter-dropdown>
+                            <button type="button" class="btn btn-default dropdown-toggle">
+                                Lint levels <span class="badge">{{selectedValuesCount(levels)}}</span> <span class="caret"></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                <li class="checkbox">
+                                    <label ng-click="toggleLevels(true)">
+                                        <input type="checkbox" class="invisible" />
+                                        All
+                                    </label>
+                                </li>
+                                <li class="checkbox">
+                                    <label ng-click="toggleLevels(false)">
+                                        <input type="checkbox" class="invisible" />
+                                        None
+                                    </label>
+                                </li>
+                                <li role="separator" class="divider"></li>
+                                <li class="checkbox" ng-repeat="(level, enabled) in levels">
+                                    <label class="text-capitalize">
+                                        <input type="checkbox" ng-model="levels[level]" />
+                                        {{level}}
+                                    </label>
+                                </li>
+                            </ul>
                         </div>
-                    </div>
-                    <div class="col-md-6 form-inline">
-                        <div class="form-group form-group-lg">
-                            <p class="h4">
-                                Lint groups
-                                <a href="https://github.com/rust-lang/rust-clippy/#clippy">(?)</a>
-                            </p>
-                            <div class="checkbox" ng-repeat="(group, enabled) in groups">
-                                <label class="text-capitalize">
-                                    <input type="checkbox" ng-model="groups[group]" />
-                                    {{group}}
-                                </label>
-                            </div>
+                        <div class="btn-group" filter-dropdown>
+                            <button type="button" class="btn btn-default dropdown-toggle">
+                                Lint groups <span class="badge">{{selectedValuesCount(groups)}}</span> <span class="caret"></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                <li class="checkbox">
+                                    <label ng-click="toggleGroups(true)">
+                                        <input type="checkbox" class="invisible" />
+                                        All
+                                    </label>
+                                </li>
+                                <li class="checkbox">
+                                    <label ng-click="toggleGroups(false)">
+                                        <input type="checkbox" class="invisible" />
+                                        None
+                                    </label>
+                                </li>
+                                <li role="separator" class="divider"></li>
+                                <li class="checkbox" ng-repeat="(group, enabled) in groups">
+                                    <label class="text-capitalize">
+                                        <input type="checkbox" ng-model="groups[group]" />
+                                        {{group}}
+                                    </label>
+                                </li>
+                            </ul>
                         </div>
                     </div>
-                </div>
-                <div class="panel-body row">
-                    <div class="col-md-12 form-horizontal">
+                    <div class="col-12 col-md-8 search-control">
                         <div class="input-group">
                             <label class="input-group-addon" id="filter-label" for="filter-input">Filter:</label>
                             <input type="text" class="form-control" placeholder="Keywords or search string" id="filter-input" ng-model="search" ng-model-options="{debounce: 50}"/>
@@ -336,7 +426,7 @@ Otherwise, have a great day =^.^=
                     </h2>
                 </header>
 
-                <div class="list-group lint-docs" ng-class="{collapse: true, in: open[lint.id]}">
+                <div class="list-group lint-docs" ng-if="open[lint.id]" ng-class="{collapse: true, in: open[lint.id]}">
                     <div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
                     <div class="lint-additional-info-container">
                         <!-- Applicability -->
@@ -365,7 +455,7 @@ Otherwise, have a great day =^.^=
     </div>
 
     <a href="https://github.com/rust-lang/rust-clippy">
-        <img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
+        <img style="position: absolute; top: 0; right: 0; border: 0; clip-path: polygon(0% 0%, 100% 0%, 100% 100%);" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
     </a>
 
     <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js"></script>
@@ -429,6 +519,46 @@ Otherwise, have a great day =^.^=
                 );
             };
         })
+        .directive('themeDropdown', function ($document) {
+            return {
+                restrict: 'A',
+                link: function ($scope, $element, $attr) {
+                    $element.bind('click', function () {
+                        $element.toggleClass('open');
+                        $element.addClass('open-recent');
+                    });
+
+                    $document.bind('click', function () {
+                        if (!$element.hasClass('open-recent')) {
+                            $element.removeClass('open');
+                        }
+                        $element.removeClass('open-recent');
+                    })
+                }
+            }
+        })
+        .directive('filterDropdown', function ($document) {
+            return {
+                restrict: 'A',
+                link: function ($scope, $element, $attr) {
+                    $element.bind('click', function (event) {
+                        if (event.target.closest('button')) {
+                            $element.toggleClass('open');
+                        } else {
+                            $element.addClass('open');
+                        }
+                        $element.addClass('open-recent');
+                    });
+
+                    $document.bind('click', function () {
+                        if (!$element.hasClass('open-recent')) {
+                            $element.removeClass('open');
+                        }
+                        $element.removeClass('open-recent');
+                    })
+                }
+            }
+        })
         .directive('onFinishRender', function ($timeout) {
             return {
                 restrict: 'A',
@@ -462,6 +592,38 @@ Otherwise, have a great day =^.^=
                 suspicious: true,
             };
             $scope.groups = GROUPS_FILTER_DEFAULT;
+            const THEMES_DEFAULT = {
+                light: "Light",
+                rust: "Rust",
+                coal: "Coal",
+                navy: "Navy",
+                ayu: "Ayu"
+            };
+            $scope.themes = THEMES_DEFAULT;
+
+            $scope.selectTheme = function (theme) {
+                setTheme(theme, true);
+            }
+
+            $scope.toggleLevels = function (value) {
+                const levels = $scope.levels;
+                for (const key in levels) {
+                    if (levels.hasOwnProperty(key)) {
+                        levels[key] = value;
+                    }
+                }
+            };
+            $scope.toggleGroups = function (value) {
+                const groups = $scope.groups;
+                for (const key in groups) {
+                    if (groups.hasOwnProperty(key)) {
+                        groups[key] = value;
+                    }
+                }
+            };
+            $scope.selectedValuesCount = function (obj) {
+                return Object.values(obj).filter(x => x).length;
+            }
             $scope.byGroups = function (lint) {
                 return $scope.groups[lint.group];
             };
@@ -558,28 +720,6 @@ Otherwise, have a great day =^.^=
         }
     }
 
-    function setupListeners() {
-        let themeIcon = document.getElementById("theme-icon");
-        let themeMenu = document.getElementById("theme-menu");
-        themeIcon.addEventListener("click", function(e) {
-            if (themeMenu.style.display == "none") {
-                themeMenu.style.display = "block";
-            } else {
-                themeMenu.style.display = "none";
-            }
-        });
-
-        let children = themeMenu.children;
-        for (let index = 0; index < children.length; index++) {
-            let child = children[index];
-            child.addEventListener("click", function(e) {
-                setTheme(child.id, true);
-            });
-        }
-    }
-
-    setupListeners();
-
     function setTheme(theme, store) {
         let enableHighlight = false;
         let enableNight = false;