--- /dev/null
- ## Unreleased / In Rust Nightly
+# Changelog
+
+All notable changes to this project will be documented in this file.
+See [Changelog Update](book/src/development/infrastructure/changelog_update.md) if you want to update this
+document.
+
- [3c7e7dbc...master](https://github.com/rust-lang/rust-clippy/compare/3c7e7dbc...master)
++## Unreleased / Beta / In Rust Nightly
+
- Current stable, released 2022-09-22
++[b52fb523...master](https://github.com/rust-lang/rust-clippy/compare/b52fb523...master)
++
++## Rust 1.65
++
++Current stable, released 2022-11-03
++
++[3c7e7dbc...b52fb523](https://github.com/rust-lang/rust-clippy/compare/3c7e7dbc...b52fb523)
++
++### Important Changes
++
++* Clippy now has an `--explain <LINT>` command to show the lint description in the console
++ [#8952](https://github.com/rust-lang/rust-clippy/pull/8952)
++
++### New Lints
++
++* [`unused_peekable`]
++ [#9258](https://github.com/rust-lang/rust-clippy/pull/9258)
++* [`collapsible_str_replace`]
++ [#9269](https://github.com/rust-lang/rust-clippy/pull/9269)
++* [`manual_string_new`]
++ [#9295](https://github.com/rust-lang/rust-clippy/pull/9295)
++* [`iter_on_empty_collections`]
++ [#9187](https://github.com/rust-lang/rust-clippy/pull/9187)
++* [`iter_on_single_items`]
++ [#9187](https://github.com/rust-lang/rust-clippy/pull/9187)
++* [`bool_to_int_with_if`]
++ [#9412](https://github.com/rust-lang/rust-clippy/pull/9412)
++* [`multi_assignments`]
++ [#9379](https://github.com/rust-lang/rust-clippy/pull/9379)
++* [`result_large_err`]
++ [#9373](https://github.com/rust-lang/rust-clippy/pull/9373)
++* [`partialeq_to_none`]
++ [#9288](https://github.com/rust-lang/rust-clippy/pull/9288)
++* [`suspicious_to_owned`]
++ [#8984](https://github.com/rust-lang/rust-clippy/pull/8984)
++* [`cast_slice_from_raw_parts`]
++ [#9247](https://github.com/rust-lang/rust-clippy/pull/9247)
++* [`manual_instant_elapsed`]
++ [#9264](https://github.com/rust-lang/rust-clippy/pull/9264)
++
++### Moves and Deprecations
++
++* Moved [`significant_drop_in_scrutinee`] to `nursery` (now allow-by-default)
++ [#9302](https://github.com/rust-lang/rust-clippy/pull/9302)
++* Rename `logic_bug` to [`overly_complex_bool_expr`]
++ [#9306](https://github.com/rust-lang/rust-clippy/pull/9306)
++* Rename `arithmetic` to [`arithmetic_side_effects`]
++ [#9443](https://github.com/rust-lang/rust-clippy/pull/9443)
++* Moved [`only_used_in_recursion`] to complexity (now warn-by-default)
++ [#8804](https://github.com/rust-lang/rust-clippy/pull/8804)
++* Moved [`assertions_on_result_states`] to restriction (now allow-by-default)
++ [#9273](https://github.com/rust-lang/rust-clippy/pull/9273)
++* Renamed `blacklisted_name` to [`disallowed_names`]
++ [#8974](https://github.com/rust-lang/rust-clippy/pull/8974)
++
++### Enhancements
++
++* [`option_if_let_else`]: Now also checks for match expressions
++ [#8696](https://github.com/rust-lang/rust-clippy/pull/8696)
++* [`explicit_auto_deref`]: Now lints on implicit returns in closures
++ [#9126](https://github.com/rust-lang/rust-clippy/pull/9126)
++* [`needless_borrow`]: Now considers trait implementations
++ [#9136](https://github.com/rust-lang/rust-clippy/pull/9136)
++* [`suboptimal_flops`], [`imprecise_flops`]: Now lint on constant expressions
++ [#9404](https://github.com/rust-lang/rust-clippy/pull/9404)
++* [`if_let_mutex`]: Now detects mutex behind references and warns about deadlocks
++ [#9318](https://github.com/rust-lang/rust-clippy/pull/9318)
++
++### False Positive Fixes
++
++* [`unit_arg`] [`default_trait_access`] [`missing_docs_in_private_items`]: No longer
++ trigger in code generated from proc-macros
++ [#8694](https://github.com/rust-lang/rust-clippy/pull/8694)
++* [`unwrap_used`]: Now lints uses of `unwrap_err`
++ [#9338](https://github.com/rust-lang/rust-clippy/pull/9338)
++* [`expect_used`]: Now lints uses of `expect_err`
++ [#9338](https://github.com/rust-lang/rust-clippy/pull/9338)
++* [`transmute_undefined_repr`]: Now longer lints if the first field is compatible
++ with the other type
++ [#9287](https://github.com/rust-lang/rust-clippy/pull/9287)
++* [`unnecessary_to_owned`]: No longer lints, if type change cased errors in
++ the caller function
++ [#9424](https://github.com/rust-lang/rust-clippy/pull/9424)
++* [`match_like_matches_macro`]: No longer lints, if there are comments inside the
++ match expression
++ [#9276](https://github.com/rust-lang/rust-clippy/pull/9276)
++* [`partialeq_to_none`]: No longer trigger in code generated from macros
++ [#9389](https://github.com/rust-lang/rust-clippy/pull/9389)
++* [`arithmetic_side_effects`]: No longer lints expressions that only use literals
++ [#9365](https://github.com/rust-lang/rust-clippy/pull/9365)
++* [`explicit_auto_deref`]: Now ignores references on block expressions when the type
++ is `Sized`, on `dyn Trait` returns and when the suggestion is non-trivial
++ [#9126](https://github.com/rust-lang/rust-clippy/pull/9126)
++* [`trait_duplication_in_bounds`]: Now better tracks bounds to avoid false positives
++ [#9167](https://github.com/rust-lang/rust-clippy/pull/9167)
++* [`format_in_format_args`]: Now suggests cases where the result is formatted again
++ [#9349](https://github.com/rust-lang/rust-clippy/pull/9349)
++* [`only_used_in_recursion`]: No longer lints on function without recursions and
++ takes external functions into account
++ [#8804](https://github.com/rust-lang/rust-clippy/pull/8804)
++* [`missing_const_for_fn`]: No longer lints in proc-macros
++ [#9308](https://github.com/rust-lang/rust-clippy/pull/9308)
++* [`non_ascii_literal`]: Allow non-ascii comments in tests and make sure `#[allow]`
++ attributes work in tests
++ [#9327](https://github.com/rust-lang/rust-clippy/pull/9327)
++* [`question_mark`]: No longer lint `if let`s with subpatterns
++ [#9348](https://github.com/rust-lang/rust-clippy/pull/9348)
++* [`needless_collect`]: No longer lints in loops
++ [#8992](https://github.com/rust-lang/rust-clippy/pull/8992)
++* [`mut_mutex_lock`]: No longer lints if the mutex is behind an immutable reference
++ [#9418](https://github.com/rust-lang/rust-clippy/pull/9418)
++* [`needless_return`]: Now ignores returns with arguments
++ [#9381](https://github.com/rust-lang/rust-clippy/pull/9381)
++* [`range_plus_one`], [`range_minus_one`]: Now ignores code with macros
++ [#9446](https://github.com/rust-lang/rust-clippy/pull/9446)
++* [`assertions_on_result_states`]: No longer lints on the unit type
++ [#9273](https://github.com/rust-lang/rust-clippy/pull/9273)
++
++### Suggestion Fixes/Improvements
++
++* [`unwrap_or_else_default`]: Now suggests `unwrap_or_default()` for empty strings
++ [#9421](https://github.com/rust-lang/rust-clippy/pull/9421)
++* [`if_then_some_else_none`]: Now also suggests `bool::then_some`
++ [#9289](https://github.com/rust-lang/rust-clippy/pull/9289)
++* [`redundant_closure_call`]: The suggestion now works for async closures
++ [#9053](https://github.com/rust-lang/rust-clippy/pull/9053)
++* [`suboptimal_flops`]: Now suggests parenthesis when they are required
++ [#9394](https://github.com/rust-lang/rust-clippy/pull/9394)
++* [`case_sensitive_file_extension_comparisons`]: Now suggests `map_or(..)` instead of `map(..).unwrap_or`
++ [#9341](https://github.com/rust-lang/rust-clippy/pull/9341)
++* Deprecated configuration values can now be updated automatically
++ [#9252](https://github.com/rust-lang/rust-clippy/pull/9252)
++* [`or_fun_call`]: Now suggest `Entry::or_default` for `Entry::or_insert(Default::default())`
++ [#9342](https://github.com/rust-lang/rust-clippy/pull/9342)
++* [`unwrap_used`]: Only suggests `expect` if [`expect_used`] is allowed
++ [#9223](https://github.com/rust-lang/rust-clippy/pull/9223)
++
++### ICE Fixes
++
++* Fix ICE in [`useless_format`] for literals
++ [#9406](https://github.com/rust-lang/rust-clippy/pull/9406)
++* Fix infinite loop in [`vec_init_then_push`]
++ [#9441](https://github.com/rust-lang/rust-clippy/pull/9441)
++* Fix ICE when reading literals with weird proc-macro spans
++ [#9303](https://github.com/rust-lang/rust-clippy/pull/9303)
+
+## Rust 1.64
+
++Released 2022-09-22
+
+[d7b5cbf0...3c7e7dbc](https://github.com/rust-lang/rust-clippy/compare/d7b5cbf0...3c7e7dbc)
+
+### New Lints
+
+* [`arithmetic_side_effects`]
+ [#9130](https://github.com/rust-lang/rust-clippy/pull/9130)
+* [`invalid_utf8_in_unchecked`]
+ [#9105](https://github.com/rust-lang/rust-clippy/pull/9105)
+* [`assertions_on_result_states`]
+ [#9225](https://github.com/rust-lang/rust-clippy/pull/9225)
+* [`manual_find`]
+ [#8649](https://github.com/rust-lang/rust-clippy/pull/8649)
+* [`manual_retain`]
+ [#8972](https://github.com/rust-lang/rust-clippy/pull/8972)
+* [`default_instead_of_iter_empty`]
+ [#8989](https://github.com/rust-lang/rust-clippy/pull/8989)
+* [`manual_rem_euclid`]
+ [#9031](https://github.com/rust-lang/rust-clippy/pull/9031)
+* [`obfuscated_if_else`]
+ [#9148](https://github.com/rust-lang/rust-clippy/pull/9148)
+* [`std_instead_of_core`]
+ [#9103](https://github.com/rust-lang/rust-clippy/pull/9103)
+* [`std_instead_of_alloc`]
+ [#9103](https://github.com/rust-lang/rust-clippy/pull/9103)
+* [`alloc_instead_of_core`]
+ [#9103](https://github.com/rust-lang/rust-clippy/pull/9103)
+* [`explicit_auto_deref`]
+ [#8355](https://github.com/rust-lang/rust-clippy/pull/8355)
+
+
+### Moves and Deprecations
+
+* Moved [`format_push_string`] to `restriction` (now allow-by-default)
+ [#9161](https://github.com/rust-lang/rust-clippy/pull/9161)
+
+### Enhancements
+
+* [`significant_drop_in_scrutinee`]: Now gives more context in the lint message
+ [#8981](https://github.com/rust-lang/rust-clippy/pull/8981)
+* [`single_match`], [`single_match_else`]: Now catches more `Option` cases
+ [#8985](https://github.com/rust-lang/rust-clippy/pull/8985)
+* [`unused_async`]: Now works for async methods
+ [#9025](https://github.com/rust-lang/rust-clippy/pull/9025)
+* [`manual_filter_map`], [`manual_find_map`]: Now lint more expressions
+ [#8958](https://github.com/rust-lang/rust-clippy/pull/8958)
+* [`question_mark`]: Now works for simple `if let` expressions
+ [#8356](https://github.com/rust-lang/rust-clippy/pull/8356)
+* [`undocumented_unsafe_blocks`]: Now finds comments before the start of closures
+ [#9117](https://github.com/rust-lang/rust-clippy/pull/9117)
+* [`trait_duplication_in_bounds`]: Now catches duplicate bounds in where clauses
+ [#8703](https://github.com/rust-lang/rust-clippy/pull/8703)
+* [`shadow_reuse`], [`shadow_same`], [`shadow_unrelated`]: Now lint in const blocks
+ [#9124](https://github.com/rust-lang/rust-clippy/pull/9124)
+* [`slow_vector_initialization`]: Now detects cases with `vec.capacity()`
+ [#8953](https://github.com/rust-lang/rust-clippy/pull/8953)
+* [`unused_self`]: Now respects the `avoid-breaking-exported-api` config option
+ [#9199](https://github.com/rust-lang/rust-clippy/pull/9199)
+* [`box_collection`]: Now supports all std collections
+ [#9170](https://github.com/rust-lang/rust-clippy/pull/9170)
+
+### False Positive Fixes
+
+* [`significant_drop_in_scrutinee`]: Now ignores calls to `IntoIterator::into_iter`
+ [#9140](https://github.com/rust-lang/rust-clippy/pull/9140)
+* [`while_let_loop`]: Now ignores cases when the significant drop order would change
+ [#8981](https://github.com/rust-lang/rust-clippy/pull/8981)
+* [`branches_sharing_code`]: Now ignores cases where moved variables have a significant
+ drop or variable modifications can affect the conditions
+ [#9138](https://github.com/rust-lang/rust-clippy/pull/9138)
+* [`let_underscore_lock`]: Now ignores bindings that aren't locked
+ [#8990](https://github.com/rust-lang/rust-clippy/pull/8990)
+* [`trivially_copy_pass_by_ref`]: Now tracks lifetimes and ignores cases where unsafe
+ pointers are used
+ [#8639](https://github.com/rust-lang/rust-clippy/pull/8639)
+* [`let_unit_value`]: No longer ignores `#[allow]` attributes on the value
+ [#9082](https://github.com/rust-lang/rust-clippy/pull/9082)
+* [`declare_interior_mutable_const`]: Now ignores the `thread_local!` macro
+ [#9015](https://github.com/rust-lang/rust-clippy/pull/9015)
+* [`if_same_then_else`]: Now ignores branches with `todo!` and `unimplemented!`
+ [#9006](https://github.com/rust-lang/rust-clippy/pull/9006)
+* [`enum_variant_names`]: Now ignores names with `_` prefixes
+ [#9032](https://github.com/rust-lang/rust-clippy/pull/9032)
+* [`let_unit_value`]: Now ignores cases, where the unit type is manually specified
+ [#9056](https://github.com/rust-lang/rust-clippy/pull/9056)
+* [`match_same_arms`]: Now ignores branches with `todo!`
+ [#9207](https://github.com/rust-lang/rust-clippy/pull/9207)
+* [`assign_op_pattern`]: Ignores cases that break borrowing rules
+ [#9214](https://github.com/rust-lang/rust-clippy/pull/9214)
+* [`extra_unused_lifetimes`]: No longer triggers in derive macros
+ [#9037](https://github.com/rust-lang/rust-clippy/pull/9037)
+* [`mismatching_type_param_order`]: Now ignores complicated generic parameters
+ [#9146](https://github.com/rust-lang/rust-clippy/pull/9146)
+* [`equatable_if_let`]: No longer lints in macros
+ [#9074](https://github.com/rust-lang/rust-clippy/pull/9074)
+* [`new_without_default`]: Now ignores generics and lifetime parameters on `fn new`
+ [#9115](https://github.com/rust-lang/rust-clippy/pull/9115)
+* [`needless_borrow`]: Now ignores cases that result in the execution of different traits
+ [#9096](https://github.com/rust-lang/rust-clippy/pull/9096)
+* [`declare_interior_mutable_const`]: No longer triggers in thread-local initializers
+ [#9246](https://github.com/rust-lang/rust-clippy/pull/9246)
+
+### Suggestion Fixes/Improvements
+
+* [`type_repetition_in_bounds`]: The suggestion now works with maybe bounds
+ [#9132](https://github.com/rust-lang/rust-clippy/pull/9132)
+* [`transmute_ptr_to_ref`]: Now suggests `pointer::cast` when possible
+ [#8939](https://github.com/rust-lang/rust-clippy/pull/8939)
+* [`useless_format`]: Now suggests the correct variable name
+ [#9237](https://github.com/rust-lang/rust-clippy/pull/9237)
+* [`or_fun_call`]: The lint emission will now only span over the `unwrap_or` call
+ [#9144](https://github.com/rust-lang/rust-clippy/pull/9144)
+* [`neg_multiply`]: Now suggests adding parentheses around suggestion if needed
+ [#9026](https://github.com/rust-lang/rust-clippy/pull/9026)
+* [`unnecessary_lazy_evaluations`]: Now suggest for `bool::then_some` for lazy evaluation
+ [#9099](https://github.com/rust-lang/rust-clippy/pull/9099)
+* [`manual_flatten`]: Improved message for long code snippets
+ [#9156](https://github.com/rust-lang/rust-clippy/pull/9156)
+* [`explicit_counter_loop`]: The suggestion is now machine applicable
+ [#9149](https://github.com/rust-lang/rust-clippy/pull/9149)
+* [`needless_borrow`]: Now keeps parentheses around fields, when needed
+ [#9210](https://github.com/rust-lang/rust-clippy/pull/9210)
+* [`while_let_on_iterator`]: The suggestion now works in `FnOnce` closures
+ [#9134](https://github.com/rust-lang/rust-clippy/pull/9134)
+
+### ICE Fixes
+
+* Fix ICEs related to `#![feature(generic_const_exprs)]` usage
+ [#9241](https://github.com/rust-lang/rust-clippy/pull/9241)
+* Fix ICEs related to reference lints
+ [#9093](https://github.com/rust-lang/rust-clippy/pull/9093)
+* [`question_mark`]: Fix ICE on zero field tuple structs
+ [#9244](https://github.com/rust-lang/rust-clippy/pull/9244)
+
+### Documentation Improvements
+
+* [`needless_option_take`]: Now includes a "What it does" and "Why is this bad?" section.
+ [#9022](https://github.com/rust-lang/rust-clippy/pull/9022)
+
+### Others
+
+* Using `--cap-lints=allow` and only `--force-warn`ing some will now work with Clippy's driver
+ [#9036](https://github.com/rust-lang/rust-clippy/pull/9036)
+* Clippy now tries to read the `rust-version` from `Cargo.toml` to identify the
+ minimum supported rust version
+ [#8774](https://github.com/rust-lang/rust-clippy/pull/8774)
+
+## Rust 1.63
+
+Released 2022-08-11
+
+[7c21f91b...d7b5cbf0](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...d7b5cbf0)
+
+### New Lints
+
+* [`borrow_deref_ref`]
+ [#7930](https://github.com/rust-lang/rust-clippy/pull/7930)
+* [`doc_link_with_quotes`]
+ [#8385](https://github.com/rust-lang/rust-clippy/pull/8385)
+* [`no_effect_replace`]
+ [#8754](https://github.com/rust-lang/rust-clippy/pull/8754)
+* [`rc_clone_in_vec_init`]
+ [#8769](https://github.com/rust-lang/rust-clippy/pull/8769)
+* [`derive_partial_eq_without_eq`]
+ [#8796](https://github.com/rust-lang/rust-clippy/pull/8796)
+* [`mismatching_type_param_order`]
+ [#8831](https://github.com/rust-lang/rust-clippy/pull/8831)
+* [`duplicate_mod`] [#8832](https://github.com/rust-lang/rust-clippy/pull/8832)
+* [`unused_rounding`]
+ [#8866](https://github.com/rust-lang/rust-clippy/pull/8866)
+* [`get_first`] [#8882](https://github.com/rust-lang/rust-clippy/pull/8882)
+* [`swap_ptr_to_ref`]
+ [#8916](https://github.com/rust-lang/rust-clippy/pull/8916)
+* [`almost_complete_letter_range`]
+ [#8918](https://github.com/rust-lang/rust-clippy/pull/8918)
+* [`needless_parens_on_range_literals`]
+ [#8933](https://github.com/rust-lang/rust-clippy/pull/8933)
+* [`as_underscore`] [#8934](https://github.com/rust-lang/rust-clippy/pull/8934)
+
+### Moves and Deprecations
+
+* Rename `eval_order_dependence` to [`mixed_read_write_in_expression`], move to
+ `nursery` [#8621](https://github.com/rust-lang/rust-clippy/pull/8621)
+
+### Enhancements
+
+* [`undocumented_unsafe_blocks`]: Now also lints on unsafe trait implementations
+ [#8761](https://github.com/rust-lang/rust-clippy/pull/8761)
+* [`empty_line_after_outer_attr`]: Now also lints on argumentless macros
+ [#8790](https://github.com/rust-lang/rust-clippy/pull/8790)
+* [`expect_used`]: Now can be disabled in tests with the `allow-expect-in-tests`
+ option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802)
+* [`unwrap_used`]: Now can be disabled in tests with the `allow-unwrap-in-tests`
+ option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802)
+* [`disallowed_methods`]: Now also lints indirect usages
+ [#8852](https://github.com/rust-lang/rust-clippy/pull/8852)
+* [`get_last_with_len`]: Now also lints `VecDeque` and any deref to slice
+ [#8862](https://github.com/rust-lang/rust-clippy/pull/8862)
+* [`manual_range_contains`]: Now also lints on chains of `&&` and `||`
+ [#8884](https://github.com/rust-lang/rust-clippy/pull/8884)
+* [`rc_clone_in_vec_init`]: Now also lints on `Weak`
+ [#8885](https://github.com/rust-lang/rust-clippy/pull/8885)
+* [`dbg_macro`]: Introduce `allow-dbg-in-tests` config option
+ [#8897](https://github.com/rust-lang/rust-clippy/pull/8897)
+* [`use_self`]: Now also lints on `TupleStruct` and `Struct` patterns
+ [#8899](https://github.com/rust-lang/rust-clippy/pull/8899)
+* [`manual_find_map`] and [`manual_filter_map`]: Now also lints on more complex
+ method chains inside `map`
+ [#8930](https://github.com/rust-lang/rust-clippy/pull/8930)
+* [`needless_return`]: Now also lints on macro expressions in return statements
+ [#8932](https://github.com/rust-lang/rust-clippy/pull/8932)
+* [`doc_markdown`]: Users can now indicate, that the `doc-valid-idents` config
+ should extend the default and not replace it
+ [#8944](https://github.com/rust-lang/rust-clippy/pull/8944)
+* [`disallowed_names`]: Users can now indicate, that the `disallowed-names`
+ config should extend the default and not replace it
+ [#8944](https://github.com/rust-lang/rust-clippy/pull/8944)
+* [`never_loop`]: Now checks for `continue` in struct expression
+ [#9002](https://github.com/rust-lang/rust-clippy/pull/9002)
+
+### False Positive Fixes
+
+* [`useless_transmute`]: No longer lints on types with erased regions
+ [#8564](https://github.com/rust-lang/rust-clippy/pull/8564)
+* [`vec_init_then_push`]: No longer lints when further extended
+ [#8699](https://github.com/rust-lang/rust-clippy/pull/8699)
+* [`cmp_owned`]: No longer lints on `From::from` for `Copy` types
+ [#8807](https://github.com/rust-lang/rust-clippy/pull/8807)
+* [`redundant_allocation`]: No longer lints on fat pointers that would become
+ thin pointers [#8813](https://github.com/rust-lang/rust-clippy/pull/8813)
+* [`derive_partial_eq_without_eq`]:
+ * Handle differing predicates applied by `#[derive(PartialEq)]` and
+ `#[derive(Eq)]`
+ [#8869](https://github.com/rust-lang/rust-clippy/pull/8869)
+ * No longer lints on non-public types and better handles generics
+ [#8950](https://github.com/rust-lang/rust-clippy/pull/8950)
+* [`empty_line_after_outer_attr`]: No longer lints empty lines in inner
+ string values [#8892](https://github.com/rust-lang/rust-clippy/pull/8892)
+* [`branches_sharing_code`]: No longer lints when using different binding names
+ [#8901](https://github.com/rust-lang/rust-clippy/pull/8901)
+* [`significant_drop_in_scrutinee`]: No longer lints on Try `?` and `await`
+ desugared expressions [#8902](https://github.com/rust-lang/rust-clippy/pull/8902)
+* [`checked_conversions`]: No longer lints in `const` contexts
+ [#8907](https://github.com/rust-lang/rust-clippy/pull/8907)
+* [`iter_overeager_cloned`]: No longer lints on `.cloned().flatten()` when
+ `T::Item` doesn't implement `IntoIterator`
+ [#8960](https://github.com/rust-lang/rust-clippy/pull/8960)
+
+### Suggestion Fixes/Improvements
+
+* [`vec_init_then_push`]: Suggest to remove `mut` binding when possible
+ [#8699](https://github.com/rust-lang/rust-clippy/pull/8699)
+* [`manual_range_contains`]: Fix suggestion for integers with different signs
+ [#8763](https://github.com/rust-lang/rust-clippy/pull/8763)
+* [`identity_op`]: Add parenthesis to suggestions where required
+ [#8786](https://github.com/rust-lang/rust-clippy/pull/8786)
+* [`cast_lossless`]: No longer gives wrong suggestion on `usize`/`isize`->`f64`
+ [#8778](https://github.com/rust-lang/rust-clippy/pull/8778)
+* [`rc_clone_in_vec_init`]: Add suggestion
+ [#8814](https://github.com/rust-lang/rust-clippy/pull/8814)
+* The "unknown field" error messages for config files now wraps the field names
+ [#8823](https://github.com/rust-lang/rust-clippy/pull/8823)
+* [`cast_abs_to_unsigned`]: Do not remove cast if it's required
+ [#8876](https://github.com/rust-lang/rust-clippy/pull/8876)
+* [`significant_drop_in_scrutinee`]: Improve lint message for types that are not
+ references and not trivially clone-able
+ [#8902](https://github.com/rust-lang/rust-clippy/pull/8902)
+* [`for_loops_over_fallibles`]: Now suggests the correct variant of `iter()`,
+ `iter_mut()` or `into_iter()`
+ [#8941](https://github.com/rust-lang/rust-clippy/pull/8941)
+
+### ICE Fixes
+
+* Fix ICE in [`let_unit_value`] when calling a `static`/`const` callable type
+ [#8835](https://github.com/rust-lang/rust-clippy/pull/8835)
+* Fix ICEs on callable `static`/`const`s
+ [#8896](https://github.com/rust-lang/rust-clippy/pull/8896)
+* [`needless_late_init`]
+ [#8912](https://github.com/rust-lang/rust-clippy/pull/8912)
+* Fix ICE in shadow lints
+ [#8913](https://github.com/rust-lang/rust-clippy/pull/8913)
+
+### Documentation Improvements
+
+* Clippy has a [Book](https://doc.rust-lang.org/nightly/clippy/) now!
+ [#7359](https://github.com/rust-lang/rust-clippy/pull/7359)
+* Add a *copy lint name*-button to Clippy's lint list
+ [#8839](https://github.com/rust-lang/rust-clippy/pull/8839)
+* Display past names of renamed lints on Clippy's lint list
+ [#8843](https://github.com/rust-lang/rust-clippy/pull/8843)
+* Add the ability to show the lint output in the lint list
+ [#8947](https://github.com/rust-lang/rust-clippy/pull/8947)
+
+## Rust 1.62
+
+Released 2022-06-30
+
+[d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b)
+
+### New Lints
+
+* [`large_include_file`]
+ [#8727](https://github.com/rust-lang/rust-clippy/pull/8727)
+* [`cast_abs_to_unsigned`]
+ [#8635](https://github.com/rust-lang/rust-clippy/pull/8635)
+* [`err_expect`]
+ [#8606](https://github.com/rust-lang/rust-clippy/pull/8606)
+* [`unnecessary_owned_empty_strings`]
+ [#8660](https://github.com/rust-lang/rust-clippy/pull/8660)
+* [`empty_structs_with_brackets`]
+ [#8594](https://github.com/rust-lang/rust-clippy/pull/8594)
+* [`crate_in_macro_def`]
+ [#8576](https://github.com/rust-lang/rust-clippy/pull/8576)
+* [`needless_option_take`]
+ [#8665](https://github.com/rust-lang/rust-clippy/pull/8665)
+* [`bytes_count_to_len`]
+ [#8711](https://github.com/rust-lang/rust-clippy/pull/8711)
+* [`is_digit_ascii_radix`]
+ [#8624](https://github.com/rust-lang/rust-clippy/pull/8624)
+* [`await_holding_invalid_type`]
+ [#8707](https://github.com/rust-lang/rust-clippy/pull/8707)
+* [`trim_split_whitespace`]
+ [#8575](https://github.com/rust-lang/rust-clippy/pull/8575)
+* [`pub_use`]
+ [#8670](https://github.com/rust-lang/rust-clippy/pull/8670)
+* [`format_push_string`]
+ [#8626](https://github.com/rust-lang/rust-clippy/pull/8626)
+* [`empty_drop`]
+ [#8571](https://github.com/rust-lang/rust-clippy/pull/8571)
+* [`drop_non_drop`]
+ [#8630](https://github.com/rust-lang/rust-clippy/pull/8630)
+* [`forget_non_drop`]
+ [#8630](https://github.com/rust-lang/rust-clippy/pull/8630)
+
+### Moves and Deprecations
+
+* Move [`only_used_in_recursion`] to `nursery` (now allow-by-default)
+ [#8783](https://github.com/rust-lang/rust-clippy/pull/8783)
+* Move [`stable_sort_primitive`] to `pedantic` (now allow-by-default)
+ [#8716](https://github.com/rust-lang/rust-clippy/pull/8716)
+
+### Enhancements
+
+* Remove overlap between [`manual_split_once`] and [`needless_splitn`]
+ [#8631](https://github.com/rust-lang/rust-clippy/pull/8631)
+* [`map_identity`]: Now checks for needless `map_err`
+ [#8487](https://github.com/rust-lang/rust-clippy/pull/8487)
+* [`extra_unused_lifetimes`]: Now checks for impl lifetimes
+ [#8737](https://github.com/rust-lang/rust-clippy/pull/8737)
+* [`cast_possible_truncation`]: Now catches more cases with larger shift or divide operations
+ [#8687](https://github.com/rust-lang/rust-clippy/pull/8687)
+* [`identity_op`]: Now checks for modulo expressions
+ [#8519](https://github.com/rust-lang/rust-clippy/pull/8519)
+* [`panic`]: No longer lint in constant context
+ [#8592](https://github.com/rust-lang/rust-clippy/pull/8592)
+* [`manual_split_once`]: Now lints manual iteration of `splitn`
+ [#8717](https://github.com/rust-lang/rust-clippy/pull/8717)
+* [`self_named_module_files`], [`mod_module_files`]: Now handle relative module paths
+ [#8611](https://github.com/rust-lang/rust-clippy/pull/8611)
+* [`unsound_collection_transmute`]: Now has better size and alignment checks
+ [#8648](https://github.com/rust-lang/rust-clippy/pull/8648)
+* [`unnested_or_patterns`]: Ignore cases, where the suggestion would be longer
+ [#8619](https://github.com/rust-lang/rust-clippy/pull/8619)
+
+### False Positive Fixes
+
+* [`rest_pat_in_fully_bound_structs`]: Now ignores structs marked with `#[non_exhaustive]`
+ [#8690](https://github.com/rust-lang/rust-clippy/pull/8690)
+* [`needless_late_init`]: No longer lints `if let` statements, `let mut` bindings or instances that
+ changes the drop order significantly
+ [#8617](https://github.com/rust-lang/rust-clippy/pull/8617)
+* [`unnecessary_cast`]: No longer lints to casts to aliased or non-primitive types
+ [#8596](https://github.com/rust-lang/rust-clippy/pull/8596)
+* [`init_numbered_fields`]: No longer lints type aliases
+ [#8780](https://github.com/rust-lang/rust-clippy/pull/8780)
+* [`needless_option_as_deref`]: No longer lints for `as_deref_mut` on `Option` values that can't be moved
+ [#8646](https://github.com/rust-lang/rust-clippy/pull/8646)
+* [`mistyped_literal_suffixes`]: Now ignores float literals without an exponent
+ [#8742](https://github.com/rust-lang/rust-clippy/pull/8742)
+* [`undocumented_unsafe_blocks`]: Now ignores unsafe blocks from proc-macros and works better for sub-expressions
+ [#8450](https://github.com/rust-lang/rust-clippy/pull/8450)
+* [`same_functions_in_if_condition`]: Now allows different constants, even if they have the same value
+ [#8673](https://github.com/rust-lang/rust-clippy/pull/8673)
+* [`needless_match`]: Now checks for more complex types and ignores type coercion
+ [#8549](https://github.com/rust-lang/rust-clippy/pull/8549)
+* [`assertions_on_constants`]: Now ignores constants from `cfg!` macros
+ [#8614](https://github.com/rust-lang/rust-clippy/pull/8614)
+* [`indexing_slicing`]: Fix false positives with constant indices in
+ [#8588](https://github.com/rust-lang/rust-clippy/pull/8588)
+* [`iter_with_drain`]: Now ignores iterator references
+ [#8668](https://github.com/rust-lang/rust-clippy/pull/8668)
+* [`useless_attribute`]: Now allows [`redundant_pub_crate`] on `use` items
+ [#8743](https://github.com/rust-lang/rust-clippy/pull/8743)
+* [`cast_ptr_alignment`]: Now ignores expressions, when used for unaligned reads and writes
+ [#8632](https://github.com/rust-lang/rust-clippy/pull/8632)
+* [`wrong_self_convention`]: Now allows `&mut self` and no self as arguments for `is_*` methods
+ [#8738](https://github.com/rust-lang/rust-clippy/pull/8738)
+* [`mut_from_ref`]: Only lint in unsafe code
+ [#8647](https://github.com/rust-lang/rust-clippy/pull/8647)
+* [`redundant_pub_crate`]: Now allows macro exports
+ [#8736](https://github.com/rust-lang/rust-clippy/pull/8736)
+* [`needless_match`]: Ignores cases where the else block expression is different
+ [#8700](https://github.com/rust-lang/rust-clippy/pull/8700)
+* [`transmute_int_to_char`]: Now allows transmutations in `const` code
+ [#8610](https://github.com/rust-lang/rust-clippy/pull/8610)
+* [`manual_non_exhaustive`]: Ignores cases, where the enum value is used
+ [#8645](https://github.com/rust-lang/rust-clippy/pull/8645)
+* [`redundant_closure`]: Now ignores coerced closure
+ [#8431](https://github.com/rust-lang/rust-clippy/pull/8431)
+* [`identity_op`]: Is now ignored in cases where extra brackets would be needed
+ [#8730](https://github.com/rust-lang/rust-clippy/pull/8730)
+* [`let_unit_value`]: Now ignores cases which are used for type inference
+ [#8563](https://github.com/rust-lang/rust-clippy/pull/8563)
+
+### Suggestion Fixes/Improvements
+
+* [`manual_split_once`]: Fixed incorrect suggestions for single result accesses
+ [#8631](https://github.com/rust-lang/rust-clippy/pull/8631)
+* [`bytes_nth`]: Fix typos in the diagnostic message
+ [#8403](https://github.com/rust-lang/rust-clippy/pull/8403)
+* [`mistyped_literal_suffixes`]: Now suggests the correct integer types
+ [#8742](https://github.com/rust-lang/rust-clippy/pull/8742)
+* [`unnecessary_to_owned`]: Fixed suggestion based on the configured msrv
+ [#8692](https://github.com/rust-lang/rust-clippy/pull/8692)
+* [`single_element_loop`]: Improve lint for Edition 2021 arrays
+ [#8616](https://github.com/rust-lang/rust-clippy/pull/8616)
+* [`manual_bits`]: Now includes a cast for proper type conversion, when needed
+ [#8677](https://github.com/rust-lang/rust-clippy/pull/8677)
+* [`option_map_unit_fn`], [`result_map_unit_fn`]: Fix some incorrect suggestions
+ [#8584](https://github.com/rust-lang/rust-clippy/pull/8584)
+* [`collapsible_else_if`]: Add whitespace in suggestion
+ [#8729](https://github.com/rust-lang/rust-clippy/pull/8729)
+* [`transmute_bytes_to_str`]: Now suggest `from_utf8_unchecked` in `const` context
+ [#8612](https://github.com/rust-lang/rust-clippy/pull/8612)
+* [`map_clone`]: Improve message and suggestion based on the msrv
+ [#8688](https://github.com/rust-lang/rust-clippy/pull/8688)
+* [`needless_late_init`]: Now shows the `let` statement where it was first initialized
+ [#8779](https://github.com/rust-lang/rust-clippy/pull/8779)
+
+### ICE Fixes
+
+* [`only_used_in_recursion`]
+ [#8691](https://github.com/rust-lang/rust-clippy/pull/8691)
+* [`cast_slice_different_sizes`]
+ [#8720](https://github.com/rust-lang/rust-clippy/pull/8720)
+* [`iter_overeager_cloned`]
+ [#8602](https://github.com/rust-lang/rust-clippy/pull/8602)
+* [`undocumented_unsafe_blocks`]
+ [#8686](https://github.com/rust-lang/rust-clippy/pull/8686)
+
+## Rust 1.61
+
+Released 2022-05-19
+
+[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
+
+### New Lints
+
+* [`only_used_in_recursion`]
+ [#8422](https://github.com/rust-lang/rust-clippy/pull/8422)
+* [`cast_enum_truncation`]
+ [#8381](https://github.com/rust-lang/rust-clippy/pull/8381)
+* [`missing_spin_loop`]
+ [#8174](https://github.com/rust-lang/rust-clippy/pull/8174)
+* [`deref_by_slicing`]
+ [#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
+* [`needless_match`]
+ [#8471](https://github.com/rust-lang/rust-clippy/pull/8471)
+* [`allow_attributes_without_reason`] (Requires `#![feature(lint_reasons)]`)
+ [#8504](https://github.com/rust-lang/rust-clippy/pull/8504)
+* [`print_in_format_impl`]
+ [#8253](https://github.com/rust-lang/rust-clippy/pull/8253)
+* [`unnecessary_find_map`]
+ [#8489](https://github.com/rust-lang/rust-clippy/pull/8489)
+* [`or_then_unwrap`]
+ [#8561](https://github.com/rust-lang/rust-clippy/pull/8561)
+* [`unnecessary_join`]
+ [#8579](https://github.com/rust-lang/rust-clippy/pull/8579)
+* [`iter_with_drain`]
+ [#8483](https://github.com/rust-lang/rust-clippy/pull/8483)
+* [`cast_enum_constructor`]
+ [#8562](https://github.com/rust-lang/rust-clippy/pull/8562)
+* [`cast_slice_different_sizes`]
+ [#8445](https://github.com/rust-lang/rust-clippy/pull/8445)
+
+### Moves and Deprecations
+
+* Moved [`transmute_undefined_repr`] to `nursery` (now allow-by-default)
+ [#8432](https://github.com/rust-lang/rust-clippy/pull/8432)
+* Moved [`try_err`] to `restriction`
+ [#8544](https://github.com/rust-lang/rust-clippy/pull/8544)
+* Move [`iter_with_drain`] to `nursery`
+ [#8541](https://github.com/rust-lang/rust-clippy/pull/8541)
+* Renamed `to_string_in_display` to [`recursive_format_impl`]
+ [#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
+
+### Enhancements
+
+* [`dbg_macro`]: The lint level can now be set with crate attributes and works inside macros
+ [#8411](https://github.com/rust-lang/rust-clippy/pull/8411)
+* [`ptr_as_ptr`]: Now works inside macros
+ [#8442](https://github.com/rust-lang/rust-clippy/pull/8442)
+* [`use_self`]: Now works for variants in match expressions
+ [#8456](https://github.com/rust-lang/rust-clippy/pull/8456)
+* [`await_holding_lock`]: Now lints for `parking_lot::{Mutex, RwLock}`
+ [#8419](https://github.com/rust-lang/rust-clippy/pull/8419)
+* [`recursive_format_impl`]: Now checks for format calls on `self`
+ [#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
+
+### False Positive Fixes
+
+* [`new_without_default`]: No longer lints for `new()` methods with `#[doc(hidden)]`
+ [#8472](https://github.com/rust-lang/rust-clippy/pull/8472)
+* [`transmute_undefined_repr`]: No longer lints for single field structs with `#[repr(C)]`,
+ generic parameters, wide pointers, unions, tuples and allow several forms of type erasure
+ [#8425](https://github.com/rust-lang/rust-clippy/pull/8425)
+ [#8553](https://github.com/rust-lang/rust-clippy/pull/8553)
+ [#8440](https://github.com/rust-lang/rust-clippy/pull/8440)
+ [#8547](https://github.com/rust-lang/rust-clippy/pull/8547)
+* [`match_single_binding`], [`match_same_arms`], [`match_as_ref`], [`match_bool`]: No longer
+ lint `match` expressions with `cfg`ed arms
+ [#8443](https://github.com/rust-lang/rust-clippy/pull/8443)
+* [`single_component_path_imports`]: No longer lint on macros
+ [#8537](https://github.com/rust-lang/rust-clippy/pull/8537)
+* [`ptr_arg`]: Allow `&mut` arguments for `Cow<_>`
+ [#8552](https://github.com/rust-lang/rust-clippy/pull/8552)
+* [`needless_borrow`]: No longer lints for method calls
+ [#8441](https://github.com/rust-lang/rust-clippy/pull/8441)
+* [`match_same_arms`]: Now ensures that interposing arm patterns don't overlap
+ [#8232](https://github.com/rust-lang/rust-clippy/pull/8232)
+* [`default_trait_access`]: Now allows `Default::default` in update expressions
+ [#8433](https://github.com/rust-lang/rust-clippy/pull/8433)
+
+### Suggestion Fixes/Improvements
+
+* [`redundant_slicing`]: Fixed suggestion for a method calls
+ [#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
+* [`map_flatten`]: Long suggestions will now be split up into two help messages
+ [#8520](https://github.com/rust-lang/rust-clippy/pull/8520)
+* [`unnecessary_lazy_evaluations`]: Now shows suggestions for longer code snippets
+ [#8543](https://github.com/rust-lang/rust-clippy/pull/8543)
+* [`unnecessary_sort_by`]: Now suggests `Reverse` including the path
+ [#8462](https://github.com/rust-lang/rust-clippy/pull/8462)
+* [`search_is_some`]: More suggestions are now `MachineApplicable`
+ [#8536](https://github.com/rust-lang/rust-clippy/pull/8536)
+
+### Documentation Improvements
+
+* [`new_without_default`]: Document `pub` requirement for the struct and fields
+ [#8429](https://github.com/rust-lang/rust-clippy/pull/8429)
+
+## Rust 1.60
+
+Released 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 escapes 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
+
+Released 2022-02-24
+
+[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
+
+### New Lints
+
+* [`index_refutable_slice`]
+ [#7643](https://github.com/rust-lang/rust-clippy/pull/7643)
+* [`needless_splitn`]
+ [#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
+* [`unnecessary_to_owned`]
+ [#7978](https://github.com/rust-lang/rust-clippy/pull/7978)
+* [`needless_late_init`]
+ [#7995](https://github.com/rust-lang/rust-clippy/pull/7995)
+* [`octal_escapes`] [#8007](https://github.com/rust-lang/rust-clippy/pull/8007)
+* [`return_self_not_must_use`]
+ [#8071](https://github.com/rust-lang/rust-clippy/pull/8071)
+* [`init_numbered_fields`]
+ [#8170](https://github.com/rust-lang/rust-clippy/pull/8170)
+
+### Moves and Deprecations
+
+* Move `if_then_panic` to `pedantic` and rename to [`manual_assert`] (now
+ allow-by-default) [#7810](https://github.com/rust-lang/rust-clippy/pull/7810)
+* Rename `disallow_type` to [`disallowed_types`] and `disallowed_method` to
+ [`disallowed_methods`]
+ [#7984](https://github.com/rust-lang/rust-clippy/pull/7984)
+* Move [`map_flatten`] to `complexity` (now warn-by-default)
+ [#8054](https://github.com/rust-lang/rust-clippy/pull/8054)
+
+### Enhancements
+
+* [`match_overlapping_arm`]: Fix false negative where after included ranges,
+ overlapping ranges weren't linted anymore
+ [#7909](https://github.com/rust-lang/rust-clippy/pull/7909)
+* [`deprecated_cfg_attr`]: Now takes the specified MSRV into account
+ [#7944](https://github.com/rust-lang/rust-clippy/pull/7944)
+* [`cast_lossless`]: Now also lints for `bool` to integer casts
+ [#7948](https://github.com/rust-lang/rust-clippy/pull/7948)
+* [`let_underscore_lock`]: Also emit lints for the `parking_lot` crate
+ [#7957](https://github.com/rust-lang/rust-clippy/pull/7957)
+* [`needless_borrow`]
+ [#7977](https://github.com/rust-lang/rust-clippy/pull/7977)
+ * Lint when a borrow is auto-dereffed more than once
+ * Lint in the trailing expression of a block for a match arm
+* [`strlen_on_c_strings`]
+ [8001](https://github.com/rust-lang/rust-clippy/pull/8001)
+ * Lint when used without a fully-qualified path
+ * Suggest removing the surrounding unsafe block when possible
+* [`non_ascii_literal`]: Now also lints on `char`s, not just `string`s
+ [#8034](https://github.com/rust-lang/rust-clippy/pull/8034)
+* [`single_char_pattern`]: Now also lints on `split_inclusive`, `split_once`,
+ `rsplit_once`, `replace`, and `replacen`
+ [#8077](https://github.com/rust-lang/rust-clippy/pull/8077)
+* [`unwrap_or_else_default`]: Now also lints on `std` constructors like
+ `Vec::new`, `HashSet::new`, and `HashMap::new`
+ [#8163](https://github.com/rust-lang/rust-clippy/pull/8163)
+* [`shadow_reuse`]: Now also lints on shadowed `if let` bindings, instead of
+ [`shadow_unrelated`]
+ [#8165](https://github.com/rust-lang/rust-clippy/pull/8165)
+
+### False Positive Fixes
+
+* [`or_fun_call`], [`unnecessary_lazy_evaluations`]: Improve heuristics, so that
+ cheap functions (e.g. calling `.len()` on a `Vec`) won't get linted anymore
+ [#7639](https://github.com/rust-lang/rust-clippy/pull/7639)
+* [`manual_split_once`]: No longer suggests code changing the original behavior
+ [#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
+* Don't show [`no_effect`] or [`unnecessary_operation`] warning for unit struct
+ implementing `FnOnce`
+ [#7898](https://github.com/rust-lang/rust-clippy/pull/7898)
+* [`semicolon_if_nothing_returned`]: Fixed a bug, where the lint wrongly
+ triggered on `let-else` statements
+ [#7955](https://github.com/rust-lang/rust-clippy/pull/7955)
+* [`if_then_some_else_none`]: No longer lints if there is an early return
+ [#7980](https://github.com/rust-lang/rust-clippy/pull/7980)
+* [`needless_collect`]: No longer suggests removal of `collect` when removal
+ would create code requiring mutably borrowing a value multiple times
+ [#7982](https://github.com/rust-lang/rust-clippy/pull/7982)
+* [`shadow_same`]: Fix false positive for `async` function's params
+ [#7997](https://github.com/rust-lang/rust-clippy/pull/7997)
+* [`suboptimal_flops`]: No longer triggers in constant functions
+ [#8009](https://github.com/rust-lang/rust-clippy/pull/8009)
+* [`type_complexity`]: No longer lints on associated types in traits
+ [#8030](https://github.com/rust-lang/rust-clippy/pull/8030)
+* [`question_mark`]: No longer lints if returned object is not local
+ [#8080](https://github.com/rust-lang/rust-clippy/pull/8080)
+* [`option_if_let_else`]: No longer lint on complex sub-patterns
+ [#8086](https://github.com/rust-lang/rust-clippy/pull/8086)
+* [`blocks_in_if_conditions`]: No longer lints on empty closures
+ [#8100](https://github.com/rust-lang/rust-clippy/pull/8100)
+* [`enum_variant_names`]: No longer lint when first prefix is only a substring
+ of a camel-case word
+ [#8127](https://github.com/rust-lang/rust-clippy/pull/8127)
+* [`identity_op`]: Only lint on integral operands
+ [#8183](https://github.com/rust-lang/rust-clippy/pull/8183)
+
+### Suggestion Fixes/Improvements
+
+* [`search_is_some`]: Fix suggestion for `any()` not taking item by reference
+ [#7463](https://github.com/rust-lang/rust-clippy/pull/7463)
+* [`almost_swapped`]: Now detects if there is a `no_std` or `no_core` attribute
+ and adapts the suggestion accordingly
+ [#7877](https://github.com/rust-lang/rust-clippy/pull/7877)
+* [`redundant_pattern_matching`]: Fix suggestion for deref expressions
+ [#7949](https://github.com/rust-lang/rust-clippy/pull/7949)
+* [`explicit_counter_loop`]: Now also produces a suggestion for non-`usize`
+ types [#7950](https://github.com/rust-lang/rust-clippy/pull/7950)
+* [`manual_map`]: Fix suggestion when used with unsafe functions and blocks
+ [#7968](https://github.com/rust-lang/rust-clippy/pull/7968)
+* [`option_map_or_none`]: Suggest `map` over `and_then` when possible
+ [#7971](https://github.com/rust-lang/rust-clippy/pull/7971)
+* [`option_if_let_else`]: No longer expands macros in the suggestion
+ [#7974](https://github.com/rust-lang/rust-clippy/pull/7974)
+* [`iter_cloned_collect`]: Suggest `copied` over `cloned` when possible
+ [#8006](https://github.com/rust-lang/rust-clippy/pull/8006)
+* [`doc_markdown`]: No longer uses inline hints to improve readability of
+ suggestion [#8011](https://github.com/rust-lang/rust-clippy/pull/8011)
+* [`needless_question_mark`]: Now better explains the suggestion
+ [#8028](https://github.com/rust-lang/rust-clippy/pull/8028)
+* [`single_char_pattern`]: Escape backslash `\` in suggestion
+ [#8067](https://github.com/rust-lang/rust-clippy/pull/8067)
+* [`needless_bool`]: Suggest `a != b` over `!(a == b)`
+ [#8117](https://github.com/rust-lang/rust-clippy/pull/8117)
+* [`iter_skip_next`]: Suggest to add a `mut` if it is necessary in order to
+ apply this lints suggestion
+ [#8133](https://github.com/rust-lang/rust-clippy/pull/8133)
+* [`neg_multiply`]: Now produces a suggestion
+ [#8144](https://github.com/rust-lang/rust-clippy/pull/8144)
+* [`needless_return`]: Now suggests the unit type `()` over an empty block `{}`
+ in match arms [#8185](https://github.com/rust-lang/rust-clippy/pull/8185)
+* [`suboptimal_flops`]: Now gives a syntactically correct suggestion for
+ `to_radians` and `to_degrees`
+ [#8187](https://github.com/rust-lang/rust-clippy/pull/8187)
+
+### ICE Fixes
+
+* [`undocumented_unsafe_blocks`]
+ [#7945](https://github.com/rust-lang/rust-clippy/pull/7945)
+ [#7988](https://github.com/rust-lang/rust-clippy/pull/7988)
+* [`unnecessary_cast`]
+ [#8167](https://github.com/rust-lang/rust-clippy/pull/8167)
+
+### Documentation Improvements
+
+* [`print_stdout`], [`print_stderr`], [`dbg_macro`]: Document how the lint level
+ can be changed crate-wide
+ [#8040](https://github.com/rust-lang/rust-clippy/pull/8040)
+* Added a note to the `README` that config changes don't apply to already
+ compiled code [#8175](https://github.com/rust-lang/rust-clippy/pull/8175)
+
+### Others
+
+* [Clippy's lint
+ list](https://rust-lang.github.io/rust-clippy/master/index.html) now displays
+ the version a lint was added. :tada:
+ [#7813](https://github.com/rust-lang/rust-clippy/pull/7813)
+* New and improved issue templates
+ [#8032](https://github.com/rust-lang/rust-clippy/pull/8032)
+* _Dev:_ Add `cargo dev lint` command, to run your modified Clippy version on a
+ file [#7917](https://github.com/rust-lang/rust-clippy/pull/7917)
+
+## Rust 1.58
+
+Released 2022-01-13
+
+[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
+
+### Rust 1.58.1
+
+* Move [`non_send_fields_in_send_ty`] to `nursery` (now allow-by-default)
+ [#8075](https://github.com/rust-lang/rust-clippy/pull/8075)
+* [`useless_format`]: Handle implicit named arguments
+ [#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
+
+### New lints
+
+* [`transmute_num_to_bytes`]
+ [#7805](https://github.com/rust-lang/rust-clippy/pull/7805)
+* [`match_str_case_mismatch`]
+ [#7806](https://github.com/rust-lang/rust-clippy/pull/7806)
+* [`format_in_format_args`], [`to_string_in_format_args`]
+ [#7743](https://github.com/rust-lang/rust-clippy/pull/7743)
+* [`uninit_vec`]
+ [#7682](https://github.com/rust-lang/rust-clippy/pull/7682)
+* [`fn_to_numeric_cast_any`]
+ [#7705](https://github.com/rust-lang/rust-clippy/pull/7705)
+* [`undocumented_unsafe_blocks`]
+ [#7748](https://github.com/rust-lang/rust-clippy/pull/7748)
+* [`trailing_empty_array`]
+ [#7838](https://github.com/rust-lang/rust-clippy/pull/7838)
+* [`string_slice`]
+ [#7878](https://github.com/rust-lang/rust-clippy/pull/7878)
+
+### Moves or deprecations of lints
+
+* Move [`non_send_fields_in_send_ty`] to `suspicious`
+ [#7874](https://github.com/rust-lang/rust-clippy/pull/7874)
+* Move [`non_ascii_literal`] to `restriction`
+ [#7907](https://github.com/rust-lang/rust-clippy/pull/7907)
+
+### Changes that expand what code existing lints cover
+
+* [`question_mark`] now covers `Result`
+ [#7840](https://github.com/rust-lang/rust-clippy/pull/7840)
+* Make [`useless_format`] recognize bare `format!("")`
+ [#7801](https://github.com/rust-lang/rust-clippy/pull/7801)
+* Lint on underscored variables with no side effects in [`no_effect`]
+ [#7775](https://github.com/rust-lang/rust-clippy/pull/7775)
+* Expand [`match_ref_pats`] to check for multiple reference patterns
+ [#7800](https://github.com/rust-lang/rust-clippy/pull/7800)
+
+### False positive fixes
+
+* Fix false positive of [`implicit_saturating_sub`] with `else` clause
+ [#7832](https://github.com/rust-lang/rust-clippy/pull/7832)
+* Fix [`question_mark`] when there is call in conditional predicate
+ [#7860](https://github.com/rust-lang/rust-clippy/pull/7860)
+* [`mut_mut`] no longer lints when type is defined in external macros
+ [#7795](https://github.com/rust-lang/rust-clippy/pull/7795)
+* Avoid [`eq_op`] in test functions
+ [#7811](https://github.com/rust-lang/rust-clippy/pull/7811)
+* [`cast_possible_truncation`] no longer lints when cast is coming from `signum`
+ method call [#7850](https://github.com/rust-lang/rust-clippy/pull/7850)
+* [`match_str_case_mismatch`] no longer lints on uncased characters
+ [#7865](https://github.com/rust-lang/rust-clippy/pull/7865)
+* [`ptr_arg`] no longer lints references to type aliases
+ [#7890](https://github.com/rust-lang/rust-clippy/pull/7890)
+* [`missing_safety_doc`] now also accepts "implementation safety" headers
+ [#7856](https://github.com/rust-lang/rust-clippy/pull/7856)
+* [`missing_safety_doc`] no longer lints if any parent has `#[doc(hidden)]`
+ attribute [#7849](https://github.com/rust-lang/rust-clippy/pull/7849)
+* [`if_not_else`] now ignores else-if statements
+ [#7895](https://github.com/rust-lang/rust-clippy/pull/7895)
+* Avoid linting [`cast_possible_truncation`] on bit-reducing operations
+ [#7819](https://github.com/rust-lang/rust-clippy/pull/7819)
+* Avoid linting [`field_reassign_with_default`] when `Drop` and `Copy` are
+ involved [#7794](https://github.com/rust-lang/rust-clippy/pull/7794)
+* [`unnecessary_sort_by`] now checks if argument implements `Ord` trait
+ [#7824](https://github.com/rust-lang/rust-clippy/pull/7824)
+* Fix false positive in [`match_overlapping_arm`]
+ [#7847](https://github.com/rust-lang/rust-clippy/pull/7847)
+* Prevent [`needless_lifetimes`] false positive in `async` function definition
+ [#7901](https://github.com/rust-lang/rust-clippy/pull/7901)
+
+### Suggestion fixes/improvements
+
+* Keep an initial `::` when [`doc_markdown`] suggests to use ticks
+ [#7916](https://github.com/rust-lang/rust-clippy/pull/7916)
+* Add a machine applicable suggestion for the [`doc_markdown`] missing backticks
+ lint [#7904](https://github.com/rust-lang/rust-clippy/pull/7904)
+* [`equatable_if_let`] no longer expands macros in the suggestion
+ [#7788](https://github.com/rust-lang/rust-clippy/pull/7788)
+* Make [`shadow_reuse`] suggestion less verbose
+ [#7782](https://github.com/rust-lang/rust-clippy/pull/7782)
+
+### ICE fixes
+
+* Fix ICE in [`enum_variant_names`]
+ [#7873](https://github.com/rust-lang/rust-clippy/pull/7873)
+* Fix ICE in [`undocumented_unsafe_blocks`]
+ [#7891](https://github.com/rust-lang/rust-clippy/pull/7891)
+
+### Documentation improvements
+
+* Fixed naive doc formatting for `#[must_use]` lints ([`must_use_unit`],
+ [`double_must_use`], [`must_use_candidate`], [`let_underscore_must_use`])
+ [#7827](https://github.com/rust-lang/rust-clippy/pull/7827)
+* Fix typo in example for [`match_result_ok`]
+ [#7815](https://github.com/rust-lang/rust-clippy/pull/7815)
+
+### Others
+
+* Allow giving reasons for [`disallowed_types`]
+ [#7791](https://github.com/rust-lang/rust-clippy/pull/7791)
+* Fix [`manual_assert`] and [`match_wild_err_arm`] for `#![no_std]` and Rust
+ 2021. [#7851](https://github.com/rust-lang/rust-clippy/pull/7851)
+* Fix regression in [`semicolon_if_nothing_returned`] on macros containing while
+ loops [#7789](https://github.com/rust-lang/rust-clippy/pull/7789)
+* Added a new configuration `literal-suffix-style` to enforce a certain style
+ writing [`unseparated_literal_suffix`]
+ [#7726](https://github.com/rust-lang/rust-clippy/pull/7726)
+
+## Rust 1.57
+
+Released 2021-12-02
+
+[7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa)
+
+### New Lints
+
+* [`negative_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`redundant_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`mod_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`self_named_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`manual_split_once`]
+ [#7565](https://github.com/rust-lang/rust-clippy/pull/7565)
+* [`derivable_impls`]
+ [#7570](https://github.com/rust-lang/rust-clippy/pull/7570)
+* [`needless_option_as_deref`]
+ [#7596](https://github.com/rust-lang/rust-clippy/pull/7596)
+* [`iter_not_returning_iterator`]
+ [#7610](https://github.com/rust-lang/rust-clippy/pull/7610)
+* [`same_name_method`]
+ [#7653](https://github.com/rust-lang/rust-clippy/pull/7653)
+* [`manual_assert`] [#7669](https://github.com/rust-lang/rust-clippy/pull/7669)
+* [`non_send_fields_in_send_ty`]
+ [#7709](https://github.com/rust-lang/rust-clippy/pull/7709)
+* [`equatable_if_let`]
+ [#7762](https://github.com/rust-lang/rust-clippy/pull/7762)
+
+### Moves and Deprecations
+
+* Move [`shadow_unrelated`] to `restriction`
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* Move [`option_if_let_else`] to `nursery`
+ [#7568](https://github.com/rust-lang/rust-clippy/pull/7568)
+* Move [`branches_sharing_code`] to `nursery`
+ [#7595](https://github.com/rust-lang/rust-clippy/pull/7595)
+* Rename `if_let_some_result` to [`match_result_ok`] which now also handles
+ `while let` cases [#7608](https://github.com/rust-lang/rust-clippy/pull/7608)
+* Move [`many_single_char_names`] to `pedantic`
+ [#7671](https://github.com/rust-lang/rust-clippy/pull/7671)
+* Move [`float_cmp`] to `pedantic`
+ [#7692](https://github.com/rust-lang/rust-clippy/pull/7692)
+* Rename `box_vec` to [`box_collection`] and lint on more general cases
+ [#7693](https://github.com/rust-lang/rust-clippy/pull/7693)
+* Uplift `invalid_atomic_ordering` to rustc
+ [rust-lang/rust#84039](https://github.com/rust-lang/rust/pull/84039)
+
+### Enhancements
+
+* Rewrite the `shadow*` lints, so that they find a lot more shadows and are not
+ limited to certain patterns
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* The `avoid-breaking-exported-api` configuration now also works for
+ [`box_collection`], [`redundant_allocation`], [`rc_buffer`], [`vec_box`],
+ [`option_option`], [`linkedlist`], [`rc_mutex`]
+ [#7560](https://github.com/rust-lang/rust-clippy/pull/7560)
+* [`unnecessary_unwrap`]: Now also checks for `expect`s
+ [#7584](https://github.com/rust-lang/rust-clippy/pull/7584)
+* [`disallowed_methods`]: Allow adding a reason that will be displayed with the
+ lint message
+ [#7621](https://github.com/rust-lang/rust-clippy/pull/7621)
+* [`approx_constant`]: Now checks the MSRV for `LOG10_2` and `LOG2_10`
+ [#7629](https://github.com/rust-lang/rust-clippy/pull/7629)
+* [`approx_constant`]: Add `TAU`
+ [#7642](https://github.com/rust-lang/rust-clippy/pull/7642)
+* [`needless_borrow`]: Now also lints on needless mutable borrows
+ [#7657](https://github.com/rust-lang/rust-clippy/pull/7657)
+* [`missing_safety_doc`]: Now also lints on unsafe traits
+ [#7734](https://github.com/rust-lang/rust-clippy/pull/7734)
+
+### False Positive Fixes
+
+* [`manual_map`]: No longer lints when the option is borrowed in the match and
+ also consumed in the arm
+ [#7531](https://github.com/rust-lang/rust-clippy/pull/7531)
+* [`filter_next`]: No longer lints if `filter` method is not the
+ `Iterator::filter` method
+ [#7562](https://github.com/rust-lang/rust-clippy/pull/7562)
+* [`manual_flatten`]: No longer lints if expression is used after `if let`
+ [#7566](https://github.com/rust-lang/rust-clippy/pull/7566)
+* [`option_if_let_else`]: Multiple fixes
+ [#7573](https://github.com/rust-lang/rust-clippy/pull/7573)
+ * `break` and `continue` statements local to the would-be closure are
+ allowed
+ * Don't lint in const contexts
+ * Don't lint when yield expressions are used
+ * Don't lint when the captures made by the would-be closure conflict with
+ the other branch
+ * Don't lint when a field of a local is used when the type could be
+ potentially moved from
+ * In some cases, don't lint when scrutinee expression conflicts with the
+ captures of the would-be closure
+* [`redundant_allocation`]: No longer lints on `Box<Box<dyn T>>` which replaces
+ wide pointers with thin pointers
+ [#7592](https://github.com/rust-lang/rust-clippy/pull/7592)
+* [`bool_assert_comparison`]: No longer lints on types that do not implement the
+ `Not` trait with `Output = bool`
+ [#7605](https://github.com/rust-lang/rust-clippy/pull/7605)
+* [`mut_range_bound`]: No longer lints on range bound mutations, that are
+ immediately followed by a `break;`
+ [#7607](https://github.com/rust-lang/rust-clippy/pull/7607)
+* [`mutable_key_type`]: Improve accuracy and document remaining false positives
+ and false negatives
+ [#7640](https://github.com/rust-lang/rust-clippy/pull/7640)
+* [`redundant_closure`]: Rewrite the lint to fix various false positives and
+ false negatives [#7661](https://github.com/rust-lang/rust-clippy/pull/7661)
+* [`large_enum_variant`]: No longer wrongly identifies the second largest
+ variant [#7677](https://github.com/rust-lang/rust-clippy/pull/7677)
+* [`needless_return`]: No longer lints on let-else expressions
+ [#7685](https://github.com/rust-lang/rust-clippy/pull/7685)
+* [`suspicious_else_formatting`]: No longer lints in proc-macros
+ [#7707](https://github.com/rust-lang/rust-clippy/pull/7707)
+* [`excessive_precision`]: No longer lints when in some cases the float was
+ already written in the shortest form
+ [#7722](https://github.com/rust-lang/rust-clippy/pull/7722)
+* [`doc_markdown`]: No longer lints on intra-doc links
+ [#7772](https://github.com/rust-lang/rust-clippy/pull/7772)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_operation`]: Recommend using an `assert!` instead of using a
+ function call in an indexing operation
+ [#7453](https://github.com/rust-lang/rust-clippy/pull/7453)
+* [`manual_split_once`]: Produce semantically equivalent suggestion when
+ `rsplitn` is used [#7663](https://github.com/rust-lang/rust-clippy/pull/7663)
+* [`while_let_on_iterator`]: Produce correct suggestion when using `&mut`
+ [#7690](https://github.com/rust-lang/rust-clippy/pull/7690)
+* [`manual_assert`]: No better handles complex conditions
+ [#7741](https://github.com/rust-lang/rust-clippy/pull/7741)
+* Correctly handle signs in exponents in numeric literals lints
+ [#7747](https://github.com/rust-lang/rust-clippy/pull/7747)
+* [`suspicious_map`]: Now also suggests to use `inspect` as an alternative
+ [#7770](https://github.com/rust-lang/rust-clippy/pull/7770)
+* Drop exponent from suggestion if it is 0 in numeric literals lints
+ [#7774](https://github.com/rust-lang/rust-clippy/pull/7774)
+
+### ICE Fixes
+
+* [`implicit_hasher`]
+ [#7761](https://github.com/rust-lang/rust-clippy/pull/7761)
+
+### Others
+
+* Clippy now uses the 2021
+ [Edition!](https://www.youtube.com/watch?v=q0aNduqb2Ro)
+ [#7664](https://github.com/rust-lang/rust-clippy/pull/7664)
+
+## Rust 1.56
+
+Released 2021-10-21
+
+[74d1561...7bfc26e](https://github.com/rust-lang/rust-clippy/compare/74d1561...7bfc26e)
+
+### New Lints
+
+* [`unwrap_or_else_default`]
+ [#7516](https://github.com/rust-lang/rust-clippy/pull/7516)
+
+### Enhancements
+
+* [`needless_continue`]: Now also lints in `loop { continue; }` case
+ [#7477](https://github.com/rust-lang/rust-clippy/pull/7477)
+* [`disallowed_types`]: Now also primitive types can be disallowed
+ [#7488](https://github.com/rust-lang/rust-clippy/pull/7488)
+* [`manual_swap`]: Now also lints on xor swaps
+ [#7506](https://github.com/rust-lang/rust-clippy/pull/7506)
+* [`map_flatten`]: Now also lints on the `Result` type
+ [#7522](https://github.com/rust-lang/rust-clippy/pull/7522)
+* [`no_effect`]: Now also lints on inclusive ranges
+ [#7556](https://github.com/rust-lang/rust-clippy/pull/7556)
+
+### False Positive Fixes
+
+* [`nonstandard_macro_braces`]: No longer lints on similar named nested macros
+ [#7478](https://github.com/rust-lang/rust-clippy/pull/7478)
+* [`too_many_lines`]: No longer lints in closures to avoid duplicated diagnostics
+ [#7534](https://github.com/rust-lang/rust-clippy/pull/7534)
+* [`similar_names`]: No longer complains about `iter` and `item` being too
+ similar [#7546](https://github.com/rust-lang/rust-clippy/pull/7546)
+
+### Suggestion Fixes/Improvements
+
+* [`similar_names`]: No longer suggests to insert or add an underscore as a fix
+ [#7221](https://github.com/rust-lang/rust-clippy/pull/7221)
+* [`new_without_default`]: No longer shows the full qualified type path when
+ suggesting adding a `Default` implementation
+ [#7493](https://github.com/rust-lang/rust-clippy/pull/7493)
+* [`while_let_on_iterator`]: Now suggests re-borrowing mutable references
+ [#7520](https://github.com/rust-lang/rust-clippy/pull/7520)
+* [`extend_with_drain`]: Improve code suggestion for mutable and immutable
+ references [#7533](https://github.com/rust-lang/rust-clippy/pull/7533)
+* [`trivially_copy_pass_by_ref`]: Now properly handles `Self` type
+ [#7535](https://github.com/rust-lang/rust-clippy/pull/7535)
+* [`never_loop`]: Now suggests using `if let` instead of a `for` loop when
+ applicable [#7541](https://github.com/rust-lang/rust-clippy/pull/7541)
+
+### Documentation Improvements
+
+* Clippy now uses a lint to generate its lint documentation. [Lints all the way
+ down](https://en.wikipedia.org/wiki/Turtles_all_the_way_down).
+ [#7502](https://github.com/rust-lang/rust-clippy/pull/7502)
+* Reworked Clippy's website:
+ [#7172](https://github.com/rust-lang/rust-clippy/issues/7172)
+ [#7279](https://github.com/rust-lang/rust-clippy/pull/7279)
+ * Added applicability information about lints
+ * Added a link to jump into the implementation
+ * Improved loading times
+ * Adapted some styling
+* `cargo clippy --help` now also explains the `--fix` and `--no-deps` flag
+ [#7492](https://github.com/rust-lang/rust-clippy/pull/7492)
+* [`unnested_or_patterns`]: Removed `or_patterns` feature gate in the code
+ example [#7507](https://github.com/rust-lang/rust-clippy/pull/7507)
+
+## Rust 1.55
+
+Released 2021-09-09
+
+[3ae8faf...74d1561](https://github.com/rust-lang/rust-clippy/compare/3ae8faf...74d1561)
+
+### Important Changes
+
+* Stabilized `cargo clippy --fix` :tada:
+ [#7405](https://github.com/rust-lang/rust-clippy/pull/7405)
+
+### New Lints
+
+* [`rc_mutex`]
+ [#7316](https://github.com/rust-lang/rust-clippy/pull/7316)
+* [`nonstandard_macro_braces`]
+ [#7299](https://github.com/rust-lang/rust-clippy/pull/7299)
+* [`strlen_on_c_strings`]
+ [#7243](https://github.com/rust-lang/rust-clippy/pull/7243)
+* [`self_named_constructors`]
+ [#7403](https://github.com/rust-lang/rust-clippy/pull/7403)
+* [`disallowed_script_idents`]
+ [#7400](https://github.com/rust-lang/rust-clippy/pull/7400)
+* [`disallowed_types`]
+ [#7315](https://github.com/rust-lang/rust-clippy/pull/7315)
+* [`missing_enforced_import_renames`]
+ [#7300](https://github.com/rust-lang/rust-clippy/pull/7300)
+* [`extend_with_drain`]
+ [#7270](https://github.com/rust-lang/rust-clippy/pull/7270)
+
+### Moves and Deprecations
+
+* Moved [`from_iter_instead_of_collect`] to `pedantic`
+ [#7375](https://github.com/rust-lang/rust-clippy/pull/7375)
+* Added `suspicious` as a new lint group for *code that is most likely wrong or useless*
+ [#7350](https://github.com/rust-lang/rust-clippy/pull/7350)
+ * Moved [`blanket_clippy_restriction_lints`] to `suspicious`
+ * Moved [`empty_loop`] to `suspicious`
+ * Moved [`eval_order_dependence`] to `suspicious`
+ * Moved [`float_equality_without_abs`] to `suspicious`
+ * Moved [`for_loops_over_fallibles`] to `suspicious`
+ * Moved [`misrefactored_assign_op`] to `suspicious`
+ * Moved [`mut_range_bound`] to `suspicious`
+ * Moved [`mutable_key_type`] to `suspicious`
+ * Moved [`suspicious_arithmetic_impl`] to `suspicious`
+ * Moved [`suspicious_assignment_formatting`] to `suspicious`
+ * Moved [`suspicious_else_formatting`] to `suspicious`
+ * Moved [`suspicious_map`] to `suspicious`
+ * Moved [`suspicious_op_assign_impl`] to `suspicious`
+ * Moved [`suspicious_unary_op_formatting`] to `suspicious`
+
+### Enhancements
+
+* [`while_let_on_iterator`]: Now suggests `&mut iter` inside closures
+ [#7262](https://github.com/rust-lang/rust-clippy/pull/7262)
+* [`doc_markdown`]:
+ * Now detects unbalanced ticks
+ [#7357](https://github.com/rust-lang/rust-clippy/pull/7357)
+ * Add `FreeBSD` to the default configuration as an allowed identifier
+ [#7334](https://github.com/rust-lang/rust-clippy/pull/7334)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]: Now allows wildcards for enums with unstable
+ or hidden variants
+ [#7407](https://github.com/rust-lang/rust-clippy/pull/7407)
+* [`redundant_allocation`]: Now additionally supports the `Arc<>` type
+ [#7308](https://github.com/rust-lang/rust-clippy/pull/7308)
+* [`disallowed_names`]: Now allows disallowed names in test code
+ [#7379](https://github.com/rust-lang/rust-clippy/pull/7379)
+* [`redundant_closure`]: Suggests `&mut` for `FnMut`
+ [#7437](https://github.com/rust-lang/rust-clippy/pull/7437)
+* [`disallowed_methods`], [`disallowed_types`]: The configuration values `disallowed-method` and `disallowed-type`
+ no longer require fully qualified paths
+ [#7345](https://github.com/rust-lang/rust-clippy/pull/7345)
+* [`zst_offset`]: Fixed lint invocation after it was accidentally suppressed
+ [#7396](https://github.com/rust-lang/rust-clippy/pull/7396)
+
+### False Positive Fixes
+
+* [`default_numeric_fallback`]: No longer lints on float literals as function arguments
+ [#7446](https://github.com/rust-lang/rust-clippy/pull/7446)
+* [`use_self`]: No longer lints on type parameters
+ [#7288](https://github.com/rust-lang/rust-clippy/pull/7288)
+* [`unimplemented`]: Now ignores the `assert` and `debug_assert` macros
+ [#7439](https://github.com/rust-lang/rust-clippy/pull/7439)
+* [`branches_sharing_code`]: Now always checks for block expressions
+ [#7462](https://github.com/rust-lang/rust-clippy/pull/7462)
+* [`field_reassign_with_default`]: No longer triggers in macros
+ [#7160](https://github.com/rust-lang/rust-clippy/pull/7160)
+* [`redundant_clone`]: No longer lints on required clones for borrowed data
+ [#7346](https://github.com/rust-lang/rust-clippy/pull/7346)
+* [`default_numeric_fallback`]: No longer triggers in external macros
+ [#7325](https://github.com/rust-lang/rust-clippy/pull/7325)
+* [`needless_bool`]: No longer lints in macros
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`useless_format`]: No longer triggers when additional text is being appended
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`assertions_on_constants`]: `cfg!(...)` is no longer considered to be a constant
+ [#7319](https://github.com/rust-lang/rust-clippy/pull/7319)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_collect`]: Now show correct lint messages for shadowed values
+ [#7289](https://github.com/rust-lang/rust-clippy/pull/7289)
+* [`wrong_pub_self_convention`]: The deprecated message now suggest the correct configuration value
+ [#7382](https://github.com/rust-lang/rust-clippy/pull/7382)
+* [`semicolon_if_nothing_returned`]: Allow missing semicolon in blocks with only one expression
+ [#7326](https://github.com/rust-lang/rust-clippy/pull/7326)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#7470](https://github.com/rust-lang/rust-clippy/pull/7470)
+* [`redundant_pattern_matching`]
+ [#7471](https://github.com/rust-lang/rust-clippy/pull/7471)
+* [`modulo_one`]
+ [#7473](https://github.com/rust-lang/rust-clippy/pull/7473)
+* [`use_self`]
+ [#7428](https://github.com/rust-lang/rust-clippy/pull/7428)
+
+## Rust 1.54
+
+Released 2021-07-29
+
+[7c7683c...3ae8faf](https://github.com/rust-lang/rust-clippy/compare/7c7683c...3ae8faf)
+
+### New Lints
+
+- [`ref_binding_to_reference`]
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`needless_bitwise_bool`]
+ [#7133](https://github.com/rust-lang/rust-clippy/pull/7133)
+- [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225)
+- [`manual_str_repeat`]
+ [#7265](https://github.com/rust-lang/rust-clippy/pull/7265)
+- [`suspicious_splitn`]
+ [#7292](https://github.com/rust-lang/rust-clippy/pull/7292)
+
+### Moves and Deprecations
+
+- Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of
+ the new `avoid-breaking-exported-api` config option (see
+ [Enhancements](#1-54-enhancements))
+ [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- Move [`inconsistent_struct_constructor`] to `pedantic`
+ [#7193](https://github.com/rust-lang/rust-clippy/pull/7193)
+- Move [`needless_borrow`] to `style` (now warn-by-default)
+ [#7254](https://github.com/rust-lang/rust-clippy/pull/7254)
+- Move [`suspicious_operation_groupings`] to `nursery`
+ [#7266](https://github.com/rust-lang/rust-clippy/pull/7266)
+- Move [`semicolon_if_nothing_returned`] to `pedantic`
+ [#7268](https://github.com/rust-lang/rust-clippy/pull/7268)
+
+### Enhancements <a name="1-54-enhancements"></a>
+
+- [`while_let_on_iterator`]: Now also lints in nested loops
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix`
+ [#7156](https://github.com/rust-lang/rust-clippy/pull/7156)
+- [`needless_collect`]: Now also lints on assignments with type annotations
+ [#7163](https://github.com/rust-lang/rust-clippy/pull/7163)
+- [`if_then_some_else_none`]: Now works with the MSRV config
+ [#7177](https://github.com/rust-lang/rust-clippy/pull/7177)
+- Add `avoid-breaking-exported-api` config option for the lints
+ [`enum_variant_names`], [`large_types_passed_by_value`],
+ [`trivially_copy_pass_by_ref`], [`unnecessary_wraps`],
+ [`upper_case_acronyms`], and [`wrong_self_convention`]. We recommend to set
+ this configuration option to `false` before a major release (1.0/2.0/...) to
+ clean up the API [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- [`needless_collect`]: Now lints on even more data structures
+ [#7188](https://github.com/rust-lang/rust-clippy/pull/7188)
+- [`missing_docs_in_private_items`]: No longer sees `#[<name> = "<value>"]` like
+ attributes as sufficient documentation
+ [#7281](https://github.com/rust-lang/rust-clippy/pull/7281)
+- [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]:
+ Now work as expected when used with `allow`
+ [#7282](https://github.com/rust-lang/rust-clippy/pull/7282)
+
+### False Positive Fixes
+
+- [`implicit_return`]: Now takes all diverging functions in account to avoid
+ false positives [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+- [`while_let_on_iterator`]: No longer lints when the iterator is a struct field
+ and the struct is used in the loop
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`multiple_inherent_impl`]: No longer lints with generic arguments
+ [#7089](https://github.com/rust-lang/rust-clippy/pull/7089)
+- [`comparison_chain`]: No longer lints in a `const` context
+ [#7118](https://github.com/rust-lang/rust-clippy/pull/7118)
+- [`while_immutable_condition`]: Fix false positive where mutation in the loop
+ variable wasn't picked up
+ [#7144](https://github.com/rust-lang/rust-clippy/pull/7144)
+- [`default_trait_access`]: No longer lints in macros
+ [#7150](https://github.com/rust-lang/rust-clippy/pull/7150)
+- [`needless_question_mark`]: No longer lints when the inner value is implicitly
+ dereferenced [#7165](https://github.com/rust-lang/rust-clippy/pull/7165)
+- [`unused_unit`]: No longer lints when multiple macro contexts are involved
+ [#7167](https://github.com/rust-lang/rust-clippy/pull/7167)
+- [`eval_order_dependence`]: Fix false positive in async context
+ [#7174](https://github.com/rust-lang/rust-clippy/pull/7174)
+- [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the
+ type [#7175](https://github.com/rust-lang/rust-clippy/pull/7175)
+- [`wrong_self_convention`]: No longer lints in trait implementations of
+ non-`Copy` types [#7182](https://github.com/rust-lang/rust-clippy/pull/7182)
+- [`suboptimal_flops`]: No longer lints on `powi(2)`
+ [#7201](https://github.com/rust-lang/rust-clippy/pull/7201)
+- [`wrong_self_convention`]: No longer lints if there is no implicit `self`
+ [#7215](https://github.com/rust-lang/rust-clippy/pull/7215)
+- [`option_if_let_else`]: No longer lints on `else if let` pattern
+ [#7216](https://github.com/rust-lang/rust-clippy/pull/7216)
+- [`use_self`], [`useless_conversion`]: Fix false positives when generic
+ arguments are involved
+ [#7223](https://github.com/rust-lang/rust-clippy/pull/7223)
+- [`manual_unwrap_or`]: Fix false positive with deref coercion
+ [#7233](https://github.com/rust-lang/rust-clippy/pull/7233)
+- [`similar_names`]: No longer lints on `wparam`/`lparam`
+ [#7255](https://github.com/rust-lang/rust-clippy/pull/7255)
+- [`redundant_closure`]: No longer lints on using the `vec![]` macro in a
+ closure [#7263](https://github.com/rust-lang/rust-clippy/pull/7263)
+
+### Suggestion Fixes/Improvements
+
+- [`implicit_return`]
+ [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+ - Fix suggestion for async functions
+ - Improve suggestion with macros
+ - Suggest to change `break` to `return` when appropriate
+- [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`match_single_binding`]: Improve suggestion when match scrutinee has side
+ effects [#7095](https://github.com/rust-lang/rust-clippy/pull/7095)
+- [`needless_borrow`]: Now suggests to also change usage sites as needed
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`write_with_newline`]: Improve suggestion when only `\n` is written to the
+ buffer [#7183](https://github.com/rust-lang/rust-clippy/pull/7183)
+- [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also
+ when a `<_ as Trait>::_` is involved
+ [#7264](https://github.com/rust-lang/rust-clippy/pull/7264)
+- [`not_unsafe_ptr_arg_deref`]: Improved error message
+ [#7294](https://github.com/rust-lang/rust-clippy/pull/7294)
+
+### ICE Fixes
+
+- Fix ICE when running Clippy on `libstd`
+ [#7140](https://github.com/rust-lang/rust-clippy/pull/7140)
+- [`implicit_return`]
+ [#7242](https://github.com/rust-lang/rust-clippy/pull/7242)
+
+## Rust 1.53
+
+Released 2021-06-17
+
+[6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c)
+
+### New Lints
+
+* [`option_filter_map`]
+ [#6342](https://github.com/rust-lang/rust-clippy/pull/6342)
+* [`branches_sharing_code`]
+ [#6463](https://github.com/rust-lang/rust-clippy/pull/6463)
+* [`needless_for_each`]
+ [#6706](https://github.com/rust-lang/rust-clippy/pull/6706)
+* [`if_then_some_else_none`]
+ [#6859](https://github.com/rust-lang/rust-clippy/pull/6859)
+* [`non_octal_unix_permissions`]
+ [#7001](https://github.com/rust-lang/rust-clippy/pull/7001)
+* [`unnecessary_self_imports`]
+ [#7072](https://github.com/rust-lang/rust-clippy/pull/7072)
+* [`bool_assert_comparison`]
+ [#7083](https://github.com/rust-lang/rust-clippy/pull/7083)
+* [`cloned_instead_of_copied`]
+ [#7098](https://github.com/rust-lang/rust-clippy/pull/7098)
+* [`flat_map_option`]
+ [#7101](https://github.com/rust-lang/rust-clippy/pull/7101)
+
+### Moves and Deprecations
+
+* Deprecate [`filter_map`] lint
+ [#7059](https://github.com/rust-lang/rust-clippy/pull/7059)
+* Move [`transmute_ptr_to_ptr`] to `pedantic`
+ [#7102](https://github.com/rust-lang/rust-clippy/pull/7102)
+
+### Enhancements
+
+* [`mem_replace_with_default`]: Also lint on common std constructors
+ [#6820](https://github.com/rust-lang/rust-clippy/pull/6820)
+* [`wrong_self_convention`]: Also lint on `to_*_mut` methods
+ [#6828](https://github.com/rust-lang/rust-clippy/pull/6828)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]:
+ [#6863](https://github.com/rust-lang/rust-clippy/pull/6863)
+ * Attempt to find a common path prefix in suggestion
+ * Don't lint on `Option` and `Result`
+ * Consider `Self` prefix
+* [`explicit_deref_methods`]: Also lint on chained `deref` calls
+ [#6865](https://github.com/rust-lang/rust-clippy/pull/6865)
+* [`or_fun_call`]: Also lint on `unsafe` blocks
+ [#6928](https://github.com/rust-lang/rust-clippy/pull/6928)
+* [`vec_box`], [`linkedlist`], [`option_option`]: Also lint in `const` and
+ `static` items [#6938](https://github.com/rust-lang/rust-clippy/pull/6938)
+* [`search_is_some`]: Also check for `is_none`
+ [#6942](https://github.com/rust-lang/rust-clippy/pull/6942)
+* [`string_lit_as_bytes`]: Also lint on `into_bytes`
+ [#6959](https://github.com/rust-lang/rust-clippy/pull/6959)
+* [`len_without_is_empty`]: Also lint if function signatures of `len` and
+ `is_empty` don't match
+ [#6980](https://github.com/rust-lang/rust-clippy/pull/6980)
+* [`redundant_pattern_matching`]: Also lint if the pattern is a `&` pattern
+ [#6991](https://github.com/rust-lang/rust-clippy/pull/6991)
+* [`clone_on_copy`]: Also lint on chained method calls taking `self` by value
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`missing_panics_doc`]: Also lint on `assert_eq!` and `assert_ne!`
+ [#7029](https://github.com/rust-lang/rust-clippy/pull/7029)
+* [`needless_return`]: Also lint in `async` functions
+ [#7067](https://github.com/rust-lang/rust-clippy/pull/7067)
+* [`unused_io_amount`]: Also lint on expressions like `_.read().ok()?`
+ [#7100](https://github.com/rust-lang/rust-clippy/pull/7100)
+* [`iter_cloned_collect`]: Also lint on large arrays, since const-generics are
+ now stable [#7138](https://github.com/rust-lang/rust-clippy/pull/7138)
+
+### False Positive Fixes
+
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6805](https://github.com/rust-lang/rust-clippy/pull/6805)
+* [`suspicious_map`]: No longer lints when side effects may occur inside the
+ `map` call [#6831](https://github.com/rust-lang/rust-clippy/pull/6831)
+* [`manual_map`], [`manual_unwrap_or`]: No longer lints in `const` functions
+ [#6917](https://github.com/rust-lang/rust-clippy/pull/6917)
+* [`wrong_self_convention`]: Now respects `Copy` types
+ [#6924](https://github.com/rust-lang/rust-clippy/pull/6924)
+* [`needless_question_mark`]: No longer lints if the `?` and the `Some(..)` come
+ from different macro contexts [#6935](https://github.com/rust-lang/rust-clippy/pull/6935)
+* [`map_entry`]: Better detect if the entry API can be used
+ [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`or_fun_call`]: No longer lints on some `len` function calls
+ [#6950](https://github.com/rust-lang/rust-clippy/pull/6950)
+* [`new_ret_no_self`]: No longer lints when `Self` is returned with different
+ generic arguments [#6952](https://github.com/rust-lang/rust-clippy/pull/6952)
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6981](https://github.com/rust-lang/rust-clippy/pull/6981)
+* [`explicit_into_iter_loop`]: Only lint when `into_iter` is an implementation
+ of `IntoIterator` [#6982](https://github.com/rust-lang/rust-clippy/pull/6982)
+* [`expl_impl_clone_on_copy`]: Take generic constraints into account before
+ suggesting to use `derive` instead
+ [#6993](https://github.com/rust-lang/rust-clippy/pull/6993)
+* [`missing_panics_doc`]: No longer lints when only debug-assertions are used
+ [#6996](https://github.com/rust-lang/rust-clippy/pull/6996)
+* [`clone_on_copy`]: Only lint when using the `Clone` trait
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`wrong_self_convention`]: No longer lints inside a trait implementation
+ [#7002](https://github.com/rust-lang/rust-clippy/pull/7002)
+* [`redundant_clone`]: No longer lints when the cloned value is modified while
+ the clone is in use
+ [#7011](https://github.com/rust-lang/rust-clippy/pull/7011)
+* [`same_item_push`]: No longer lints if the `Vec` is used in the loop body
+ [#7018](https://github.com/rust-lang/rust-clippy/pull/7018)
+* [`cargo_common_metadata`]: Remove author requirement
+ [#7026](https://github.com/rust-lang/rust-clippy/pull/7026)
+* [`panic_in_result_fn`]: No longer lints on `debug_assert` family
+ [#7060](https://github.com/rust-lang/rust-clippy/pull/7060)
+* [`panic`]: No longer wrongfully lints on `debug_assert` with message
+ [#7063](https://github.com/rust-lang/rust-clippy/pull/7063)
+* [`wrong_self_convention`]: No longer lints in trait implementations where no
+ `self` is involved [#7064](https://github.com/rust-lang/rust-clippy/pull/7064)
+* [`missing_const_for_fn`]: No longer lints when unstable `const` function is
+ involved [#7076](https://github.com/rust-lang/rust-clippy/pull/7076)
+* [`suspicious_else_formatting`]: Allow Allman style braces
+ [#7087](https://github.com/rust-lang/rust-clippy/pull/7087)
+* [`inconsistent_struct_constructor`]: No longer lints in macros
+ [#7097](https://github.com/rust-lang/rust-clippy/pull/7097)
+* [`single_component_path_imports`]: No longer lints on macro re-exports
+ [#7120](https://github.com/rust-lang/rust-clippy/pull/7120)
+
+### Suggestion Fixes/Improvements
+
+* [`redundant_pattern_matching`]: Add a note when applying this lint would
+ change the drop order
+ [#6568](https://github.com/rust-lang/rust-clippy/pull/6568)
+* [`write_literal`], [`print_literal`]: Add auto-applicable suggestion
+ [#6821](https://github.com/rust-lang/rust-clippy/pull/6821)
+* [`manual_map`]: Fix suggestion for complex `if let ... else` chains
+ [#6856](https://github.com/rust-lang/rust-clippy/pull/6856)
+* [`inconsistent_struct_constructor`]: Make lint description and message clearer
+ [#6892](https://github.com/rust-lang/rust-clippy/pull/6892)
+* [`map_entry`]: Now suggests `or_insert`, `insert_with` or `match _.entry(_)`
+ as appropriate [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`manual_flatten`]: Suggest to insert `copied` if necessary
+ [#6962](https://github.com/rust-lang/rust-clippy/pull/6962)
+* [`redundant_slicing`]: Fix suggestion when a re-borrow might be required or
+ when the value is from a macro call
+ [#6975](https://github.com/rust-lang/rust-clippy/pull/6975)
+* [`match_wildcard_for_single_variants`]: Fix suggestion for hidden variant
+ [#6988](https://github.com/rust-lang/rust-clippy/pull/6988)
+* [`clone_on_copy`]: Correct suggestion when the cloned value is a macro call
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`manual_map`]: Fix suggestion at the end of an if chain
+ [#7004](https://github.com/rust-lang/rust-clippy/pull/7004)
+* Fix needless parenthesis output in multiple lint suggestions
+ [#7013](https://github.com/rust-lang/rust-clippy/pull/7013)
+* [`needless_collect`]: Better explanation in the lint message
+ [#7020](https://github.com/rust-lang/rust-clippy/pull/7020)
+* [`useless_vec`]: Now considers mutability
+ [#7036](https://github.com/rust-lang/rust-clippy/pull/7036)
+* [`useless_format`]: Wrap the content in braces if necessary
+ [#7092](https://github.com/rust-lang/rust-clippy/pull/7092)
+* [`single_match`]: Don't suggest an equality check for types which don't
+ implement `PartialEq`
+ [#7093](https://github.com/rust-lang/rust-clippy/pull/7093)
+* [`from_over_into`]: Mention type in help message
+ [#7099](https://github.com/rust-lang/rust-clippy/pull/7099)
+* [`manual_unwrap_or`]: Fix invalid code suggestion due to a macro call
+ [#7136](https://github.com/rust-lang/rust-clippy/pull/7136)
+
+### ICE Fixes
+
+* [`macro_use_imports`]
+ [#7022](https://github.com/rust-lang/rust-clippy/pull/7022)
+* [`missing_panics_doc`]
+ [#7034](https://github.com/rust-lang/rust-clippy/pull/7034)
+* [`tabs_in_doc_comments`]
+ [#7039](https://github.com/rust-lang/rust-clippy/pull/7039)
+* [`missing_const_for_fn`]
+ [#7128](https://github.com/rust-lang/rust-clippy/pull/7128)
+
+### Others
+
+* [Clippy's lint
+ list](https://rust-lang.github.io/rust-clippy/master/index.html) now supports
+ themes [#7030](https://github.com/rust-lang/rust-clippy/pull/7030)
+* Lints that were uplifted to `rustc` now mention the new `rustc` name in the
+ deprecation warning
+ [#7056](https://github.com/rust-lang/rust-clippy/pull/7056)
+
+## Rust 1.52
+
+Released 2021-05-06
+
+[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
+
+### New Lints
+
+* [`from_str_radix_10`]
+ [#6717](https://github.com/rust-lang/rust-clippy/pull/6717)
+* [`implicit_clone`]
+ [#6730](https://github.com/rust-lang/rust-clippy/pull/6730)
+* [`semicolon_if_nothing_returned`]
+ [#6681](https://github.com/rust-lang/rust-clippy/pull/6681)
+* [`manual_flatten`]
+ [#6646](https://github.com/rust-lang/rust-clippy/pull/6646)
+* [`inconsistent_struct_constructor`]
+ [#6769](https://github.com/rust-lang/rust-clippy/pull/6769)
+* [`iter_count`]
+ [#6791](https://github.com/rust-lang/rust-clippy/pull/6791)
+* [`default_numeric_fallback`]
+ [#6662](https://github.com/rust-lang/rust-clippy/pull/6662)
+* [`bytes_nth`]
+ [#6695](https://github.com/rust-lang/rust-clippy/pull/6695)
+* [`filter_map_identity`]
+ [#6685](https://github.com/rust-lang/rust-clippy/pull/6685)
+* [`manual_map`]
+ [#6573](https://github.com/rust-lang/rust-clippy/pull/6573)
+
+### Moves and Deprecations
+
+* Moved [`upper_case_acronyms`] to `pedantic`
+ [#6775](https://github.com/rust-lang/rust-clippy/pull/6775)
+* Moved [`manual_map`] to `nursery`
+ [#6796](https://github.com/rust-lang/rust-clippy/pull/6796)
+* Moved [`unnecessary_wraps`] to `pedantic`
+ [#6765](https://github.com/rust-lang/rust-clippy/pull/6765)
+* Moved [`trivial_regex`] to `nursery`
+ [#6696](https://github.com/rust-lang/rust-clippy/pull/6696)
+* Moved [`naive_bytecount`] to `pedantic`
+ [#6825](https://github.com/rust-lang/rust-clippy/pull/6825)
+* Moved [`upper_case_acronyms`] to `style`
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* Moved [`manual_map`] to `style`
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+
+### Enhancements
+
+* [`disallowed_methods`]: Now supports functions in addition to methods
+ [#6674](https://github.com/rust-lang/rust-clippy/pull/6674)
+* [`upper_case_acronyms`]: Added a new configuration `upper-case-acronyms-aggressive` to
+ trigger the lint if there is more than one uppercase character next to each other
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* [`collapsible_match`]: Now supports block comparison with different value names
+ [#6754](https://github.com/rust-lang/rust-clippy/pull/6754)
+* [`unnecessary_wraps`]: Will now suggest removing unnecessary wrapped return unit type, like `Option<()>`
+ [#6665](https://github.com/rust-lang/rust-clippy/pull/6665)
+* Improved value usage detection in closures
+ [#6698](https://github.com/rust-lang/rust-clippy/pull/6698)
+
+### False Positive Fixes
+
+* [`use_self`]: No longer lints in macros
+ [#6833](https://github.com/rust-lang/rust-clippy/pull/6833)
+* [`use_self`]: Fixed multiple false positives for: generics, associated types and derive implementations
+ [#6179](https://github.com/rust-lang/rust-clippy/pull/6179)
+* [`missing_inline_in_public_items`]: No longer lints for procedural macros
+ [#6814](https://github.com/rust-lang/rust-clippy/pull/6814)
+* [`inherent_to_string`]: No longer lints on functions with function generics
+ [#6771](https://github.com/rust-lang/rust-clippy/pull/6771)
+* [`doc_markdown`]: Add `OpenDNS` to the default configuration as an allowed identifier
+ [#6783](https://github.com/rust-lang/rust-clippy/pull/6783)
+* [`missing_panics_doc`]: No longer lints on [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html)
+ [#6700](https://github.com/rust-lang/rust-clippy/pull/6700)
+* [`collapsible_if`]: No longer lints on if statements with attributes
+ [#6701](https://github.com/rust-lang/rust-clippy/pull/6701)
+* [`match_same_arms`]: Only considers empty blocks as equal if the tokens contained are the same
+ [#6843](https://github.com/rust-lang/rust-clippy/pull/6843)
+* [`redundant_closure`]: Now ignores macros
+ [#6871](https://github.com/rust-lang/rust-clippy/pull/6871)
+* [`manual_map`]: Fixed false positives when control flow statements like `return`, `break` etc. are used
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* [`vec_init_then_push`]: Fixed false positives for loops and if statements
+ [#6697](https://github.com/rust-lang/rust-clippy/pull/6697)
+* [`len_without_is_empty`]: Will now consider multiple impl blocks and `#[allow]` on
+ the `len` method as well as the type definition.
+ [#6853](https://github.com/rust-lang/rust-clippy/pull/6853)
+* [`let_underscore_drop`]: Only lints on types which implement `Drop`
+ [#6682](https://github.com/rust-lang/rust-clippy/pull/6682)
+* [`unit_arg`]: No longer lints on unit arguments when they come from a path expression.
+ [#6601](https://github.com/rust-lang/rust-clippy/pull/6601)
+* [`cargo_common_metadata`]: No longer lints if
+ [`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)
+ is defined in the manifest
+ [#6650](https://github.com/rust-lang/rust-clippy/pull/6650)
+
+### Suggestion Fixes/Improvements
+
+* [`collapsible_match`]: Fixed lint message capitalization
+ [#6766](https://github.com/rust-lang/rust-clippy/pull/6766)
+* [`or_fun_call`]: Improved suggestions for `or_insert(vec![])`
+ [#6790](https://github.com/rust-lang/rust-clippy/pull/6790)
+* [`manual_map`]: No longer expands macros in the suggestions
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* Aligned Clippy's lint messages with the rustc dev guide
+ [#6787](https://github.com/rust-lang/rust-clippy/pull/6787)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#6866](https://github.com/rust-lang/rust-clippy/pull/6866)
+
+### Documentation Improvements
+
+* [`useless_format`]: Improved the documentation example
+ [#6854](https://github.com/rust-lang/rust-clippy/pull/6854)
+* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper
+ [#6782](https://github.com/rust-lang/rust-clippy/pull/6782)
+
+### Others
+* Running `cargo clippy` after `cargo check` now works as expected
+ (`cargo clippy` and `cargo check` no longer shares the same build cache)
+ [#6687](https://github.com/rust-lang/rust-clippy/pull/6687)
+* Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed.
+ [#6834](https://github.com/rust-lang/rust-clippy/pull/6834)
+* Extracted Clippy's `utils` module into the new `clippy_utils` crate
+ [#6756](https://github.com/rust-lang/rust-clippy/pull/6756)
+* Clippy lintcheck tool improvements
+ [#6800](https://github.com/rust-lang/rust-clippy/pull/6800)
+ [#6735](https://github.com/rust-lang/rust-clippy/pull/6735)
+ [#6764](https://github.com/rust-lang/rust-clippy/pull/6764)
+ [#6708](https://github.com/rust-lang/rust-clippy/pull/6708)
+ [#6780](https://github.com/rust-lang/rust-clippy/pull/6780)
+ [#6686](https://github.com/rust-lang/rust-clippy/pull/6686)
+
+## Rust 1.51
+
+Released 2021-03-25
+
+[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
+
+### New Lints
+
+* [`upper_case_acronyms`]
+ [#6475](https://github.com/rust-lang/rust-clippy/pull/6475)
+* [`from_over_into`] [#6476](https://github.com/rust-lang/rust-clippy/pull/6476)
+* [`case_sensitive_file_extension_comparisons`]
+ [#6500](https://github.com/rust-lang/rust-clippy/pull/6500)
+* [`needless_question_mark`]
+ [#6507](https://github.com/rust-lang/rust-clippy/pull/6507)
+* [`missing_panics_doc`]
+ [#6523](https://github.com/rust-lang/rust-clippy/pull/6523)
+* [`redundant_slicing`]
+ [#6528](https://github.com/rust-lang/rust-clippy/pull/6528)
+* [`vec_init_then_push`]
+ [#6538](https://github.com/rust-lang/rust-clippy/pull/6538)
+* [`ptr_as_ptr`] [#6542](https://github.com/rust-lang/rust-clippy/pull/6542)
+* [`collapsible_else_if`] (split out from `collapsible_if`)
+ [#6544](https://github.com/rust-lang/rust-clippy/pull/6544)
+* [`inspect_for_each`] [#6577](https://github.com/rust-lang/rust-clippy/pull/6577)
+* [`manual_filter_map`]
+ [#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
+* [`exhaustive_enums`]
+ [#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
+* [`exhaustive_structs`]
+ [#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
+
+### Moves and Deprecations
+
+* Replace [`find_map`] with [`manual_find_map`]
+ [#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
+* `unknown_clippy_lints` Now integrated in the `unknown_lints` rustc lint
+ [#6653](https://github.com/rust-lang/rust-clippy/pull/6653)
+
+### Enhancements
+
+* [`ptr_arg`] Now also suggests to use `&Path` instead of `&PathBuf`
+ [#6506](https://github.com/rust-lang/rust-clippy/pull/6506)
+* [`cast_ptr_alignment`] Also lint when the `pointer::cast` method is used
+ [#6557](https://github.com/rust-lang/rust-clippy/pull/6557)
+* [`collapsible_match`] Now also deals with `&` and `*` operators in the `match`
+ scrutinee [#6619](https://github.com/rust-lang/rust-clippy/pull/6619)
+
+### False Positive Fixes
+
+* [`similar_names`] Ignore underscore prefixed names
+ [#6403](https://github.com/rust-lang/rust-clippy/pull/6403)
+* [`print_literal`] and [`write_literal`] No longer lint numeric literals
+ [#6408](https://github.com/rust-lang/rust-clippy/pull/6408)
+* [`large_enum_variant`] No longer lints in external macros
+ [#6485](https://github.com/rust-lang/rust-clippy/pull/6485)
+* [`empty_enum`] Only lint if `never_type` feature is enabled
+ [#6513](https://github.com/rust-lang/rust-clippy/pull/6513)
+* [`field_reassign_with_default`] No longer lints in macros
+ [#6553](https://github.com/rust-lang/rust-clippy/pull/6553)
+* [`size_of_in_element_count`] No longer lints when dividing by element size
+ [#6578](https://github.com/rust-lang/rust-clippy/pull/6578)
+* [`needless_return`] No longer lints in macros
+ [#6586](https://github.com/rust-lang/rust-clippy/pull/6586)
+* [`match_overlapping_arm`] No longer lint when first arm is completely included
+ in second arm [#6603](https://github.com/rust-lang/rust-clippy/pull/6603)
+* [`doc_markdown`] Add `WebGL` to the default configuration as an allowed
+ identifier [#6605](https://github.com/rust-lang/rust-clippy/pull/6605)
+
+### Suggestion Fixes/Improvements
+
+* [`field_reassign_with_default`] Don't expand macro in lint suggestion
+ [#6531](https://github.com/rust-lang/rust-clippy/pull/6531)
+* [`match_like_matches_macro`] Strip references in suggestion
+ [#6532](https://github.com/rust-lang/rust-clippy/pull/6532)
+* [`single_match`] Suggest `if` over `if let` when possible
+ [#6574](https://github.com/rust-lang/rust-clippy/pull/6574)
+* `ref_in_deref` Use parentheses correctly in suggestion
+ [#6609](https://github.com/rust-lang/rust-clippy/pull/6609)
+* [`stable_sort_primitive`] Clarify error message
+ [#6611](https://github.com/rust-lang/rust-clippy/pull/6611)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#6582](https://github.com/rust-lang/rust-clippy/pull/6582)
+
+### Documentation Improvements
+
+* Improve search performance on the Clippy website and make it possible to
+ directly search for lints on the GitHub issue tracker
+ [#6483](https://github.com/rust-lang/rust-clippy/pull/6483)
+* Clean up `README.md` by removing outdated paragraph
+ [#6488](https://github.com/rust-lang/rust-clippy/pull/6488)
+* [`await_holding_refcell_ref`] and [`await_holding_lock`]
+ [#6585](https://github.com/rust-lang/rust-clippy/pull/6585)
+* [`as_conversions`] [#6608](https://github.com/rust-lang/rust-clippy/pull/6608)
+
+### Others
+
+* Clippy now has a [Roadmap] for 2021. If you like to get involved in a bigger
+ project, take a look at the [Roadmap project page]. All issues listed there
+ are actively mentored
+ [#6462](https://github.com/rust-lang/rust-clippy/pull/6462)
+* The Clippy version number now corresponds to the Rust version number
+ [#6526](https://github.com/rust-lang/rust-clippy/pull/6526)
+* Fix oversight which caused Clippy to lint deps in some environments, where
+ `CLIPPY_TESTS=true` was set somewhere
+ [#6575](https://github.com/rust-lang/rust-clippy/pull/6575)
+* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool
+ [#6469](https://github.com/rust-lang/rust-clippy/pull/6469)
+
+[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/proposals/roadmap-2021.md
+[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3
+
+## Rust 1.50
+
+Released 2021-02-11
+
+[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
+
+### New Lints
+
+* [`suspicious_operation_groupings`] [#6086](https://github.com/rust-lang/rust-clippy/pull/6086)
+* [`size_of_in_element_count`] [#6394](https://github.com/rust-lang/rust-clippy/pull/6394)
+* [`unnecessary_wraps`] [#6070](https://github.com/rust-lang/rust-clippy/pull/6070)
+* [`let_underscore_drop`] [#6305](https://github.com/rust-lang/rust-clippy/pull/6305)
+* [`collapsible_match`] [#6402](https://github.com/rust-lang/rust-clippy/pull/6402)
+* [`redundant_else`] [#6330](https://github.com/rust-lang/rust-clippy/pull/6330)
+* [`zero_sized_map_values`] [#6218](https://github.com/rust-lang/rust-clippy/pull/6218)
+* [`print_stderr`] [#6367](https://github.com/rust-lang/rust-clippy/pull/6367)
+* [`string_from_utf8_as_bytes`] [#6134](https://github.com/rust-lang/rust-clippy/pull/6134)
+
+### Moves and Deprecations
+
+* Previously deprecated [`str_to_string`] and [`string_to_string`] have been un-deprecated
+ as `restriction` lints [#6333](https://github.com/rust-lang/rust-clippy/pull/6333)
+* Deprecate `panic_params` lint. This is now available in rustc as `non_fmt_panics`
+ [#6351](https://github.com/rust-lang/rust-clippy/pull/6351)
+* Move [`map_err_ignore`] to `restriction`
+ [#6416](https://github.com/rust-lang/rust-clippy/pull/6416)
+* Move [`await_holding_refcell_ref`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+* Move [`await_holding_lock`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+
+### Enhancements
+
+* Add the `unreadable-literal-lint-fractions` configuration to disable
+ the `unreadable_literal` lint for fractions
+ [#6421](https://github.com/rust-lang/rust-clippy/pull/6421)
+* [`clone_on_copy`]: Now shows the type in the lint message
+ [#6443](https://github.com/rust-lang/rust-clippy/pull/6443)
+* [`redundant_pattern_matching`]: Now also lints on `std::task::Poll`
+ [#6339](https://github.com/rust-lang/rust-clippy/pull/6339)
+* [`redundant_pattern_matching`]: Additionally also lints on `std::net::IpAddr`
+ [#6377](https://github.com/rust-lang/rust-clippy/pull/6377)
+* [`search_is_some`]: Now suggests `contains` instead of `find(foo).is_some()`
+ [#6119](https://github.com/rust-lang/rust-clippy/pull/6119)
+* [`clone_double_ref`]: Now prints the reference type in the lint message
+ [#6442](https://github.com/rust-lang/rust-clippy/pull/6442)
+* [`modulo_one`]: Now also lints on -1.
+ [#6360](https://github.com/rust-lang/rust-clippy/pull/6360)
+* [`empty_loop`]: Now lints no_std crates, too
+ [#6205](https://github.com/rust-lang/rust-clippy/pull/6205)
+* [`or_fun_call`]: Now also lints when indexing `HashMap` or `BTreeMap`
+ [#6267](https://github.com/rust-lang/rust-clippy/pull/6267)
+* [`wrong_self_convention`]: Now also lints in trait definitions
+ [#6316](https://github.com/rust-lang/rust-clippy/pull/6316)
+* [`needless_borrow`]: Print the type in the lint message
+ [#6449](https://github.com/rust-lang/rust-clippy/pull/6449)
+
+[msrv_readme]: https://github.com/rust-lang/rust-clippy#specifying-the-minimum-supported-rust-version
+
+### False Positive Fixes
+
+* [`manual_range_contains`]: No longer lints in `const fn`
+ [#6382](https://github.com/rust-lang/rust-clippy/pull/6382)
+* [`unnecessary_lazy_evaluations`]: No longer lints if closure argument is used
+ [#6370](https://github.com/rust-lang/rust-clippy/pull/6370)
+* [`match_single_binding`]: Now ignores cases with `#[cfg()]` macros
+ [#6435](https://github.com/rust-lang/rust-clippy/pull/6435)
+* [`match_like_matches_macro`]: No longer lints on arms with attributes
+ [#6290](https://github.com/rust-lang/rust-clippy/pull/6290)
+* [`map_clone`]: No longer lints with deref and clone
+ [#6269](https://github.com/rust-lang/rust-clippy/pull/6269)
+* [`map_clone`]: No longer lints in the case of &mut
+ [#6301](https://github.com/rust-lang/rust-clippy/pull/6301)
+* [`needless_update`]: Now ignores `non_exhaustive` structs
+ [#6464](https://github.com/rust-lang/rust-clippy/pull/6464)
+* [`needless_collect`]: No longer lints when a collect is needed multiple times
+ [#6313](https://github.com/rust-lang/rust-clippy/pull/6313)
+* [`unnecessary_cast`] No longer lints cfg-dependent types
+ [#6369](https://github.com/rust-lang/rust-clippy/pull/6369)
+* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]:
+ Both now ignore enums with frozen variants
+ [#6110](https://github.com/rust-lang/rust-clippy/pull/6110)
+* [`field_reassign_with_default`] No longer lint for private fields
+ [#6537](https://github.com/rust-lang/rust-clippy/pull/6537)
+
+
+### Suggestion Fixes/Improvements
+
+* [`vec_box`]: Provide correct type scope suggestion
+ [#6271](https://github.com/rust-lang/rust-clippy/pull/6271)
+* [`manual_range_contains`]: Give correct suggestion when using floats
+ [#6320](https://github.com/rust-lang/rust-clippy/pull/6320)
+* [`unnecessary_lazy_evaluations`]: Don't always mark suggestion as MachineApplicable
+ [#6272](https://github.com/rust-lang/rust-clippy/pull/6272)
+* [`manual_async_fn`]: Improve suggestion formatting
+ [#6294](https://github.com/rust-lang/rust-clippy/pull/6294)
+* [`unnecessary_cast`]: Fix incorrectly formatted float literal suggestion
+ [#6362](https://github.com/rust-lang/rust-clippy/pull/6362)
+
+### ICE Fixes
+
+* Fix a crash in [`from_iter_instead_of_collect`]
+ [#6304](https://github.com/rust-lang/rust-clippy/pull/6304)
+* Fix a silent crash when parsing doc comments in [`needless_doctest_main`]
+ [#6458](https://github.com/rust-lang/rust-clippy/pull/6458)
+
+### Documentation Improvements
+
+* The lint website search has been improved ([#6477](https://github.com/rust-lang/rust-clippy/pull/6477)):
+ * Searching for lints with dashes and spaces is possible now. For example
+ `missing-errors-doc` and `missing errors doc` are now valid aliases for lint names
+ * Improved fuzzy search in lint descriptions
+* Various README improvements
+ [#6287](https://github.com/rust-lang/rust-clippy/pull/6287)
+* Add known problems to [`comparison_chain`] documentation
+ [#6390](https://github.com/rust-lang/rust-clippy/pull/6390)
+* Fix example used in [`cargo_common_metadata`]
+ [#6293](https://github.com/rust-lang/rust-clippy/pull/6293)
+* Improve [`map_clone`] documentation
+ [#6340](https://github.com/rust-lang/rust-clippy/pull/6340)
+
+### Others
+
+* You can now tell Clippy about the MSRV your project supports. Please refer to
+ the specific README section to learn more about MSRV support [here][msrv_readme]
+ [#6201](https://github.com/rust-lang/rust-clippy/pull/6201)
+* Add `--no-deps` option to avoid running on path dependencies in workspaces
+ [#6188](https://github.com/rust-lang/rust-clippy/pull/6188)
+
+## Rust 1.49
+
+Released 2020-12-31
+
+[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1)
+
+### New Lints
+
+* [`field_reassign_with_default`] [#5911](https://github.com/rust-lang/rust-clippy/pull/5911)
+* [`await_holding_refcell_ref`] [#6029](https://github.com/rust-lang/rust-clippy/pull/6029)
+* [`disallowed_methods`] [#6081](https://github.com/rust-lang/rust-clippy/pull/6081)
+* [`inline_asm_x86_att_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`inline_asm_x86_intel_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`from_iter_instead_of_collect`] [#6101](https://github.com/rust-lang/rust-clippy/pull/6101)
+* [`mut_mutex_lock`] [#6103](https://github.com/rust-lang/rust-clippy/pull/6103)
+* [`single_element_loop`] [#6109](https://github.com/rust-lang/rust-clippy/pull/6109)
+* [`manual_unwrap_or`] [#6123](https://github.com/rust-lang/rust-clippy/pull/6123)
+* [`large_types_passed_by_value`] [#6135](https://github.com/rust-lang/rust-clippy/pull/6135)
+* [`result_unit_err`] [#6157](https://github.com/rust-lang/rust-clippy/pull/6157)
+* [`ref_option_ref`] [#6165](https://github.com/rust-lang/rust-clippy/pull/6165)
+* [`manual_range_contains`] [#6177](https://github.com/rust-lang/rust-clippy/pull/6177)
+* [`unusual_byte_groupings`] [#6183](https://github.com/rust-lang/rust-clippy/pull/6183)
+* [`comparison_to_empty`] [#6226](https://github.com/rust-lang/rust-clippy/pull/6226)
+* [`map_collect_result_unit`] [#6227](https://github.com/rust-lang/rust-clippy/pull/6227)
+* [`manual_ok_or`] [#6233](https://github.com/rust-lang/rust-clippy/pull/6233)
+
+### Moves and Deprecations
+
+* Rename `single_char_push_str` to [`single_char_add_str`]
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* Rename `zero_width_space` to [`invisible_characters`]
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* Deprecate `drop_bounds` (uplifted)
+ [#6111](https://github.com/rust-lang/rust-clippy/pull/6111)
+* Move [`string_lit_as_bytes`] to `nursery`
+ [#6117](https://github.com/rust-lang/rust-clippy/pull/6117)
+* Move [`rc_buffer`] to `restriction`
+ [#6128](https://github.com/rust-lang/rust-clippy/pull/6128)
+
+### Enhancements
+
+* [`manual_memcpy`]: Also lint when there are loop counters (and produce a
+ reliable suggestion)
+ [#5727](https://github.com/rust-lang/rust-clippy/pull/5727)
+* [`single_char_add_str`]: Also lint on `String::insert_str`
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* [`invisible_characters`]: Also lint the characters `\u{AD}` and `\u{2060}`
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* [`eq_op`]: Also lint on the `assert_*!` macro family
+ [#6167](https://github.com/rust-lang/rust-clippy/pull/6167)
+* [`items_after_statements`]: Also lint in local macro expansions
+ [#6176](https://github.com/rust-lang/rust-clippy/pull/6176)
+* [`unnecessary_cast`]: Also lint casts on integer and float literals
+ [#6187](https://github.com/rust-lang/rust-clippy/pull/6187)
+* [`manual_unwrap_or`]: Also lint `Result::unwrap_or`
+ [#6190](https://github.com/rust-lang/rust-clippy/pull/6190)
+* [`match_like_matches_macro`]: Also lint when `match` has more than two arms
+ [#6216](https://github.com/rust-lang/rust-clippy/pull/6216)
+* [`integer_arithmetic`]: Better handle `/` an `%` operators
+ [#6229](https://github.com/rust-lang/rust-clippy/pull/6229)
+
+### False Positive Fixes
+
+* [`needless_lifetimes`]: Bail out if the function has a `where` clause with the
+ lifetime [#5978](https://github.com/rust-lang/rust-clippy/pull/5978)
+* [`explicit_counter_loop`]: No longer lints, when loop counter is used after it
+ is incremented [#6076](https://github.com/rust-lang/rust-clippy/pull/6076)
+* [`or_fun_call`]: Revert changes addressing the handling of `const fn`
+ [#6077](https://github.com/rust-lang/rust-clippy/pull/6077)
+* [`needless_range_loop`]: No longer lints, when the iterable is used in the
+ range [#6102](https://github.com/rust-lang/rust-clippy/pull/6102)
+* [`inconsistent_digit_grouping`]: Fix bug when using floating point exponent
+ [#6104](https://github.com/rust-lang/rust-clippy/pull/6104)
+* [`mistyped_literal_suffixes`]: No longer lints on the fractional part of a
+ float (e.g. `713.32_64`)
+ [#6114](https://github.com/rust-lang/rust-clippy/pull/6114)
+* [`invalid_regex`]: No longer lint on unicode characters within `bytes::Regex`
+ [#6132](https://github.com/rust-lang/rust-clippy/pull/6132)
+* [`boxed_local`]: No longer lints on `extern fn` arguments
+ [#6133](https://github.com/rust-lang/rust-clippy/pull/6133)
+* [`needless_lifetimes`]: Fix regression, where lifetime is used in `where`
+ clause [#6198](https://github.com/rust-lang/rust-clippy/pull/6198)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_sort_by`]: Avoid dereferencing the suggested closure parameter
+ [#6078](https://github.com/rust-lang/rust-clippy/pull/6078)
+* [`needless_arbitrary_self_type`]: Correctly handle expanded code
+ [#6093](https://github.com/rust-lang/rust-clippy/pull/6093)
+* [`useless_format`]: Preserve raw strings in suggestion
+ [#6151](https://github.com/rust-lang/rust-clippy/pull/6151)
+* [`empty_loop`]: Suggest alternatives
+ [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`borrowed_box`]: Correctly add parentheses in suggestion
+ [#6200](https://github.com/rust-lang/rust-clippy/pull/6200)
+* [`unused_unit`]: Improve suggestion formatting
+ [#6247](https://github.com/rust-lang/rust-clippy/pull/6247)
+
+### Documentation Improvements
+
+* Some doc improvements:
+ * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090)
+ * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`doc_markdown`]: Document problematic link text style
+ [#6107](https://github.com/rust-lang/rust-clippy/pull/6107)
+
+## Rust 1.48
+
+Released 2020-11-19
+
+[09bd400...e636b88](https://github.com/rust-lang/rust-clippy/compare/09bd400...e636b88)
+
+### New lints
+
+* [`self_assignment`] [#5894](https://github.com/rust-lang/rust-clippy/pull/5894)
+* [`unnecessary_lazy_evaluations`] [#5720](https://github.com/rust-lang/rust-clippy/pull/5720)
+* [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038)
+* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998)
+* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044)
+* `to_string_in_display` [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
+* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
+
+### Moves and Deprecations
+
+* Downgrade [`verbose_bit_mask`] to pedantic
+ [#6036](https://github.com/rust-lang/rust-clippy/pull/6036)
+
+### Enhancements
+
+* Extend [`precedence`] to handle chains of methods combined with unary negation
+ [#5928](https://github.com/rust-lang/rust-clippy/pull/5928)
+* [`useless_vec`]: add a configuration value for the maximum allowed size on the stack
+ [#5907](https://github.com/rust-lang/rust-clippy/pull/5907)
+* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr`
+ [#5884](https://github.com/rust-lang/rust-clippy/pull/5884)
+* `invalid_atomic_ordering`: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
+ [#6025](https://github.com/rust-lang/rust-clippy/pull/6025)
+* Avoid [`redundant_pattern_matching`] triggering in macros
+ [#6069](https://github.com/rust-lang/rust-clippy/pull/6069)
+* [`option_if_let_else`]: distinguish pure from impure `else` expressions
+ [#5937](https://github.com/rust-lang/rust-clippy/pull/5937)
+* [`needless_doctest_main`]: parse doctests instead of using textual search
+ [#5912](https://github.com/rust-lang/rust-clippy/pull/5912)
+* [`wildcard_imports`]: allow `prelude` to appear in any segment of an import
+ [#5929](https://github.com/rust-lang/rust-clippy/pull/5929)
+* Re-enable [`len_zero`] for ranges now that `range_is_empty` is stable
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+* [`option_as_ref_deref`]: catch fully-qualified calls to `Deref::deref` and `DerefMut::deref_mut`
+ [#5933](https://github.com/rust-lang/rust-clippy/pull/5933)
+
+### False Positive Fixes
+
+* [`useless_attribute`]: permit allowing [`wildcard_imports`] and [`enum_glob_use`]
+ [#5994](https://github.com/rust-lang/rust-clippy/pull/5994)
+* [`transmute_ptr_to_ptr`]: avoid suggesting dereferencing raw pointers in const contexts
+ [#5999](https://github.com/rust-lang/rust-clippy/pull/5999)
+* [`redundant_closure_call`]: take into account usages of the closure in nested functions and closures
+ [#5920](https://github.com/rust-lang/rust-clippy/pull/5920)
+* Fix false positive in [`borrow_interior_mutable_const`] when referencing a field behind a pointer
+ [#5949](https://github.com/rust-lang/rust-clippy/pull/5949)
+* [`doc_markdown`]: allow using "GraphQL" without backticks
+ [#5996](https://github.com/rust-lang/rust-clippy/pull/5996)
+* `to_string_in_display`: avoid linting when calling `to_string()` on anything that is not `self`
+ [#5971](https://github.com/rust-lang/rust-clippy/pull/5971)
+* [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays
+ [#6034](https://github.com/rust-lang/rust-clippy/pull/6034)
+* [`should_implement_trait`]: ignore methods with lifetime parameters
+ [#5725](https://github.com/rust-lang/rust-clippy/pull/5725)
+* [`needless_return`]: avoid linting if a temporary borrows a local variable
+ [#5903](https://github.com/rust-lang/rust-clippy/pull/5903)
+* Restrict [`unnecessary_sort_by`] to non-reference, Copy types
+ [#6006](https://github.com/rust-lang/rust-clippy/pull/6006)
+* Avoid suggesting `from_bits`/`to_bits` in const contexts in [`transmute_int_to_float`]
+ [#5919](https://github.com/rust-lang/rust-clippy/pull/5919)
+* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: improve detection of interior mutable types
+ [#6046](https://github.com/rust-lang/rust-clippy/pull/6046)
+
+### Suggestion Fixes/Improvements
+
+* [`let_and_return`]: add a cast to the suggestion when the return expression has adjustments
+ [#5946](https://github.com/rust-lang/rust-clippy/pull/5946)
+* [`useless_conversion`]: show the type in the error message
+ [#6035](https://github.com/rust-lang/rust-clippy/pull/6035)
+* [`unnecessary_mut_passed`]: discriminate between functions and methods in the error message
+ [#5892](https://github.com/rust-lang/rust-clippy/pull/5892)
+* [`float_cmp`] and [`float_cmp_const`]: change wording to make margin of error less ambiguous
+ [#6043](https://github.com/rust-lang/rust-clippy/pull/6043)
+* [`default_trait_access`]: do not use unnecessary type parameters in the suggestion
+ [#5993](https://github.com/rust-lang/rust-clippy/pull/5993)
+* [`collapsible_if`]: don't use expanded code in the suggestion
+ [#5992](https://github.com/rust-lang/rust-clippy/pull/5992)
+* Do not suggest empty format strings in [`print_with_newline`] and [`write_with_newline`]
+ [#6042](https://github.com/rust-lang/rust-clippy/pull/6042)
+* [`unit_arg`]: improve the readability of the suggestion
+ [#5931](https://github.com/rust-lang/rust-clippy/pull/5931)
+* [`stable_sort_primitive`]: print the type that is being sorted in the lint message
+ [#5935](https://github.com/rust-lang/rust-clippy/pull/5935)
+* Show line count and max lines in [`too_many_lines`] lint message
+ [#6009](https://github.com/rust-lang/rust-clippy/pull/6009)
+* Keep parentheses in the suggestion of [`useless_conversion`] where applicable
+ [#5900](https://github.com/rust-lang/rust-clippy/pull/5900)
+* [`option_map_unit_fn`] and [`result_map_unit_fn`]: print the unit type `()` explicitly
+ [#6024](https://github.com/rust-lang/rust-clippy/pull/6024)
+* [`redundant_allocation`]: suggest replacing `Rc<Box<T>>` with `Rc<T>`
+ [#5899](https://github.com/rust-lang/rust-clippy/pull/5899)
+* Make lint messages adhere to rustc dev guide conventions
+ [#5893](https://github.com/rust-lang/rust-clippy/pull/5893)
+
+### ICE Fixes
+
+* Fix ICE in [`repeat_once`]
+ [#5948](https://github.com/rust-lang/rust-clippy/pull/5948)
+
+### Documentation Improvements
+
+* [`mutable_key_type`]: explain potential for false positives when the interior mutable type is not accessed in the `Hash` implementation
+ [#6019](https://github.com/rust-lang/rust-clippy/pull/6019)
+* [`unnecessary_mut_passed`]: fix typo
+ [#5913](https://github.com/rust-lang/rust-clippy/pull/5913)
+* Add example of false positive to [`ptr_arg`] docs.
+ [#5885](https://github.com/rust-lang/rust-clippy/pull/5885)
+* [`box_vec`](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection), [`vec_box`] and [`borrowed_box`]: add link to the documentation of `Box`
+ [#6023](https://github.com/rust-lang/rust-clippy/pull/6023)
+
+## Rust 1.47
+
+Released 2020-10-08
+
+[c2c07fa...09bd400](https://github.com/rust-lang/rust-clippy/compare/c2c07fa...09bd400)
+
+### New lints
+
+* [`derive_ord_xor_partial_ord`] [#5848](https://github.com/rust-lang/rust-clippy/pull/5848)
+* [`trait_duplication_in_bounds`] [#5852](https://github.com/rust-lang/rust-clippy/pull/5852)
+* [`map_identity`] [#5694](https://github.com/rust-lang/rust-clippy/pull/5694)
+* [`unit_return_expecting_ord`] [#5737](https://github.com/rust-lang/rust-clippy/pull/5737)
+* [`pattern_type_mismatch`] [#4841](https://github.com/rust-lang/rust-clippy/pull/4841)
+* [`repeat_once`] [#5773](https://github.com/rust-lang/rust-clippy/pull/5773)
+* [`same_item_push`] [#5825](https://github.com/rust-lang/rust-clippy/pull/5825)
+* [`needless_arbitrary_self_type`] [#5869](https://github.com/rust-lang/rust-clippy/pull/5869)
+* [`match_like_matches_macro`] [#5769](https://github.com/rust-lang/rust-clippy/pull/5769)
+* [`stable_sort_primitive`] [#5809](https://github.com/rust-lang/rust-clippy/pull/5809)
+* [`blanket_clippy_restriction_lints`] [#5750](https://github.com/rust-lang/rust-clippy/pull/5750)
+* [`option_if_let_else`] [#5301](https://github.com/rust-lang/rust-clippy/pull/5301)
+
+### Moves and Deprecations
+
+* Deprecate [`regex_macro`] lint
+ [#5760](https://github.com/rust-lang/rust-clippy/pull/5760)
+* Move [`range_minus_one`] to `pedantic`
+ [#5752](https://github.com/rust-lang/rust-clippy/pull/5752)
+
+### Enhancements
+
+* Improve [`needless_collect`] by catching `collect` calls followed by `iter` or `into_iter` calls
+ [#5837](https://github.com/rust-lang/rust-clippy/pull/5837)
+* [`panic`], [`todo`], [`unimplemented`] and [`unreachable`] now detect calls with formatting
+ [#5811](https://github.com/rust-lang/rust-clippy/pull/5811)
+* Detect more cases of [`suboptimal_flops`] and [`imprecise_flops`]
+ [#5443](https://github.com/rust-lang/rust-clippy/pull/5443)
+* Handle asymmetrical implementations of `PartialEq` in [`cmp_owned`]
+ [#5701](https://github.com/rust-lang/rust-clippy/pull/5701)
+* Make it possible to allow [`unsafe_derive_deserialize`]
+ [#5870](https://github.com/rust-lang/rust-clippy/pull/5870)
+* Catch `ord.min(a).max(b)` where a < b in [`min_max`]
+ [#5871](https://github.com/rust-lang/rust-clippy/pull/5871)
+* Make [`clone_on_copy`] suggestion machine applicable
+ [#5745](https://github.com/rust-lang/rust-clippy/pull/5745)
+* Enable [`len_zero`] on ranges now that `is_empty` is stable on them
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+
+### False Positive Fixes
+
+* Avoid triggering [`or_fun_call`] with const fns that take no arguments
+ [#5889](https://github.com/rust-lang/rust-clippy/pull/5889)
+* Fix [`redundant_closure_call`] false positive for closures that have multiple calls
+ [#5800](https://github.com/rust-lang/rust-clippy/pull/5800)
+* Don't lint cases involving `ManuallyDrop` in [`redundant_clone`]
+ [#5824](https://github.com/rust-lang/rust-clippy/pull/5824)
+* Treat a single expression the same as a single statement in the 2nd arm of a match in [`single_match_else`]
+ [#5771](https://github.com/rust-lang/rust-clippy/pull/5771)
+* Don't trigger [`unnested_or_patterns`] if the feature `or_patterns` is not enabled
+ [#5758](https://github.com/rust-lang/rust-clippy/pull/5758)
+* Avoid linting if key borrows in [`unnecessary_sort_by`]
+ [#5756](https://github.com/rust-lang/rust-clippy/pull/5756)
+* Consider `Try` impl for `Poll` when generating suggestions in [`try_err`]
+ [#5857](https://github.com/rust-lang/rust-clippy/pull/5857)
+* Take input lifetimes into account in `manual_async_fn`
+ [#5859](https://github.com/rust-lang/rust-clippy/pull/5859)
+* Fix multiple false positives in [`type_repetition_in_bounds`] and add a configuration option
+ [#5761](https://github.com/rust-lang/rust-clippy/pull/5761)
+* Limit the [`suspicious_arithmetic_impl`] lint to one binary operation
+ [#5820](https://github.com/rust-lang/rust-clippy/pull/5820)
+
+### Suggestion Fixes/Improvements
+
+* Improve readability of [`shadow_unrelated`] suggestion by truncating the RHS snippet
+ [#5788](https://github.com/rust-lang/rust-clippy/pull/5788)
+* Suggest `filter_map` instead of `flat_map` when mapping to `Option` in [`map_flatten`]
+ [#5846](https://github.com/rust-lang/rust-clippy/pull/5846)
+* Ensure suggestion is shown correctly for long method call chains in [`iter_nth_zero`]
+ [#5793](https://github.com/rust-lang/rust-clippy/pull/5793)
+* Drop borrow operator in suggestions of [`redundant_pattern_matching`]
+ [#5815](https://github.com/rust-lang/rust-clippy/pull/5815)
+* Add suggestion for [`iter_skip_next`]
+ [#5843](https://github.com/rust-lang/rust-clippy/pull/5843)
+* Improve [`collapsible_if`] fix suggestion
+ [#5732](https://github.com/rust-lang/rust-clippy/pull/5732)
+
+### ICE Fixes
+
+* Fix ICE caused by [`needless_collect`]
+ [#5877](https://github.com/rust-lang/rust-clippy/pull/5877)
+* Fix ICE caused by [`unnested_or_patterns`]
+ [#5784](https://github.com/rust-lang/rust-clippy/pull/5784)
+
+### Documentation Improvements
+
+* Fix grammar of [`await_holding_lock`] documentation
+ [#5748](https://github.com/rust-lang/rust-clippy/pull/5748)
+
+### Others
+
+* Make lints adhere to the rustc dev guide
+ [#5888](https://github.com/rust-lang/rust-clippy/pull/5888)
+
+## Rust 1.46
+
+Released 2020-08-27
+
+[7ea7cd1...c2c07fa](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...c2c07fa)
+
+### New lints
+
+* [`unnested_or_patterns`] [#5378](https://github.com/rust-lang/rust-clippy/pull/5378)
+* [`iter_next_slice`] [#5597](https://github.com/rust-lang/rust-clippy/pull/5597)
+* [`unnecessary_sort_by`] [#5623](https://github.com/rust-lang/rust-clippy/pull/5623)
+* [`vec_resize_to_zero`] [#5637](https://github.com/rust-lang/rust-clippy/pull/5637)
+
+### Moves and Deprecations
+
+* Move [`cast_ptr_alignment`] to pedantic [#5667](https://github.com/rust-lang/rust-clippy/pull/5667)
+
+### Enhancements
+
+* Improve [`mem_replace_with_uninit`] lint [#5695](https://github.com/rust-lang/rust-clippy/pull/5695)
+
+### False Positive Fixes
+
+* [`len_zero`]: Avoid linting ranges when the `range_is_empty` feature is not enabled
+ [#5656](https://github.com/rust-lang/rust-clippy/pull/5656)
+* [`let_and_return`]: Don't lint if a temporary borrow is involved
+ [#5680](https://github.com/rust-lang/rust-clippy/pull/5680)
+* [`reversed_empty_ranges`]: Avoid linting `N..N` in for loop arguments in
+ [#5692](https://github.com/rust-lang/rust-clippy/pull/5692)
+* [`if_same_then_else`]: Don't assume multiplication is always commutative
+ [#5702](https://github.com/rust-lang/rust-clippy/pull/5702)
+* [`disallowed_names`]: Remove `bar` from the default configuration
+ [#5712](https://github.com/rust-lang/rust-clippy/pull/5712)
+* [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts
+ [#5724](https://github.com/rust-lang/rust-clippy/pull/5724)
+
+### Suggestion Fixes/Improvements
+
+* Fix suggestion of [`unit_arg`] lint, so that it suggest semantic equivalent code
+ [#4455](https://github.com/rust-lang/rust-clippy/pull/4455)
+* Add auto applicable suggestion to [`macro_use_imports`]
+ [#5279](https://github.com/rust-lang/rust-clippy/pull/5279)
+
+### ICE Fixes
+
+* Fix ICE in the `consts` module of Clippy [#5709](https://github.com/rust-lang/rust-clippy/pull/5709)
+
+### Documentation Improvements
+
+* Improve code examples across multiple lints [#5664](https://github.com/rust-lang/rust-clippy/pull/5664)
+
+### Others
+
+* Introduce a `--rustc` flag to `clippy-driver`, which turns `clippy-driver`
+ into `rustc` and passes all the given arguments to `rustc`. This is especially
+ useful for tools that need the `rustc` version Clippy was compiled with,
+ instead of the Clippy version. E.g. `clippy-driver --rustc --version` will
+ print the output of `rustc --version`.
+ [#5178](https://github.com/rust-lang/rust-clippy/pull/5178)
+* New issue templates now make it easier to complain if Clippy is too annoying
+ or not annoying enough! [#5735](https://github.com/rust-lang/rust-clippy/pull/5735)
+
+## Rust 1.45
+
+Released 2020-07-16
+
+[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1)
+
+### New lints
+
+* [`match_wildcard_for_single_variants`] [#5582](https://github.com/rust-lang/rust-clippy/pull/5582)
+* [`unsafe_derive_deserialize`] [#5493](https://github.com/rust-lang/rust-clippy/pull/5493)
+* [`if_let_mutex`] [#5332](https://github.com/rust-lang/rust-clippy/pull/5332)
+* [`mismatched_target_os`] [#5506](https://github.com/rust-lang/rust-clippy/pull/5506)
+* [`await_holding_lock`] [#5439](https://github.com/rust-lang/rust-clippy/pull/5439)
+* [`match_on_vec_items`] [#5522](https://github.com/rust-lang/rust-clippy/pull/5522)
+* [`manual_async_fn`] [#5576](https://github.com/rust-lang/rust-clippy/pull/5576)
+* [`reversed_empty_ranges`] [#5583](https://github.com/rust-lang/rust-clippy/pull/5583)
+* [`manual_non_exhaustive`] [#5550](https://github.com/rust-lang/rust-clippy/pull/5550)
+
+### Moves and Deprecations
+
+* Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408)
+* Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622)
+* Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599)
+* Generalize `option_and_then_some` and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529)
+* Rename `identity_conversion` to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568)
+* Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` into [`blocks_in_if_conditions`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` into [`map_unwrap_or`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_unwrap_used` and `result_unwrap_used` into [`unwrap_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_expect_used` and `result_expect_used` into [`expect_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `for_loop_over_option` and `for_loop_over_result` into [`for_loops_over_fallibles`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+
+### Enhancements
+
+* Avoid running cargo lints when not enabled to improve performance. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505)
+* Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631)
+* Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592)
+* Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616)
+
+### False Positive Fixes
+
+* [`while_let_on_iterator`] [#5525](https://github.com/rust-lang/rust-clippy/pull/5525)
+* [`empty_line_after_outer_attr`] [#5609](https://github.com/rust-lang/rust-clippy/pull/5609)
+* [`unnecessary_unwrap`] [#5558](https://github.com/rust-lang/rust-clippy/pull/5558)
+* [`comparison_chain`] [#5596](https://github.com/rust-lang/rust-clippy/pull/5596)
+* Don't trigger [`used_underscore_binding`] in await desugaring. [#5535](https://github.com/rust-lang/rust-clippy/pull/5535)
+* Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491)
+* Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602)
+* Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564)
+* Whitelist more words in [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611)
+* Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636)
+* Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647)
+* Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`]
+and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651)
+* Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429)
+
+### Suggestion Improvements
+
+* Simplify suggestions in [`manual_memcpy`]. [#5536](https://github.com/rust-lang/rust-clippy/pull/5536)
+* Fix suggestion in [`redundant_pattern_matching`] for macros. [#5511](https://github.com/rust-lang/rust-clippy/pull/5511)
+* Avoid suggesting `copied()` for mutable references in [`map_clone`]. [#5530](https://github.com/rust-lang/rust-clippy/pull/5530)
+* Improve help message for [`clone_double_ref`]. [#5547](https://github.com/rust-lang/rust-clippy/pull/5547)
+
+### ICE Fixes
+
+* Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590)
+* Fix ICE on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499)
+
+### Documentation
+
+* Clarify the documentation of [`unnecessary_mut_passed`]. [#5639](https://github.com/rust-lang/rust-clippy/pull/5639)
+* Extend example for [`unneeded_field_pattern`]. [#5541](https://github.com/rust-lang/rust-clippy/pull/5541)
+
+## Rust 1.44
+
+Released 2020-06-04
+
+[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8)
+
+### New lints
+
+* [`explicit_deref_methods`] [#5226](https://github.com/rust-lang/rust-clippy/pull/5226)
+* [`implicit_saturating_sub`] [#5427](https://github.com/rust-lang/rust-clippy/pull/5427)
+* [`macro_use_imports`] [#5230](https://github.com/rust-lang/rust-clippy/pull/5230)
+* [`verbose_file_reads`] [#5272](https://github.com/rust-lang/rust-clippy/pull/5272)
+* [`future_not_send`] [#5423](https://github.com/rust-lang/rust-clippy/pull/5423)
+* [`redundant_pub_crate`] [#5319](https://github.com/rust-lang/rust-clippy/pull/5319)
+* [`large_const_arrays`] [#5248](https://github.com/rust-lang/rust-clippy/pull/5248)
+* [`result_map_or_into_option`] [#5415](https://github.com/rust-lang/rust-clippy/pull/5415)
+* [`redundant_allocation`] [#5349](https://github.com/rust-lang/rust-clippy/pull/5349)
+* [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+* [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+
+
+### Moves and Deprecations
+
+* Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380)
+* Move [`cognitive_complexity`] to nursery [#5428](https://github.com/rust-lang/rust-clippy/pull/5428)
+* Move [`useless_transmute`] to nursery [#5364](https://github.com/rust-lang/rust-clippy/pull/5364)
+* Downgrade [`inefficient_to_string`] to pedantic [#5412](https://github.com/rust-lang/rust-clippy/pull/5412)
+* Downgrade [`option_option`] to pedantic [#5401](https://github.com/rust-lang/rust-clippy/pull/5401)
+* Downgrade [`unreadable_literal`] to pedantic [#5419](https://github.com/rust-lang/rust-clippy/pull/5419)
+* Downgrade [`let_unit_value`] to pedantic [#5409](https://github.com/rust-lang/rust-clippy/pull/5409)
+* Downgrade [`trivially_copy_pass_by_ref`] to pedantic [#5410](https://github.com/rust-lang/rust-clippy/pull/5410)
+* Downgrade [`implicit_hasher`] to pedantic [#5411](https://github.com/rust-lang/rust-clippy/pull/5411)
+
+### Enhancements
+
+* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to
+ auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363)
+* Make [`redundant_clone`] also trigger on cases where the cloned value is not
+ consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304)
+* Expand [`integer_arithmetic`] to also disallow bit-shifting [#5430](https://github.com/rust-lang/rust-clippy/pull/5430)
+* [`option_as_ref_deref`] now detects more deref cases [#5425](https://github.com/rust-lang/rust-clippy/pull/5425)
+* [`large_enum_variant`] now report the sizes of the largest and second-largest variants [#5466](https://github.com/rust-lang/rust-clippy/pull/5466)
+* [`bool_comparison`] now also checks for inequality comparisons that can be
+ written more concisely [#5365](https://github.com/rust-lang/rust-clippy/pull/5365)
+* Expand [`clone_on_copy`] to work in method call arguments as well [#5441](https://github.com/rust-lang/rust-clippy/pull/5441)
+* [`redundant_pattern_matching`] now also handles `while let` [#5483](https://github.com/rust-lang/rust-clippy/pull/5483)
+* [`integer_arithmetic`] now also lints references of integers [#5329](https://github.com/rust-lang/rust-clippy/pull/5329)
+* Expand [`float_cmp_const`] to also work on arrays [#5345](https://github.com/rust-lang/rust-clippy/pull/5345)
+* Trigger [`map_flatten`] when map is called on an `Option` [#5473](https://github.com/rust-lang/rust-clippy/pull/5473)
+
+### False Positive Fixes
+
+* [`many_single_char_names`] [#5468](https://github.com/rust-lang/rust-clippy/pull/5468)
+* [`should_implement_trait`] [#5437](https://github.com/rust-lang/rust-clippy/pull/5437)
+* [`unused_self`] [#5387](https://github.com/rust-lang/rust-clippy/pull/5387)
+* [`redundant_clone`] [#5453](https://github.com/rust-lang/rust-clippy/pull/5453)
+* [`precedence`] [#5445](https://github.com/rust-lang/rust-clippy/pull/5445)
+* [`suspicious_op_assign_impl`] [#5424](https://github.com/rust-lang/rust-clippy/pull/5424)
+* [`needless_lifetimes`] [#5293](https://github.com/rust-lang/rust-clippy/pull/5293)
+* [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287)
+* [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451)
+
+
+### Suggestion Improvements
+
+* Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481)
+* Improve the suggested placeholder in [`option_map_unit_fn`] [#5292](https://github.com/rust-lang/rust-clippy/pull/5292)
+* Improve suggestion for [`match_single_binding`] when triggered inside a closure [#5350](https://github.com/rust-lang/rust-clippy/pull/5350)
+
+### ICE Fixes
+
+* Handle the unstable `trivial_bounds` feature [#5296](https://github.com/rust-lang/rust-clippy/pull/5296)
+* `shadow_*` lints [#5297](https://github.com/rust-lang/rust-clippy/pull/5297)
+
+### Documentation
+
+* Fix documentation generation for configurable lints [#5353](https://github.com/rust-lang/rust-clippy/pull/5353)
+* Update documentation for [`new_ret_no_self`] [#5448](https://github.com/rust-lang/rust-clippy/pull/5448)
+* The documentation for [`option_option`] now suggest using a tri-state enum [#5403](https://github.com/rust-lang/rust-clippy/pull/5403)
+* Fix bit mask example in [`verbose_bit_mask`] documentation [#5454](https://github.com/rust-lang/rust-clippy/pull/5454)
+* [`wildcard_imports`] documentation now mentions that `use ...::prelude::*` is
+ not linted [#5312](https://github.com/rust-lang/rust-clippy/pull/5312)
+
+## Rust 1.43
+
+Released 2020-04-23
+
+[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b)
+
+### New lints
+
+* [`imprecise_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`suboptimal_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`wildcard_imports`] [#5029](https://github.com/rust-lang/rust-clippy/pull/5029)
+* [`single_component_path_imports`] [#5058](https://github.com/rust-lang/rust-clippy/pull/5058)
+* [`match_single_binding`] [#5061](https://github.com/rust-lang/rust-clippy/pull/5061)
+* [`let_underscore_lock`] [#5101](https://github.com/rust-lang/rust-clippy/pull/5101)
+* [`struct_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`fn_params_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`option_env_unwrap`] [#5148](https://github.com/rust-lang/rust-clippy/pull/5148)
+* [`lossy_float_literal`] [#5202](https://github.com/rust-lang/rust-clippy/pull/5202)
+* [`rest_pat_in_fully_bound_structs`] [#5258](https://github.com/rust-lang/rust-clippy/pull/5258)
+
+### Moves and Deprecations
+
+* Move [`unneeded_field_pattern`] to pedantic group [#5200](https://github.com/rust-lang/rust-clippy/pull/5200)
+
+### Enhancements
+
+* Make [`missing_errors_doc`] lint also trigger on `async` functions
+ [#5181](https://github.com/rust-lang/rust-clippy/pull/5181)
+* Add more constants to [`approx_constant`] [#5193](https://github.com/rust-lang/rust-clippy/pull/5193)
+* Extend [`question_mark`] lint [#5266](https://github.com/rust-lang/rust-clippy/pull/5266)
+
+### False Positive Fixes
+
+* [`use_debug`] [#5047](https://github.com/rust-lang/rust-clippy/pull/5047)
+* [`unnecessary_unwrap`] [#5132](https://github.com/rust-lang/rust-clippy/pull/5132)
+* [`zero_prefixed_literal`] [#5170](https://github.com/rust-lang/rust-clippy/pull/5170)
+* [`missing_const_for_fn`] [#5216](https://github.com/rust-lang/rust-clippy/pull/5216)
+
+### Suggestion Improvements
+
+* Improve suggestion when blocks of code are suggested [#5134](https://github.com/rust-lang/rust-clippy/pull/5134)
+
+### ICE Fixes
+
+* `misc_early` lints [#5129](https://github.com/rust-lang/rust-clippy/pull/5129)
+* [`missing_errors_doc`] [#5213](https://github.com/rust-lang/rust-clippy/pull/5213)
+* Fix ICE when evaluating `usize`s [#5256](https://github.com/rust-lang/rust-clippy/pull/5256)
+
+### Documentation
+
+* Improve documentation of [`iter_nth_zero`]
+* Add documentation pages for stable releases [#5171](https://github.com/rust-lang/rust-clippy/pull/5171)
+
+### Others
+
+* Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190)
+
+
+## Rust 1.42
+
+Released 2020-03-12
+
+[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206)
+
+### New lints
+
+* [`filetype_is_file`] [#4543](https://github.com/rust-lang/rust-clippy/pull/4543)
+* [`let_underscore_must_use`] [#4823](https://github.com/rust-lang/rust-clippy/pull/4823)
+* [`modulo_arithmetic`] [#4867](https://github.com/rust-lang/rust-clippy/pull/4867)
+* [`mem_replace_with_default`] [#4881](https://github.com/rust-lang/rust-clippy/pull/4881)
+* [`mutable_key_type`] [#4885](https://github.com/rust-lang/rust-clippy/pull/4885)
+* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945)
+* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960)
+* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966)
+* `invalid_atomic_ordering` [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
+* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067)
+
+### Moves and Deprecations
+
+* Move [`transmute_float_to_int`] from nursery to complexity group
+ [#5015](https://github.com/rust-lang/rust-clippy/pull/5015)
+* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057)
+* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Deprecate `unused_label` [#4930](https://github.com/rust-lang/rust-clippy/pull/4930)
+
+### Enhancements
+
+* Lint vectored IO in [`unused_io_amount`] [#5027](https://github.com/rust-lang/rust-clippy/pull/5027)
+* Make [`vec_box`] configurable by adding a size threshold [#5081](https://github.com/rust-lang/rust-clippy/pull/5081)
+* Also lint constants in [`cmp_nan`] [#4910](https://github.com/rust-lang/rust-clippy/pull/4910)
+* Fix false negative in [`expect_fun_call`] [#4915](https://github.com/rust-lang/rust-clippy/pull/4915)
+* Fix false negative in [`redundant_clone`] [#5017](https://github.com/rust-lang/rust-clippy/pull/5017)
+
+### False Positive Fixes
+
+* [`map_clone`] [#4937](https://github.com/rust-lang/rust-clippy/pull/4937)
+* [`replace_consts`] [#4977](https://github.com/rust-lang/rust-clippy/pull/4977)
+* [`let_and_return`] [#5008](https://github.com/rust-lang/rust-clippy/pull/5008)
+* [`eq_op`] [#5079](https://github.com/rust-lang/rust-clippy/pull/5079)
+* [`possible_missing_comma`] [#5083](https://github.com/rust-lang/rust-clippy/pull/5083)
+* [`debug_assert_with_mut_call`] [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Don't trigger [`let_underscore_must_use`] in external macros
+ [#5082](https://github.com/rust-lang/rust-clippy/pull/5082)
+* Don't trigger [`empty_loop`] in `no_std` crates [#5086](https://github.com/rust-lang/rust-clippy/pull/5086)
+
+### Suggestion Improvements
+
+* `option_map_unwrap_or` [#4634](https://github.com/rust-lang/rust-clippy/pull/4634)
+* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934)
+* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935)
+* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956)
+* `unknown_clippy_lints` [#4963](https://github.com/rust-lang/rust-clippy/pull/4963)
+* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978)
+* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022)
+* `if_let_some_result` [#5032](https://github.com/rust-lang/rust-clippy/pull/5032)
+
+### ICE fixes
+
+* [`unsound_collection_transmute`] [#4975](https://github.com/rust-lang/rust-clippy/pull/4975)
+
+### Documentation
+
+* Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`]
+
+
+## Rust 1.41
+
+Released 2020-01-30
+
+[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7)
+
+* New Lints:
+ * [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697)
+ * [`to_digit_is_some`] [#4801](https://github.com/rust-lang/rust-clippy/pull/4801)
+ * [`tabs_in_doc_comments`] [#4806](https://github.com/rust-lang/rust-clippy/pull/4806)
+ * [`large_stack_arrays`] [#4807](https://github.com/rust-lang/rust-clippy/pull/4807)
+ * [`same_functions_in_if_condition`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814)
+ * [`zst_offset`] [#4816](https://github.com/rust-lang/rust-clippy/pull/4816)
+ * [`as_conversions`] [#4821](https://github.com/rust-lang/rust-clippy/pull/4821)
+ * [`missing_errors_doc`] [#4884](https://github.com/rust-lang/rust-clippy/pull/4884)
+ * [`transmute_float_to_int`] [#4889](https://github.com/rust-lang/rust-clippy/pull/4889)
+* Remove plugin interface, see
+ [Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for
+ details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714)
+* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863)
+* Deprecate `into_iter_on_array` [#4788](https://github.com/rust-lang/rust-clippy/pull/4788)
+* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes
+ [#4808](https://github.com/rust-lang/rust-clippy/pull/4808)
+* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842)
+* Fix false positive in `while_immutable_condition` [#4730](https://github.com/rust-lang/rust-clippy/pull/4730)
+* Fix false positive in `explicit_counter_loop` [#4803](https://github.com/rust-lang/rust-clippy/pull/4803)
+* Fix false positive in `must_use_candidate` [#4794](https://github.com/rust-lang/rust-clippy/pull/4794)
+* Fix false positive in `print_with_newline` and `write_with_newline`
+ [#4769](https://github.com/rust-lang/rust-clippy/pull/4769)
+* Fix false positive in `derive_hash_xor_eq` [#4766](https://github.com/rust-lang/rust-clippy/pull/4766)
+* Fix false positive in `missing_inline_in_public_items` [#4870](https://github.com/rust-lang/rust-clippy/pull/4870)
+* Fix false positive in `string_add` [#4880](https://github.com/rust-lang/rust-clippy/pull/4880)
+* Fix false positive in `float_arithmetic` [#4851](https://github.com/rust-lang/rust-clippy/pull/4851)
+* Fix false positive in `cast_sign_loss` [#4883](https://github.com/rust-lang/rust-clippy/pull/4883)
+* Fix false positive in `manual_swap` [#4877](https://github.com/rust-lang/rust-clippy/pull/4877)
+* Fix ICEs occurring while checking some block expressions [#4772](https://github.com/rust-lang/rust-clippy/pull/4772)
+* Fix ICE in `use_self` [#4776](https://github.com/rust-lang/rust-clippy/pull/4776)
+* Fix ICEs related to `const_generics` [#4780](https://github.com/rust-lang/rust-clippy/pull/4780)
+* Display help when running `clippy-driver` without arguments, instead of ICEing
+ [#4810](https://github.com/rust-lang/rust-clippy/pull/4810)
+* Clippy has its own ICE message now [#4588](https://github.com/rust-lang/rust-clippy/pull/4588)
+* Show deprecated lints in the documentation again [#4757](https://github.com/rust-lang/rust-clippy/pull/4757)
+* Improve Documentation by adding positive examples to some lints
+ [#4832](https://github.com/rust-lang/rust-clippy/pull/4832)
+
+## Rust 1.40
+
+Released 2019-12-19
+
+[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb)
+
+* New Lints:
+ * [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537)
+ * [`needless_doctest_main`] [#4603](https://github.com/rust-lang/rust-clippy/pull/4603)
+ * [`suspicious_unary_op_formatting`] [#4615](https://github.com/rust-lang/rust-clippy/pull/4615)
+ * [`debug_assert_with_mut_call`] [#4680](https://github.com/rust-lang/rust-clippy/pull/4680)
+ * [`unused_self`] [#4619](https://github.com/rust-lang/rust-clippy/pull/4619)
+ * [`inefficient_to_string`] [#4683](https://github.com/rust-lang/rust-clippy/pull/4683)
+ * [`must_use_unit`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`must_use_candidate`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`double_must_use`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`comparison_chain`] [#4569](https://github.com/rust-lang/rust-clippy/pull/4569)
+ * [`unsound_collection_transmute`] [#4592](https://github.com/rust-lang/rust-clippy/pull/4592)
+ * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `option_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `result_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+* Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736)
+* Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613)
+* Expand `integer_arithmetic` to also detect mutating arithmetic like `+=` [#4585](https://github.com/rust-lang/rust-clippy/pull/4585)
+* Fix false positive in `nonminimal_bool` [#4568](https://github.com/rust-lang/rust-clippy/pull/4568)
+* Fix false positive in `missing_safety_doc` [#4611](https://github.com/rust-lang/rust-clippy/pull/4611)
+* Fix false positive in `cast_sign_loss` [#4614](https://github.com/rust-lang/rust-clippy/pull/4614)
+* Fix false positive in `redundant_clone` [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Fix false positive in `try_err` [#4721](https://github.com/rust-lang/rust-clippy/pull/4721)
+* Fix false positive in `toplevel_ref_arg` [#4570](https://github.com/rust-lang/rust-clippy/pull/4570)
+* Fix false positive in `multiple_inherent_impl` [#4593](https://github.com/rust-lang/rust-clippy/pull/4593)
+* Improve more suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4575](https://github.com/rust-lang/rust-clippy/pull/4575)
+* Improve suggestion for `zero_ptr` [#4599](https://github.com/rust-lang/rust-clippy/pull/4599)
+* Improve suggestion for `explicit_counter_loop` [#4691](https://github.com/rust-lang/rust-clippy/pull/4691)
+* Improve suggestion for `mul_add` [#4602](https://github.com/rust-lang/rust-clippy/pull/4602)
+* Improve suggestion for `assertions_on_constants` [#4635](https://github.com/rust-lang/rust-clippy/pull/4635)
+* Fix ICE in `use_self` [#4671](https://github.com/rust-lang/rust-clippy/pull/4671)
+* Fix ICE when encountering const casts [#4590](https://github.com/rust-lang/rust-clippy/pull/4590)
+
+## Rust 1.39
+
+Released 2019-11-07
+
+[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b)
+
+* New Lints:
+ * [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479)
+ * [`flat_map_identity`] [#4231](https://github.com/rust-lang/rust-clippy/pull/4231)
+ * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535)
+ * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511)
+ * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394)
+ * `option_and_then_some` [#4386](https://github.com/rust-lang/rust-clippy/pull/4386)
+ * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498)
+* Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348)
+* Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403)
+* Move `cast_lossless` to pedantic group [#4539](https://github.com/rust-lang/rust-clippy/pull/4539)
+* `temporary_cstring_as_ptr` now catches more cases [#4425](https://github.com/rust-lang/rust-clippy/pull/4425)
+* `use_self` now works in constructors, too [#4525](https://github.com/rust-lang/rust-clippy/pull/4525)
+* `cargo_common_metadata` now checks for license files [#4518](https://github.com/rust-lang/rust-clippy/pull/4518)
+* `cognitive_complexity` now includes the measured complexity in the warning message [#4469](https://github.com/rust-lang/rust-clippy/pull/4469)
+* Fix false positives in `block_in_if_*` lints [#4458](https://github.com/rust-lang/rust-clippy/pull/4458)
+* Fix false positive in `cast_lossless` [#4473](https://github.com/rust-lang/rust-clippy/pull/4473)
+* Fix false positive in `clone_on_copy` [#4411](https://github.com/rust-lang/rust-clippy/pull/4411)
+* Fix false positive in `deref_addrof` [#4487](https://github.com/rust-lang/rust-clippy/pull/4487)
+* Fix false positive in `too_many_lines` [#4490](https://github.com/rust-lang/rust-clippy/pull/4490)
+* Fix false positive in `new_ret_no_self` [#4365](https://github.com/rust-lang/rust-clippy/pull/4365)
+* Fix false positive in `manual_swap` [#4478](https://github.com/rust-lang/rust-clippy/pull/4478)
+* Fix false positive in `missing_const_for_fn` [#4450](https://github.com/rust-lang/rust-clippy/pull/4450)
+* Fix false positive in `extra_unused_lifetimes` [#4477](https://github.com/rust-lang/rust-clippy/pull/4477)
+* Fix false positive in `inherent_to_string` [#4460](https://github.com/rust-lang/rust-clippy/pull/4460)
+* Fix false positive in `map_entry` [#4495](https://github.com/rust-lang/rust-clippy/pull/4495)
+* Fix false positive in `unused_unit` [#4445](https://github.com/rust-lang/rust-clippy/pull/4445)
+* Fix false positive in `redundant_pattern` [#4489](https://github.com/rust-lang/rust-clippy/pull/4489)
+* Fix false positive in `wrong_self_convention` [#4369](https://github.com/rust-lang/rust-clippy/pull/4369)
+* Improve various suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4558](https://github.com/rust-lang/rust-clippy/pull/4558)
+* Improve suggestions for `redundant_pattern_matching` [#4352](https://github.com/rust-lang/rust-clippy/pull/4352)
+* Improve suggestions for `explicit_write` [#4544](https://github.com/rust-lang/rust-clippy/pull/4544)
+* Improve suggestion for `or_fun_call` [#4522](https://github.com/rust-lang/rust-clippy/pull/4522)
+* Improve suggestion for `match_as_ref` [#4446](https://github.com/rust-lang/rust-clippy/pull/4446)
+* Improve suggestion for `unnecessary_fold_span` [#4382](https://github.com/rust-lang/rust-clippy/pull/4382)
+* Add suggestions for `unseparated_literal_suffix` [#4401](https://github.com/rust-lang/rust-clippy/pull/4401)
+* Add suggestions for `char_lit_as_u8` [#4418](https://github.com/rust-lang/rust-clippy/pull/4418)
+
+## Rust 1.38
+
+Released 2019-09-26
+
+[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860)
+
+* New Lints:
+ * [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203)
+ * [`inherent_to_string`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766)
+ * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222)
+* Move `{unnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307)
+* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308)
+* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262)
+* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337)
+* Fix false positive in `pub_enum_variant_names` and `enum_variant_names` [#4345](https://github.com/rust-lang/rust-clippy/pull/4345)
+* Fix false positive in `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Fix false positive in `string_lit_as_bytes` [#4233](https://github.com/rust-lang/rust-clippy/pull/4233)
+* Fix false positive in `needless_lifetimes` [#4266](https://github.com/rust-lang/rust-clippy/pull/4266)
+* Fix false positive in `float_cmp` [#4275](https://github.com/rust-lang/rust-clippy/pull/4275)
+* Fix false positives in `needless_return` [#4274](https://github.com/rust-lang/rust-clippy/pull/4274)
+* Fix false negative in `match_same_arms` [#4246](https://github.com/rust-lang/rust-clippy/pull/4246)
+* Fix incorrect suggestion for `needless_bool` [#4335](https://github.com/rust-lang/rust-clippy/pull/4335)
+* Improve suggestion for `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Improve suggestion for `single_char_literal` [#4361](https://github.com/rust-lang/rust-clippy/pull/4361)
+* Improve suggestion for `len_zero` [#4314](https://github.com/rust-lang/rust-clippy/pull/4314)
+* Fix ICE in `implicit_hasher` [#4268](https://github.com/rust-lang/rust-clippy/pull/4268)
+* Fix allow bug in `trivially_copy_pass_by_ref` [#4250](https://github.com/rust-lang/rust-clippy/pull/4250)
+
+## Rust 1.37
+
+Released 2019-08-15
+
+[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e)
+
+* New Lints:
+ * [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088)
+ * [`get_last_with_len`] [#3832](https://github.com/rust-lang/rust-clippy/pull/3832)
+ * [`integer_division`] [#4195](https://github.com/rust-lang/rust-clippy/pull/4195)
+* Renamed Lint: `const_static_lifetime` is now called [`redundant_static_lifetimes`].
+ The lint now covers statics in addition to consts [#4162](https://github.com/rust-lang/rust-clippy/pull/4162)
+* [`match_same_arms`] now warns for all identical arms, instead of only the first one [#4102](https://github.com/rust-lang/rust-clippy/pull/4102)
+* [`needless_return`] now works with void functions [#4220](https://github.com/rust-lang/rust-clippy/pull/4220)
+* Fix false positive in [`redundant_closure`] [#4190](https://github.com/rust-lang/rust-clippy/pull/4190)
+* Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107)
+* Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214)
+* Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136)
+* Improve suggestions for `option_map_unwrap_or_else` and `result_map_unwrap_or_else` [#4164](https://github.com/rust-lang/rust-clippy/pull/4164)
+* Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119)
+* Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137)
+* Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071)
+* Add macro check for [`unreadable_literal`] [#4099](https://github.com/rust-lang/rust-clippy/pull/4099)
+
+## Rust 1.36
+
+Released 2019-07-04
+
+[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7)
+
+* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039)
+* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954)
+* Move `path_buf_push_overwrite` to the nursery [#4013](https://github.com/rust-lang/rust-clippy/pull/4013)
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Allow allowing of [`toplevel_ref_arg`] lint [#4007](https://github.com/rust-lang/rust-clippy/pull/4007)
+* Fix false negative in [`or_fun_call`] pertaining to nested constructors [#4084](https://github.com/rust-lang/rust-clippy/pull/4084)
+* Fix false positive in [`or_fun_call`] pertaining to enum variant constructors [#4018](https://github.com/rust-lang/rust-clippy/pull/4018)
+* Fix false positive in [`useless_let_if_seq`] pertaining to interior mutability [#4035](https://github.com/rust-lang/rust-clippy/pull/4035)
+* Fix false positive in [`redundant_closure`] pertaining to non-function types [#4008](https://github.com/rust-lang/rust-clippy/pull/4008)
+* Fix false positive in [`let_and_return`] pertaining to attributes on `let`s [#4024](https://github.com/rust-lang/rust-clippy/pull/4024)
+* Fix false positive in [`module_name_repetitions`] lint pertaining to attributes [#4006](https://github.com/rust-lang/rust-clippy/pull/4006)
+* Fix false positive on [`assertions_on_constants`] pertaining to `debug_assert!` [#3989](https://github.com/rust-lang/rust-clippy/pull/3989)
+* Improve suggestion in [`map_clone`] to suggest `.copied()` where applicable [#3970](https://github.com/rust-lang/rust-clippy/pull/3970) [#4043](https://github.com/rust-lang/rust-clippy/pull/4043)
+* Improve suggestion for [`search_is_some`] [#4049](https://github.com/rust-lang/rust-clippy/pull/4049)
+* Improve suggestion applicability for [`naive_bytecount`] [#3984](https://github.com/rust-lang/rust-clippy/pull/3984)
+* Improve suggestion applicability for [`while_let_loop`] [#3975](https://github.com/rust-lang/rust-clippy/pull/3975)
+* Improve diagnostics for [`too_many_arguments`] [#4053](https://github.com/rust-lang/rust-clippy/pull/4053)
+* Improve diagnostics for [`cast_lossless`] [#4021](https://github.com/rust-lang/rust-clippy/pull/4021)
+* Deal with macro checks in desugarings better [#4082](https://github.com/rust-lang/rust-clippy/pull/4082)
+* Add macro check for [`unnecessary_cast`] [#4026](https://github.com/rust-lang/rust-clippy/pull/4026)
+* Remove [`approx_constant`]'s documentation's "Known problems" section. [#4027](https://github.com/rust-lang/rust-clippy/pull/4027)
+* Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960)
+* Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931)
+
+
+## Rust 1.35
+
+Released 2019-05-20
+
+[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e)
+
+* New lint: `drop_bounds` to detect `T: Drop` bounds
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code
+* Move [`get_unwrap`] to the restriction category
+* Improve suggestions for [`iter_cloned_collect`]
+* Improve suggestions for [`cast_lossless`] to suggest suffixed literals
+* Fix false positives in [`print_with_newline`] and [`write_with_newline`] pertaining to raw strings
+* Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()`
+* Fix false positive in [`bool_comparison`] pertaining to non-bool types
+* Fix false positive in [`redundant_closure`] pertaining to differences in borrows
+* Fix false positive in `option_map_unwrap_or` on non-copy types
+* Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls
+* Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros
+* Fix false positive in [`needless_continue`] pertaining to loop labels
+* Fix false positive for [`boxed_local`] pertaining to arguments moved into closures
+* Fix false positive for [`use_self`] in nested functions
+* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846)
+* Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables
+* Fix suggestion for [`single_char_pattern`] to correctly escape single quotes
+* Avoid triggering [`redundant_closure`] in macros
+* ICE fixes: [#3805](https://github.com/rust-lang/rust-clippy/pull/3805), [#3772](https://github.com/rust-lang/rust-clippy/pull/3772), [#3741](https://github.com/rust-lang/rust-clippy/pull/3741)
+
+## Rust 1.34
+
+Released 2019-04-10
+
+[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380)
+
+* New lint: [`assertions_on_constants`] to detect for example `assert!(true)`
+* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro
+* New lint: [`missing_const_for_fn`] that can suggest functions to be made `const`
+* New lint: [`too_many_lines`] to detect functions with excessive LOC. It can be
+ configured using the `too-many-lines-threshold` configuration.
+* New lint: [`wildcard_enum_match_arm`] to check for wildcard enum matches using `_`
+* Expand `redundant_closure` to also work for methods (not only functions)
+* Fix ICEs in `vec_box`, `needless_pass_by_value` and `implicit_hasher`
+* Fix false positive in `cast_sign_loss`
+* Fix false positive in `integer_arithmetic`
+* Fix false positive in `unit_arg`
+* Fix false positives in `implicit_return`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `cast_lossless`
+* Fix incorrect suggestion for `expect_fun_call`
+* Fix incorrect suggestion for `needless_bool`
+* Fix incorrect suggestion for `needless_range_loop`
+* Fix incorrect suggestion for `use_self`
+* Fix incorrect suggestion for `while_let_on_iterator`
+* Clippy is now slightly easier to invoke in non-cargo contexts. See
+ [#3665][pull3665] for more details.
+* We now have [improved documentation][adding_lints] on how to add new lints
+
+## Rust 1.33
+
+Released 2019-02-26
+
+[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724)
+
+* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`]
+* The `rust-clippy` repository is now part of the `rust-lang` org.
+* Rename `stutter` to `module_name_repetitions`
+* Merge `new_without_default_derive` into `new_without_default` lint
+* Move `large_digit_groups` from `style` group to `pedantic`
+* Expand `bool_comparison` to check for `<`, `<=`, `>`, `>=`, and `!=`
+ comparisons against booleans
+* Expand `no_effect` to detect writes to constants such as `A_CONST.field = 2`
+* Expand `redundant_clone` to work on struct fields
+* Expand `suspicious_else_formatting` to detect `if .. {..} {..}`
+* Expand `use_self` to work on tuple structs and also in local macros
+* Fix ICE in `result_map_unit_fn` and `option_map_unit_fn`
+* Fix false positives in `implicit_return`
+* Fix false positives in `use_self`
+* Fix false negative in `clone_on_copy`
+* Fix false positive in `doc_markdown`
+* Fix false positive in `empty_loop`
+* Fix false positive in `if_same_then_else`
+* Fix false positive in `infinite_iter`
+* Fix false positive in `question_mark`
+* Fix false positive in `useless_asref`
+* Fix false positive in `wildcard_dependencies`
+* Fix false positive in `write_with_newline`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `get_unwrap`
+
+## Rust 1.32
+
+Released 2019-01-17
+
+[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be)
+
+* New lints: [`slow_vector_initialization`], `mem_discriminant_non_enum`,
+ [`redundant_clone`], [`wildcard_dependencies`],
+ [`into_iter_on_ref`], `into_iter_on_array`, [`deprecated_cfg_attr`],
+ [`cargo_common_metadata`]
+* Add support for `u128` and `i128` to integer related lints
+* Add float support to `mistyped_literal_suffixes`
+* Fix false positives in `use_self`
+* Fix false positives in `missing_comma`
+* Fix false positives in `new_ret_no_self`
+* Fix false positives in `possible_missing_comma`
+* Fix false positive in `integer_arithmetic` in constant items
+* Fix false positive in `needless_borrow`
+* Fix false positive in `out_of_bounds_indexing`
+* Fix false positive in `new_without_default_derive`
+* Fix false positive in `string_lit_as_bytes`
+* Fix false negative in `out_of_bounds_indexing`
+* Fix false negative in `use_self`. It will now also check existential types
+* Fix incorrect suggestion for `redundant_closure_call`
+* Fix various suggestions that contained expanded macros
+* Fix `bool_comparison` triggering 3 times on on on the same code
+* Expand `trivially_copy_pass_by_ref` to work on trait methods
+* Improve suggestion for `needless_range_loop`
+* Move `needless_pass_by_value` from `pedantic` group to `style`
+
+## Rust 1.31
+
+Released 2018-12-06
+
+[125907ad..2e26fdc2](https://github.com/rust-lang/rust-clippy/compare/125907ad..2e26fdc2)
+
+* Clippy has been relicensed under a dual MIT / Apache license.
+ See [#3093](https://github.com/rust-lang/rust-clippy/issues/3093) for more
+ information.
+* With Rust 1.31, Clippy is no longer available via crates.io. The recommended
+ installation method is via `rustup component add clippy`.
+* New lints: [`redundant_pattern_matching`], [`unnecessary_filter_map`],
+ [`unused_unit`], [`map_flatten`], [`mem_replace_option_with_none`]
+* Fix ICE in `if_let_redundant_pattern_matching`
+* Fix ICE in `needless_pass_by_value` when encountering a generic function
+ argument with a lifetime parameter
+* Fix ICE in `needless_range_loop`
+* Fix ICE in `single_char_pattern` when encountering a constant value
+* Fix false positive in `assign_op_pattern`
+* Fix false positive in `boxed_local` on trait implementations
+* Fix false positive in `cmp_owned`
+* Fix false positive in `collapsible_if` when conditionals have comments
+* Fix false positive in `double_parens`
+* Fix false positive in `excessive_precision`
+* Fix false positive in `explicit_counter_loop`
+* Fix false positive in `fn_to_numeric_cast_with_truncation`
+* Fix false positive in `map_clone`
+* Fix false positive in `new_ret_no_self`
+* Fix false positive in `new_without_default` when `new` is unsafe
+* Fix false positive in `type_complexity` when using extern types
+* Fix false positive in `useless_format`
+* Fix false positive in `wrong_self_convention`
+* Fix incorrect suggestion for `excessive_precision`
+* Fix incorrect suggestion for `expect_fun_call`
+* Fix incorrect suggestion for `get_unwrap`
+* Fix incorrect suggestion for `useless_format`
+* `fn_to_numeric_cast_with_truncation` lint can be disabled again
+* Improve suggestions for `manual_memcpy`
+* Improve help message for `needless_lifetimes`
+
+## Rust 1.30
+
+Released 2018-10-25
+
+[14207503...125907ad](https://github.com/rust-lang/rust-clippy/compare/14207503...125907ad)
+
+* Deprecate `assign_ops` lint
+* New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`],
+ [`needless_collect`], [`copy_iterator`]
+* `cargo clippy -V` now includes the Clippy commit hash of the Rust
+ Clippy component
+* Fix ICE in `implicit_hasher`
+* Fix ICE when encountering `println!("{}" a);`
+* Fix ICE when encountering a macro call in match statements
+* Fix false positive in `default_trait_access`
+* Fix false positive in `trivially_copy_pass_by_ref`
+* Fix false positive in `similar_names`
+* Fix false positive in `redundant_field_name`
+* Fix false positive in `expect_fun_call`
+* Fix false negative in `identity_conversion`
+* Fix false negative in `explicit_counter_loop`
+* Fix `range_plus_one` suggestion and false negative
+* `print_with_newline` / `write_with_newline`: don't warn about string with several `\n`s in them
+* Fix `useless_attribute` to also whitelist `unused_extern_crates`
+* Fix incorrect suggestion for `single_char_pattern`
+* Improve suggestion for `identity_conversion` lint
+* Move `explicit_iter_loop` and `explicit_into_iter_loop` from `style` group to `pedantic`
+* Move `range_plus_one` and `range_minus_one` from `nursery` group to `complexity`
+* Move `shadow_unrelated` from `restriction` group to `pedantic`
+* Move `indexing_slicing` from `pedantic` group to `restriction`
+
+## Rust 1.29
+
+Released 2018-09-13
+
+[v0.0.212...14207503](https://github.com/rust-lang/rust-clippy/compare/v0.0.212...14207503)
+
+* :tada: :tada: **Rust 1.29 is the first stable Rust that includes a bundled Clippy** :tada:
+ :tada:
+ You can now run `rustup component add clippy-preview` and then `cargo
+ clippy` to run Clippy. This should put an end to the continuous nightly
+ upgrades for Clippy users.
+* Clippy now follows the Rust versioning scheme instead of its own
+* Fix ICE when encountering a `while let (..) = x.iter()` construct
+* Fix false positives in `use_self`
+* Fix false positive in `trivially_copy_pass_by_ref`
+* Fix false positive in `useless_attribute` lint
+* Fix false positive in `print_literal`
+* Fix `use_self` regressions
+* Improve lint message for `neg_cmp_op_on_partial_ord`
+* Improve suggestion highlight for `single_char_pattern`
+* Improve suggestions for various print/write macro lints
+* Improve website header
+
+## 0.0.212 (2018-07-10)
+* Rustup to *rustc 1.29.0-nightly (e06c87544 2018-07-06)*
+
+## 0.0.211
+* Rustup to *rustc 1.28.0-nightly (e3bf634e0 2018-06-28)*
+
+## 0.0.210
+* Rustup to *rustc 1.28.0-nightly (01cc982e9 2018-06-24)*
+
+## 0.0.209
+* Rustup to *rustc 1.28.0-nightly (523097979 2018-06-18)*
+
+## 0.0.208
+* Rustup to *rustc 1.28.0-nightly (86a8f1a63 2018-06-17)*
+
+## 0.0.207
+* Rustup to *rustc 1.28.0-nightly (2a0062974 2018-06-09)*
+
+## 0.0.206
+* Rustup to *rustc 1.28.0-nightly (5bf68db6e 2018-05-28)*
+
+## 0.0.205
+* Rustup to *rustc 1.28.0-nightly (990d8aa74 2018-05-25)*
+* Rename `unused_lifetimes` to `extra_unused_lifetimes` because of naming conflict with new rustc lint
+
+## 0.0.204
+* Rustup to *rustc 1.28.0-nightly (71e87be38 2018-05-22)*
+
+## 0.0.203
+* Rustup to *rustc 1.28.0-nightly (a3085756e 2018-05-19)*
+* Clippy attributes are now of the form `clippy::cyclomatic_complexity` instead of `clippy(cyclomatic_complexity)`
+
+## 0.0.202
+* Rustup to *rustc 1.28.0-nightly (952f344cd 2018-05-18)*
+
+## 0.0.201
+* Rustup to *rustc 1.27.0-nightly (2f2a11dfc 2018-05-16)*
+
+## 0.0.200
+* Rustup to *rustc 1.27.0-nightly (9fae15374 2018-05-13)*
+
+## 0.0.199
+* Rustup to *rustc 1.27.0-nightly (ff2ac35db 2018-05-12)*
+
+## 0.0.198
+* Rustup to *rustc 1.27.0-nightly (acd3871ba 2018-05-10)*
+
+## 0.0.197
+* Rustup to *rustc 1.27.0-nightly (428ea5f6b 2018-05-06)*
+
+## 0.0.196
+* Rustup to *rustc 1.27.0-nightly (e82261dfb 2018-05-03)*
+
+## 0.0.195
+* Rustup to *rustc 1.27.0-nightly (ac3c2288f 2018-04-18)*
+
+## 0.0.194
+* Rustup to *rustc 1.27.0-nightly (bd40cbbe1 2018-04-14)*
+* New lints: [`cast_ptr_alignment`], [`transmute_ptr_to_ptr`], [`write_literal`], [`write_with_newline`], [`writeln_empty_string`]
+
+## 0.0.193
+* Rustup to *rustc 1.27.0-nightly (eeea94c11 2018-04-06)*
+
+## 0.0.192
+* Rustup to *rustc 1.27.0-nightly (fb44b4c0e 2018-04-04)*
+* New lint: [`print_literal`]
+
+## 0.0.191
+* Rustup to *rustc 1.26.0-nightly (ae544ee1c 2018-03-29)*
+* Lint audit; categorize lints as style, correctness, complexity, pedantic, nursery, restriction.
+
+## 0.0.190
+* Fix a bunch of intermittent cargo bugs
+
+## 0.0.189
+* Rustup to *rustc 1.26.0-nightly (5508b2714 2018-03-18)*
+
+## 0.0.188
+* Rustup to *rustc 1.26.0-nightly (392645394 2018-03-15)*
+* New lint: [`while_immutable_condition`]
+
+## 0.0.187
+* Rustup to *rustc 1.26.0-nightly (322d7f7b9 2018-02-25)*
+* New lints: [`redundant_field_names`], [`suspicious_arithmetic_impl`], [`suspicious_op_assign_impl`]
+
+## 0.0.186
+* Rustup to *rustc 1.25.0-nightly (0c6091fbd 2018-02-04)*
+* Various false positive fixes
+
+## 0.0.185
+* Rustup to *rustc 1.25.0-nightly (56733bc9f 2018-02-01)*
+* New lint: [`question_mark`]
+
+## 0.0.184
+* Rustup to *rustc 1.25.0-nightly (90eb44a58 2018-01-29)*
+* New lints: [`double_comparisons`], [`empty_line_after_outer_attr`]
+
+## 0.0.183
+* Rustup to *rustc 1.25.0-nightly (21882aad7 2018-01-28)*
+* New lint: [`misaligned_transmute`]
+
+## 0.0.182
+* Rustup to *rustc 1.25.0-nightly (a0dcecff9 2018-01-24)*
+* New lint: [`decimal_literal_representation`]
+
+## 0.0.181
+* Rustup to *rustc 1.25.0-nightly (97520ccb1 2018-01-21)*
+* New lints: [`else_if_without_else`], [`option_option`], [`unit_arg`], [`unnecessary_fold`]
+* Removed `unit_expr`
+* Various false positive fixes for [`needless_pass_by_value`]
+
+## 0.0.180
+* Rustup to *rustc 1.25.0-nightly (3f92e8d89 2018-01-14)*
+
+## 0.0.179
+* Rustup to *rustc 1.25.0-nightly (61452e506 2018-01-09)*
+
+## 0.0.178
+* Rustup to *rustc 1.25.0-nightly (ee220daca 2018-01-07)*
+
+## 0.0.177
+* Rustup to *rustc 1.24.0-nightly (250b49205 2017-12-21)*
+* New lint: [`match_as_ref`]
+
+## 0.0.176
+* Rustup to *rustc 1.24.0-nightly (0077d128d 2017-12-14)*
+
+## 0.0.175
+* Rustup to *rustc 1.24.0-nightly (bb42071f6 2017-12-01)*
+
+## 0.0.174
+* Rustup to *rustc 1.23.0-nightly (63739ab7b 2017-11-21)*
+
+## 0.0.173
+* Rustup to *rustc 1.23.0-nightly (33374fa9d 2017-11-20)*
+
+## 0.0.172
+* Rustup to *rustc 1.23.0-nightly (d0f8e2913 2017-11-16)*
+
+## 0.0.171
+* Rustup to *rustc 1.23.0-nightly (ff0f5de3b 2017-11-14)*
+
+## 0.0.170
+* Rustup to *rustc 1.23.0-nightly (d6b06c63a 2017-11-09)*
+
+## 0.0.169
+* Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)*
+* New lints: [`just_underscores_and_digits`], `result_map_unwrap_or_else`, [`transmute_bytes_to_str`]
+
+## 0.0.168
+* Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)*
+
+## 0.0.167
+* Rustup to *rustc 1.23.0-nightly (90ef3372e 2017-10-29)*
+* New lints: `const_static_lifetime`, [`erasing_op`], [`fallible_impl_from`], [`println_empty_string`], [`useless_asref`]
+
+## 0.0.166
+* Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)*
+* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], `invalid_ref`, [`option_map_or_none`],
+ [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`],
+ [`transmute_int_to_float`]
+
+## 0.0.165
+* Rust upgrade to rustc 1.22.0-nightly (0e6f4cf51 2017-09-27)
+* New lint: [`mut_range_bound`]
+
+## 0.0.164
+* Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)*
+* New lint: [`int_plus_one`]
+
+## 0.0.163
+* Update to *rustc 1.22.0-nightly (14039a42a 2017-09-22)*
+
+## 0.0.162
+* Update to *rustc 1.22.0-nightly (0701b37d9 2017-09-18)*
+* New lint: [`chars_last_cmp`]
+* Improved suggestions for [`needless_borrow`], [`ptr_arg`],
+
+## 0.0.161
+* Update to *rustc 1.22.0-nightly (539f2083d 2017-09-13)*
+
+## 0.0.160
+* Update to *rustc 1.22.0-nightly (dd08c3070 2017-09-12)*
+
+## 0.0.159
+* Update to *rustc 1.22.0-nightly (eba374fb2 2017-09-11)*
+* New lint: [`clone_on_ref_ptr`]
+
+## 0.0.158
+* New lint: [`manual_memcpy`]
+* [`cast_lossless`] no longer has redundant parentheses in its suggestions
+* Update to *rustc 1.22.0-nightly (dead08cb3 2017-09-08)*
+
+## 0.0.157 - 2017-09-04
+* Update to *rustc 1.22.0-nightly (981ce7d8d 2017-09-03)*
+* New lint: `unit_expr`
+
+## 0.0.156 - 2017-09-03
+* Update to *rustc 1.22.0-nightly (744dd6c1d 2017-09-02)*
+
+## 0.0.155
+* Update to *rustc 1.21.0-nightly (c11f689d2 2017-08-29)*
+* New lint: [`infinite_iter`], [`maybe_infinite_iter`], [`cast_lossless`]
+
+## 0.0.154
+* Update to *rustc 1.21.0-nightly (2c0558f63 2017-08-24)*
+* Fix [`use_self`] triggering inside derives
+* Add support for linting an entire workspace with `cargo clippy --all`
+* New lint: [`naive_bytecount`]
+
+## 0.0.153
+* Update to *rustc 1.21.0-nightly (8c303ed87 2017-08-20)*
+* New lint: [`use_self`]
+
+## 0.0.152
+* Update to *rustc 1.21.0-nightly (df511d554 2017-08-14)*
+
+## 0.0.151
+* Update to *rustc 1.21.0-nightly (13d94d5fa 2017-08-10)*
+
+## 0.0.150
+* Update to *rustc 1.21.0-nightly (215e0b10e 2017-08-08)*
+
+## 0.0.148
+* Update to *rustc 1.21.0-nightly (37c7d0ebb 2017-07-31)*
+* New lints: [`unreadable_literal`], [`inconsistent_digit_grouping`], [`large_digit_groups`]
+
+## 0.0.147
+* Update to *rustc 1.21.0-nightly (aac223f4f 2017-07-30)*
+
+## 0.0.146
+* Update to *rustc 1.21.0-nightly (52a330969 2017-07-27)*
+* Fixes false positives in `inline_always`
+* Fixes false negatives in `panic_params`
+
+## 0.0.145
+* Update to *rustc 1.20.0-nightly (afe145d22 2017-07-23)*
+
+## 0.0.144
+* Update to *rustc 1.20.0-nightly (086eaa78e 2017-07-15)*
+
+## 0.0.143
+* Update to *rustc 1.20.0-nightly (d84693b93 2017-07-09)*
+* Fix `cargo clippy` crashing on `dylib` projects
+* Fix false positives around `nested_while_let` and `never_loop`
+
+## 0.0.142
+* Update to *rustc 1.20.0-nightly (067971139 2017-07-02)*
+
+## 0.0.141
+* Rewrite of the `doc_markdown` lint.
+* Deprecated [`range_step_by_zero`]
+* New lint: [`iterator_step_by_zero`]
+* New lint: [`needless_borrowed_reference`]
+* Update to *rustc 1.20.0-nightly (69c65d296 2017-06-28)*
+
+## 0.0.140 - 2017-06-16
+* Update to *rustc 1.19.0-nightly (258ae6dd9 2017-06-15)*
+
+## 0.0.139 — 2017-06-10
+* Update to *rustc 1.19.0-nightly (4bf5c99af 2017-06-10)*
+* Fix bugs with for loop desugaring
+* Check for [`AsRef`]/[`AsMut`] arguments in [`wrong_self_convention`]
+
+## 0.0.138 — 2017-06-05
+* Update to *rustc 1.19.0-nightly (0418fa9d3 2017-06-04)*
+
+## 0.0.137 — 2017-06-05
+* Update to *rustc 1.19.0-nightly (6684d176c 2017-06-03)*
+
+## 0.0.136 — 2017—05—26
+* Update to *rustc 1.19.0-nightly (557967766 2017-05-26)*
+
+## 0.0.135 — 2017—05—24
+* Update to *rustc 1.19.0-nightly (5b13bff52 2017-05-23)*
+
+## 0.0.134 — 2017—05—19
+* Update to *rustc 1.19.0-nightly (0ed1ec9f9 2017-05-18)*
+
+## 0.0.133 — 2017—05—14
+* Update to *rustc 1.19.0-nightly (826d8f385 2017-05-13)*
+
+## 0.0.132 — 2017—05—05
+* Fix various bugs and some ices
+
+## 0.0.131 — 2017—05—04
+* Update to *rustc 1.19.0-nightly (2d4ed8e0c 2017-05-03)*
+
+## 0.0.130 — 2017—05—03
+* Update to *rustc 1.19.0-nightly (6a5fc9eec 2017-05-02)*
+
+## 0.0.129 — 2017-05-01
+* Update to *rustc 1.19.0-nightly (06fb4d256 2017-04-30)*
+
+## 0.0.128 — 2017-04-28
+* Update to *rustc 1.18.0-nightly (94e884b63 2017-04-27)*
+
+## 0.0.127 — 2017-04-27
+* Update to *rustc 1.18.0-nightly (036983201 2017-04-26)*
+* New lint: [`needless_continue`]
+
+## 0.0.126 — 2017-04-24
+* Update to *rustc 1.18.0-nightly (2bd4b5c6d 2017-04-23)*
+
+## 0.0.125 — 2017-04-19
+* Update to *rustc 1.18.0-nightly (9f2abadca 2017-04-18)*
+
+## 0.0.124 — 2017-04-16
+* Update to *rustc 1.18.0-nightly (d5cf1cb64 2017-04-15)*
+
+## 0.0.123 — 2017-04-07
+* Fix various false positives
+
+## 0.0.122 — 2017-04-07
+* Rustup to *rustc 1.18.0-nightly (91ae22a01 2017-04-05)*
+* New lint: [`op_ref`]
+
+## 0.0.121 — 2017-03-21
+* Rustup to *rustc 1.17.0-nightly (134c4a0f0 2017-03-20)*
+
+## 0.0.120 — 2017-03-17
+* Rustup to *rustc 1.17.0-nightly (0aeb9c129 2017-03-15)*
+
+## 0.0.119 — 2017-03-13
+* Rustup to *rustc 1.17.0-nightly (824c9ebbd 2017-03-12)*
+
+## 0.0.118 — 2017-03-05
+* Rustup to *rustc 1.17.0-nightly (b1e31766d 2017-03-03)*
+
+## 0.0.117 — 2017-03-01
+* Rustup to *rustc 1.17.0-nightly (be760566c 2017-02-28)*
+
+## 0.0.116 — 2017-02-28
+* Fix `cargo clippy` on 64 bit windows systems
+
+## 0.0.115 — 2017-02-27
+* Rustup to *rustc 1.17.0-nightly (60a0edc6c 2017-02-26)*
+* New lints: [`zero_ptr`], [`never_loop`], [`mut_from_ref`]
+
+## 0.0.114 — 2017-02-08
+* Rustup to *rustc 1.17.0-nightly (c49d10207 2017-02-07)*
+* Tests are now ui tests (testing the exact output of rustc)
+
+## 0.0.113 — 2017-02-04
+* Rustup to *rustc 1.16.0-nightly (eedaa94e3 2017-02-02)*
+* New lint: [`large_enum_variant`]
+* `explicit_into_iter_loop` provides suggestions
+
+## 0.0.112 — 2017-01-27
+* Rustup to *rustc 1.16.0-nightly (df8debf6d 2017-01-25)*
+
+## 0.0.111 — 2017-01-21
+* Rustup to *rustc 1.16.0-nightly (a52da95ce 2017-01-20)*
+
+## 0.0.110 — 2017-01-20
+* Add badges and categories to `Cargo.toml`
+
+## 0.0.109 — 2017-01-19
+* Update to *rustc 1.16.0-nightly (c07a6ae77 2017-01-17)*
+
+## 0.0.108 — 2017-01-12
+* Update to *rustc 1.16.0-nightly (2782e8f8f 2017-01-12)*
+
+## 0.0.107 — 2017-01-11
+* Update regex dependency
+* Fix FP when matching `&&mut` by `&ref`
+* Reintroduce `for (_, x) in &mut hash_map` -> `for x in hash_map.values_mut()`
+* New lints: [`unused_io_amount`], [`forget_ref`], [`short_circuit_statement`]
+
+## 0.0.106 — 2017-01-04
+* Fix FP introduced by rustup in [`wrong_self_convention`]
+
+## 0.0.105 — 2017-01-04
+* Update to *rustc 1.16.0-nightly (468227129 2017-01-03)*
+* New lints: [`deref_addrof`], [`double_parens`], [`pub_enum_variant_names`]
+* Fix suggestion in [`new_without_default`]
+* FP fix in [`absurd_extreme_comparisons`]
+
+## 0.0.104 — 2016-12-15
+* Update to *rustc 1.15.0-nightly (8f02c429a 2016-12-15)*
+
+## 0.0.103 — 2016-11-25
+* Update to *rustc 1.15.0-nightly (d5814b03e 2016-11-23)*
+
+## 0.0.102 — 2016-11-24
+* Update to *rustc 1.15.0-nightly (3bf2be9ce 2016-11-22)*
+
+## 0.0.101 — 2016-11-23
+* Update to *rustc 1.15.0-nightly (7b3eeea22 2016-11-21)*
+* New lint: [`string_extend_chars`]
+
+## 0.0.100 — 2016-11-20
+* Update to *rustc 1.15.0-nightly (ac635aa95 2016-11-18)*
+
+## 0.0.99 — 2016-11-18
+* Update to rustc 1.15.0-nightly (0ed951993 2016-11-14)
+* New lint: [`get_unwrap`]
+
+## 0.0.98 — 2016-11-08
+* Fixes an issue due to a change in how cargo handles `--sysroot`, which broke `cargo clippy`
+
+## 0.0.97 — 2016-11-03
+* For convenience, `cargo clippy` defines a `cargo-clippy` feature. This was
+ previously added for a short time under the name `clippy` but removed for
+ compatibility.
+* `cargo clippy --help` is more helping (and less helpful :smile:)
+* Rustup to *rustc 1.14.0-nightly (5665bdf3e 2016-11-02)*
+* New lints: [`if_let_redundant_pattern_matching`], [`partialeq_ne_impl`]
+
+## 0.0.96 — 2016-10-22
+* Rustup to *rustc 1.14.0-nightly (f09420685 2016-10-20)*
+* New lint: [`iter_skip_next`]
+
+## 0.0.95 — 2016-10-06
+* Rustup to *rustc 1.14.0-nightly (3210fd5c2 2016-10-05)*
+
+## 0.0.94 — 2016-10-04
+* Fixes bustage on Windows due to forbidden directory name
+
+## 0.0.93 — 2016-10-03
+* Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)*
+* `option_map_unwrap_or` and `option_map_unwrap_or_else` are now
+ allowed by default.
+* New lint: [`explicit_into_iter_loop`]
+
+## 0.0.92 — 2016-09-30
+* Rustup to *rustc 1.14.0-nightly (289f3a4ca 2016-09-29)*
+
+## 0.0.91 — 2016-09-28
+* Rustup to *rustc 1.13.0-nightly (d0623cf7b 2016-09-26)*
+
+## 0.0.90 — 2016-09-09
+* Rustup to *rustc 1.13.0-nightly (f1f40f850 2016-09-09)*
+
+## 0.0.89 — 2016-09-06
+* Rustup to *rustc 1.13.0-nightly (cbe4de78e 2016-09-05)*
+
+## 0.0.88 — 2016-09-04
+* Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)*
+* The following lints are not new but were only usable through the `clippy`
+ lint groups: [`filter_next`], `for_loop_over_option`,
+ `for_loop_over_result` and [`match_overlapping_arm`]. You should now be
+ able to `#[allow/deny]` them individually and they are available directly
+ through `cargo clippy`.
+
+## 0.0.87 — 2016-08-31
+* Rustup to *rustc 1.13.0-nightly (eac41469d 2016-08-30)*
+* New lints: [`builtin_type_shadow`]
+* Fix FP in [`zero_prefixed_literal`] and `0b`/`0o`
+
+## 0.0.86 — 2016-08-28
+* Rustup to *rustc 1.13.0-nightly (a23064af5 2016-08-27)*
+* New lints: [`missing_docs_in_private_items`], [`zero_prefixed_literal`]
+
+## 0.0.85 — 2016-08-19
+* Fix ICE with [`useless_attribute`]
+* [`useless_attribute`] ignores `unused_imports` on `use` statements
+
+## 0.0.84 — 2016-08-18
+* Rustup to *rustc 1.13.0-nightly (aef6971ca 2016-08-17)*
+
+## 0.0.83 — 2016-08-17
+* Rustup to *rustc 1.12.0-nightly (1bf5fa326 2016-08-16)*
+* New lints: [`print_with_newline`], [`useless_attribute`]
+
+## 0.0.82 — 2016-08-17
+* Rustup to *rustc 1.12.0-nightly (197be89f3 2016-08-15)*
+* New lint: [`module_inception`]
+
+## 0.0.81 — 2016-08-14
+* Rustup to *rustc 1.12.0-nightly (1deb02ea6 2016-08-12)*
+* New lints: [`eval_order_dependence`], [`mixed_case_hex_literals`], [`unseparated_literal_suffix`]
+* False positive fix in [`too_many_arguments`]
+* Addition of functionality to [`needless_borrow`]
+* Suggestions for [`clone_on_copy`]
+* Bug fix in [`wrong_self_convention`]
+* Doc improvements
+
+## 0.0.80 — 2016-07-31
+* Rustup to *rustc 1.12.0-nightly (1225e122f 2016-07-30)*
+* New lints: [`misrefactored_assign_op`], [`serde_api_misuse`]
+
+## 0.0.79 — 2016-07-10
+* Rustup to *rustc 1.12.0-nightly (f93aaf84c 2016-07-09)*
+* Major suggestions refactoring
+
+## 0.0.78 — 2016-07-02
+* Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)*
+* New lints: [`wrong_transmute`], [`double_neg`], [`filter_map`]
+* For compatibility, `cargo clippy` does not defines the `clippy` feature
+ introduced in 0.0.76 anymore
+* [`collapsible_if`] now considers `if let`
+
+## 0.0.77 — 2016-06-21
+* Rustup to *rustc 1.11.0-nightly (5522e678b 2016-06-20)*
+* New lints: `stutter` and [`iter_nth`]
+
+## 0.0.76 — 2016-06-10
+* Rustup to *rustc 1.11.0-nightly (7d2f75a95 2016-06-09)*
+* `cargo clippy` now automatically defines the `clippy` feature
+* New lint: [`not_unsafe_ptr_arg_deref`]
+
+## 0.0.75 — 2016-06-08
+* Rustup to *rustc 1.11.0-nightly (763f9234b 2016-06-06)*
+
+## 0.0.74 — 2016-06-07
+* Fix bug with `cargo-clippy` JSON parsing
+* Add the `CLIPPY_DISABLE_DOCS_LINKS` environment variable to deactivate the
+ “for further information visit *lint-link*” message.
+
+## 0.0.73 — 2016-06-05
+* Fix false positives in [`useless_let_if_seq`]
+
+## 0.0.72 — 2016-06-04
+* Fix false positives in [`useless_let_if_seq`]
+
+## 0.0.71 — 2016-05-31
+* Rustup to *rustc 1.11.0-nightly (a967611d8 2016-05-30)*
+* New lint: [`useless_let_if_seq`]
+
+## 0.0.70 — 2016-05-28
+* Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)*
+* [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`,
+ `RegexBuilder::new` and byte regexes
+
+## 0.0.69 — 2016-05-20
+* Rustup to *rustc 1.10.0-nightly (476fe6eef 2016-05-21)*
+* [`used_underscore_binding`] has been made `Allow` temporarily
+
+## 0.0.68 — 2016-05-17
+* Rustup to *rustc 1.10.0-nightly (cd6a40017 2016-05-16)*
+* New lint: [`unnecessary_operation`]
+
+## 0.0.67 — 2016-05-12
+* Rustup to *rustc 1.10.0-nightly (22ac88f1a 2016-05-11)*
+
+## 0.0.66 — 2016-05-11
+* New `cargo clippy` subcommand
+* New lints: [`assign_op_pattern`], [`assign_ops`], [`needless_borrow`]
+
+## 0.0.65 — 2016-05-08
+* Rustup to *rustc 1.10.0-nightly (62e2b2fb7 2016-05-06)*
+* New lints: [`float_arithmetic`], [`integer_arithmetic`]
+
+## 0.0.64 — 2016-04-26
+* Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)*
+* New lints: `temporary_cstring_as_ptr`, [`unsafe_removed_from_name`], and [`mem_forget`]
+
+## 0.0.63 — 2016-04-08
+* Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)*
+
+## 0.0.62 — 2016-04-07
+* Rustup to *rustc 1.9.0-nightly (bf5da36f1 2016-04-06)*
+
+## 0.0.61 — 2016-04-03
+* Rustup to *rustc 1.9.0-nightly (5ab11d72c 2016-04-02)*
+* New lint: [`invalid_upcast_comparisons`]
+
+## 0.0.60 — 2016-04-01
+* Rustup to *rustc 1.9.0-nightly (e1195c24b 2016-03-31)*
+
+## 0.0.59 — 2016-03-31
+* Rustup to *rustc 1.9.0-nightly (30a3849f2 2016-03-30)*
+* New lints: [`logic_bug`], [`nonminimal_bool`]
+* Fixed: [`match_same_arms`] now ignores arms with guards
+* Improved: [`useless_vec`] now warns on `for … in vec![…]`
+
+## 0.0.58 — 2016-03-27
+* Rustup to *rustc 1.9.0-nightly (d5a91e695 2016-03-26)*
+* New lint: [`doc_markdown`]
+
+## 0.0.57 — 2016-03-27
+* Update to *rustc 1.9.0-nightly (a1e29daf1 2016-03-25)*
+* Deprecated lints: [`str_to_string`], [`string_to_string`], [`unstable_as_slice`], [`unstable_as_mut_slice`]
+* New lint: [`crosspointer_transmute`]
+
+## 0.0.56 — 2016-03-23
+* Update to *rustc 1.9.0-nightly (0dcc413e4 2016-03-22)*
+* New lints: [`many_single_char_names`] and [`similar_names`]
+
+## 0.0.55 — 2016-03-21
+* Update to *rustc 1.9.0-nightly (02310fd31 2016-03-19)*
+
+## 0.0.54 — 2016-03-16
+* Update to *rustc 1.9.0-nightly (c66d2380a 2016-03-15)*
+
+## 0.0.53 — 2016-03-15
+* Add a [configuration file]
+
+## ~~0.0.52~~
+
+## 0.0.51 — 2016-03-13
+* Add `str` to types considered by [`len_zero`]
+* New lints: [`indexing_slicing`]
+
+## 0.0.50 — 2016-03-11
+* Update to *rustc 1.9.0-nightly (c9629d61c 2016-03-10)*
+
+## 0.0.49 — 2016-03-09
+* Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)*
+* New lints: [`overflow_check_conditional`], `unused_label`, [`new_without_default`]
+
+## 0.0.48 — 2016-03-07
+* Fixed: ICE in [`needless_range_loop`] with globals
+
+## 0.0.47 — 2016-03-07
+* Update to *rustc 1.9.0-nightly (998a6720b 2016-03-07)*
+* New lint: [`redundant_closure_call`]
+
+[`AsMut`]: https://doc.rust-lang.org/std/convert/trait.AsMut.html
+[`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
+[configuration file]: ./rust-clippy#configuration
+[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665
+[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md
+[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md
+
+<!-- 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
+[`alloc_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#alloc_instead_of_core
+[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
+[`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range
+[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
+[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
+[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
+[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
+[`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut
+[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
+[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
+[`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states
+[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
+[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
+[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
+[`await_holding_invalid_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type
+[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
+[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
+[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
+[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
+[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
+[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
+[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr
+[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt
+[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
+[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
+[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
+[`bool_to_int_with_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_to_int_with_if
+[`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr
+[`borrow_deref_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref
+[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
+[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
+[`box_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection
+[`box_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_default
+[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec
+[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
+[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
+[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
+[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
+[`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_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
+[`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_nan_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_nan_to_int
+[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
+[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
+[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
+[`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
+[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts
+[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
+[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
+[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
+[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
+[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
+[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
+[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
+[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
+[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
+[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
+[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
+[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
+[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if
+[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
+[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
+[`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace
+[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
+[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
+[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
+[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
+[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
+[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
+[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
+[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
+[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
+[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
+[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
+[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
+[`default_instead_of_iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_instead_of_iter_empty
+[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
+[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
+[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
+[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
+[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
+[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
+[`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing
+[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
+[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
+[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
+[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
+[`disallowed_macros`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros
+[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
+[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
+[`disallowed_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names
+[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
+[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
+[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
+[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
+[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
+[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
+[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
+[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
+[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
+[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
+[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
+[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
+[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
+[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
+[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
+[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
+[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
+[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
+[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
+[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
+[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
+[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
+[`empty_structs_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_structs_with_brackets
+[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
+[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
+[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
+[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
+[`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let
+[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
+[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
+[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
+[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
+[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
+[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
+[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit
+[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
+[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
+[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
+[`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref
+[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
+[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
+[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
+[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop
+[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write
+[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice
+[`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain
+[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes
+[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from
+[`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default
+[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file
+[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
+[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
+[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
+[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
+[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
+[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
+[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
+[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
+[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
+[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
+[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
+[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
+[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
+[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
+[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
+[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
+[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option
+[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
+[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
+[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
+[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
+[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
+[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
+[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
+[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
+[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
++[`from_raw_with_void_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_raw_with_void_ptr
+[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
+[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
+[`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first
+[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
+[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
+[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
+[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
+[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
+[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching
+[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result
+[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else
+[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
+[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
+[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
+[`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone
+[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
+[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
+[`implicit_saturating_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_add
+[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
+[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
+[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
+[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
+[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
+[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
+[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
+[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
+[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
+[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
+[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
+[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
+[`init_numbered_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#init_numbered_fields
+[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
+[`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax
+[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
+[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
+[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
+[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
+[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
+[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
+[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
+[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
+[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
+[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
+[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
+[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
+[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
+[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked
+[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
+[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
+[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
+[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
+[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
+[`iter_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map
+[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
+[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
+[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
+[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
+[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
+[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
+[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
+[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
+[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
+[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
+[`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
+[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
+[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
+[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
+[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
+[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
+[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
+[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
+[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
+[`let_underscore_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop
++[`let_underscore_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_future
+[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
+[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use
+[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
+[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
+[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
+[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
+[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
+[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
+[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
+[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
+[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
+[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
+[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
+[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
+[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
+[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
+[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
+[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
++[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
++[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
+[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
+[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
+[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
+[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
+[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
+[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
+[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
+[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
+[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
+[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
+[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new
+[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
+[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
+[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
+[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
+[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
+[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
+[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
+[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore
+[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
+[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
+[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
+[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
+[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool
+[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
+[`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items
+[`match_overlapping_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_overlapping_arm
+[`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats
+[`match_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_result_ok
+[`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms
+[`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding
+[`match_str_case_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_str_case_mismatch
+[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
+[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
+[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
+[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
+[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
+[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
+[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default
+[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit
+[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
+[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
+[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
+[`mismatching_type_param_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatching_type_param_order
+[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
+[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
+[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
+[`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames
+[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
+[`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
+[`missing_trait_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_trait_methods
+[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
+[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
+[`mixed_read_write_in_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_read_write_in_expression
+[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
+[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
+[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
+[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
+[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
+[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
+[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
+[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
+[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
+[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit
+[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
+[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
+[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock
+[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
+[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
+[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
+[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
+[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
+[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
+[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
+[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
+[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
+[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
+[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
+[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
+[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
+[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
+[`needless_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_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
+[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals
+[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
+[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
+[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
+[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
+[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
+[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
+[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
+[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
+[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
+[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
+[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
+[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
+[`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive
+[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
+[`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace
+[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
+[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
+[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
+[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
+[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
+[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
+[`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces
+[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
+[`obfuscated_if_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#obfuscated_if_else
+[`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_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
+[`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
+[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
+[`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
+[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
+[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
+[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
+[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or
+[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else
+[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
+[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used
+[`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
+[`overly_complex_bool_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#overly_complex_bool_expr
+[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
+[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
+[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
+[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
+[`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields
+[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
+[`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none
+[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
+[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
+[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
+[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
+[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
+[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
+[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
+[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
+[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
+[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline
+[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string
+[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
+[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr
+[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
+[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
+[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
+[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
+[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
+[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
+[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
+[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
+[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
+[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
+[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
+[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
+[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec
+[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
+[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
+[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
+[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
+[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
+[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
+[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
+[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
+[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
+[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
+[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
+[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
+[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
+[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
+[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
+[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
+[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
+[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
+[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
+[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
+[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
+[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
+[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
+[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
+[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else
+[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err
+[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
+[`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use
+[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
+[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
+[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
+[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
+[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
++[`seek_from_current`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current
++[`seek_to_start_instead_of_rewind`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_to_start_instead_of_rewind
+[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
+[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
+[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
+[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
+[`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix
+[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
+[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
+[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
+[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated
+[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
+[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
+[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
+[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
+[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
+[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
+[`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names
+[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
+[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
+[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
+[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
+[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
+[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
+[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
+[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
+[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
+[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
+[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
+[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
+[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
+[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
+[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
+[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
+[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
+[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
+[`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice
+[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
+[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
+[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
+[`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter
+[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
+[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
+[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
+[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
+[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
+[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
+[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
+[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
+[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
+[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
++[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
+[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
+[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
+[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
+[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
+[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
+[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
+[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
+[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
+[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
+[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
+[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
+[`trailing_empty_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#trailing_empty_array
+[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds
+[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
+[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int
+[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
+[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
+[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
+[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
+[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
+[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
+[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
+[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
+[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
+[`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
+[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
+[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
+[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
+[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
+[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
++[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
+[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
+[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
+[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
+[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
+[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
+[`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
+[`uninlined_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
+[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
+[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
+[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
+[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
+[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
+[`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
+[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
++[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
+[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
+[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
+[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
+[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
+[`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
+[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern
+[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
+[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns
+[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
+[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
+[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize
+[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
+[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
+[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix
+[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute
+[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
+[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
+[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
+[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
+[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs
+[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
+[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
+[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable
+[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
+[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
+[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
+[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
+[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
+[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
+[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
+[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
+[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
+[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
+[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding
+[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
+[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
+[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
+[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
+[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq
+[`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute
+[`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec
+[`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box
+[`vec_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_init_then_push
+[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero
+[`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask
+[`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads
+[`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
+[`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition
+[`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop
+[`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator
+[`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies
+[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm
+[`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports
+[`wildcard_in_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_in_or_patterns
+[`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal
+[`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline
+[`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string
+[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention
+[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention
+[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute
+[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
+[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
+[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
+[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
+[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
+[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
+<!-- end autogenerated links to lint list -->
--- /dev/null
- A version of this document [can be found online](https://www.rust-lang.org/conduct.html).
-
- ## Conduct
-
- **Contact**: [rust-mods@rust-lang.org](mailto:rust-mods@rust-lang.org)
-
- * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience,
- gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
- religion, nationality, or other similar characteristic.
- * On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and
- welcoming environment for all.
- * Please be kind and courteous. There's no need to be mean or rude.
- * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and
- numerous costs. There is seldom a right answer.
- * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and
- see how it works.
- * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We
- interpret the term "harassment" as including the definition in the <a href="http://citizencodeofconduct.org/">Citizen
- Code of Conduct</a>; if you have any lack of clarity about what might be included in that concept, please read their
- definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
- * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or
- made uncomfortable by a community member, please contact one of the channel ops or any of the [Rust moderation
- team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a
- safe place for you and we've got your back.
- * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
-
- ## Moderation
-
-
- These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation,
- please contact the [Rust moderation team][mod_team].
-
- 1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks,
- are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
- 2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
- 3. Moderators will first respond to such remarks with a warning.
- 4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
- 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
- 6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended
- party a genuine apology.
- 7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a
- different moderator, **in private**. Complaints about bans in-channel are not allowed.
- 8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate
- situation, they should expect less leeway than others.
-
- In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically
- unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly
- if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can
- drive people away from the community entirely.
-
- And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was
- they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good
- there was something you could've communicated better — remember that it's your responsibility to make your fellow
- Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about
- cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their
- trust.
-
- The enforcement policies listed above apply to all official Rust venues; including official IRC channels (#rust,
- #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo);
- GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org
- (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Rust Code of Conduct, please contact the
- maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider
- explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
-
- *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the
- [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
-
- [mod_team]: https://www.rust-lang.org/team.html#Moderation-team
+# The Rust Code of Conduct
+
++The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html).
--- /dev/null
+# Contributing to Clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+**First**: if you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyway. You won't be
+yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change
+something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.
+
+Clippy welcomes contributions from everyone. There are many ways to contribute to Clippy and the following document
+explains how you can contribute and how to get started. If you have any questions about contributing or need help with
+anything, feel free to ask questions on issues or visit the `#clippy` on [Zulip].
+
+All contributors are expected to follow the [Rust Code of Conduct].
+
+- [Contributing to Clippy](#contributing-to-clippy)
+ - [The Clippy book](#the-clippy-book)
+ - [High level approach](#high-level-approach)
+ - [Finding something to fix/improve](#finding-something-to-fiximprove)
+ - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
+ - [IntelliJ Rust](#intellij-rust)
+ - [Rust Analyzer](#rust-analyzer)
+ - [How Clippy works](#how-clippy-works)
+ - [Issue and PR triage](#issue-and-pr-triage)
+ - [Bors and Homu](#bors-and-homu)
+ - [Contributions](#contributions)
++ - [License](#license)
+
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
+[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
+
+## The Clippy book
+
+If you're new to Clippy and don't know where to start, the [Clippy book] includes
+a [developer guide] and is a good place to start your journey.
+
+[Clippy book]: https://doc.rust-lang.org/nightly/clippy/index.html
+[developer guide]: https://doc.rust-lang.org/nightly/clippy/development/index.html
+
+## High level approach
+
+1. Find something to fix/improve
+2. Change code (likely some file in `clippy_lints/src/`)
+3. Follow the instructions in the [Basics docs](book/src/development/basics.md)
+ to get set up
+4. Run `cargo test` in the root directory and wiggle code until it passes
+5. Open a PR (also can be done after 2. if you run into problems)
+
+## Finding something to fix/improve
+
+All issues on Clippy are mentored, if you want help simply ask someone from the
+Clippy team directly by mentioning them in the issue or over on [Zulip]. All
+currently active team members can be found
+[here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3)
+
+Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy
+issues. You can use `@rustbot claim` to assign the issue to yourself.
+
+There are also some abandoned PRs, marked with [`S-inactive-closed`].
+Pretty often these PRs are nearly completed and just need some extra steps
+(formatting, addressing review comments, ...) to be merged. If you want to
+complete such a PR, please leave a comment in the PR and open a new one based
+on it.
+
+Issues marked [`T-AST`] involve simple matching of the syntax tree structure,
+and are generally easier than [`T-middle`] issues, which involve types
+and resolved paths.
+
+[`T-AST`] issues will generally need you to match against a predefined syntax structure.
+To figure out how this syntax structure is encoded in the AST, it is recommended to run
+`rustc -Z unpretty=ast-tree` on an example of the structure and compare with the [nodes in the AST docs].
+Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
+But we can make it nest-less by using [let chains], [like this][nest-less].
+
+[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good-first-issue`]
+first. Sometimes they are only somewhat involved code wise, but not difficult per-se.
+Note that [`E-medium`] issues may require some knowledge of Clippy internals or some
+debugging to find the actual problem behind the issue.
+
+[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+[`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
+[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
+[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST
+[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle
+[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium
+[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty
+[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/
+[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/mem_forget.rs#L31-L45
+[let chains]: https://github.com/rust-lang/rust/pull/94927
+[nest-less]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/bit_mask.rs#L133-L159
+
+## Getting code-completion for rustc internals to work
+
+### IntelliJ Rust
+Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals
+using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not
+available via a `rustup` component at the time of writing.
+To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via
+`git clone https://github.com/rust-lang/rust/`.
+Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies
+which `IntelliJ Rust` will be able to understand.
+Run `cargo dev setup intellij --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
+you just cloned.
+The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
+Clippy's `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
+Just make sure to remove the dependencies again before finally making a pull request!
+
+[rustc_repo]: https://github.com/rust-lang/rust/
+[IntelliJ_rust_homepage]: https://intellij-rust.github.io/
+
+### Rust Analyzer
+For [`rust-analyzer`][ra_homepage] to work correctly make sure that in the `rust-analyzer` configuration you set
+
+```json
+{ "rust-analyzer.rustc.source": "discover" }
+```
+
+You should be able to see information on things like `Expr` or `EarlyContext` now if you hover them, also
+a lot more type hints.
+
+To have `rust-analyzer` also work in the `clippy_dev` and `lintcheck` crates, add the following configuration
+
+```json
+{
+ "rust-analyzer.linkedProjects": [
+ "./Cargo.toml",
+ "clippy_dev/Cargo.toml",
+ "lintcheck/Cargo.toml",
+ ]
+}
+```
+
+[ra_homepage]: https://rust-analyzer.github.io/
+
+## How Clippy works
+
+[`clippy_lints/src/lib.rs`][lint_crate_entry] imports all the different lint modules and registers in the [`LintStore`].
+For example, the [`else_if_without_else`][else_if_without_else] lint is registered like this:
+
+```rust
+// ./clippy_lints/src/lib.rs
+
+// ...
+pub mod else_if_without_else;
+// ...
+
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // ...
+ store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
+ // ...
+
+ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ // ...
+ LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ // ...
+ ]);
+}
+```
+
+The [`rustc_lint::LintStore`][`LintStore`] provides two methods to register lints:
+[register_early_pass][reg_early_pass] and [register_late_pass][reg_late_pass]. Both take an object
+that implements an [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass] respectively. This is done in
+every single lint. It's worth noting that the majority of `clippy_lints/src/lib.rs` is autogenerated by `cargo dev
+update_lints`. When you are writing your own lint, you can use that script to save you some time.
+
+```rust
+// ./clippy_lints/src/else_if_without_else.rs
+
+use rustc_lint::{EarlyLintPass, EarlyContext};
+
+// ...
+
+pub struct ElseIfWithoutElse;
+
+// ...
+
+impl EarlyLintPass for ElseIfWithoutElse {
+ // ... the functions needed, to make the lint work
+}
+```
+
+The difference between `EarlyLintPass` and `LateLintPass` is that the methods of the `EarlyLintPass` trait only provide
+AST information. The methods of the `LateLintPass` trait are executed after type checking and contain type information
+via the `LateContext` parameter.
+
+That's why the `else_if_without_else` example uses the `register_early_pass` function. Because the
+[actual lint logic][else_if_without_else] does not depend on any type information.
+
+[lint_crate_entry]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs
+[else_if_without_else]: https://github.com/rust-lang/rust-clippy/blob/4253aa7137cb7378acc96133c787e49a345c2b3c/clippy_lints/src/else_if_without_else.rs
+[`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html
+[reg_early_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_early_pass
+[reg_late_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_late_pass
+[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
+[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
+
+## Issue and PR triage
+
+Clippy is following the [Rust triage procedure][triage] for issues and pull
+requests.
+
+However, we are a smaller project with all contributors being volunteers
+currently. Between writing new lints, fixing issues, reviewing pull requests and
+responding to issues there may not always be enough time to stay on top of it
+all.
+
+Our highest priority is fixing [crashes][l-crash] and [bugs][l-bug], for example
+an ICE in a popular crate that many other crates depend on. We don't
+want Clippy to crash on your code and we want it to be as reliable as the
+suggestions from Rust compiler errors.
+
+We have prioritization labels and a sync-blocker label, which are described below.
+- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent.
+- [P-medium][p-medium]: Should be addressed by a team member until the next sync.
+- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport.
+- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync.
+Or rather: before the sync this should be addressed,
+e.g. by removing a lint again, so it doesn't hit beta/stable.
+
+## Bors and Homu
+
+We use a bot powered by [Homu][homu] to help automate testing and landing of pull
+requests in Clippy. The bot's username is @bors.
+
+You can find the Clippy bors queue [here][homu_queue].
+
+If you have @bors permissions, you can find an overview of the available
+commands [here][homu_instructions].
+
+[triage]: https://forge.rust-lang.org/release/triage-procedure.html
+[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
+[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
+[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low
+[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
+[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
+[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
+[homu]: https://github.com/rust-lang/homu
+[homu_instructions]: https://bors.rust-lang.org/
+[homu_queue]: https://bors.rust-lang.org/queue/clippy
+
+## Contributions
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
++All PRs should include a `changelog` entry with a short comment explaining the change. The rule of thumb is basically,
++"what do you believe is important from an outsider's perspective?" Often, PRs are only related to a single property of a
++lint, and then it's good to mention that one. Otherwise, it's better to include too much detail than too little.
++
++Clippy's [changelog] is created from these comments. Every release, someone gets all commits from bors with a
++`changelog: XYZ` entry and combines them into the changelog. This is a manual process.
++
++Examples:
++- New lint
++ ```
++ changelog: new lint: [`missing_trait_methods`]
++ ```
++- False positive fix
++ ```
++ changelog: Fix [`unused_peekable`] false positive when peeked in a closure or called as `f(&mut peekable)`
++ ```
++- Purely internal change
++ ```
++ changelog: none
++ ```
++
++Note this it is fine for a PR to include multiple `changelog` entries, e.g.:
++```
++changelog: Something 1
++changelog: Something 2
++changelog: Something 3
++```
++
++[changelog]: CHANGELOG.md
++
++## License
++
+All code in this repository is under the [Apache-2.0] or the [MIT] license.
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
+
+[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
+[MIT]: https://opensource.org/licenses/MIT
--- /dev/null
- version = "0.1.66"
+[package]
+name = "clippy"
++version = "0.1.67"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+categories = ["development-tools", "development-tools::cargo-plugins"]
+build = "build.rs"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "cargo-clippy"
+test = false
+path = "src/main.rs"
+
+[[bin]]
+name = "clippy-driver"
+path = "src/driver.rs"
+
+[dependencies]
+clippy_lints = { path = "clippy_lints" }
+semver = "1.0"
+rustc_tools_util = "0.2.1"
+tempfile = { version = "3.2", optional = true }
+termize = "0.1"
+
+[dev-dependencies]
+compiletest_rs = { version = "0.9", features = ["tmp"] }
+tester = "0.9"
+regex = "1.5"
+toml = "0.5"
+walkdir = "2.3"
+# This is used by the `collect-metadata` alias.
+filetime = "0.2"
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0"
+
+# UI test dependencies
+clippy_utils = { path = "clippy_utils" }
+derive-new = "0.5"
+if_chain = "1.0"
+itertools = "0.10.1"
+quote = "1.0"
+serde = { version = "1.0.125", features = ["derive"] }
+syn = { version = "1.0", features = ["full"] }
+futures = "0.3"
+parking_lot = "0.12"
+tokio = { version = "1", features = ["io-util"] }
+rustc-semver = "1.1"
+
+[build-dependencies]
+rustc_tools_util = "0.2.1"
+
+[features]
+deny-warnings = ["clippy_lints/deny-warnings"]
+integration = ["tempfile"]
+internal = ["clippy_lints/internal", "tempfile"]
+
+[package.metadata.rust-analyzer]
+# This package uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- [![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%22+event%3Apush+branch%3Aauto)
+# Clippy
+
- > **Note**
- >
- > Configuration changes will not apply for code that has already been compiled and cached under `./target/`;
- > for example, adding a new string to `doc-valid-idents` may still result in Clippy flagging that string. To be sure
- > that any configuration changes are applied, you may want to run `cargo clean` and re-compile your crate from scratch.
-
++[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto)
+[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license)
+
+A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
+
+[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
+
+Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
+You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
+
+| Category | Description | Default level |
+| --------------------- | ----------------------------------------------------------------------------------- | ------------- |
+| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** |
+| `clippy::correctness` | code that is outright wrong or useless | **deny** |
+| `clippy::suspicious` | code that is most likely wrong or useless | **warn** |
+| `clippy::style` | code that should be written in a more idiomatic way | **warn** |
+| `clippy::complexity` | code that does something simple but in a complex way | **warn** |
+| `clippy::perf` | code that can be written to run faster | **warn** |
+| `clippy::pedantic` | lints which are rather strict or have occasional false positives | allow |
+| `clippy::nursery` | new lints that are still under development | allow |
+| `clippy::cargo` | lints for the cargo manifest | allow |
+
+More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas!
+
+The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also contains "restriction lints", which are
+for things which are usually not considered "bad", but may be useful to turn on in specific cases. These should be used
+very selectively, if at all.
+
+Table of contents:
+
+* [Usage instructions](#usage)
+* [Configuration](#configuration)
+* [Contributing](#contributing)
+* [License](#license)
+
+## Usage
+
+Below are instructions on how to use Clippy as a cargo subcommand,
+in projects that do not use cargo, or in Travis CI.
+
+### As a cargo subcommand (`cargo clippy`)
+
+One way to use Clippy is by installing Clippy through rustup as a cargo
+subcommand.
+
+#### Step 1: Install Rustup
+
+You can install [Rustup](https://rustup.rs/) on supported platforms. This will help
+us install Clippy and its dependencies.
+
+If you already have Rustup installed, update to ensure you have the latest
+Rustup and compiler:
+
+```terminal
+rustup update
+```
+
+#### Step 2: Install Clippy
+
+Once you have rustup and the latest stable release (at least Rust 1.29) installed, run the following command:
+
+```terminal
+rustup component add clippy
+```
+If it says that it can't find the `clippy` component, please run `rustup self update`.
+
+#### Step 3: Run Clippy
+
+Now you can run Clippy by invoking the following command:
+
+```terminal
+cargo clippy
+```
+
+#### Automatically applying Clippy suggestions
+
+Clippy can automatically apply some lint suggestions, just like the compiler.
+
+```terminal
+cargo clippy --fix
+```
+
+#### Workspaces
+
+All the usual workspace options should work with Clippy. For example the following command
+will run Clippy on the `example` crate:
+
+```terminal
+cargo clippy -p example
+```
+
+As with `cargo check`, this includes dependencies that are members of the workspace, like path dependencies.
+If you want to run Clippy **only** on the given crate, use the `--no-deps` option like this:
+
+```terminal
+cargo clippy -p example -- --no-deps
+```
+
+### Using `clippy-driver`
+
+Clippy can also be used in projects that do not use cargo. To do so, run `clippy-driver`
+with the same arguments you use for `rustc`. For example:
+
+```terminal
+clippy-driver --edition 2018 -Cpanic=abort foo.rs
+```
+
+Note that `clippy-driver` is designed for running Clippy only and should not be used as a general
+replacement for `rustc`. `clippy-driver` may produce artifacts that are not optimized as expected,
+for example.
+
+### Travis CI
+
+You can add Clippy to Travis CI in the same way you use it locally:
+
+```yml
+language: rust
+rust:
+ - stable
+ - beta
+before_script:
+ - rustup component add clippy
+script:
+ - cargo clippy
+ # if you want the build job to fail when encountering warnings, use
+ - cargo clippy -- -D warnings
+ # in order to also check tests and non-default crate features, use
+ - cargo clippy --all-targets --all-features -- -D warnings
+ - cargo test
+ # etc.
+```
+
+Note that adding `-D warnings` will cause your build to fail if **any** warnings are found in your code.
+That includes warnings found by rustc (e.g. `dead_code`, etc.). If you want to avoid this and only cause
+an error for Clippy warnings, use `#![deny(clippy::all)]` in your code or `-D clippy::all` on the command
+line. (You can swap `clippy::all` with the specific lint category you are targeting.)
+
+## Configuration
+
+### Allowing/denying lints
+
+You can add options to your code to `allow`/`warn`/`deny` Clippy lints:
+
+* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`).
+ Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html).
+
+* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`,
+ `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive
+ lints prone to false positives.
+
+* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.)
+
+* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc.
+
+Note: `allow` means to suppress the lint for your code. With `warn` the lint
+will only emit a warning, while with `deny` the lint will emit an error, when
+triggering for your code. An error causes clippy to exit with an error code, so
+is useful in scripts like CI/CD.
+
+If you do not want to include your lint levels in your code, you can globally
+enable/disable lints by passing extra flags to Clippy during the run:
+
+To allow `lint_name`, run
+
+```terminal
+cargo clippy -- -A clippy::lint_name
+```
+
+And to warn on `lint_name`, run
+
+```terminal
+cargo clippy -- -W clippy::lint_name
+```
+
+This also works with lint groups. For example, you
+can run Clippy with warnings for all lints enabled:
+```terminal
+cargo clippy -- -W clippy::pedantic
+```
+
+If you care only about a single lint, you can allow all others and then explicitly warn on
+the lint(s) you are interested in:
+```terminal
+cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::...
+```
+
+### Configure the behavior of some lints
+
+Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a basic `variable =
+value` mapping e.g.
+
+```toml
+avoid-breaking-exported-api = false
+disallowed-names = ["toto", "tata", "titi"]
+cognitive-complexity-threshold = 30
+```
+
+See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which
+lints can be configured and the meaning of the variables.
+
+> **Note**
+>
+> `clippy.toml` or `.clippy.toml` cannot be used to allow/deny lints.
+
+To deactivate the “for further information visit *lint-link*” message you can
+define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
+
+### Specifying the minimum supported Rust version
+
+Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
+specifying the minimum supported Rust version (MSRV) in the clippy configuration file.
+
+```toml
+msrv = "1.30.0"
+```
+
+Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
+in the `Cargo.toml` can be used.
+
+```toml
+# Cargo.toml
+rust-version = "1.30"
+```
+
+The MSRV can also be specified as an inner attribute, like below.
+
+```rust
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "1.30.0"]
+
+fn main() {
+ ...
+}
+```
+
+You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
+is equivalent to `msrv = 1.30.0`.
+
+Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
+
+Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
+
+## Contributing
+
+If you want to contribute to Clippy, you can find more information in [CONTRIBUTING.md](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md).
+
+## License
+
+Copyright 2014-2022 The Rust Project Developers
+
+Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
+<LICENSE-MIT or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)>, at your
+option. Files in the project may not be
+copied, modified, or distributed except according to those terms.
--- /dev/null
- tempfile = "3.2"
+[package]
+name = "clippy_dev"
+version = "0.0.1"
+edition = "2021"
+
+[dependencies]
+aho-corasick = "0.7"
+clap = "3.2"
+indoc = "1.0"
+itertools = "0.10.1"
+opener = "0.5"
+shell-escape = "0.1"
+walkdir = "2.3"
+
+[features]
+deny-warnings = []
+
+[package.metadata.rust-analyzer]
+# This package uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- // Run in a tempdir as changes to clippy do not retrigger linting
- let target = tempfile::Builder::new()
- .prefix("clippy")
- .tempdir()
- .expect("failed to create tempdir");
-
+use crate::cargo_clippy_path;
+use std::process::{self, Command, ExitStatus};
+use std::{fs, io};
+
+fn exit_if_err(status: io::Result<ExitStatus>) {
+ match status.expect("failed to run command").code() {
+ Some(0) => {},
+ Some(n) => process::exit(n),
+ None => {
+ eprintln!("Killed by signal");
+ process::exit(1);
+ },
+ }
+}
+
+pub fn run<'a>(path: &str, args: impl Iterator<Item = &'a String>) {
+ let is_file = match fs::metadata(path) {
+ Ok(metadata) => metadata.is_file(),
+ Err(e) => {
+ eprintln!("Failed to read {path}: {e:?}");
+ process::exit(1);
+ },
+ };
+
+ if is_file {
+ exit_if_err(
+ Command::new("cargo")
+ .args(["run", "--bin", "clippy-driver", "--"])
+ .args(["-L", "./target/debug"])
+ .args(["-Z", "no-codegen"])
+ .args(["--edition", "2021"])
+ .arg(path)
+ .args(args)
+ .status(),
+ );
+ } else {
+ exit_if_err(Command::new("cargo").arg("build").status());
+
- .env("CARGO_TARGET_DIR", target.as_ref())
+ let status = Command::new(cargo_clippy_path())
+ .arg("clippy")
+ .args(args)
+ .current_dir(path)
- target.close().expect("failed to remove tempdir");
+ .status();
+
+ exit_if_err(status);
+ }
+}
--- /dev/null
- use std::collections::{BTreeSet, HashMap, HashSet};
+use crate::clippy_project_root;
+use aho_corasick::AhoCorasickBuilder;
+use indoc::writedoc;
+use itertools::Itertools;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
- "clippy_lints/src/lib.register_lints.rs",
++use std::collections::{HashMap, HashSet};
+use std::ffi::OsStr;
+use std::fmt::Write;
+use std::fs::{self, OpenOptions};
+use std::io::{self, Read, Seek, SeekFrom, Write as _};
+use std::ops::Range;
+use std::path::{Path, PathBuf};
+use walkdir::{DirEntry, WalkDir};
+
+const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
+ // Use that command to update this file and do not edit by hand.\n\
+ // Manual edits will be overwritten.\n\n";
+
+const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum UpdateMode {
+ Check,
+ Change,
+}
+
+/// Runs the `update_lints` command.
+///
+/// This updates various generated values from the lint source code.
+///
+/// `update_mode` indicates if the files should be updated or if updates should be checked for.
+///
+/// # Panics
+///
+/// Panics if a file path could not read from or then written to
+pub fn update(update_mode: UpdateMode) {
+ let (lints, deprecated_lints, renamed_lints) = gather_all();
+ generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints);
++ remove_old_files(update_mode);
++}
++
++/// Remove files no longer needed after <https://github.com/rust-lang/rust-clippy/pull/9541>
++/// that may be reintroduced unintentionally
++///
++/// FIXME: This is a temporary measure that should be removed when there are no more PRs that
++/// include the stray files
++fn remove_old_files(update_mode: UpdateMode) {
++ let mut failed = false;
++ let mut remove_file = |path: &Path| match update_mode {
++ UpdateMode::Check => {
++ if path.exists() {
++ failed = true;
++ println!("unexpected file: {}", path.display());
++ }
++ },
++ UpdateMode::Change => {
++ if fs::remove_file(path).is_ok() {
++ println!("removed file: {}", path.display());
++ }
++ },
++ };
++
++ let files = [
++ "clippy_lints/src/lib.register_all.rs",
++ "clippy_lints/src/lib.register_cargo.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",
++ "src/docs.rs",
++ ];
++
++ for file in files {
++ remove_file(Path::new(file));
++ }
++
++ if let Ok(docs_dir) = fs::read_dir("src/docs") {
++ for doc_file in docs_dir {
++ let path = doc_file.unwrap().path();
++ remove_file(&path);
++ }
++ }
++
++ if failed {
++ exit_with_failure();
++ }
+}
+
+fn generate_lint_files(
+ update_mode: UpdateMode,
+ lints: &[Lint],
+ deprecated_lints: &[DeprecatedLint],
+ renamed_lints: &[RenamedLint],
+) {
+ let internal_lints = Lint::internal_lints(lints);
+ let mut usable_lints = Lint::usable_lints(lints);
+ usable_lints.sort_by_key(|lint| lint.name.clone());
+
+ replace_region_in_file(
+ update_mode,
+ Path::new("README.md"),
+ "[There are over ",
+ " lints included in this crate!]",
+ |res| {
+ write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
+ },
+ );
+
+ replace_region_in_file(
+ update_mode,
+ Path::new("book/src/README.md"),
+ "[There are over ",
+ " lints included in this crate!]",
+ |res| {
+ write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
+ },
+ );
+
+ replace_region_in_file(
+ update_mode,
+ Path::new("CHANGELOG.md"),
+ "<!-- begin autogenerated links to lint list -->\n",
+ "<!-- end autogenerated links to lint list -->",
+ |res| {
+ for lint in usable_lints
+ .iter()
+ .map(|l| &*l.name)
+ .chain(deprecated_lints.iter().map(|l| &*l.name))
+ .chain(
+ renamed_lints
+ .iter()
+ .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)),
+ )
+ .sorted()
+ {
+ writeln!(res, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap();
+ }
+ },
+ );
+
+ // This has to be in lib.rs, otherwise rustfmt doesn't work
+ replace_region_in_file(
+ update_mode,
+ Path::new("clippy_lints/src/lib.rs"),
+ "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n",
+ "// end lints modules, do not remove this comment, it’s used in `update_lints`",
+ |res| {
+ for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
+ writeln!(res, "mod {lint_mod};").unwrap();
+ }
+ },
+ );
+
+ process_file(
- &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
++ "clippy_lints/src/declared_lints.rs",
+ update_mode,
- let all_group_lints = usable_lints.iter().filter(|l| {
- matches!(
- &*l.group,
- "correctness" | "suspicious" | "style" | "complexity" | "perf"
- )
- });
- let content = gen_lint_group_list("all", all_group_lints);
- process_file("clippy_lints/src/lib.register_all.rs", update_mode, &content);
-
- update_docs(update_mode, &usable_lints);
-
- for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
- let content = gen_lint_group_list(&lint_group, lints.iter());
- process_file(
- format!("clippy_lints/src/lib.register_{lint_group}.rs"),
- update_mode,
- &content,
- );
- }
-
++ &gen_declared_lints(internal_lints.iter(), usable_lints.iter()),
+ );
+ process_file(
+ "clippy_lints/src/lib.deprecated.rs",
+ update_mode,
+ &gen_deprecated(deprecated_lints),
+ );
+
- fn update_docs(update_mode: UpdateMode, usable_lints: &[Lint]) {
- replace_region_in_file(update_mode, Path::new("src/docs.rs"), "docs! {\n", "\n}\n", |res| {
- for name in usable_lints.iter().map(|lint| lint.name.clone()).sorted() {
- writeln!(res, r#" "{name}","#).unwrap();
- }
- });
-
- if update_mode == UpdateMode::Check {
- let mut extra = BTreeSet::new();
- let mut lint_names = usable_lints
- .iter()
- .map(|lint| lint.name.clone())
- .collect::<BTreeSet<_>>();
- for file in std::fs::read_dir("src/docs").unwrap() {
- let filename = file.unwrap().file_name().into_string().unwrap();
- if let Some(name) = filename.strip_suffix(".txt") {
- if !lint_names.remove(name) {
- extra.insert(name.to_string());
- }
- }
- }
-
- let failed = print_lint_names("extra lint docs:", &extra) | print_lint_names("missing lint docs:", &lint_names);
-
- if failed {
- exit_with_failure();
- }
- } else {
- if std::fs::remove_dir_all("src/docs").is_err() {
- eprintln!("could not remove src/docs directory");
- }
- if std::fs::create_dir("src/docs").is_err() {
- eprintln!("could not recreate src/docs directory");
- }
- }
- for lint in usable_lints {
- process_file(
- Path::new("src/docs").join(lint.name.clone() + ".txt"),
- update_mode,
- &lint.documentation,
- );
- }
- }
-
- fn print_lint_names(header: &str, lints: &BTreeSet<String>) -> bool {
- if lints.is_empty() {
- return false;
- }
- println!("{header}");
- for lint in lints.iter().sorted() {
- println!(" {lint}");
- }
- println!();
- true
- }
-
+ let content = gen_deprecated_lints_test(deprecated_lints);
+ process_file("tests/ui/deprecated.rs", update_mode, &content);
+
+ let content = gen_renamed_lints_test(renamed_lints);
+ process_file("tests/ui/rename.rs", update_mode, &content);
+}
+
- documentation: String,
+pub fn print_lints() {
+ let (lint_list, _, _) = gather_all();
+ let usable_lints = Lint::usable_lints(&lint_list);
+ let usable_lint_count = usable_lints.len();
+ let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
+
+ for (lint_group, mut lints) in grouped_by_lint_group {
+ println!("\n## {lint_group}");
+
+ lints.sort_by_key(|l| l.name.clone());
+
+ for lint in lints {
+ println!("* [{}]({DOCS_LINK}#{}) ({})", lint.name, lint.name, lint.desc);
+ }
+ }
+
+ println!("there are {usable_lint_count} lints");
+}
+
+/// Runs the `rename_lint` command.
+///
+/// This does the following:
+/// * Adds an entry to `renamed_lints.rs`.
+/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
+/// * Renames the lint struct to the new name.
+/// * Renames the module containing the lint struct to the new name if it shares a name with the
+/// lint.
+///
+/// # Panics
+/// Panics for the following conditions:
+/// * If a file path could not read from or then written to
+/// * If either lint name has a prefix
+/// * If `old_name` doesn't name an existing lint.
+/// * If `old_name` names a deprecated or renamed lint.
+#[allow(clippy::too_many_lines)]
+pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
+ if let Some((prefix, _)) = old_name.split_once("::") {
+ panic!("`{old_name}` should not contain the `{prefix}` prefix");
+ }
+ if let Some((prefix, _)) = new_name.split_once("::") {
+ panic!("`{new_name}` should not contain the `{prefix}` prefix");
+ }
+
+ let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
+ let mut old_lint_index = None;
+ let mut found_new_name = false;
+ for (i, lint) in lints.iter().enumerate() {
+ if lint.name == old_name {
+ old_lint_index = Some(i);
+ } else if lint.name == new_name {
+ found_new_name = true;
+ }
+ }
+ let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{old_name}`"));
+
+ let lint = RenamedLint {
+ old_name: format!("clippy::{old_name}"),
+ new_name: if uplift {
+ new_name.into()
+ } else {
+ format!("clippy::{new_name}")
+ },
+ };
+
+ // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
+ // case.
+ assert!(
+ !renamed_lints.iter().any(|l| lint.old_name == l.old_name),
+ "`{old_name}` has already been renamed"
+ );
+ assert!(
+ !deprecated_lints.iter().any(|l| lint.old_name == l.name),
+ "`{old_name}` has already been deprecated"
+ );
+
+ // Update all lint level attributes. (`clippy::lint_name`)
+ for file in WalkDir::new(clippy_project_root())
+ .into_iter()
+ .map(Result::unwrap)
+ .filter(|f| {
+ let name = f.path().file_name();
+ let ext = f.path().extension();
+ (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
+ && name != Some(OsStr::new("rename.rs"))
+ && name != Some(OsStr::new("renamed_lints.rs"))
+ })
+ {
+ rewrite_file(file.path(), |s| {
+ replace_ident_like(s, &[(&lint.old_name, &lint.new_name)])
+ });
+ }
+
+ renamed_lints.push(lint);
+ renamed_lints.sort_by(|lhs, rhs| {
+ lhs.new_name
+ .starts_with("clippy::")
+ .cmp(&rhs.new_name.starts_with("clippy::"))
+ .reverse()
+ .then_with(|| lhs.old_name.cmp(&rhs.old_name))
+ });
+
+ write_file(
+ Path::new("clippy_lints/src/renamed_lints.rs"),
+ &gen_renamed_lints_list(&renamed_lints),
+ );
+
+ if uplift {
+ write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
+ println!(
+ "`{old_name}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually."
+ );
+ } else if found_new_name {
+ write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
+ println!(
+ "`{new_name}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually."
+ );
+ } else {
+ // Rename the lint struct and source files sharing a name with the lint.
+ let lint = &mut lints[old_lint_index];
+ let old_name_upper = old_name.to_uppercase();
+ let new_name_upper = new_name.to_uppercase();
+ lint.name = new_name.into();
+
+ // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
+ if try_rename_file(
+ Path::new(&format!("tests/ui/{old_name}.rs")),
+ Path::new(&format!("tests/ui/{new_name}.rs")),
+ ) {
+ try_rename_file(
+ Path::new(&format!("tests/ui/{old_name}.stderr")),
+ Path::new(&format!("tests/ui/{new_name}.stderr")),
+ );
+ try_rename_file(
+ Path::new(&format!("tests/ui/{old_name}.fixed")),
+ Path::new(&format!("tests/ui/{new_name}.fixed")),
+ );
+ }
+
+ // Try to rename the file containing the lint if the file name matches the lint's name.
+ let replacements;
+ let replacements = if lint.module == old_name
+ && try_rename_file(
+ Path::new(&format!("clippy_lints/src/{old_name}.rs")),
+ Path::new(&format!("clippy_lints/src/{new_name}.rs")),
+ ) {
+ // Edit the module name in the lint list. Note there could be multiple lints.
+ for lint in lints.iter_mut().filter(|l| l.module == old_name) {
+ lint.module = new_name.into();
+ }
+ replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
+ replacements.as_slice()
+ } else if !lint.module.contains("::")
+ // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
+ && try_rename_file(
+ Path::new(&format!("clippy_lints/src/{}/{old_name}.rs", lint.module)),
+ Path::new(&format!("clippy_lints/src/{}/{new_name}.rs", lint.module)),
+ )
+ {
+ // Edit the module name in the lint list. Note there could be multiple lints, or none.
+ let renamed_mod = format!("{}::{old_name}", lint.module);
+ for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
+ lint.module = format!("{}::{new_name}", lint.module);
+ }
+ replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
+ replacements.as_slice()
+ } else {
+ replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
+ &replacements[0..1]
+ };
+
+ // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
+ // renamed.
+ for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) {
+ rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
+ }
+
+ generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
+ println!("{old_name} has been successfully renamed");
+ }
+
+ println!("note: `cargo uitest` still needs to be run to update the test results");
+}
+
+const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
+/// Runs the `deprecate` command
+///
+/// This does the following:
+/// * Adds an entry to `deprecated_lints.rs`.
+/// * Removes the lint declaration (and the entire file if applicable)
+///
+/// # Panics
+///
+/// If a file path could not read from or written to
+pub fn deprecate(name: &str, reason: Option<&String>) {
+ fn finish(
+ (lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>),
+ name: &str,
+ reason: &str,
+ ) {
+ deprecated_lints.push(DeprecatedLint {
+ name: name.to_string(),
+ reason: reason.to_string(),
+ declaration_range: Range::default(),
+ });
+
+ generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
+ println!("info: `{name}` has successfully been deprecated");
+
+ if reason == DEFAULT_DEPRECATION_REASON {
+ println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`");
+ }
+ println!("note: you must run `cargo uitest` to update the test results");
+ }
+
+ let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str);
+ let name_lower = name.to_lowercase();
+ let name_upper = name.to_uppercase();
+
+ let (mut lints, deprecated_lints, renamed_lints) = gather_all();
+ let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{name}`"); return; };
+
+ let mod_path = {
+ let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
+ if mod_path.is_dir() {
+ mod_path = mod_path.join("mod");
+ }
+
+ mod_path.set_extension("rs");
+ mod_path
+ };
+
+ let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs");
+
+ if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) {
+ declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap();
+ finish((lints, deprecated_lints, renamed_lints), name, reason);
+ return;
+ }
+
+ eprintln!("error: lint not found");
+}
+
+fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
+ fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
+ lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
+ }
+
+ fn remove_test_assets(name: &str) {
+ let test_file_stem = format!("tests/ui/{name}");
+ let path = Path::new(&test_file_stem);
+
+ // Some lints have their own directories, delete them
+ if path.is_dir() {
+ fs::remove_dir_all(path).ok();
+ return;
+ }
+
+ // Remove all related test files
+ fs::remove_file(path.with_extension("rs")).ok();
+ fs::remove_file(path.with_extension("stderr")).ok();
+ fs::remove_file(path.with_extension("fixed")).ok();
+ }
+
+ fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
+ let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
+ content
+ .find("declare_lint_pass!")
+ .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
+ });
+ let mut impl_lint_pass_end = content[impl_lint_pass_start..]
+ .find(']')
+ .expect("failed to find `impl_lint_pass` terminator");
+
+ impl_lint_pass_end += impl_lint_pass_start;
+ if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) {
+ let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
+ for c in content[lint_name_end..impl_lint_pass_end].chars() {
+ // Remove trailing whitespace
+ if c == ',' || c.is_whitespace() {
+ lint_name_end += 1;
+ } else {
+ break;
+ }
+ }
+
+ content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
+ }
+ }
+
+ if path.exists() {
+ if let Some(lint) = lints.iter().find(|l| l.name == name) {
+ if lint.module == name {
+ // The lint name is the same as the file, we can just delete the entire file
+ fs::remove_file(path)?;
+ } else {
+ // We can't delete the entire file, just remove the declaration
+
+ if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
+ // Remove clippy_lints/src/some_mod/some_lint.rs
+ let mut lint_mod_path = path.to_path_buf();
+ lint_mod_path.set_file_name(name);
+ lint_mod_path.set_extension("rs");
+
+ fs::remove_file(lint_mod_path).ok();
+ }
+
+ let mut content =
+ fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
+
+ eprintln!(
+ "warn: you will have to manually remove any code related to `{name}` from `{}`",
+ path.display()
+ );
+
+ assert!(
+ content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
+ "error: `{}` does not contain lint `{}`'s declaration",
+ path.display(),
+ lint.name
+ );
+
+ // Remove lint declaration (declare_clippy_lint!)
+ content.replace_range(lint.declaration_range.clone(), "");
+
+ // Remove the module declaration (mod xyz;)
+ let mod_decl = format!("\nmod {name};");
+ content = content.replacen(&mod_decl, "", 1);
+
+ remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
+ fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
+ }
+
+ remove_test_assets(name);
+ remove_lint(name, lints);
+ return Ok(true);
+ }
+ }
+
+ Ok(false)
+}
+
+fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
+ let mut file = OpenOptions::new().write(true).open(path)?;
+
+ file.seek(SeekFrom::End(0))?;
+
+ let version = crate::new_lint::get_stabilization_version();
+ let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON {
+ "TODO"
+ } else {
+ reason
+ };
+
+ writedoc!(
+ file,
+ "
+
+ declare_deprecated_lint! {{
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// {}
+ #[clippy::version = \"{}\"]
+ pub {},
+ \"{}\"
+ }}
+
+ ",
+ deprecation_reason,
+ version,
+ name,
+ reason,
+ )
+}
+
+/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
+/// were no replacements.
+fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
+ fn is_ident_char(c: u8) -> bool {
+ matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
+ }
+
+ let searcher = AhoCorasickBuilder::new()
+ .dfa(true)
+ .match_kind(aho_corasick::MatchKind::LeftmostLongest)
+ .build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
+ .unwrap();
+
+ let mut result = String::with_capacity(contents.len() + 1024);
+ let mut pos = 0;
+ let mut edited = false;
+ for m in searcher.find_iter(contents) {
+ let (old, new) = replacements[m.pattern()];
+ result.push_str(&contents[pos..m.start()]);
+ result.push_str(
+ if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
+ && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0))
+ {
+ edited = true;
+ new
+ } else {
+ old
+ },
+ );
+ pos = m.end();
+ }
+ result.push_str(&contents[pos..]);
+ edited.then_some(result)
+}
+
+fn round_to_fifty(count: usize) -> usize {
+ count / 50 * 50
+}
+
+fn process_file(path: impl AsRef<Path>, update_mode: UpdateMode, content: &str) {
+ if update_mode == UpdateMode::Check {
+ let old_content =
+ fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {e}", path.as_ref().display()));
+ if content != old_content {
+ exit_with_failure();
+ }
+ } else {
+ fs::write(&path, content.as_bytes())
+ .unwrap_or_else(|e| panic!("Cannot write to {}: {e}", path.as_ref().display()));
+ }
+}
+
+fn exit_with_failure() {
+ println!(
+ "Not all lints defined properly. \
+ Please run `cargo dev update_lints` to make sure all lints are defined properly."
+ );
+ std::process::exit(1);
+}
+
+/// Lint data parsed from the Clippy source code.
+#[derive(Clone, PartialEq, Eq, Debug)]
+struct Lint {
+ name: String,
+ group: String,
+ desc: String,
+ module: String,
+ declaration_range: Range<usize>,
- fn new(
- name: &str,
- group: &str,
- desc: &str,
- module: &str,
- declaration_range: Range<usize>,
- documentation: String,
- ) -> Self {
+}
+
+impl Lint {
+ #[must_use]
- documentation,
++ fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range<usize>) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ group: group.into(),
+ desc: remove_line_splices(desc),
+ module: module.into(),
+ declaration_range,
- /// Generates the code for registering a group
- fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
- let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
- details.sort_unstable();
-
- let mut output = GENERATED_FILE_COMMENT.to_string();
-
- let _ = writeln!(
- output,
- "store.register_group(true, \"clippy::{group_name}\", Some(\"clippy_{group_name}\"), vec![",
- );
- for (module, name) in details {
- let _ = writeln!(output, " LintId::of({module}::{name}),");
- }
- output.push_str("])\n");
-
- output
- }
-
+ }
+ }
+
+ /// Returns all non-deprecated lints and non-internal lints
+ #[must_use]
+ fn usable_lints(lints: &[Self]) -> Vec<Self> {
+ lints
+ .iter()
+ .filter(|l| !l.group.starts_with("internal"))
+ .cloned()
+ .collect()
+ }
+
+ /// Returns all internal lints (not `internal_warn` lints)
+ #[must_use]
+ fn internal_lints(lints: &[Self]) -> Vec<Self> {
+ lints.iter().filter(|l| l.group == "internal").cloned().collect()
+ }
+
+ /// Returns the lints in a `HashMap`, grouped by the different lint groups
+ #[must_use]
+ fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
+ lints.map(|lint| (lint.group.to_string(), lint)).into_group_map()
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+struct DeprecatedLint {
+ name: String,
+ reason: String,
+ declaration_range: Range<usize>,
+}
+impl DeprecatedLint {
+ fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ reason: remove_line_splices(reason),
+ declaration_range,
+ }
+ }
+}
+
+struct RenamedLint {
+ old_name: String,
+ new_name: String,
+}
+impl RenamedLint {
+ fn new(old_name: &str, new_name: &str) -> Self {
+ Self {
+ old_name: remove_line_splices(old_name),
+ new_name: remove_line_splices(new_name),
+ }
+ }
+}
+
- fn gen_register_lint_list<'a>(
+/// Generates the `register_removed` code
+#[must_use]
+fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+ output.push_str("{\n");
+ for lint in lints {
+ let _ = write!(
+ output,
+ concat!(
+ " store.register_removed(\n",
+ " \"clippy::{}\",\n",
+ " \"{}\",\n",
+ " );\n"
+ ),
+ lint.name, lint.reason,
+ );
+ }
+ output.push_str("}\n");
+
+ output
+}
+
+/// Generates the code for registering lints
+#[must_use]
- output.push_str("store.register_lints(&[\n");
++fn gen_declared_lints<'a>(
+ internal_lints: impl Iterator<Item = &'a Lint>,
+ usable_lints: impl Iterator<Item = &'a Lint>,
+) -> String {
+ let mut details: Vec<_> = internal_lints
+ .map(|l| (false, &l.module, l.name.to_uppercase()))
+ .chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
+ .collect();
+ details.sort_unstable();
+
+ let mut output = GENERATED_FILE_COMMENT.to_string();
- let _ = writeln!(output, " {module_name}::{lint_name},");
++ output.push_str("pub(crate) static LINTS: &[&crate::LintInfo] = &[\n");
+
+ for (is_public, module_name, lint_name) in details {
+ if !is_public {
+ output.push_str(" #[cfg(feature = \"internal\")]\n");
+ }
- output.push_str("])\n");
++ let _ = writeln!(output, " crate::{module_name}::{lint_name}_INFO,");
+ }
- let mut docs = String::with_capacity(128);
- let mut iter = iter.by_ref().filter(|t| !matches!(t.token_kind, TokenKind::Whitespace));
++ output.push_str("];\n");
+
+ output
+}
+
+fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
+ let mut res: String = GENERATED_FILE_COMMENT.into();
+ for lint in lints {
+ writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap();
+ }
+ res.push_str("\nfn main() {}\n");
+ res
+}
+
+fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
+ let mut seen_lints = HashSet::new();
+ let mut res: String = GENERATED_FILE_COMMENT.into();
+ res.push_str("// run-rustfix\n\n");
+ for lint in lints {
+ if seen_lints.insert(&lint.new_name) {
+ writeln!(res, "#![allow({})]", lint.new_name).unwrap();
+ }
+ }
+ seen_lints.clear();
+ for lint in lints {
+ if seen_lints.insert(&lint.old_name) {
+ writeln!(res, "#![warn({})]", lint.old_name).unwrap();
+ }
+ }
+ res.push_str("\nfn main() {}\n");
+ res
+}
+
+fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
+ const HEADER: &str = "\
+ // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
+ #[rustfmt::skip]\n\
+ pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
+
+ let mut res = String::from(HEADER);
+ for lint in lints {
+ writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
+ }
+ res.push_str("];\n");
+ res
+}
+
+/// Gathers all lints defined in `clippy_lints/src`
+fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
+ let mut lints = Vec::with_capacity(1000);
+ let mut deprecated_lints = Vec::with_capacity(50);
+ let mut renamed_lints = Vec::with_capacity(50);
+
+ for (rel_path, file) in clippy_lints_src_files() {
+ let path = file.path();
+ let contents =
+ fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display()));
+ let module = rel_path
+ .components()
+ .map(|c| c.as_os_str().to_str().unwrap())
+ .collect::<Vec<_>>()
+ .join("::");
+
+ // If the lints are stored in mod.rs, we get the module name from
+ // the containing directory:
+ let module = if let Some(module) = module.strip_suffix("::mod.rs") {
+ module
+ } else {
+ module.strip_suffix(".rs").unwrap_or(&module)
+ };
+
+ match module {
+ "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints),
+ "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints),
+ _ => parse_contents(&contents, module, &mut lints),
+ }
+ }
+ (lints, deprecated_lints, renamed_lints)
+}
+
+fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
+ let root_path = clippy_project_root().join("clippy_lints/src");
+ let iter = WalkDir::new(&root_path).into_iter();
+ iter.map(Result::unwrap)
+ .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
+ .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
+}
+
+macro_rules! match_tokens {
+ ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
+ {
+ $(#[allow(clippy::redundant_pattern)] let Some(LintDeclSearchResult {
+ token_kind: TokenKind::$token $({$($fields)*})?,
+ content: $($capture @)? _,
+ ..
+ }) = $iter.next() else {
+ continue;
+ };)*
+ #[allow(clippy::unused_unit)]
+ { ($($($capture,)?)*) }
+ }
+ }
+}
+
+pub(crate) use match_tokens;
+
+pub(crate) struct LintDeclSearchResult<'a> {
+ pub token_kind: TokenKind,
+ pub content: &'a str,
+ pub range: Range<usize>,
+}
+
+/// Parse a source file looking for `declare_clippy_lint` macro invocations.
+fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
+ let mut offset = 0usize;
+ let mut iter = tokenize(contents).map(|t| {
+ let range = offset..offset + t.len as usize;
+ offset = range.end;
+
+ LintDeclSearchResult {
+ token_kind: t.kind,
+ content: &contents[range.clone()],
+ range,
+ }
+ });
+
+ while let Some(LintDeclSearchResult { range, .. }) = iter.find(
+ |LintDeclSearchResult {
+ token_kind, content, ..
+ }| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint",
+ ) {
+ let start = range.start;
- let mut in_code = false;
- while let Some(t) = iter.next() {
- match t.token_kind {
- TokenKind::LineComment { .. } => {
- if let Some(line) = t.content.strip_prefix("/// ").or_else(|| t.content.strip_prefix("///")) {
- if line.starts_with("```") {
- docs += "```\n";
- in_code = !in_code;
- } else if !(in_code && line.starts_with("# ")) {
- docs += line;
- docs.push('\n');
- }
- }
- },
- TokenKind::Pound => {
- match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
- break;
- },
- TokenKind::Ident => {
- break;
- },
- _ => {},
- }
++ let mut iter = iter
++ .by_ref()
++ .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
+ // matches `!{`
+ match_tokens!(iter, Bang OpenBrace);
- docs.pop(); // remove final newline
++ match iter.next() {
++ // #[clippy::version = "version"] pub
++ Some(LintDeclSearchResult {
++ token_kind: TokenKind::Pound,
++ ..
++ }) => {
++ match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
++ },
++ // pub
++ Some(LintDeclSearchResult {
++ token_kind: TokenKind::Ident,
++ ..
++ }) => (),
++ _ => continue,
+ }
- lints.push(Lint::new(name, group, desc, module, start..range.end, docs));
+
+ let (name, group, desc) = match_tokens!(
+ iter,
+ // LINT_NAME
+ Ident(name) Comma
+ // group,
+ Ident(group) Comma
+ // "description"
+ Literal{..}(desc)
+ );
+
+ if let Some(LintDeclSearchResult {
+ token_kind: TokenKind::CloseBrace,
+ range,
+ ..
+ }) = iter.next()
+ {
- String::new(),
++ lints.push(Lint::new(name, group, desc, module, start..range.end));
+ }
+ }
+}
+
+/// Parse a source file looking for `declare_deprecated_lint` macro invocations.
+fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
+ let mut offset = 0usize;
+ let mut iter = tokenize(contents).map(|t| {
+ let range = offset..offset + t.len as usize;
+ offset = range.end;
+
+ LintDeclSearchResult {
+ token_kind: t.kind,
+ content: &contents[range.clone()],
+ range,
+ }
+ });
+
+ while let Some(LintDeclSearchResult { range, .. }) = iter.find(
+ |LintDeclSearchResult {
+ token_kind, content, ..
+ }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint",
+ ) {
+ let start = range.start;
+
+ let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| {
+ !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })
+ });
+ let (name, reason) = match_tokens!(
+ iter,
+ // !{
+ Bang OpenBrace
+ // #[clippy::version = "version"]
+ Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket
+ // pub LINT_NAME,
+ Ident Ident(name) Comma
+ // "description"
+ Literal{kind: LiteralKind::Str{..},..}(reason)
+ );
+
+ if let Some(LintDeclSearchResult {
+ token_kind: TokenKind::CloseBrace,
+ range,
+ ..
+ }) = iter.next()
+ {
+ lints.push(DeprecatedLint::new(name, reason, start..range.end));
+ }
+ }
+}
+
+fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
+ for line in contents.lines() {
+ let mut offset = 0usize;
+ let mut iter = tokenize(line).map(|t| {
+ let range = offset..offset + t.len as usize;
+ offset = range.end;
+
+ LintDeclSearchResult {
+ token_kind: t.kind,
+ content: &line[range.clone()],
+ range,
+ }
+ });
+
+ let (old_name, new_name) = match_tokens!(
+ iter,
+ // ("old_name",
+ Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma
+ // "new_name"),
+ Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
+ );
+ lints.push(RenamedLint::new(old_name, new_name));
+ }
+}
+
+/// Removes the line splices and surrounding quotes from a string literal
+fn remove_line_splices(s: &str) -> String {
+ let s = s
+ .strip_prefix('r')
+ .unwrap_or(s)
+ .trim_matches('#')
+ .strip_prefix('"')
+ .and_then(|s| s.strip_suffix('"'))
+ .unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
+ let mut res = String::with_capacity(s.len());
+ unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, ch| {
+ if ch.is_ok() {
+ res.push_str(&s[range]);
+ }
+ });
+ res
+}
+
+/// Replaces a region in a file delimited by two lines matching regexes.
+///
+/// `path` is the relative path to the file on which you want to perform the replacement.
+///
+/// See `replace_region_in_text` for documentation of the other options.
+///
+/// # Panics
+///
+/// Panics if the path could not read or then written
+fn replace_region_in_file(
+ update_mode: UpdateMode,
+ path: &Path,
+ start: &str,
+ end: &str,
+ write_replacement: impl FnMut(&mut String),
+) {
+ let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display()));
+ let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) {
+ Ok(x) => x,
+ Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()),
+ };
+
+ match update_mode {
+ UpdateMode::Check if contents != new_contents => exit_with_failure(),
+ UpdateMode::Check => (),
+ UpdateMode::Change => {
+ if let Err(e) = fs::write(path, new_contents.as_bytes()) {
+ panic!("Cannot write to `{}`: {e}", path.display());
+ }
+ },
+ }
+}
+
+/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
+/// were found, or the missing delimiter if not.
+fn replace_region_in_text<'a>(
+ text: &str,
+ start: &'a str,
+ end: &'a str,
+ mut write_replacement: impl FnMut(&mut String),
+) -> Result<String, &'a str> {
+ let (text_start, rest) = text.split_once(start).ok_or(start)?;
+ let (_, text_end) = rest.split_once(end).ok_or(end)?;
+
+ let mut res = String::with_capacity(text.len() + 4096);
+ res.push_str(text_start);
+ res.push_str(start);
+ write_replacement(&mut res);
+ res.push_str(end);
+ res.push_str(text_end);
+
+ Ok(res)
+}
+
+fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
+ match fs::OpenOptions::new().create_new(true).write(true).open(new_name) {
+ Ok(file) => drop(file),
+ Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
+ Err(e) => panic_file(e, new_name, "create"),
+ };
+ match fs::rename(old_name, new_name) {
+ Ok(()) => true,
+ Err(e) => {
+ drop(fs::remove_file(new_name));
+ if e.kind() == io::ErrorKind::NotFound {
+ false
+ } else {
+ panic_file(e, old_name, "rename");
+ }
+ },
+ }
+}
+
+#[allow(clippy::needless_pass_by_value)]
+fn panic_file(error: io::Error, name: &Path, action: &str) -> ! {
+ panic!("failed to {action} file `{}`: {error}", name.display())
+}
+
+fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
+ let mut file = fs::OpenOptions::new()
+ .write(true)
+ .read(true)
+ .open(path)
+ .unwrap_or_else(|e| panic_file(e, path, "open"));
+ let mut buf = String::new();
+ file.read_to_string(&mut buf)
+ .unwrap_or_else(|e| panic_file(e, path, "read"));
+ if let Some(new_contents) = f(&buf) {
+ file.rewind().unwrap_or_else(|e| panic_file(e, path, "write"));
+ file.write_all(new_contents.as_bytes())
+ .unwrap_or_else(|e| panic_file(e, path, "write"));
+ file.set_len(new_contents.len() as u64)
+ .unwrap_or_else(|e| panic_file(e, path, "write"));
+ }
+}
+
+fn write_file(path: &Path, contents: &str) {
+ fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write"));
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_contents() {
+ static CONTENTS: &str = r#"
+ declare_clippy_lint! {
+ #[clippy::version = "Hello Clippy!"]
+ pub PTR_ARG,
+ style,
+ "really long \
+ text"
+ }
+
+ declare_clippy_lint!{
+ #[clippy::version = "Test version"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "single line"
+ }
+ "#;
+ let mut result = Vec::new();
+ parse_contents(CONTENTS, "module_name", &mut result);
+ for r in &mut result {
+ r.declaration_range = Range::default();
+ }
+
+ let expected = vec![
+ Lint::new(
+ "ptr_arg",
+ "style",
+ "\"really long text\"",
+ "module_name",
+ Range::default(),
- String::new(),
+ ),
+ Lint::new(
+ "doc_markdown",
+ "pedantic",
+ "\"single line\"",
+ "module_name",
+ Range::default(),
- String::new(),
+ ),
+ ];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_parse_deprecated_contents() {
+ static DEPRECATED_CONTENTS: &str = r#"
+ /// some doc comment
+ declare_deprecated_lint! {
+ #[clippy::version = "I'm a version"]
+ pub SHOULD_ASSERT_EQ,
+ "`assert!()` will be more flexible with RFC 2011"
+ }
+ "#;
+
+ let mut result = Vec::new();
+ parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
+ for r in &mut result {
+ r.declaration_range = Range::default();
+ }
+
+ let expected = vec![DeprecatedLint::new(
+ "should_assert_eq",
+ "\"`assert!()` will be more flexible with RFC 2011\"",
+ Range::default(),
+ )];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_usable_lints() {
+ let lints = vec![
+ Lint::new(
+ "should_assert_eq2",
+ "Not Deprecated",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
- String::new(),
+ ),
+ Lint::new(
+ "should_assert_eq2",
+ "internal",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
- String::new(),
+ ),
+ Lint::new(
+ "should_assert_eq2",
+ "internal_style",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
- String::new(),
+ ),
+ ];
+ let expected = vec![Lint::new(
+ "should_assert_eq2",
+ "Not Deprecated",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
- Lint::new(
- "should_assert_eq",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
+ )];
+ assert_eq!(expected, Lint::usable_lints(&lints));
+ }
+
+ #[test]
+ fn test_by_lint_group() {
+ let lints = vec![
- String::new(),
- ),
- Lint::new(
- "incorrect_match",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
++ Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
+ Lint::new(
+ "should_assert_eq2",
+ "group2",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
- Lint::new(
- "should_assert_eq",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
- Lint::new(
- "incorrect_match",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
+ ),
++ Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
+ ];
+ let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
+ expected.insert(
+ "group1".to_string(),
+ vec![
- String::new(),
++ Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
++ Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
+ ],
+ );
+ expected.insert(
+ "group2".to_string(),
+ vec![Lint::new(
+ "should_assert_eq2",
+ "group2",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
-
- #[test]
- fn test_gen_lint_group_list() {
- let lints = vec![
- Lint::new(
- "abc",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
- Lint::new(
- "should_assert_eq",
- "group1",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
- Lint::new(
- "internal",
- "internal_style",
- "\"abc\"",
- "module_name",
- Range::default(),
- String::new(),
- ),
- ];
- let expected = GENERATED_FILE_COMMENT.to_string()
- + &[
- "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![",
- " LintId::of(module_name::ABC),",
- " LintId::of(module_name::INTERNAL),",
- " LintId::of(module_name::SHOULD_ASSERT_EQ),",
- "])",
- ]
- .join("\n")
- + "\n";
-
- let result = gen_lint_group_list("group1", lints.iter());
-
- assert_eq!(expected, result);
- }
+ )],
+ );
+ assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
+ }
+
+ #[test]
+ fn test_gen_deprecated() {
+ let lints = vec![
+ DeprecatedLint::new(
+ "should_assert_eq",
+ "\"has been superseded by should_assert_eq2\"",
+ Range::default(),
+ ),
+ DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()),
+ ];
+
+ let expected = GENERATED_FILE_COMMENT.to_string()
+ + &[
+ "{",
+ " store.register_removed(",
+ " \"clippy::should_assert_eq\",",
+ " \"has been superseded by should_assert_eq2\",",
+ " );",
+ " store.register_removed(",
+ " \"clippy::another_deprecated\",",
+ " \"will be removed\",",
+ " );",
+ "}",
+ ]
+ .join("\n")
+ + "\n";
+
+ assert_eq!(expected, gen_deprecated(&lints));
+ }
+}
--- /dev/null
- version = "0.1.66"
+[package]
+name = "clippy_lints"
++version = "0.1.67"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+edition = "2021"
+
+[dependencies]
+cargo_metadata = "0.14"
+clippy_utils = { path = "../clippy_utils" }
++declare_clippy_lint = { path = "../declare_clippy_lint" }
+if_chain = "1.0"
+itertools = "0.10.1"
+pulldown-cmark = { version = "0.9", default-features = false }
+quine-mc_cluskey = "0.2"
+regex-syntax = "0.6"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = { version = "1.0", optional = true }
+tempfile = { version = "3.2", optional = true }
+toml = "0.5"
+unicode-normalization = "0.1"
+unicode-script = { version = "0.5", default-features = false }
+semver = "1.0"
+rustc-semver = "1.1"
+# NOTE: cargo requires serde feat in its url dep
+# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
+url = { version = "2.2", features = ["serde"] }
+
+[features]
+deny-warnings = ["clippy_utils/deny-warnings"]
+# build clippy with internal lints enabled, off by default
+internal = ["clippy_utils/internal", "serde_json", "tempfile"]
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+//! checks for attributes
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::macros::{is_panic, macro_backtrace};
+use clippy_utils::msrvs;
+use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
+use clippy_utils::{extract_msrv_attr, meets_msrv};
+use if_chain::if_chain;
+use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
+};
- use rustc_span::sym;
++use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
- | "unsafe_removed_from_name",
+use rustc_span::symbol::Symbol;
++use rustc_span::{sym, DUMMY_SP};
+use semver::Version;
+
+static UNIX_SYSTEMS: &[&str] = &[
+ "android",
+ "dragonfly",
+ "emscripten",
+ "freebsd",
+ "fuchsia",
+ "haiku",
+ "illumos",
+ "ios",
+ "l4re",
+ "linux",
+ "macos",
+ "netbsd",
+ "openbsd",
+ "redox",
+ "solaris",
+ "vxworks",
+];
+
+// NOTE: windows is excluded from the list because it's also a valid target family.
+static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items annotated with `#[inline(always)]`,
+ /// unless the annotated function is empty or simply panics.
+ ///
+ /// ### Why is this bad?
+ /// While there are valid uses of this annotation (and once
+ /// you know when to use it, by all means `allow` this lint), it's a common
+ /// newbie-mistake to pepper one's code with it.
+ ///
+ /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
+ /// measure if that additional function call really affects your runtime profile
+ /// sufficiently to make up for the increase in compile time.
+ ///
+ /// ### Known problems
+ /// False positives, big time. This lint is meant to be
+ /// deactivated by everyone doing serious performance work. This means having
+ /// done the measurement.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[inline(always)]
+ /// fn not_quite_hot_code(..) { ... }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INLINE_ALWAYS,
+ pedantic,
+ "use of `#[inline(always)]`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `extern crate` and `use` items annotated with
+ /// lint attributes.
+ ///
+ /// This lint permits lint attributes for lints emitted on the items themself.
+ /// For `use` items these lints are:
+ /// * deprecated
+ /// * unreachable_pub
+ /// * unused_imports
+ /// * clippy::enum_glob_use
+ /// * clippy::macro_use_imports
+ /// * clippy::wildcard_imports
+ ///
+ /// For `extern crate` items these lints are:
+ /// * `unused_imports` on items with `#[macro_use]`
+ ///
+ /// ### Why is this bad?
+ /// Lint attributes have no effect on crate imports. Most
+ /// likely a `!` was forgotten.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[deny(dead_code)]
+ /// extern crate foo;
+ /// #[forbid(dead_code)]
+ /// use foo::bar;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// #[allow(unused_imports)]
+ /// use foo::baz;
+ /// #[allow(unused_imports)]
+ /// #[macro_use]
+ /// extern crate baz;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_ATTRIBUTE,
+ correctness,
+ "use of lint attributes on `extern crate` items"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[deprecated]` annotations with a `since`
+ /// field that is not a valid semantic version.
+ ///
+ /// ### Why is this bad?
+ /// For checking the version of the deprecation, it must be
+ /// a valid semver. Failing that, the contained information is useless.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[deprecated(since = "forever")]
+ /// fn something_else() { /* ... */ }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DEPRECATED_SEMVER,
+ correctness,
+ "use of `#[deprecated(since = \"x\")]` where x is not semver"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty lines after outer attributes
+ ///
+ /// ### Why is this bad?
+ /// Most likely the attribute was meant to be an inner attribute using a '!'.
+ /// If it was meant to be an outer attribute, then the following item
+ /// should not be separated by empty lines.
+ ///
+ /// ### Known problems
+ /// Can cause false positives.
+ ///
+ /// From the clippy side it's difficult to detect empty lines between an attributes and the
+ /// following item because empty lines and comments are not part of the AST. The parsing
+ /// currently works for basic cases but is not perfect.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[allow(dead_code)]
+ ///
+ /// fn not_quite_good_code() { }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// // Good (as inner attribute)
+ /// #![allow(dead_code)]
+ ///
+ /// fn this_is_fine() { }
+ ///
+ /// // or
+ ///
+ /// // Good (as outer attribute)
+ /// #[allow(dead_code)]
+ /// fn this_is_fine_too() { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LINE_AFTER_OUTER_ATTR,
+ nursery,
+ "empty line after outer attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
+ ///
+ /// ### Why is this bad?
+ /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
+ /// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #![deny(clippy::restriction)]
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// #![deny(clippy::as_conversions)]
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub BLANKET_CLIPPY_RESTRICTION_LINTS,
+ suspicious,
+ "enabling the complete restriction group"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
+ /// with `#[rustfmt::skip]`.
+ ///
+ /// ### Why is this bad?
+ /// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
+ /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
+ ///
+ /// ### Known problems
+ /// This lint doesn't detect crate level inner attributes, because they get
+ /// processed before the PreExpansionPass lints get executed. See
+ /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[cfg_attr(rustfmt, rustfmt_skip)]
+ /// fn main() { }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// #[rustfmt::skip]
+ /// fn main() { }
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub DEPRECATED_CFG_ATTR,
+ complexity,
+ "usage of `cfg_attr(rustfmt)` instead of tool attributes"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cfg attributes having operating systems used in target family position.
+ ///
+ /// ### Why is this bad?
+ /// The configuration option will not be recognised and the related item will not be included
+ /// by the conditional compilation engine.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[cfg(linux)]
+ /// fn conditional() { }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # mod hidden {
+ /// #[cfg(target_os = "linux")]
+ /// fn conditional() { }
+ /// # }
+ ///
+ /// // or
+ ///
+ /// #[cfg(unix)]
+ /// fn conditional() { }
+ /// ```
+ /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
+ #[clippy::version = "1.45.0"]
+ pub MISMATCHED_TARGET_OS,
+ correctness,
+ "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
+ /// ```rust
+ /// #![feature(lint_reasons)]
+ ///
+ /// #![allow(clippy::some_lint)]
+ /// ```
+ ///
+ /// Use instead:
+ /// ```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,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Attributes {
++ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
++ for (name, level) in &cx.sess().opts.lint_opts {
++ if name == "clippy::restriction" && *level > Level::Allow {
++ span_lint_and_then(
++ cx,
++ BLANKET_CLIPPY_RESTRICTION_LINTS,
++ DUMMY_SP,
++ "`clippy::restriction` is not meant to be enabled as a group",
++ |diag| {
++ diag.note(format!(
++ "because of the command line `--{} clippy::restriction`",
++ level.as_str()
++ ));
++ diag.help("enable the restriction lints you need individually");
++ },
++ );
++ }
++ }
++ }
++
+ fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
+ if let Some(items) = &attr.meta_item_list() {
+ if let Some(ident) = attr.ident() {
+ 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;
+ }
+ for item in items {
+ if_chain! {
+ if let NestedMetaItem::MetaItem(mi) = &item;
+ if let MetaItemKind::NameValue(lit) = &mi.kind;
+ if mi.has_name(sym::since);
+ then {
+ check_semver(cx, item.span(), lit);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if is_relevant_item(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, attrs);
+ }
+ match item.kind {
+ ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
+ let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
+
+ for attr in attrs {
+ if in_external_macro(cx.sess(), attr.span) {
+ return;
+ }
+ if let Some(lint_list) = &attr.meta_item_list() {
+ if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
+ for lint in lint_list {
+ match item.kind {
+ ItemKind::Use(..) => {
+ if is_word(lint, sym::unused_imports)
+ || is_word(lint, sym::deprecated)
+ || is_word(lint, sym!(unreachable_pub))
+ || is_word(lint, sym!(unused))
+ || extract_clippy_lint(lint).map_or(false, |s| {
+ matches!(
+ s.as_str(),
+ "wildcard_imports"
+ | "enum_glob_use"
+ | "redundant_pub_crate"
+ | "macro_use_imports"
- "restriction lints are not meant to be all enabled",
++ | "unsafe_removed_from_name"
++ | "module_name_repetitions"
++ | "single_component_path_imports"
+ )
+ })
+ {
+ return;
+ }
+ },
+ ItemKind::ExternCrate(..) => {
+ if is_word(lint, sym::unused_imports) && skip_unused_imports {
+ return;
+ }
+ if is_word(lint, sym!(unused_extern_crates)) {
+ return;
+ }
+ },
+ _ => {},
+ }
+ }
+ let line_span = first_line_of_span(cx, attr.span);
+
+ if let Some(mut sugg) = snippet_opt(cx, line_span) {
+ if sugg.contains("#[") {
+ span_lint_and_then(
+ cx,
+ USELESS_ATTRIBUTE,
+ line_span,
+ "useless lint attribute",
+ |diag| {
+ sugg = sugg.replacen("#[", "#![", 1);
+ diag.span_suggestion(
+ line_span,
+ "if you just forgot a `!`, use",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if is_relevant_impl(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if is_relevant_trait(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+}
+
+/// Returns the lint name if it is clippy lint.
+fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
+ if_chain! {
+ if let Some(meta_item) = lint.meta_item();
+ if meta_item.path.segments.len() > 1;
+ if let tool_name = meta_item.path.segments[0].ident;
+ if tool_name.name == sym::clippy;
+ then {
+ let lint_name = meta_item.path.segments.last().unwrap().ident.name;
+ return Some(lint_name);
+ }
+ }
+ None
+}
+
+fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
+ for lint in items {
+ if let Some(lint_name) = extract_clippy_lint(lint) {
+ if lint_name.as_str() == "restriction" && name != sym::allow {
+ span_lint_and_help(
+ cx,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+ lint.span(),
- "try enabling only the lints you really need",
++ "`clippy::restriction` is not meant to be enabled as a group",
+ None,
++ "enable the restriction lints you need individually",
+ );
+ }
+ }
+ }
+}
+
+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;
+ }
+
++ // Check if the attribute is in an external macro and therefore out of the developer's control
++ if in_external_macro(cx.sess(), attr.span) {
++ 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)
+ } else {
+ true
+ }
+}
+
+fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
+ match item.kind {
+ ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value),
+ _ => false,
+ }
+}
+
+fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
+ match item.kind {
+ TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
+ TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
+ },
+ _ => false,
+ }
+}
+
+fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
+ block.stmts.first().map_or(
+ block
+ .expr
+ .as_ref()
+ .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
+ |stmt| match &stmt.kind {
+ StmtKind::Local(_) => true,
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
+ StmtKind::Item(_) => false,
+ },
+ )
+}
+
+fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
+ if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
+ is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
+ }) {
+ return false;
+ }
+ match &expr.kind {
+ ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
+ ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
+ ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
+ _ => true,
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
+ if span.from_expansion() {
+ return;
+ }
+
+ for attr in attrs {
+ if let Some(values) = attr.meta_item_list() {
+ if values.len() != 1 || !attr.has_name(sym::inline) {
+ continue;
+ }
+ if is_word(&values[0], sym::always) {
+ span_lint(
+ cx,
+ INLINE_ALWAYS,
+ attr.span,
+ &format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
+ );
+ }
+ }
+ }
+}
+
+fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
+ if let LitKind::Str(is, _) = lit.kind {
+ if Version::parse(is.as_str()).is_ok() {
+ return;
+ }
+ }
+ span_lint(
+ cx,
+ DEPRECATED_SEMVER,
+ span,
+ "the since field must contain a semver-compliant version",
+ );
+}
+
+fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
+ if let NestedMetaItem::MetaItem(mi) = &nmi {
+ mi.is_word() && mi.has_name(expected)
+ } else {
+ false
+ }
+}
+
+pub struct EarlyAttributes {
+ pub msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(EarlyAttributes => [
+ DEPRECATED_CFG_ATTR,
+ MISMATCHED_TARGET_OS,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+]);
+
+impl EarlyLintPass for EarlyAttributes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ check_empty_line_after_outer_attr(cx, item);
+ }
+
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
+ check_deprecated_cfg_attr(cx, attr, self.msrv);
+ check_mismatched_target_os(cx, attr);
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ let mut iter = item.attrs.iter().peekable();
+ while let Some(attr) = iter.next() {
+ if matches!(attr.kind, AttrKind::Normal(..))
+ && attr.style == AttrStyle::Outer
+ && is_present_in_source(cx, attr.span)
+ {
+ let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
+ let end_of_attr_to_next_attr_or_item = Span::new(
+ attr.span.hi(),
+ iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
+ item.span.ctxt(),
+ item.span.parent(),
+ );
+
+ if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
+ let lines = snippet.split('\n').collect::<Vec<_>>();
+ let lines = without_block_comments(lines);
+
+ if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
+ span_lint(
+ cx,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+ begin_of_attr_to_item,
+ "found an empty line after an outer attribute. \
+ Perhaps you forgot to add a `!` to make it an inner attribute?",
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option<RustcVersion>) {
+ if_chain! {
+ if meets_msrv(msrv, msrvs::TOOL_ATTRIBUTES);
+ // check cfg_attr
+ if attr.has_name(sym::cfg_attr);
+ if let Some(items) = attr.meta_item_list();
+ if items.len() == 2;
+ // check for `rustfmt`
+ if let Some(feature_item) = items[0].meta_item();
+ if feature_item.has_name(sym::rustfmt);
+ // check for `rustfmt_skip` and `rustfmt::skip`
+ if let Some(skip_item) = &items[1].meta_item();
+ if skip_item.has_name(sym!(rustfmt_skip))
+ || skip_item
+ .path
+ .segments
+ .last()
+ .expect("empty path in attribute")
+ .ident
+ .name
+ == sym::skip;
+ // Only lint outer attributes, because custom inner attributes are unstable
+ // Tracking issue: https://github.com/rust-lang/rust/issues/54726
+ if attr.style == AttrStyle::Outer;
+ then {
+ span_lint_and_sugg(
+ cx,
+ DEPRECATED_CFG_ATTR,
+ attr.span,
+ "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
+ "use",
+ "#[rustfmt::skip]".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
+ fn find_os(name: &str) -> Option<&'static str> {
+ UNIX_SYSTEMS
+ .iter()
+ .chain(NON_UNIX_SYSTEMS.iter())
+ .find(|&&os| os == name)
+ .copied()
+ }
+
+ fn is_unix(name: &str) -> bool {
+ UNIX_SYSTEMS.iter().any(|&os| os == name)
+ }
+
+ fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
+ let mut mismatched = Vec::new();
+
+ for item in items {
+ if let NestedMetaItem::MetaItem(meta) = item {
+ match &meta.kind {
+ MetaItemKind::List(list) => {
+ mismatched.extend(find_mismatched_target_os(list));
+ },
+ MetaItemKind::Word => {
+ if_chain! {
+ if let Some(ident) = meta.ident();
+ if let Some(os) = find_os(ident.name.as_str());
+ then {
+ mismatched.push((os, ident.span));
+ }
+ }
+ },
+ MetaItemKind::NameValue(..) => {},
+ }
+ }
+ }
+
+ mismatched
+ }
+
+ if_chain! {
+ if attr.has_name(sym::cfg);
+ if let Some(list) = attr.meta_item_list();
+ let mismatched = find_mismatched_target_os(&list);
+ if !mismatched.is_empty();
+ then {
+ let mess = "operating system used in target family position";
+
+ span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
+ // Avoid showing the unix suggestion multiple times in case
+ // we have more than one mismatch for unix-like systems
+ let mut unix_suggested = false;
+
+ for (os, span) in mismatched {
+ let sugg = format!("target_os = \"{os}\"");
+ diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
+
+ if !unix_suggested && is_unix(os) {
+ diag.help("did you mean `unix`?");
+ unix_suggested = true;
+ }
+ }
+ });
+ }
+ }
+}
+
+fn is_lint_level(symbol: Symbol) -> bool {
+ matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
+}
--- /dev/null
- use rustc_hir::def::{Namespace, Res};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{match_def_path, paths};
+use rustc_data_structures::fx::FxHashMap;
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
+use rustc_hir::def_id::DefId;
+use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::GeneratorInteriorTypeCause;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+
+use crate::utils::conf::DisallowedPath;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to await while holding a non-async-aware MutexGuard.
+ ///
+ /// ### Why is this bad?
+ /// The Mutex types found in std::sync and parking_lot
+ /// are not designed to operate in an async context across await points.
+ ///
+ /// There are two potential solutions. One is to use an async-aware Mutex
+ /// type. Many asynchronous foundation crates provide such a Mutex type. The
+ /// other solution is to ensure the mutex is unlocked before calling await,
+ /// either by introducing a scope or an explicit call to Drop::drop.
+ ///
+ /// ### Known problems
+ /// Will report false positive for explicitly dropped guards
+ /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
+ /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::sync::Mutex;
+ /// # async fn baz() {}
+ /// async fn foo(x: &Mutex<u32>) {
+ /// let mut guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// baz().await;
+ /// }
+ ///
+ /// async fn bar(x: &Mutex<u32>) {
+ /// let mut guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// drop(guard); // explicit drop
+ /// baz().await;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::sync::Mutex;
+ /// # async fn baz() {}
+ /// async fn foo(x: &Mutex<u32>) {
+ /// {
+ /// let mut guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// }
+ /// baz().await;
+ /// }
+ ///
+ /// async fn bar(x: &Mutex<u32>) {
+ /// {
+ /// let mut guard = x.lock().unwrap();
+ /// *guard += 1;
+ /// } // guard dropped here at end of scope
+ /// baz().await;
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub AWAIT_HOLDING_LOCK,
+ suspicious,
+ "inside an async function, holding a `MutexGuard` while calling `await`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
+ ///
+ /// ### Why is this bad?
+ /// `RefCell` refs only check for exclusive mutable access
+ /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
+ /// risks panics from a mutable ref shared while other refs are outstanding.
+ ///
+ /// ### Known problems
+ /// Will report false positive for explicitly dropped refs
+ /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
+ /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::cell::RefCell;
+ /// # async fn baz() {}
+ /// async fn foo(x: &RefCell<u32>) {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// baz().await;
+ /// }
+ ///
+ /// async fn bar(x: &RefCell<u32>) {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// drop(y); // explicit drop
+ /// baz().await;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::cell::RefCell;
+ /// # async fn baz() {}
+ /// async fn foo(x: &RefCell<u32>) {
+ /// {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// }
+ /// baz().await;
+ /// }
+ ///
+ /// async fn bar(x: &RefCell<u32>) {
+ /// {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// } // y dropped here at end of scope
+ /// baz().await;
+ /// }
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub AWAIT_HOLDING_REFCELL_REF,
+ suspicious,
+ "inside an async function, holding a `RefCell` ref while calling `await`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Allows users to configure types which should not be held across `await`
+ /// suspension points.
+ ///
+ /// ### Why is this bad?
+ /// There are some types which are perfectly "safe" to be used concurrently
+ /// from a memory access perspective but will cause bugs at runtime if they
+ /// are held in such a way.
+ ///
+ /// ### Example
+ ///
+ /// ```toml
+ /// await-holding-invalid-types = [
+ /// # You can specify a type name
+ /// "CustomLockType",
+ /// # You can (optionally) specify a reason
+ /// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
+ /// ]
+ /// ```
+ ///
+ /// ```rust
+ /// # async fn baz() {}
+ /// struct CustomLockType;
+ /// struct OtherCustomLockType;
+ /// async fn foo() {
+ /// let _x = CustomLockType;
+ /// let _y = OtherCustomLockType;
+ /// baz().await; // Lint violation
+ /// }
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub AWAIT_HOLDING_INVALID_TYPE,
+ suspicious,
+ "holding a type across an await point which is not allowed to be held as per the configuration"
+}
+
+impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
+
+#[derive(Debug)]
+pub struct AwaitHolding {
+ conf_invalid_types: Vec<DisallowedPath>,
+ def_ids: FxHashMap<DefId, DisallowedPath>,
+}
+
+impl AwaitHolding {
+ pub(crate) fn new(conf_invalid_types: Vec<DisallowedPath>) -> Self {
+ Self {
+ conf_invalid_types,
+ def_ids: FxHashMap::default(),
+ }
+ }
+}
+
+impl LateLintPass<'_> for AwaitHolding {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for conf in &self.conf_invalid_types {
+ let segs: Vec<_> = conf.path().split("::").collect();
++ for id in clippy_utils::def_path_def_ids(cx, &segs) {
+ self.def_ids.insert(id, conf.clone());
+ }
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
+ use AsyncGeneratorKind::{Block, Closure, Fn};
+ if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
+ let body_id = BodyId {
+ hir_id: body.value.hir_id,
+ };
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ self.check_interior_types(
+ cx,
+ typeck_results.generator_interior_types.as_ref().skip_binder(),
+ body.value.span,
+ );
+ }
+ }
+}
+
+impl AwaitHolding {
+ fn check_interior_types(&self, 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()) {
+ span_lint_and_then(
+ cx,
+ AWAIT_HOLDING_LOCK,
+ ty_cause.span,
+ "this `MutexGuard` is held across an `await` point",
+ |diag| {
+ diag.help(
+ "consider using an async-aware `Mutex` type or ensuring the \
+ `MutexGuard` is dropped before calling await",
+ );
+ diag.span_note(
+ ty_cause.scope_span.unwrap_or(span),
+ "these are all the `await` points this lock is held through",
+ );
+ },
+ );
+ } else if is_refcell_ref(cx, adt.did()) {
+ span_lint_and_then(
+ cx,
+ AWAIT_HOLDING_REFCELL_REF,
+ ty_cause.span,
+ "this `RefCell` reference is held across an `await` point",
+ |diag| {
+ diag.help("ensure the reference is dropped before calling `await`");
+ diag.span_note(
+ ty_cause.scope_span.unwrap_or(span),
+ "these are all the `await` points this reference is held through",
+ );
+ },
+ );
+ } else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
+ emit_invalid_type(cx, ty_cause.span, disallowed);
+ }
+ }
+ }
+ }
+}
+
+fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) {
+ span_lint_and_then(
+ cx,
+ AWAIT_HOLDING_INVALID_TYPE,
+ span,
+ &format!(
+ "`{}` may not be held across an `await` point per `clippy.toml`",
+ disallowed.path()
+ ),
+ |diag| {
+ if let Some(reason) = disallowed.reason() {
+ diag.note(reason);
+ }
+ },
+ );
+}
+
+fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx.is_diagnostic_item(sym::MutexGuard, def_id)
+ || cx.tcx.is_diagnostic_item(sym::RwLockReadGuard, def_id)
+ || cx.tcx.is_diagnostic_item(sym::RwLockWriteGuard, def_id)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
+}
+
+fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
+}
--- /dev/null
- use clippy_utils::{diagnostics::span_lint_and_then, is_else_clause, is_integer_literal, sugg::Sugg};
++use clippy_utils::higher::If;
+use rustc_ast::LitKind;
+use rustc_hir::{Block, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
- /// Coercion or `from()` is idiomatic way to convert bool to a number.
++use clippy_utils::{diagnostics::span_lint_and_then, in_constant, is_else_clause, is_integer_literal, sugg::Sugg};
+use rustc_errors::Applicability;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Instead of using an if statement to convert a bool to an int,
+ /// this lint suggests using a `from()` function or an `as` coercion.
+ ///
+ /// ### Why is this bad?
- style,
++ /// Coercion or `from()` is another way to convert bool to a number.
+ /// Both methods are guaranteed to return 1 for true, and 0 for false.
+ ///
+ /// See https://doc.rust-lang.org/std/primitive.bool.html#impl-From%3Cbool%3E
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let condition = false;
+ /// if condition {
+ /// 1_i64
+ /// } else {
+ /// 0
+ /// };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let condition = false;
+ /// i64::from(condition);
+ /// ```
+ /// or
+ /// ```rust
+ /// # let condition = false;
+ /// condition as i64;
+ /// ```
+ #[clippy::version = "1.65.0"]
+ pub BOOL_TO_INT_WITH_IF,
- fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
- if !expr.span.from_expansion() {
- check_if_else(ctx, expr);
++ pedantic,
+ "using if to convert bool to int"
+}
+declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
+
+impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
- fn check_if_else<'tcx>(ctx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
- if let ExprKind::If(check, then, Some(else_)) = expr.kind
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
++ if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) {
++ check_if_else(cx, expr);
+ }
+ }
+}
+
- && let Some(else_lit) = int_literal(else_)
++fn check_if_else<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
++ if let Some(If { cond, then, r#else: Some(r#else) }) = If::hir(expr)
+ && let Some(then_lit) = int_literal(then)
- let mut sugg = Sugg::hir_with_applicability(ctx, check, "..", &mut applicability);
++ && let Some(else_lit) = int_literal(r#else)
+ {
+ let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) {
+ false
+ } else if is_integer_literal(then_lit, 0) && is_integer_literal(else_lit, 1) {
+ true
+ } else {
+ // Expression isn't boolean, exit
+ return;
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = {
- let ty = ctx.typeck_results().expr_ty(then_lit); // then and else must be of same type
++ let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
+ if inverted {
+ sugg = !sugg;
+ }
+ sugg
+ };
+
- let wrap_in_curly = is_else_clause(ctx.tcx, expr);
++ let ty = cx.typeck_results().expr_ty(then_lit); // then and else must be of same type
+
+ let suggestion = {
- span_lint_and_then(ctx,
++ let wrap_in_curly = is_else_clause(cx.tcx, expr);
+ let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into());
+ if wrap_in_curly {
+ s = s.blockify();
+ }
+ s
+ }; // when used in else clause if statement should be wrapped in curly braces
+
+ let into_snippet = snippet.clone().maybe_par();
+ let as_snippet = snippet.as_ty(ty);
+
++ span_lint_and_then(cx,
+ BOOL_TO_INT_WITH_IF,
+ expr.span,
+ "boolean to int conversion using if",
+ |diag| {
+ diag.span_suggestion(
+ expr.span,
+ "replace with from",
+ suggestion,
+ applicability,
+ );
+ diag.note(format!("`{as_snippet}` or `{into_snippet}.into()` can also be valid options"));
+ });
+ };
+}
+
+// If block contains only a int literal expression, return literal expression
+fn int_literal<'tcx>(expr: &'tcx rustc_hir::Expr<'tcx>) -> Option<&'tcx rustc_hir::Expr<'tcx>> {
+ if let ExprKind::Block(block, _) = expr.kind
+ && let Block {
+ stmts: [], // Shouldn't lint if statements with side effects
+ expr: Some(expr),
+ ..
+ } = block
+ && let ExprKind::Lit(lit) = &expr.kind
+ && let LitKind::Int(_, _) = lit.node
+ {
+ Some(expr)
+ } else {
+ None
+ }
+}
--- /dev/null
- fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that can be written more
+ /// concisely.
+ ///
+ /// ### Why is this bad?
+ /// Readability of boolean expressions suffers from
+ /// unnecessary duplication.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior of `||` and
+ /// `&&`. Ignores `|`, `&` and `^`.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a && true {}
+ /// if !(a == b) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// if a {}
+ /// if a != b {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NONMINIMAL_BOOL,
+ complexity,
+ "boolean expressions that can be written more concisely"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that contain terminals that
+ /// can be eliminated.
+ ///
+ /// ### Why is this bad?
+ /// This is most likely a logic bug.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // The `b` is unnecessary, the expression is equivalent to `if a`.
+ /// if a && b || a { ... }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// if a {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OVERLY_COMPLEX_BOOL_EXPR,
+ correctness,
+ "boolean expressions that contain terminals which can be eliminated"
+}
+
+// For each pairs, both orders are considered.
+const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")];
+
+declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]);
+
+impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ NonminimalBoolVisitor { cx }.visit_body(body);
+ }
+}
+
+struct NonminimalBoolVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+use quine_mc_cluskey::Bool;
+struct Hir2Qmm<'a, 'tcx, 'v> {
+ terminals: Vec<&'v Expr<'v>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
+ fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
+ for a in a {
+ if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
+ if binop.node == op {
+ v = self.extract(op, &[lhs, rhs], v)?;
+ continue;
+ }
+ }
+ v.push(self.run(a)?);
+ }
+ Ok(v)
+ }
+
+ fn run(&mut self, e: &'v Expr<'_>) -> Result<Bool, String> {
+ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
+ match bin_op_kind {
+ BinOpKind::Eq => Some(BinOpKind::Ne),
+ BinOpKind::Ne => Some(BinOpKind::Eq),
+ BinOpKind::Gt => Some(BinOpKind::Le),
+ BinOpKind::Ge => Some(BinOpKind::Lt),
+ BinOpKind::Lt => Some(BinOpKind::Ge),
+ BinOpKind::Le => Some(BinOpKind::Gt),
+ _ => None,
+ }
+ }
+
+ // prevent folding of `cfg!` macros and the like
+ if !e.span.from_expansion() {
+ match &e.kind {
+ ExprKind::Unary(UnOp::Not, inner) => return Ok(Bool::Not(Box::new(self.run(inner)?))),
+ ExprKind::Binary(binop, lhs, rhs) => match &binop.node {
+ BinOpKind::Or => {
+ return Ok(Bool::Or(self.extract(BinOpKind::Or, &[lhs, rhs], Vec::new())?));
+ },
+ BinOpKind::And => {
+ return Ok(Bool::And(self.extract(BinOpKind::And, &[lhs, rhs], Vec::new())?));
+ },
+ _ => (),
+ },
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(true) => return Ok(Bool::True),
+ LitKind::Bool(false) => return Ok(Bool::False),
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+ for (n, expr) in self.terminals.iter().enumerate() {
+ if eq_expr_value(self.cx, e, expr) {
+ #[expect(clippy::cast_possible_truncation)]
+ return Ok(Bool::Term(n as u8));
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
+ if implements_ord(self.cx, e_lhs);
+ if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
+ if negate(e_binop.node) == Some(expr_binop.node);
+ if eq_expr_value(self.cx, e_lhs, expr_lhs);
+ if eq_expr_value(self.cx, e_rhs, expr_rhs);
+ then {
+ #[expect(clippy::cast_possible_truncation)]
+ return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
+ }
+ }
+ }
+ let n = self.terminals.len();
+ self.terminals.push(e);
+ if n < 32 {
+ #[expect(clippy::cast_possible_truncation)]
+ Ok(Bool::Term(n as u8))
+ } else {
+ Err("too many literals".to_owned())
+ }
+ }
+}
+
+struct SuggestContext<'a, 'tcx, 'v> {
+ terminals: &'v [&'v Expr<'v>],
+ cx: &'a LateContext<'tcx>,
+ output: String,
+}
+
+impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
+ fn recurse(&mut self, suggestion: &Bool) -> Option<()> {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match suggestion {
+ True => {
+ self.output.push_str("true");
+ },
+ False => {
+ self.output.push_str("false");
+ },
+ Not(inner) => match **inner {
+ And(_) | Or(_) => {
+ self.output.push('!');
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ },
+ Term(n) => {
+ let terminal = self.terminals[n as usize];
+ if let Some(str) = simplify_not(self.cx, terminal) {
+ self.output.push_str(&str);
+ } else {
+ self.output.push('!');
+ let snip = snippet_opt(self.cx, terminal.span)?;
+ self.output.push_str(&snip);
+ }
+ },
+ True | False | Not(_) => {
+ self.output.push('!');
+ self.recurse(inner)?;
+ },
+ },
+ And(v) => {
+ for (index, inner) in v.iter().enumerate() {
+ if index > 0 {
+ self.output.push_str(" && ");
+ }
+ if let Or(_) = *inner {
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ } else {
+ self.recurse(inner);
+ }
+ }
+ },
+ Or(v) => {
+ for (index, inner) in v.iter().rev().enumerate() {
+ if index > 0 {
+ self.output.push_str(" || ");
+ }
+ self.recurse(inner);
+ }
+ },
+ &Term(n) => {
+ let snip = snippet_opt(self.cx, self.terminals[n as usize].span.source_callsite())?;
+ self.output.push_str(&snip);
+ },
+ }
+ Some(())
+ }
+}
+
+fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ match &expr.kind {
+ ExprKind::Binary(binop, lhs, rhs) => {
+ if !implements_ord(cx, lhs) {
+ return None;
+ }
+
+ match binop.node {
+ BinOpKind::Eq => Some(" != "),
+ BinOpKind::Ne => Some(" == "),
+ BinOpKind::Lt => Some(" >= "),
+ BinOpKind::Gt => Some(" <= "),
+ BinOpKind::Le => Some(" > "),
+ BinOpKind::Ge => Some(" < "),
+ _ => None,
+ }
+ .and_then(|op| {
+ Some(format!(
+ "{}{op}{}",
+ snippet_opt(cx, lhs.span)?,
+ snippet_opt(cx, rhs.span)?
+ ))
+ })
+ },
+ ExprKind::MethodCall(path, receiver, [], _) => {
+ let type_of_receiver = cx.typeck_results().expr_ty(receiver);
+ if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option)
+ && !is_type_diagnostic_item(cx, type_of_receiver, sym::Result)
+ {
+ return None;
+ }
+ METHODS_WITH_NEGATION
+ .iter()
+ .copied()
+ .flat_map(|(a, b)| vec![(a, b), (b, a)])
+ .find(|&(a, _)| {
+ let path: &str = path.ident.name.as_str();
+ a == path
+ })
+ .and_then(|(_, neg_method)| Some(format!("{}.{neg_method}()", snippet_opt(cx, receiver.span)?)))
+ },
+ _ => None,
+ }
+}
+
+fn suggest(cx: &LateContext<'_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
+ let mut suggest_context = SuggestContext {
+ terminals,
+ cx,
+ output: String::new(),
+ };
+ suggest_context.recurse(suggestion);
+ suggest_context.output
+}
+
+fn simple_negate(b: Bool) -> Bool {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match b {
+ True => False,
+ False => True,
+ t @ Term(_) => Not(Box::new(t)),
+ And(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ Or(v)
+ },
+ Or(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ And(v)
+ },
+ Not(inner) => *inner,
+ }
+}
+
+#[derive(Default)]
+struct Stats {
+ terminals: [usize; 32],
+ negations: usize,
+ ops: usize,
+}
+
+fn terminal_stats(b: &Bool) -> Stats {
+ fn recurse(b: &Bool, stats: &mut Stats) {
+ match b {
+ True | False => stats.ops += 1,
+ Not(inner) => {
+ match **inner {
+ And(_) | Or(_) => stats.ops += 1, // brackets are also operations
+ _ => stats.negations += 1,
+ }
+ recurse(inner, stats);
+ },
+ And(v) | Or(v) => {
+ stats.ops += v.len() - 1;
+ for inner in v {
+ recurse(inner, stats);
+ }
+ },
+ &Term(n) => stats.terminals[n as usize] += 1,
+ }
+ }
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ let mut stats = Stats::default();
+ recurse(b, &mut stats);
+ stats
+}
+
+impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
+ fn bool_expr(&self, e: &'tcx Expr<'_>) {
+ let mut h2q = Hir2Qmm {
+ terminals: Vec::new(),
+ cx: self.cx,
+ };
+ if let Ok(expr) = h2q.run(e) {
+ if h2q.terminals.len() > 8 {
+ // QMC has exponentially slow behavior as the number of terminals increases
+ // 8 is reasonable, it takes approximately 0.2 seconds.
+ // See #825
+ return;
+ }
+
+ let stats = terminal_stats(&expr);
+ let mut simplified = expr.simplify();
+ for simple in Bool::Not(Box::new(expr)).simplify() {
+ match simple {
+ Bool::Not(_) | Bool::True | Bool::False => {},
+ _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
+ }
+ let simple_negated = simple_negate(simple);
+ if simplified.iter().any(|s| *s == simple_negated) {
+ continue;
+ }
+ simplified.push(simple_negated);
+ }
+ let mut improvements = Vec::with_capacity(simplified.len());
+ 'simplified: for suggestion in &simplified {
+ let simplified_stats = terminal_stats(suggestion);
+ let mut improvement = false;
+ for i in 0..32 {
+ // ignore any "simplifications" that end up requiring a terminal more often
+ // than in the original expression
+ if stats.terminals[i] < simplified_stats.terminals[i] {
+ continue 'simplified;
+ }
+ if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
+ span_lint_hir_and_then(
+ self.cx,
+ OVERLY_COMPLEX_BOOL_EXPR,
+ e.hir_id,
+ e.span,
+ "this boolean expression contains a logic bug",
+ |diag| {
+ diag.span_help(
+ h2q.terminals[i].span,
+ "this expression can be optimized out by applying boolean operations to the \
+ outer expression",
+ );
+ diag.span_suggestion(
+ e.span,
+ "it would look like the following",
+ suggest(self.cx, suggestion, &h2q.terminals),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ // don't also lint `NONMINIMAL_BOOL`
+ return;
+ }
+ // if the number of occurrences of a terminal decreases or any of the stats
+ // decreases while none increases
+ improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
+ || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
+ || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
+ }
+ if improvement {
+ improvements.push(suggestion);
+ }
+ }
+ let nonminimal_bool_lint = |suggestions: Vec<_>| {
+ span_lint_hir_and_then(
+ self.cx,
+ NONMINIMAL_BOOL,
+ e.hir_id,
+ e.span,
+ "this boolean expression can be simplified",
+ |diag| {
+ diag.span_suggestions(
+ e.span,
+ "try",
+ suggestions.into_iter(),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ };
+ if improvements.is_empty() {
+ let mut visitor = NotSimplificationVisitor { cx: self.cx };
+ visitor.visit_expr(e);
+ } else {
+ nonminimal_bool_lint(
+ improvements
+ .into_iter()
+ .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
+ .collect(),
+ );
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !e.span.from_expansion() {
+ match &e.kind {
+ ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
+ self.bool_expr(e);
+ },
+ ExprKind::Unary(UnOp::Not, inner) => {
+ if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() {
+ self.bool_expr(e);
+ }
+ },
+ _ => {},
+ }
+ }
+ walk_expr(self, e);
+ }
+}
+
++fn implements_ord(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ cx.tcx
+ .get_diagnostic_item(sym::Ord)
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+}
+
+struct NotSimplificationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind {
+ if let Some(suggestion) = simplify_not(self.cx, inner) {
+ span_lint_and_sugg(
+ self.cx,
+ NONMINIMAL_BOOL,
+ expr.span,
+ "this boolean expression can be simplified",
+ "try",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+}
--- /dev/null
- #[clippy::version = "1.64.0"]
+mod as_ptr_cast_mut;
+mod as_underscore;
+mod borrow_as_ptr;
+mod cast_abs_to_unsigned;
+mod cast_enum_constructor;
+mod cast_lossless;
+mod cast_nan_to_int;
+mod cast_possible_truncation;
+mod cast_possible_wrap;
+mod cast_precision_loss;
+mod cast_ptr_alignment;
+mod cast_ref_to_mut;
+mod cast_sign_loss;
+mod cast_slice_different_sizes;
+mod cast_slice_from_raw_parts;
+mod char_lit_as_u8;
+mod fn_to_numeric_cast;
+mod fn_to_numeric_cast_any;
+mod fn_to_numeric_cast_with_truncation;
+mod ptr_as_ptr;
+mod unnecessary_cast;
+mod utils;
+
+use clippy_utils::{is_hir_ty_cfg_dependant, meets_msrv, msrvs};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from any numerical to a float type where
+ /// the receiving type cannot store all values from the original type without
+ /// rounding errors. This possible rounding is to be expected, so this lint is
+ /// `Allow` by default.
+ ///
+ /// Basically, this warns on casting any integer with 32 or more bits to `f32`
+ /// or any 64-bit integer to `f64`.
+ ///
+ /// ### Why is this bad?
+ /// It's not bad at all. But in some applications it can be
+ /// helpful to know where precision loss can take place. This lint can help find
+ /// those places in the code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = u64::MAX;
+ /// x as f64;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_PRECISION_LOSS,
+ pedantic,
+ "casts that cause loss of precision, e.g., `x as f32` where `x: u64`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from a signed to an unsigned numerical
+ /// type. In this case, negative values wrap around to large positive values,
+ /// which can be quite surprising in practice. However, as the cast works as
+ /// defined, this lint is `Allow` by default.
+ ///
+ /// ### Why is this bad?
+ /// Possibly surprising results. You can activate this lint
+ /// as a one-time check to see where numerical wrapping can arise.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let y: i8 = -1;
+ /// y as u128; // will return 18446744073709551615
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_SIGN_LOSS,
+ pedantic,
+ "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts between numerical types that may
+ /// truncate large values. This is expected behavior, so the cast is `Allow` by
+ /// default.
+ ///
+ /// ### Why is this bad?
+ /// In some problem domains, it is good practice to avoid
+ /// truncation. This lint can be activated to help assess where additional
+ /// checks could be beneficial.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn as_u8(x: u64) -> u8 {
+ /// x as u8
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_POSSIBLE_TRUNCATION,
+ pedantic,
+ "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an unsigned type to a signed type of
+ /// the same size. Performing such a cast is a 'no-op' for the compiler,
+ /// i.e., nothing is changed at the bit level, and the binary representation of
+ /// the value is reinterpreted. This can cause wrapping if the value is too big
+ /// for the target signed type. However, the cast works as defined, so this lint
+ /// is `Allow` by default.
+ ///
+ /// ### Why is this bad?
+ /// While such a cast is not bad in itself, the results can
+ /// be surprising when this is not the intended behavior, as demonstrated by the
+ /// example below.
+ ///
+ /// ### Example
+ /// ```rust
+ /// u32::MAX as i32; // will yield a value of `-1`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_POSSIBLE_WRAP,
+ pedantic,
+ "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts between numerical types that may
+ /// be replaced by safe conversion functions.
+ ///
+ /// ### Why is this bad?
+ /// Rust's `as` keyword will perform many kinds of
+ /// conversions, including silently lossy conversions. Conversion functions such
+ /// as `i32::from` will only perform lossless conversions. Using the conversion
+ /// functions prevents conversions from turning into silent lossy conversions if
+ /// the types of the input expressions ever change, and make it easier for
+ /// people reading the code to know that the conversion is lossless.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// x as u64
+ /// }
+ /// ```
+ ///
+ /// Using `::from` would look like this:
+ ///
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// u64::from(x)
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_LOSSLESS,
+ pedantic,
+ "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts to the same type, casts of int literals to integer types
+ /// and casts of float literals to float types.
+ ///
+ /// ### Why is this bad?
+ /// It's just unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = 2i32 as i32;
+ /// let _ = 0.5 as f32;
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// let _ = 2_i32;
+ /// let _ = 0.5_f32;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_CAST,
+ complexity,
+ "cast to the same type, e.g., `x as i32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts, using `as` or `pointer::cast`,
+ /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing the resulting pointer may be undefined
+ /// behavior.
+ ///
+ /// ### Known problems
+ /// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
+ /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like
+ /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (&1u8 as *const u8) as *const u16;
+ /// let _ = (&mut 1u8 as *mut u8) as *mut u16;
+ ///
+ /// (&1u8 as *const u8).cast::<u16>();
+ /// (&mut 1u8 as *mut u8).cast::<u16>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_PTR_ALIGNMENT,
+ pedantic,
+ "cast from a pointer to a more-strictly-aligned pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of function pointers to something other than usize
+ ///
+ /// ### Why is this bad?
+ /// Casting a function pointer to anything other than usize/isize is not portable across
+ /// architectures, because you end up losing bits if the target type is too small or end up with a
+ /// bunch of extra bits that waste space and add more instructions to the final binary than
+ /// strictly necessary for the problem
+ ///
+ /// Casting to isize also doesn't make sense since there are no signed addresses.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn fun() -> i32 { 1 }
+ /// let _ = fun as i64;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # fn fun() -> i32 { 1 }
+ /// let _ = fun as usize;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FN_TO_NUMERIC_CAST,
+ style,
+ "casting a function pointer to a numeric type other than usize"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of a function pointer to a numeric type not wide enough to
+ /// store address.
+ ///
+ /// ### Why is this bad?
+ /// Such a cast discards some bits of the function's address. If this is intended, it would be more
+ /// clearly expressed by casting to usize first, then casting the usize to the intended type (with
+ /// a comment) to perform the truncation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn fn1() -> i16 {
+ /// 1
+ /// };
+ /// let _ = fn1 as i32;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// // Cast to usize first, then comment with the reason for the truncation
+ /// fn fn1() -> i16 {
+ /// 1
+ /// };
+ /// let fn_ptr = fn1 as usize;
+ /// let fn_ptr_truncated = fn_ptr as i32;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ style,
+ "casting a function pointer to a numeric type not wide enough to store the address"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of a function pointer to any integer type.
+ ///
+ /// ### Why is this bad?
+ /// Casting a function pointer to an integer can have surprising results and can occur
+ /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
+ /// low-level with function pointers then you can opt-out of casting functions to integers in
+ /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
+ /// pointer casts in your code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // fn1 is cast as `usize`
+ /// fn fn1() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn1 as usize;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// // maybe you intended to call the function?
+ /// fn fn2() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn2() as usize;
+ ///
+ /// // or
+ ///
+ /// // maybe you intended to cast it to a function type?
+ /// fn fn3() -> u16 {
+ /// 1
+ /// }
+ /// let _ = fn3 as fn() -> u16;
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub FN_TO_NUMERIC_CAST_ANY,
+ restriction,
+ "casting a function pointer to any integer type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of `&T` to `&mut T` anywhere in the code.
+ ///
+ /// ### Why is this bad?
+ /// It’s basically guaranteed to be undefined behavior.
+ /// `UnsafeCell` is the only way to obtain aliasable data that is considered
+ /// mutable.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn x(r: &i32) {
+ /// unsafe {
+ /// *(r as *const _ as *mut _) += 1;
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Instead consider using interior mutability types.
+ ///
+ /// ```rust
+ /// use std::cell::UnsafeCell;
+ ///
+ /// fn x(r: &UnsafeCell<i32>) {
+ /// unsafe {
+ /// *r.get() += 1;
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub CAST_REF_TO_MUT,
+ correctness,
+ "a cast of reference to a mutable pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions where a character literal is cast
+ /// to `u8` and suggests using a byte literal instead.
+ ///
+ /// ### Why is this bad?
+ /// In general, casting values to smaller types is
+ /// error-prone and should be avoided where possible. In the particular case of
+ /// converting a character literal to u8, it is easy to avoid by just using a
+ /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
+ /// than `'a' as u8`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// 'x' as u8
+ /// ```
+ ///
+ /// A better version, using the byte literal:
+ ///
+ /// ```rust,ignore
+ /// b'x'
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHAR_LIT_AS_U8,
+ complexity,
+ "casting a character literal to `u8` truncates"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `as` casts between raw pointers without changing its mutability,
+ /// namely `*const T` to `*const U` and `*mut T` to `*mut U`.
+ ///
+ /// ### Why is this bad?
+ /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because
+ /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr as *const i32;
+ /// let _ = mut_ptr as *mut i32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr.cast::<i32>();
+ /// let _ = mut_ptr.cast::<i32>();
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub PTR_AS_PTR,
+ pedantic,
+ "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an enum type to an integral type which will definitely truncate the
+ /// value.
+ ///
+ /// ### Why is this bad?
+ /// The resulting integral value will not match the value of the variant it came from.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum E { X = 256 };
+ /// let _ = E::X as u8;
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub CAST_ENUM_TRUNCATION,
+ suspicious,
+ "casts from an enum type to an integral type which will truncate the value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// 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.61.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"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of the `abs()` method that cast the result to unsigned.
+ ///
+ /// ### Why is this bad?
+ /// The `unsigned_abs()` method avoids panic when called on the MIN value.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = -42;
+ /// let y: u32 = x.abs() as u32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x: i32 = -42;
+ /// let y: u32 = x.unsigned_abs();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub CAST_ABS_TO_UNSIGNED,
+ suspicious,
+ "casting the result of `abs()` to an unsigned integer can panic"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for the usage of `as _` conversion using inferred type.
+ ///
+ /// ### Why is this bad?
+ /// The conversion might include lossy conversion and dangerous cast that might go
+ /// undetected due to the type being inferred.
+ ///
+ /// The lint is allowed by default as using `_` is less wordy than always specifying the type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(n: usize) {}
+ /// let n: u16 = 256;
+ /// foo(n as _);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn foo(n: usize) {}
+ /// let n: u16 = 256;
+ /// foo(n as usize);
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub AS_UNDERSCORE,
+ restriction,
+ "detects `as _` conversion"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `&expr as *const T` or
+ /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or
+ /// `ptr::addr_of_mut` instead.
+ ///
+ /// ### Why is this bad?
+ /// This would improve readability and avoid creating a reference
+ /// that points to an uninitialized value or unaligned place.
+ /// Read the `ptr::addr_of` docs for more information.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let val = 1;
+ /// let p = &val as *const i32;
+ ///
+ /// let mut val_mut = 1;
+ /// let p_mut = &mut val_mut as *mut i32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let val = 1;
+ /// let p = std::ptr::addr_of!(val);
+ ///
+ /// let mut val_mut = 1;
+ /// let p_mut = std::ptr::addr_of_mut!(val_mut);
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub BORROW_AS_PTR,
+ pedantic,
+ "borrowing just to cast to a raw pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a raw slice being cast to a slice pointer
+ ///
+ /// ### Why is this bad?
+ /// This can result in multiple `&mut` references to the same location when only a pointer is
+ /// required.
+ /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require
+ /// the same [safety requirements] to be upheld.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _;
+ /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len);
+ /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len);
+ /// ```
+ /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
++ #[clippy::version = "1.65.0"]
+ pub CAST_SLICE_FROM_RAW_PARTS,
+ suspicious,
+ "casting a slice created from a pointer and length to a slice pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer
+ ///
+ /// ### Why is this bad?
+ /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior
+ /// mutability is used, making it unlikely that having it as a mutable pointer is correct.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let string = String::with_capacity(1);
+ /// let ptr = string.as_ptr() as *mut u8;
+ /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut string = String::with_capacity(1);
+ /// let ptr = string.as_mut_ptr();
+ /// unsafe { ptr.write(4) };
+ /// ```
+ #[clippy::version = "1.66.0"]
+ pub AS_PTR_CAST_MUT,
+ nursery,
+ "casting the result of the `&self`-taking `as_ptr` to a mutabe pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a known NaN float being cast to an integer
+ ///
+ /// ### Why is this bad?
+ /// NaNs are cast into zero, so one could simply use this and make the
+ /// code more readable. The lint could also hint at a programmer error.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let _: (0.0_f32 / 0.0) as u64;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// let _: = 0_u64;
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub CAST_NAN_TO_INT,
+ suspicious,
+ "casting a known floating-point NaN into an integer"
+}
+
+pub struct Casts {
+ msrv: Option<RustcVersion>,
+}
+
+impl Casts {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Casts => [
+ CAST_PRECISION_LOSS,
+ CAST_SIGN_LOSS,
+ CAST_POSSIBLE_TRUNCATION,
+ CAST_POSSIBLE_WRAP,
+ CAST_LOSSLESS,
+ CAST_REF_TO_MUT,
+ CAST_PTR_ALIGNMENT,
+ CAST_SLICE_DIFFERENT_SIZES,
+ UNNECESSARY_CAST,
+ FN_TO_NUMERIC_CAST_ANY,
+ FN_TO_NUMERIC_CAST,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ CHAR_LIT_AS_U8,
+ PTR_AS_PTR,
+ CAST_ENUM_TRUNCATION,
+ CAST_ENUM_CONSTRUCTOR,
+ CAST_ABS_TO_UNSIGNED,
+ AS_UNDERSCORE,
+ BORROW_AS_PTR,
+ CAST_SLICE_FROM_RAW_PARTS,
+ AS_PTR_CAST_MUT,
+ CAST_NAN_TO_INT,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Casts {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !in_external_macro(cx.sess(), expr.span) {
+ ptr_as_ptr::check(cx, expr, self.msrv);
+ }
+
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Cast(cast_expr, cast_to_hir) = expr.kind {
+ if is_hir_ty_cfg_dependant(cx, cast_to_hir) {
+ return;
+ }
+ let (cast_from, cast_to) = (
+ cx.typeck_results().expr_ty(cast_expr),
+ cx.typeck_results().expr_ty(expr),
+ );
+
+ if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
+ return;
+ }
+ cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, self.msrv);
+ as_ptr_cast_mut::check(cx, expr, cast_expr, cast_to);
+ fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+
+ if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
+ cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+ if cast_from.is_numeric() {
+ cast_possible_wrap::check(cx, expr, cast_from, cast_to);
+ cast_precision_loss::check(cx, expr, cast_from, cast_to);
+ cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
+ cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
+ cast_nan_to_int::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);
+ }
+
+ as_underscore::check(cx, expr, cast_to_hir);
+
+ if meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) {
+ borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
+ }
+ }
+
+ cast_ref_to_mut::check(cx, expr);
+ 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);
+}
--- /dev/null
- use clippy_utils::LimitStack;
+//! calculate cognitive complexity and warn about overly complex functions
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::for_each_expr;
- use rustc_hir::{Body, ExprKind, FnDecl, HirId};
++use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack};
+use core::ops::ControlFlow;
+use rustc_ast::ast::Attribute;
+use rustc_hir::intravisit::FnKind;
- body: &'tcx Body<'_>,
++use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods with high cognitive complexity.
+ ///
+ /// ### Why is this bad?
+ /// Methods of high cognitive complexity tend to be hard to
+ /// both read and maintain. Also LLVM will tend to optimize small methods better.
+ ///
+ /// ### Known problems
+ /// Sometimes it's hard to find a way to reduce the
+ /// complexity.
+ ///
+ /// ### Example
+ /// You'll see it when you get the warning.
+ #[clippy::version = "1.35.0"]
+ pub COGNITIVE_COMPLEXITY,
+ nursery,
+ "functions that should be split up into multiple functions"
+}
+
+pub struct CognitiveComplexity {
+ limit: LimitStack,
+}
+
+impl CognitiveComplexity {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self {
+ limit: LimitStack::new(limit),
+ }
+ }
+}
+
+impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
+
+impl CognitiveComplexity {
+ #[expect(clippy::cast_possible_truncation)]
+ fn check<'tcx>(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
- let expr = body.value;
-
++ expr: &'tcx Expr<'_>,
+ body_span: Span,
+ ) {
+ if body_span.from_expansion() {
+ return;
+ }
+
- self.check(cx, kind, decl, body, span);
+ let mut cc = 1u64;
+ let mut returns = 0u64;
+ let _: Option<!> = for_each_expr(expr, |e| {
+ match e.kind {
+ ExprKind::If(_, _, _) => {
+ cc += 1;
+ },
+ ExprKind::Match(_, arms, _) => {
+ if arms.len() > 1 {
+ cc += 1;
+ }
+ cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
+ },
+ ExprKind::Ret(_) => returns += 1,
+ _ => {},
+ }
+ ControlFlow::Continue(())
+ });
+
+ let ret_ty = cx.typeck_results().node_type(expr.hir_id);
+ let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
+ returns
+ } else {
+ #[expect(clippy::integer_division)]
+ (returns / 2)
+ };
+
+ // prevent degenerate cases where unreachable code contains `return` statements
+ if cc >= ret_adjust {
+ cc -= ret_adjust;
+ }
+
+ if cc > self.limit.limit() {
+ let fn_span = match kind {
+ FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
+ FnKind::Closure => {
+ let header_span = body_span.with_hi(decl.output.span().lo());
+ let pos = snippet_opt(cx, header_span).and_then(|snip| {
+ let low_offset = snip.find('|')?;
+ let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?;
+ let low = header_span.lo() + BytePos(low_offset as u32);
+ let high = low + BytePos(high_offset as u32 + 1);
+
+ Some((low, high))
+ });
+
+ if let Some((low, high)) = pos {
+ Span::new(low, high, header_span.ctxt(), header_span.parent())
+ } else {
+ return;
+ }
+ },
+ };
+
+ span_lint_and_help(
+ cx,
+ COGNITIVE_COMPLEXITY,
+ fn_span,
+ &format!(
+ "the function has a cognitive complexity of ({cc}/{})",
+ self.limit.limit()
+ ),
+ None,
+ "you could split it up into multiple smaller functions",
+ );
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) {
++ let expr = if is_async_fn(kind) {
++ match get_async_fn_body(cx.tcx, body) {
++ Some(b) => b,
++ None => {
++ return;
++ },
++ }
++ } else {
++ body.value
++ };
++
++ self.check(cx, kind, decl, expr, span);
+ }
+ }
+
+ fn enter_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+ fn exit_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+}
--- /dev/null
--- /dev/null
++// This file was generated by `cargo dev update_lints`.
++// Use that command to update this file and do not edit by hand.
++// Manual edits will be overwritten.
++
++pub(crate) static LINTS: &[&crate::LintInfo] = &[
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::clippy_lints_internal::CLIPPY_LINTS_INTERNAL_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::if_chain_style::IF_CHAIN_STYLE_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
++ #[cfg(feature = "internal")]
++ crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
++ crate::almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE_INFO,
++ crate::approx_const::APPROX_CONSTANT_INFO,
++ crate::as_conversions::AS_CONVERSIONS_INFO,
++ crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO,
++ crate::asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX_INFO,
++ crate::assertions_on_constants::ASSERTIONS_ON_CONSTANTS_INFO,
++ crate::assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES_INFO,
++ crate::async_yields_async::ASYNC_YIELDS_ASYNC_INFO,
++ crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO,
++ crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO,
++ crate::attrs::DEPRECATED_CFG_ATTR_INFO,
++ crate::attrs::DEPRECATED_SEMVER_INFO,
++ crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
++ crate::attrs::INLINE_ALWAYS_INFO,
++ crate::attrs::MISMATCHED_TARGET_OS_INFO,
++ crate::attrs::USELESS_ATTRIBUTE_INFO,
++ crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
++ crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
++ crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO,
++ crate::blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS_INFO,
++ crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO,
++ crate::bool_to_int_with_if::BOOL_TO_INT_WITH_IF_INFO,
++ crate::booleans::NONMINIMAL_BOOL_INFO,
++ crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO,
++ crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
++ crate::box_default::BOX_DEFAULT_INFO,
++ crate::cargo::CARGO_COMMON_METADATA_INFO,
++ crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
++ crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
++ crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
++ crate::cargo::WILDCARD_DEPENDENCIES_INFO,
++ crate::casts::AS_PTR_CAST_MUT_INFO,
++ crate::casts::AS_UNDERSCORE_INFO,
++ crate::casts::BORROW_AS_PTR_INFO,
++ crate::casts::CAST_ABS_TO_UNSIGNED_INFO,
++ crate::casts::CAST_ENUM_CONSTRUCTOR_INFO,
++ crate::casts::CAST_ENUM_TRUNCATION_INFO,
++ crate::casts::CAST_LOSSLESS_INFO,
++ crate::casts::CAST_NAN_TO_INT_INFO,
++ crate::casts::CAST_POSSIBLE_TRUNCATION_INFO,
++ crate::casts::CAST_POSSIBLE_WRAP_INFO,
++ crate::casts::CAST_PRECISION_LOSS_INFO,
++ crate::casts::CAST_PTR_ALIGNMENT_INFO,
++ crate::casts::CAST_REF_TO_MUT_INFO,
++ crate::casts::CAST_SIGN_LOSS_INFO,
++ crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
++ crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,
++ crate::casts::CHAR_LIT_AS_U8_INFO,
++ crate::casts::FN_TO_NUMERIC_CAST_INFO,
++ crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
++ crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
++ crate::casts::PTR_AS_PTR_INFO,
++ crate::casts::UNNECESSARY_CAST_INFO,
++ crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
++ crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
++ crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
++ crate::collapsible_if::COLLAPSIBLE_IF_INFO,
++ crate::comparison_chain::COMPARISON_CHAIN_INFO,
++ crate::copies::BRANCHES_SHARING_CODE_INFO,
++ crate::copies::IFS_SAME_COND_INFO,
++ crate::copies::IF_SAME_THEN_ELSE_INFO,
++ crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO,
++ crate::copy_iterator::COPY_ITERATOR_INFO,
++ crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO,
++ crate::create_dir::CREATE_DIR_INFO,
++ crate::dbg_macro::DBG_MACRO_INFO,
++ crate::default::DEFAULT_TRAIT_ACCESS_INFO,
++ crate::default::FIELD_REASSIGN_WITH_DEFAULT_INFO,
++ crate::default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY_INFO,
++ crate::default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK_INFO,
++ crate::default_union_representation::DEFAULT_UNION_REPRESENTATION_INFO,
++ crate::dereference::EXPLICIT_AUTO_DEREF_INFO,
++ crate::dereference::EXPLICIT_DEREF_METHODS_INFO,
++ crate::dereference::NEEDLESS_BORROW_INFO,
++ crate::dereference::REF_BINDING_TO_REFERENCE_INFO,
++ crate::derivable_impls::DERIVABLE_IMPLS_INFO,
++ crate::derive::DERIVE_HASH_XOR_EQ_INFO,
++ crate::derive::DERIVE_ORD_XOR_PARTIAL_ORD_INFO,
++ crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO,
++ crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO,
++ crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO,
++ crate::disallowed_macros::DISALLOWED_MACROS_INFO,
++ crate::disallowed_methods::DISALLOWED_METHODS_INFO,
++ crate::disallowed_names::DISALLOWED_NAMES_INFO,
++ crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
++ crate::disallowed_types::DISALLOWED_TYPES_INFO,
++ crate::doc::DOC_LINK_WITH_QUOTES_INFO,
++ crate::doc::DOC_MARKDOWN_INFO,
++ crate::doc::MISSING_ERRORS_DOC_INFO,
++ crate::doc::MISSING_PANICS_DOC_INFO,
++ crate::doc::MISSING_SAFETY_DOC_INFO,
++ crate::doc::NEEDLESS_DOCTEST_MAIN_INFO,
++ crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
++ crate::double_parens::DOUBLE_PARENS_INFO,
++ crate::drop_forget_ref::DROP_COPY_INFO,
++ crate::drop_forget_ref::DROP_NON_DROP_INFO,
++ crate::drop_forget_ref::DROP_REF_INFO,
++ crate::drop_forget_ref::FORGET_COPY_INFO,
++ crate::drop_forget_ref::FORGET_NON_DROP_INFO,
++ crate::drop_forget_ref::FORGET_REF_INFO,
++ crate::drop_forget_ref::UNDROPPED_MANUALLY_DROPS_INFO,
++ crate::duplicate_mod::DUPLICATE_MOD_INFO,
++ crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
++ crate::empty_drop::EMPTY_DROP_INFO,
++ crate::empty_enum::EMPTY_ENUM_INFO,
++ crate::empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO,
++ crate::entry::MAP_ENTRY_INFO,
++ crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO,
++ crate::enum_variants::ENUM_VARIANT_NAMES_INFO,
++ crate::enum_variants::MODULE_INCEPTION_INFO,
++ crate::enum_variants::MODULE_NAME_REPETITIONS_INFO,
++ crate::equatable_if_let::EQUATABLE_IF_LET_INFO,
++ crate::escape::BOXED_LOCAL_INFO,
++ crate::eta_reduction::REDUNDANT_CLOSURE_INFO,
++ crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO,
++ crate::excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS_INFO,
++ crate::excessive_bools::STRUCT_EXCESSIVE_BOOLS_INFO,
++ crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO,
++ crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO,
++ crate::exit::EXIT_INFO,
++ crate::explicit_write::EXPLICIT_WRITE_INFO,
++ crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO,
++ crate::float_literal::EXCESSIVE_PRECISION_INFO,
++ crate::float_literal::LOSSY_FLOAT_LITERAL_INFO,
++ crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO,
++ crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
++ crate::format::USELESS_FORMAT_INFO,
++ crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
++ crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO,
++ crate::format_args::UNINLINED_FORMAT_ARGS_INFO,
++ crate::format_args::UNUSED_FORMAT_SPECS_INFO,
++ crate::format_impl::PRINT_IN_FORMAT_IMPL_INFO,
++ crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO,
++ crate::format_push_string::FORMAT_PUSH_STRING_INFO,
++ crate::formatting::POSSIBLE_MISSING_COMMA_INFO,
++ crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO,
++ crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO,
++ crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO,
++ crate::from_over_into::FROM_OVER_INTO_INFO,
++ crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO,
++ crate::from_str_radix_10::FROM_STR_RADIX_10_INFO,
++ crate::functions::DOUBLE_MUST_USE_INFO,
++ crate::functions::MUST_USE_CANDIDATE_INFO,
++ crate::functions::MUST_USE_UNIT_INFO,
++ crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO,
++ crate::functions::RESULT_LARGE_ERR_INFO,
++ crate::functions::RESULT_UNIT_ERR_INFO,
++ crate::functions::TOO_MANY_ARGUMENTS_INFO,
++ crate::functions::TOO_MANY_LINES_INFO,
++ crate::future_not_send::FUTURE_NOT_SEND_INFO,
++ crate::if_let_mutex::IF_LET_MUTEX_INFO,
++ crate::if_not_else::IF_NOT_ELSE_INFO,
++ crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO,
++ crate::implicit_hasher::IMPLICIT_HASHER_INFO,
++ crate::implicit_return::IMPLICIT_RETURN_INFO,
++ crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
++ crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
++ crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
++ crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
++ crate::indexing_slicing::INDEXING_SLICING_INFO,
++ crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO,
++ crate::infinite_iter::INFINITE_ITER_INFO,
++ crate::infinite_iter::MAYBE_INFINITE_ITER_INFO,
++ crate::inherent_impl::MULTIPLE_INHERENT_IMPL_INFO,
++ crate::inherent_to_string::INHERENT_TO_STRING_INFO,
++ crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO,
++ crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
++ crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
++ crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO,
++ crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
++ crate::int_plus_one::INT_PLUS_ONE_INFO,
++ crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
++ crate::invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED_INFO,
++ crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
++ crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
++ crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
++ crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
++ crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
++ crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
++ crate::len_zero::COMPARISON_TO_EMPTY_INFO,
++ crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
++ crate::len_zero::LEN_ZERO_INFO,
++ crate::let_if_seq::USELESS_LET_IF_SEQ_INFO,
++ crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO,
++ crate::let_underscore::LET_UNDERSCORE_LOCK_INFO,
++ crate::let_underscore::LET_UNDERSCORE_MUST_USE_INFO,
++ crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
++ crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
++ crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
++ crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
++ crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
++ crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
++ crate::literal_representation::UNREADABLE_LITERAL_INFO,
++ crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
++ crate::loops::EMPTY_LOOP_INFO,
++ crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
++ crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
++ crate::loops::EXPLICIT_ITER_LOOP_INFO,
++ crate::loops::FOR_KV_MAP_INFO,
++ crate::loops::ITER_NEXT_LOOP_INFO,
++ crate::loops::MANUAL_FIND_INFO,
++ crate::loops::MANUAL_FLATTEN_INFO,
++ crate::loops::MANUAL_MEMCPY_INFO,
++ crate::loops::MISSING_SPIN_LOOP_INFO,
++ crate::loops::MUT_RANGE_BOUND_INFO,
++ crate::loops::NEEDLESS_RANGE_LOOP_INFO,
++ crate::loops::NEVER_LOOP_INFO,
++ crate::loops::SAME_ITEM_PUSH_INFO,
++ crate::loops::SINGLE_ELEMENT_LOOP_INFO,
++ crate::loops::WHILE_IMMUTABLE_CONDITION_INFO,
++ crate::loops::WHILE_LET_LOOP_INFO,
++ crate::loops::WHILE_LET_ON_ITERATOR_INFO,
++ crate::macro_use::MACRO_USE_IMPORTS_INFO,
++ crate::main_recursion::MAIN_RECURSION_INFO,
++ crate::manual_assert::MANUAL_ASSERT_INFO,
++ crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
++ crate::manual_bits::MANUAL_BITS_INFO,
++ crate::manual_clamp::MANUAL_CLAMP_INFO,
++ crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
++ crate::manual_let_else::MANUAL_LET_ELSE_INFO,
++ crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
++ crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
++ crate::manual_retain::MANUAL_RETAIN_INFO,
++ crate::manual_string_new::MANUAL_STRING_NEW_INFO,
++ crate::manual_strip::MANUAL_STRIP_INFO,
++ crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
++ crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
++ crate::match_result_ok::MATCH_RESULT_OK_INFO,
++ crate::matches::COLLAPSIBLE_MATCH_INFO,
++ crate::matches::INFALLIBLE_DESTRUCTURING_MATCH_INFO,
++ crate::matches::MANUAL_FILTER_INFO,
++ crate::matches::MANUAL_MAP_INFO,
++ crate::matches::MANUAL_UNWRAP_OR_INFO,
++ crate::matches::MATCH_AS_REF_INFO,
++ crate::matches::MATCH_BOOL_INFO,
++ crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO,
++ crate::matches::MATCH_ON_VEC_ITEMS_INFO,
++ crate::matches::MATCH_OVERLAPPING_ARM_INFO,
++ crate::matches::MATCH_REF_PATS_INFO,
++ crate::matches::MATCH_SAME_ARMS_INFO,
++ crate::matches::MATCH_SINGLE_BINDING_INFO,
++ crate::matches::MATCH_STR_CASE_MISMATCH_INFO,
++ crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO,
++ crate::matches::MATCH_WILD_ERR_ARM_INFO,
++ crate::matches::NEEDLESS_MATCH_INFO,
++ crate::matches::REDUNDANT_PATTERN_MATCHING_INFO,
++ crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO,
++ crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO,
++ crate::matches::SINGLE_MATCH_INFO,
++ crate::matches::SINGLE_MATCH_ELSE_INFO,
++ crate::matches::TRY_ERR_INFO,
++ crate::matches::WILDCARD_ENUM_MATCH_ARM_INFO,
++ crate::matches::WILDCARD_IN_OR_PATTERNS_INFO,
++ crate::mem_forget::MEM_FORGET_INFO,
++ crate::mem_replace::MEM_REPLACE_OPTION_WITH_NONE_INFO,
++ crate::mem_replace::MEM_REPLACE_WITH_DEFAULT_INFO,
++ crate::mem_replace::MEM_REPLACE_WITH_UNINIT_INFO,
++ crate::methods::BIND_INSTEAD_OF_MAP_INFO,
++ crate::methods::BYTES_COUNT_TO_LEN_INFO,
++ crate::methods::BYTES_NTH_INFO,
++ crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
++ crate::methods::CHARS_LAST_CMP_INFO,
++ crate::methods::CHARS_NEXT_CMP_INFO,
++ crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
++ crate::methods::CLONE_DOUBLE_REF_INFO,
++ crate::methods::CLONE_ON_COPY_INFO,
++ crate::methods::CLONE_ON_REF_PTR_INFO,
++ crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
++ crate::methods::ERR_EXPECT_INFO,
++ crate::methods::EXPECT_FUN_CALL_INFO,
++ crate::methods::EXPECT_USED_INFO,
++ crate::methods::EXTEND_WITH_DRAIN_INFO,
++ crate::methods::FILETYPE_IS_FILE_INFO,
++ crate::methods::FILTER_MAP_IDENTITY_INFO,
++ crate::methods::FILTER_MAP_NEXT_INFO,
++ crate::methods::FILTER_NEXT_INFO,
++ crate::methods::FLAT_MAP_IDENTITY_INFO,
++ crate::methods::FLAT_MAP_OPTION_INFO,
++ crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO,
++ crate::methods::GET_FIRST_INFO,
++ crate::methods::GET_LAST_WITH_LEN_INFO,
++ crate::methods::GET_UNWRAP_INFO,
++ crate::methods::IMPLICIT_CLONE_INFO,
++ crate::methods::INEFFICIENT_TO_STRING_INFO,
++ crate::methods::INSPECT_FOR_EACH_INFO,
++ crate::methods::INTO_ITER_ON_REF_INFO,
++ crate::methods::IS_DIGIT_ASCII_RADIX_INFO,
++ crate::methods::ITERATOR_STEP_BY_ZERO_INFO,
++ crate::methods::ITER_CLONED_COLLECT_INFO,
++ crate::methods::ITER_COUNT_INFO,
++ crate::methods::ITER_KV_MAP_INFO,
++ crate::methods::ITER_NEXT_SLICE_INFO,
++ crate::methods::ITER_NTH_INFO,
++ crate::methods::ITER_NTH_ZERO_INFO,
++ crate::methods::ITER_ON_EMPTY_COLLECTIONS_INFO,
++ crate::methods::ITER_ON_SINGLE_ITEMS_INFO,
++ crate::methods::ITER_OVEREAGER_CLONED_INFO,
++ crate::methods::ITER_SKIP_NEXT_INFO,
++ crate::methods::ITER_WITH_DRAIN_INFO,
++ crate::methods::MANUAL_FILTER_MAP_INFO,
++ crate::methods::MANUAL_FIND_MAP_INFO,
++ crate::methods::MANUAL_OK_OR_INFO,
++ crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO,
++ crate::methods::MANUAL_SPLIT_ONCE_INFO,
++ crate::methods::MANUAL_STR_REPEAT_INFO,
++ crate::methods::MAP_CLONE_INFO,
++ crate::methods::MAP_COLLECT_RESULT_UNIT_INFO,
++ crate::methods::MAP_ERR_IGNORE_INFO,
++ crate::methods::MAP_FLATTEN_INFO,
++ crate::methods::MAP_IDENTITY_INFO,
++ crate::methods::MAP_UNWRAP_OR_INFO,
++ crate::methods::MUT_MUTEX_LOCK_INFO,
++ crate::methods::NAIVE_BYTECOUNT_INFO,
++ crate::methods::NEEDLESS_COLLECT_INFO,
++ crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,
++ crate::methods::NEEDLESS_OPTION_TAKE_INFO,
++ crate::methods::NEEDLESS_SPLITN_INFO,
++ crate::methods::NEW_RET_NO_SELF_INFO,
++ crate::methods::NONSENSICAL_OPEN_OPTIONS_INFO,
++ crate::methods::NO_EFFECT_REPLACE_INFO,
++ crate::methods::OBFUSCATED_IF_ELSE_INFO,
++ crate::methods::OK_EXPECT_INFO,
++ crate::methods::OPTION_AS_REF_DEREF_INFO,
++ crate::methods::OPTION_FILTER_MAP_INFO,
++ crate::methods::OPTION_MAP_OR_NONE_INFO,
++ crate::methods::OR_FUN_CALL_INFO,
++ crate::methods::OR_THEN_UNWRAP_INFO,
++ crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
++ crate::methods::RANGE_ZIP_WITH_LEN_INFO,
++ crate::methods::REPEAT_ONCE_INFO,
++ crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
++ crate::methods::SEARCH_IS_SOME_INFO,
++ crate::methods::SEEK_FROM_CURRENT_INFO,
++ crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO,
++ crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO,
++ crate::methods::SINGLE_CHAR_ADD_STR_INFO,
++ crate::methods::SINGLE_CHAR_PATTERN_INFO,
++ crate::methods::SKIP_WHILE_NEXT_INFO,
++ crate::methods::STABLE_SORT_PRIMITIVE_INFO,
++ crate::methods::STRING_EXTEND_CHARS_INFO,
++ crate::methods::SUSPICIOUS_MAP_INFO,
++ crate::methods::SUSPICIOUS_SPLITN_INFO,
++ crate::methods::SUSPICIOUS_TO_OWNED_INFO,
++ crate::methods::UNINIT_ASSUMED_INIT_INFO,
++ crate::methods::UNIT_HASH_INFO,
++ crate::methods::UNNECESSARY_FILTER_MAP_INFO,
++ crate::methods::UNNECESSARY_FIND_MAP_INFO,
++ crate::methods::UNNECESSARY_FOLD_INFO,
++ crate::methods::UNNECESSARY_JOIN_INFO,
++ crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
++ crate::methods::UNNECESSARY_SORT_BY_INFO,
++ crate::methods::UNNECESSARY_TO_OWNED_INFO,
++ crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,
++ crate::methods::UNWRAP_USED_INFO,
++ crate::methods::USELESS_ASREF_INFO,
++ crate::methods::VEC_RESIZE_TO_ZERO_INFO,
++ crate::methods::VERBOSE_FILE_READS_INFO,
++ crate::methods::WRONG_SELF_CONVENTION_INFO,
++ crate::methods::ZST_OFFSET_INFO,
++ crate::minmax::MIN_MAX_INFO,
++ crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
++ crate::misc::TOPLEVEL_REF_ARG_INFO,
++ crate::misc::USED_UNDERSCORE_BINDING_INFO,
++ crate::misc::ZERO_PTR_INFO,
++ crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
++ crate::misc_early::DOUBLE_NEG_INFO,
++ crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
++ crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO,
++ crate::misc_early::REDUNDANT_PATTERN_INFO,
++ crate::misc_early::SEPARATED_LITERAL_SUFFIX_INFO,
++ crate::misc_early::UNNEEDED_FIELD_PATTERN_INFO,
++ crate::misc_early::UNNEEDED_WILDCARD_PATTERN_INFO,
++ crate::misc_early::UNSEPARATED_LITERAL_SUFFIX_INFO,
++ crate::misc_early::ZERO_PREFIXED_LITERAL_INFO,
++ crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO,
++ crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO,
++ crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO,
++ crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
++ crate::missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS_INFO,
++ crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO,
++ crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO,
++ crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_INFO,
++ crate::module_style::MOD_MODULE_FILES_INFO,
++ crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
++ crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
++ crate::mut_key::MUTABLE_KEY_TYPE_INFO,
++ crate::mut_mut::MUT_MUT_INFO,
++ crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO,
++ crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO,
++ crate::mutex_atomic::MUTEX_ATOMIC_INFO,
++ crate::mutex_atomic::MUTEX_INTEGER_INFO,
++ crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO,
++ crate::needless_bool::BOOL_COMPARISON_INFO,
++ crate::needless_bool::NEEDLESS_BOOL_INFO,
++ crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO,
++ crate::needless_continue::NEEDLESS_CONTINUE_INFO,
++ crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
++ crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
++ crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
++ crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,
++ crate::needless_question_mark::NEEDLESS_QUESTION_MARK_INFO,
++ crate::needless_update::NEEDLESS_UPDATE_INFO,
++ crate::neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD_INFO,
++ crate::neg_multiply::NEG_MULTIPLY_INFO,
++ crate::new_without_default::NEW_WITHOUT_DEFAULT_INFO,
++ crate::no_effect::NO_EFFECT_INFO,
++ crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO,
++ crate::no_effect::UNNECESSARY_OPERATION_INFO,
++ crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO,
++ crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO,
++ crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO,
++ crate::non_expressive_names::MANY_SINGLE_CHAR_NAMES_INFO,
++ crate::non_expressive_names::SIMILAR_NAMES_INFO,
++ crate::non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS_INFO,
++ crate::non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY_INFO,
++ crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO,
++ crate::octal_escapes::OCTAL_ESCAPES_INFO,
++ crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO,
++ crate::operators::ABSURD_EXTREME_COMPARISONS_INFO,
++ crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO,
++ crate::operators::ASSIGN_OP_PATTERN_INFO,
++ crate::operators::BAD_BIT_MASK_INFO,
++ crate::operators::CMP_NAN_INFO,
++ crate::operators::CMP_OWNED_INFO,
++ crate::operators::DOUBLE_COMPARISONS_INFO,
++ crate::operators::DURATION_SUBSEC_INFO,
++ crate::operators::EQ_OP_INFO,
++ crate::operators::ERASING_OP_INFO,
++ crate::operators::FLOAT_ARITHMETIC_INFO,
++ crate::operators::FLOAT_CMP_INFO,
++ crate::operators::FLOAT_CMP_CONST_INFO,
++ crate::operators::FLOAT_EQUALITY_WITHOUT_ABS_INFO,
++ crate::operators::IDENTITY_OP_INFO,
++ crate::operators::INEFFECTIVE_BIT_MASK_INFO,
++ crate::operators::INTEGER_ARITHMETIC_INFO,
++ crate::operators::INTEGER_DIVISION_INFO,
++ crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
++ crate::operators::MODULO_ARITHMETIC_INFO,
++ crate::operators::MODULO_ONE_INFO,
++ crate::operators::NEEDLESS_BITWISE_BOOL_INFO,
++ crate::operators::OP_REF_INFO,
++ crate::operators::PTR_EQ_INFO,
++ crate::operators::SELF_ASSIGNMENT_INFO,
++ crate::operators::VERBOSE_BIT_MASK_INFO,
++ crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO,
++ crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO,
++ crate::overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL_INFO,
++ crate::panic_in_result_fn::PANIC_IN_RESULT_FN_INFO,
++ crate::panic_unimplemented::PANIC_INFO,
++ crate::panic_unimplemented::TODO_INFO,
++ crate::panic_unimplemented::UNIMPLEMENTED_INFO,
++ crate::panic_unimplemented::UNREACHABLE_INFO,
++ crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO,
++ crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_INFO,
++ crate::partialeq_to_none::PARTIALEQ_TO_NONE_INFO,
++ crate::pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE_INFO,
++ crate::pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF_INFO,
++ crate::pattern_type_mismatch::PATTERN_TYPE_MISMATCH_INFO,
++ crate::precedence::PRECEDENCE_INFO,
++ crate::ptr::CMP_NULL_INFO,
++ crate::ptr::INVALID_NULL_PTR_USAGE_INFO,
++ crate::ptr::MUT_FROM_REF_INFO,
++ crate::ptr::PTR_ARG_INFO,
++ crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO,
++ crate::pub_use::PUB_USE_INFO,
++ crate::question_mark::QUESTION_MARK_INFO,
++ crate::ranges::MANUAL_RANGE_CONTAINS_INFO,
++ crate::ranges::RANGE_MINUS_ONE_INFO,
++ crate::ranges::RANGE_PLUS_ONE_INFO,
++ crate::ranges::REVERSED_EMPTY_RANGES_INFO,
++ crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO,
++ crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO,
++ crate::redundant_clone::REDUNDANT_CLONE_INFO,
++ crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO,
++ crate::redundant_else::REDUNDANT_ELSE_INFO,
++ crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO,
++ crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO,
++ crate::redundant_slicing::DEREF_BY_SLICING_INFO,
++ crate::redundant_slicing::REDUNDANT_SLICING_INFO,
++ crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
++ crate::ref_option_ref::REF_OPTION_REF_INFO,
++ crate::reference::DEREF_ADDROF_INFO,
++ crate::regex::INVALID_REGEX_INFO,
++ crate::regex::TRIVIAL_REGEX_INFO,
++ crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO,
++ crate::returns::LET_AND_RETURN_INFO,
++ crate::returns::NEEDLESS_RETURN_INFO,
++ crate::same_name_method::SAME_NAME_METHOD_INFO,
++ crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO,
++ crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO,
++ crate::serde_api::SERDE_API_MISUSE_INFO,
++ crate::shadow::SHADOW_REUSE_INFO,
++ crate::shadow::SHADOW_SAME_INFO,
++ crate::shadow::SHADOW_UNRELATED_INFO,
++ crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
++ crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
++ crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
++ crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,
++ crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
++ crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,
++ crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
++ crate::strings::STRING_ADD_INFO,
++ crate::strings::STRING_ADD_ASSIGN_INFO,
++ crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO,
++ crate::strings::STRING_LIT_AS_BYTES_INFO,
++ crate::strings::STRING_SLICE_INFO,
++ crate::strings::STRING_TO_STRING_INFO,
++ crate::strings::STR_TO_STRING_INFO,
++ crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
++ crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
++ crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
++ crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
++ crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,
++ crate::suspicious_xor_used_as_pow::SUSPICIOUS_XOR_USED_AS_POW_INFO,
++ crate::swap::ALMOST_SWAPPED_INFO,
++ crate::swap::MANUAL_SWAP_INFO,
++ crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO,
++ crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
++ crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
++ crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
++ crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
++ crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
++ crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
++ crate::transmute::CROSSPOINTER_TRANSMUTE_INFO,
++ crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO,
++ crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO,
++ crate::transmute::TRANSMUTE_FLOAT_TO_INT_INFO,
++ crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO,
++ crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO,
++ crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO,
++ crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO,
++ crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO,
++ crate::transmute::TRANSMUTE_PTR_TO_REF_INFO,
++ crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO,
++ crate::transmute::TRANSMUTING_NULL_INFO,
++ crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO,
++ crate::transmute::USELESS_TRANSMUTE_INFO,
++ crate::transmute::WRONG_TRANSMUTE_INFO,
++ crate::types::BORROWED_BOX_INFO,
++ crate::types::BOX_COLLECTION_INFO,
++ crate::types::LINKEDLIST_INFO,
++ crate::types::OPTION_OPTION_INFO,
++ crate::types::RC_BUFFER_INFO,
++ crate::types::RC_MUTEX_INFO,
++ crate::types::REDUNDANT_ALLOCATION_INFO,
++ crate::types::TYPE_COMPLEXITY_INFO,
++ crate::types::VEC_BOX_INFO,
++ crate::undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS_INFO,
++ crate::unicode::INVISIBLE_CHARACTERS_INFO,
++ crate::unicode::NON_ASCII_LITERAL_INFO,
++ crate::unicode::UNICODE_NOT_NFC_INFO,
++ crate::uninit_vec::UNINIT_VEC_INFO,
++ crate::unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD_INFO,
++ crate::unit_types::LET_UNIT_VALUE_INFO,
++ crate::unit_types::UNIT_ARG_INFO,
++ crate::unit_types::UNIT_CMP_INFO,
++ crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
++ crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
++ crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
++ crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
++ crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO,
++ crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO,
++ crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO,
++ crate::unused_async::UNUSED_ASYNC_INFO,
++ crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO,
++ crate::unused_peekable::UNUSED_PEEKABLE_INFO,
++ crate::unused_rounding::UNUSED_ROUNDING_INFO,
++ crate::unused_self::UNUSED_SELF_INFO,
++ crate::unused_unit::UNUSED_UNIT_INFO,
++ crate::unwrap::PANICKING_UNWRAP_INFO,
++ crate::unwrap::UNNECESSARY_UNWRAP_INFO,
++ crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
++ crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
++ crate::use_self::USE_SELF_INFO,
++ crate::useless_conversion::USELESS_CONVERSION_INFO,
++ crate::vec::USELESS_VEC_INFO,
++ crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,
++ crate::wildcard_imports::ENUM_GLOB_USE_INFO,
++ crate::wildcard_imports::WILDCARD_IMPORTS_INFO,
++ crate::write::PRINTLN_EMPTY_STRING_INFO,
++ crate::write::PRINT_LITERAL_INFO,
++ crate::write::PRINT_STDERR_INFO,
++ crate::write::PRINT_STDOUT_INFO,
++ crate::write::PRINT_WITH_NEWLINE_INFO,
++ crate::write::USE_DEBUG_INFO,
++ crate::write::WRITELN_EMPTY_STRING_INFO,
++ crate::write::WRITE_LITERAL_INFO,
++ crate::write::WRITE_WITH_NEWLINE_INFO,
++ crate::zero_div_zero::ZERO_DIVIDED_BY_ZERO_INFO,
++ crate::zero_sized_map_values::ZERO_SIZED_MAP_VALUES_INFO,
++];
--- /dev/null
- let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) {
- x
- } else {
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::sugg::has_enclosing_paren;
+use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
+use clippy_utils::{
+ fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, meets_msrv, msrvs, path_to_local,
+ walk_to_expr_usage,
+};
+use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
+use rustc_data_structures::fx::FxIndexMap;
++use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_ty, Visitor};
+use rustc_hir::{
+ self as hir,
+ def_id::{DefId, LocalDefId},
+ BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem,
+ ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
+ TraitItemKind, TyKind, UnOp,
+};
+use rustc_index::bit_set::BitSet;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::{Rvalue, StatementKind};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
+use rustc_middle::ty::{
+ self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
+ ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults,
+};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::sym, Span, Symbol};
+use rustc_trait_selection::infer::InferCtxtExt as _;
+use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
+use std::collections::VecDeque;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `deref()` or `deref_mut()` method calls.
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing by `&*x` or `&mut *x` is clearer and more concise,
+ /// when not part of a method chain.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ops::Deref;
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b: &str = a.deref();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b = &*a;
+ /// ```
+ ///
+ /// This lint excludes:
+ /// ```rust,ignore
+ /// let _ = d.unwrap().deref();
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub EXPLICIT_DEREF_METHODS,
+ pedantic,
+ "Explicit use of deref or deref_mut method while not in a method chain."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for address of operations (`&`) that are going to
+ /// be dereferenced immediately by the compiler.
+ ///
+ /// ### Why is this bad?
+ /// Suggests that the receiver of the expression borrows
+ /// the expression.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn fun(_a: &i32) {}
+ ///
+ /// let x: &i32 = &&&&&&5;
+ /// fun(&x);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # fn fun(_a: &i32) {}
+ /// let x: &i32 = &5;
+ /// fun(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BORROW,
+ style,
+ "taking a reference that is going to be automatically dereferenced"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `ref` bindings which create a reference to a reference.
+ ///
+ /// ### Why is this bad?
+ /// The address-of operator at the use site is clearer about the need for a reference.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some("");
+ /// if let Some(ref x) = x {
+ /// // use `x` here
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = Some("");
+ /// if let Some(x) = x {
+ /// // use `&x` here
+ /// }
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub REF_BINDING_TO_REFERENCE,
+ pedantic,
+ "`ref` binding to a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for dereferencing expressions which would be covered by auto-deref.
+ ///
+ /// ### Why is this bad?
+ /// This unnecessarily complicates the code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = String::new();
+ /// let y: &str = &*x;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = String::new();
+ /// let y: &str = &x;
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub EXPLICIT_AUTO_DEREF,
+ complexity,
+ "dereferencing when the compiler would automatically dereference"
+}
+
+impl_lint_pass!(Dereferencing<'_> => [
+ EXPLICIT_DEREF_METHODS,
+ NEEDLESS_BORROW,
+ REF_BINDING_TO_REFERENCE,
+ EXPLICIT_AUTO_DEREF,
+]);
+
+#[derive(Default)]
+pub struct Dereferencing<'tcx> {
+ state: Option<(State, StateData)>,
+
+ // While parsing a `deref` method call in ufcs form, the path to the function is itself an
+ // expression. This is to store the id of that expression so it can be skipped when
+ // `check_expr` is called for it.
+ skip_expr: Option<HirId>,
+
+ /// The body the first local was found in. Used to emit lints when the traversal of the body has
+ /// been finished. Note we can't lint at the end of every body as they can be nested within each
+ /// other.
+ current_body: Option<BodyId>,
+
+ /// The list of locals currently being checked by the lint.
+ /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted.
+ /// This is needed for or patterns where one of the branches can be linted, but another can not
+ /// be.
+ ///
+ /// e.g. `m!(x) | Foo::Bar(ref x)`
+ ref_locals: FxIndexMap<HirId, Option<RefPat>>,
+
+ /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
+ /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
+ /// be moved.
+ possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+
+ // `IntoIterator` for arrays requires Rust 1.53.
+ msrv: Option<RustcVersion>,
+}
+
+impl<'tcx> Dereferencing<'tcx> {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Dereferencing::default()
+ }
+ }
+}
+
+#[derive(Debug)]
+struct StateData {
+ /// Span of the top level expression
+ span: Span,
+ hir_id: HirId,
+ position: Position,
+}
+
+#[derive(Debug)]
+struct DerefedBorrow {
+ count: usize,
+ msg: &'static str,
+ snip_expr: Option<HirId>,
+}
+
+#[derive(Debug)]
+enum State {
+ // Any number of deref method calls.
+ DerefMethod {
+ // The number of calls in a sequence which changed the referenced type
+ ty_changed_count: usize,
+ is_final_ufcs: bool,
+ /// The required mutability
+ target_mut: Mutability,
+ },
+ DerefedBorrow(DerefedBorrow),
+ ExplicitDeref {
+ mutability: Option<Mutability>,
+ },
+ ExplicitDerefField {
+ name: Symbol,
+ },
+ Reborrow {
+ mutability: Mutability,
+ },
+ Borrow {
+ mutability: Mutability,
+ },
+}
+
+// A reference operation considered by this lint pass
+enum RefOp {
+ Method(Mutability),
+ Deref,
+ AddrOf(Mutability),
+}
+
+struct RefPat {
+ /// Whether every usage of the binding is dereferenced.
+ always_deref: bool,
+ /// The spans of all the ref bindings for this local.
+ spans: Vec<Span>,
+ /// The applicability of this suggestion.
+ app: Applicability,
+ /// All the replacements which need to be made.
+ replacements: Vec<(Span, String)>,
+ /// The [`HirId`] that the lint should be emitted at.
+ hir_id: HirId,
+}
+
+impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
+ #[expect(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Skip path expressions from deref calls. e.g. `Deref::deref(e)`
+ if Some(expr.hir_id) == self.skip_expr.take() {
+ return;
+ }
+
+ if let Some(local) = path_to_local(expr) {
+ self.check_local_usage(cx, expr, local);
+ }
+
+ // Stop processing sub expressions when a macro call is seen
+ if expr.span.from_expansion() {
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ }
+
+ let typeck = cx.typeck_results();
- sig.input_with_hir(i).map(|(hir_ty, ty)| match hir_ty {
- // Type inference for closures can depend on how they're called. Only go by the explicit
- // types here.
- Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
- None => {
- if let ty::Param(param_ty) = ty.skip_binder().kind() {
- needless_borrow_impl_arg_position(
- cx,
- possible_borrowers,
- parent,
- i,
- *param_ty,
- e,
- precedence,
- msrv,
- )
- } else {
- ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
- .position_for_arg()
- }
- },
++ let Some((kind, sub_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
+ // The whole chain of reference operations has been seen
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ };
+
+ match (self.state.take(), kind) {
+ (None, kind) => {
+ let expr_ty = typeck.expr_ty(expr);
+ let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv);
+ match kind {
+ RefOp::Deref => {
+ if let Position::FieldAccess {
+ name,
+ of_union: false,
+ } = position
+ && !ty_contains_field(typeck.expr_ty(sub_expr), name)
+ {
+ self.state = Some((
+ State::ExplicitDerefField { name },
+ StateData { span: expr.span, hir_id: expr.hir_id, position },
+ ));
+ } else if position.is_deref_stable() {
+ self.state = Some((
+ State::ExplicitDeref { mutability: None },
+ StateData { span: expr.span, hir_id: expr.hir_id, position },
+ ));
+ }
+ }
+ RefOp::Method(target_mut)
+ if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
+ && position.lint_explicit_deref() =>
+ {
+ let ty_changed_count = usize::from(!deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)));
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count,
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ target_mut,
+ },
+ StateData {
+ span: expr.span,
+ hir_id: expr.hir_id,
+ position
+ },
+ ));
+ },
+ RefOp::AddrOf(mutability) => {
+ // Find the number of times the borrow is auto-derefed.
+ let mut iter = adjustments.iter();
+ let mut deref_count = 0usize;
+ let next_adjust = loop {
+ match iter.next() {
+ Some(adjust) => {
+ if !matches!(adjust.kind, Adjust::Deref(_)) {
+ break Some(adjust);
+ } else if !adjust.target.is_ref() {
+ deref_count += 1;
+ break iter.next();
+ }
+ deref_count += 1;
+ },
+ None => break None,
+ };
+ };
+
+ // Determine the required number of references before any can be removed. In all cases the
+ // reference made by the current expression will be removed. After that there are four cases to
+ // handle.
+ //
+ // 1. Auto-borrow will trigger in the current position, so no further references are required.
+ // 2. Auto-deref ends at a reference, or the underlying type, so one extra needs to be left to
+ // handle the automatically inserted re-borrow.
+ // 3. Auto-deref hits a user-defined `Deref` impl, so at least one reference needs to exist to
+ // start auto-deref.
+ // 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow
+ // adjustments will not be inserted automatically, then leave one further reference to avoid
+ // moving a mutable borrow.
+ // e.g.
+ // fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
+ // let x = match x {
+ // // Removing the borrow will cause `x` to be moved
+ // Some(x) => &mut *x,
+ // None => y
+ // };
+ // }
+ let deref_msg =
+ "this expression creates a reference which is immediately dereferenced by the compiler";
+ let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
+ let impl_msg = "the borrowed expression implements the required traits";
+
+ let (required_refs, msg, snip_expr) = if position.can_auto_borrow() {
+ (1, if deref_count == 1 { borrow_msg } else { deref_msg }, None)
+ } else if let Position::ImplArg(hir_id) = position {
+ (0, impl_msg, Some(hir_id))
+ } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
+ next_adjust.map(|a| &a.kind)
+ {
+ if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
+ {
+ (3, deref_msg, None)
+ } else {
+ (2, deref_msg, None)
+ }
+ } else {
+ (2, deref_msg, None)
+ };
+
+ if deref_count >= required_refs {
+ self.state = Some((
+ State::DerefedBorrow(DerefedBorrow {
+ // One of the required refs is for the current borrow expression, the remaining ones
+ // can't be removed without breaking the code. See earlier comment.
+ count: deref_count - required_refs,
+ msg,
+ snip_expr,
+ }),
+ StateData { span: expr.span, hir_id: expr.hir_id, position },
+ ));
+ } else if position.is_deref_stable()
+ // Auto-deref doesn't combine with other adjustments
+ && next_adjust.map_or(true, |a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_)))
+ && iter.all(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_)))
+ {
+ self.state = Some((
+ State::Borrow { mutability },
+ StateData {
+ span: expr.span,
+ hir_id: expr.hir_id,
+ position
+ },
+ ));
+ }
+ },
+ RefOp::Method(..) => (),
+ }
+ },
+ (
+ Some((
+ State::DerefMethod {
+ target_mut,
+ ty_changed_count,
+ ..
+ },
+ data,
+ )),
+ RefOp::Method(_),
+ ) => {
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) {
+ ty_changed_count
+ } else {
+ ty_changed_count + 1
+ },
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ target_mut,
+ },
+ data,
+ ));
+ },
+ (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(_)) if state.count != 0 => {
+ self.state = Some((
+ State::DerefedBorrow(DerefedBorrow {
+ count: state.count - 1,
+ ..state
+ }),
+ data,
+ ));
+ },
+ (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => {
+ let position = data.position;
+ report(cx, expr, State::DerefedBorrow(state), data);
+ if position.is_deref_stable() {
+ self.state = Some((
+ State::Borrow { mutability },
+ StateData {
+ span: expr.span,
+ hir_id: expr.hir_id,
+ position,
+ },
+ ));
+ }
+ },
+ (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => {
+ let position = data.position;
+ report(cx, expr, State::DerefedBorrow(state), data);
+ if let Position::FieldAccess{name, ..} = position
+ && !ty_contains_field(typeck.expr_ty(sub_expr), name)
+ {
+ self.state = Some((
+ State::ExplicitDerefField { name },
+ StateData { span: expr.span, hir_id: expr.hir_id, position },
+ ));
+ } else if position.is_deref_stable() {
+ self.state = Some((
+ State::ExplicitDeref { mutability: None },
+ StateData { span: expr.span, hir_id: expr.hir_id, position },
+ ));
+ }
+ },
+
+ (Some((State::Borrow { mutability }, data)), RefOp::Deref) => {
+ if typeck.expr_ty(sub_expr).is_ref() {
+ self.state = Some((State::Reborrow { mutability }, data));
+ } else {
+ self.state = Some((
+ State::ExplicitDeref {
+ mutability: Some(mutability),
+ },
+ data,
+ ));
+ }
+ },
+ (Some((State::Reborrow { mutability }, data)), RefOp::Deref) => {
+ self.state = Some((
+ State::ExplicitDeref {
+ mutability: Some(mutability),
+ },
+ data,
+ ));
+ },
+ (state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
+ self.state = state;
+ },
+ (Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
+ if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
+ {
+ self.state = Some((State::ExplicitDerefField { name }, data));
+ },
+
+ (Some((state, data)), _) => report(cx, expr, state, data),
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if let PatKind::Binding(BindingAnnotation::REF, id, name, _) = pat.kind {
+ if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) {
+ // This binding id has been seen before. Add this pattern to the list of changes.
+ if let Some(prev_pat) = opt_prev_pat {
+ if pat.span.from_expansion() {
+ // Doesn't match the context of the previous pattern. Can't lint here.
+ *opt_prev_pat = None;
+ } else {
+ prev_pat.spans.push(pat.span);
+ prev_pat.replacements.push((
+ pat.span,
+ snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut prev_pat.app)
+ .0
+ .into(),
+ ));
+ }
+ }
+ return;
+ }
+
+ if_chain! {
+ if !pat.span.from_expansion();
+ if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
+ // only lint immutable refs, because borrowed `&mut T` cannot be moved out
+ if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
+ self.current_body = self.current_body.or(cx.enclosing_body);
+ self.ref_locals.insert(
+ id,
+ Some(RefPat {
+ always_deref: true,
+ spans: vec![pat.span],
+ app,
+ replacements: vec![(pat.span, snip.into())],
+ hir_id: pat.hir_id,
+ }),
+ );
+ }
+ }
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
+ local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
+ }) {
+ self.possible_borrowers.pop();
+ }
+
+ if Some(body.id()) == self.current_body {
+ for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
+ let replacements = pat.replacements;
+ let app = pat.app;
+ let lint = if pat.always_deref {
+ NEEDLESS_BORROW
+ } else {
+ REF_BINDING_TO_REFERENCE
+ };
+ span_lint_hir_and_then(
+ cx,
+ lint,
+ pat.hir_id,
+ pat.spans,
+ "this pattern creates a reference to a reference",
+ |diag| {
+ diag.multipart_suggestion("try this", replacements, app);
+ },
+ );
+ }
+ self.current_body = None;
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn try_parse_ref_op<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ typeck: &'tcx TypeckResults<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(RefOp, &'tcx Expr<'tcx>)> {
+ let (def_id, arg) = match expr.kind {
+ ExprKind::MethodCall(_, arg, [], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(path),
+ hir_id,
+ ..
+ },
+ [arg],
+ ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg),
+ ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => {
+ return Some((RefOp::Deref, sub_expr));
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, sub_expr) => return Some((RefOp::AddrOf(mutability), sub_expr)),
+ _ => return None,
+ };
+ if tcx.is_diagnostic_item(sym::deref_method, def_id) {
+ Some((RefOp::Method(Mutability::Not), arg))
+ } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? {
+ Some((RefOp::Method(Mutability::Mut), arg))
+ } else {
+ None
+ }
+}
+
+// Checks whether the type for a deref call actually changed the type, not just the mutability of
+// the reference.
+fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
+ match (result_ty.kind(), arg_ty.kind()) {
+ (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => result_ty == arg_ty,
+
+ // The result type for a deref method is always a reference
+ // Not matching the previous pattern means the argument type is not a reference
+ // This means that the type did change
+ _ => false,
+ }
+}
+
+/// The position of an expression relative to it's parent.
+#[derive(Clone, Copy, Debug)]
+enum Position {
+ MethodReceiver,
+ /// The method is defined on a reference type. e.g. `impl Foo for &T`
+ MethodReceiverRefImpl,
+ Callee,
+ ImplArg(HirId),
+ FieldAccess {
+ name: Symbol,
+ of_union: bool,
+ }, // union fields cannot be auto borrowed
+ Postfix,
+ Deref,
+ /// Any other location which will trigger auto-deref to a specific time.
+ /// Contains the precedence of the parent expression and whether the target type is sized.
+ DerefStable(i8, bool),
+ /// Any other location which will trigger auto-reborrowing.
+ /// Contains the precedence of the parent expression.
+ ReborrowStable(i8),
+ /// Contains the precedence of the parent expression.
+ Other(i8),
+}
+impl Position {
+ fn is_deref_stable(self) -> bool {
+ matches!(self, Self::DerefStable(..))
+ }
+
+ fn is_reborrow_stable(self) -> bool {
+ matches!(self, Self::DerefStable(..) | Self::ReborrowStable(_))
+ }
+
+ fn can_auto_borrow(self) -> bool {
+ matches!(
+ self,
+ Self::MethodReceiver | Self::FieldAccess { of_union: false, .. } | Self::Callee
+ )
+ }
+
+ fn lint_explicit_deref(self) -> bool {
+ matches!(self, Self::Other(_) | Self::DerefStable(..) | Self::ReborrowStable(_))
+ }
+
+ fn precedence(self) -> i8 {
+ match self {
+ Self::MethodReceiver
+ | Self::MethodReceiverRefImpl
+ | Self::Callee
+ | Self::FieldAccess { .. }
+ | Self::Postfix => PREC_POSTFIX,
+ Self::ImplArg(_) | Self::Deref => PREC_PREFIX,
+ Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p,
+ }
+ }
+}
+
+/// Walks up the parent expressions attempting to determine both how stable the auto-deref result
+/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow
+/// locations as those follow different rules.
+#[expect(clippy::too_many_lines)]
+fn walk_parents<'tcx>(
+ cx: &LateContext<'tcx>,
+ possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+ e: &'tcx Expr<'_>,
+ msrv: Option<RustcVersion>,
+) -> (Position, &'tcx [Adjustment<'tcx>]) {
+ let mut adjustments = [].as_slice();
+ let mut precedence = 0i8;
+ let ctxt = e.span.ctxt();
+ let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| {
+ // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
+ if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) {
+ adjustments = cx.typeck_results().expr_adjustments(e);
+ }
+ match parent {
+ Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
+ Some(binding_ty_auto_deref_stability(cx, ty, precedence, List::empty()))
+ },
+ Node::Item(&Item {
+ kind: ItemKind::Static(..) | ItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => {
+ let ty = cx.tcx.type_of(owner_id.def_id);
+ Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx))
+ },
+
+ Node::Item(&Item {
+ kind: ItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => {
+ let output = cx
+ .tcx
+ .erase_late_bound_regions(cx.tcx.fn_sig(owner_id.to_def_id()).output());
+ Some(ty_auto_deref_stability(cx, output, precedence).position_for_result(cx))
+ },
+
+ Node::ExprField(field) if field.span.ctxt() == ctxt => match get_parent_expr_for_hir(cx, field.hir_id) {
+ Some(Expr {
+ hir_id,
+ kind: ExprKind::Struct(path, ..),
+ ..
+ }) => variant_of_res(cx, cx.qpath_res(path, *hir_id))
+ .and_then(|variant| variant.fields.iter().find(|f| f.name == field.ident.name))
+ .map(|field_def| {
+ ty_auto_deref_stability(cx, cx.tcx.type_of(field_def.did), precedence).position_for_arg()
+ }),
+ _ => None,
+ },
+
+ Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
+ ExprKind::Ret(_) => {
+ let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap());
+ Some(
+ if let Node::Expr(
+ closure_expr @ Expr {
+ kind: ExprKind::Closure(closure),
+ ..
+ },
+ ) = cx.tcx.hir().get(owner_id)
+ {
+ closure_result_position(cx, closure, cx.typeck_results().expr_ty(closure_expr), precedence)
+ } else {
+ let output = cx
+ .tcx
+ .erase_late_bound_regions(cx.tcx.fn_sig(cx.tcx.hir().local_def_id(owner_id)).output());
+ ty_auto_deref_stability(cx, output, precedence).position_for_result(cx)
+ },
+ )
+ },
+ ExprKind::Closure(closure) => Some(closure_result_position(
+ cx,
+ closure,
+ cx.typeck_results().expr_ty(parent),
+ precedence,
+ )),
+ ExprKind::Call(func, _) if func.hir_id == child_id => {
+ (child_id == e.hir_id).then_some(Position::Callee)
+ },
+ ExprKind::Call(func, args) => args
+ .iter()
+ .position(|arg| arg.hir_id == child_id)
+ .zip(expr_sig(cx, func))
+ .and_then(|(i, sig)| {
- ExprKind::MethodCall(_, receiver, args, _) => {
++ sig.input_with_hir(i).map(|(hir_ty, ty)| {
++ match hir_ty {
++ // Type inference for closures can depend on how they're called. Only go by the explicit
++ // types here.
++ Some(hir_ty) => {
++ binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars())
++ },
++ None => {
++ // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739
++ // `!call_is_qualified(func)` for https://github.com/rust-lang/rust-clippy/issues/9782
++ if e.hir_id == child_id
++ && !call_is_qualified(func)
++ && let ty::Param(param_ty) = ty.skip_binder().kind()
++ {
++ needless_borrow_impl_arg_position(
++ cx,
++ possible_borrowers,
++ parent,
++ i,
++ *param_ty,
++ e,
++ precedence,
++ msrv,
++ )
++ } else {
++ ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
++ .position_for_arg()
++ }
++ },
++ }
+ })
+ }),
- if let ty::Param(param_ty) = ty.kind() {
++ ExprKind::MethodCall(method, receiver, args, _) => {
+ let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
+ if receiver.hir_id == child_id {
+ // Check for calls to trait methods where the trait is implemented on a reference.
+ // Two cases need to be handled:
+ // * `self` methods on `&T` will never have auto-borrow
+ // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
+ // priority.
+ if e.hir_id != child_id {
+ return Some(Position::ReborrowStable(precedence))
+ } else if let Some(trait_id) = cx.tcx.trait_of_item(id)
+ && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
+ && let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
+ && let subs = match cx
+ .typeck_results()
+ .node_substs_opt(parent.hir_id)
+ .and_then(|subs| subs.get(1..))
+ {
+ Some(subs) => cx.tcx.mk_substs(subs.iter().copied()),
+ None => cx.tcx.mk_substs(std::iter::empty::<ty::subst::GenericArg<'_>>()),
+ } && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
+ // Trait methods taking `&self`
+ sub_ty
+ } else {
+ // Trait methods taking `self`
+ arg_ty
+ } && impl_ty.is_ref()
+ && let infcx = cx.tcx.infer_ctxt().build()
+ && infcx
+ .type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
+ .must_apply_modulo_regions()
+ {
+ return Some(Position::MethodReceiverRefImpl)
+ }
+ return Some(Position::MethodReceiver);
+ }
+ args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
+ let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
- #[expect(clippy::too_many_arguments)]
++ // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739
++ // `method.args.is_none()` for https://github.com/rust-lang/rust-clippy/issues/9782
++ if e.hir_id == child_id && method.args.is_none() && let ty::Param(param_ty) = ty.kind() {
+ needless_borrow_impl_arg_position(
+ cx,
+ possible_borrowers,
+ parent,
+ i + 1,
+ *param_ty,
+ e,
+ precedence,
+ msrv,
+ )
+ } else {
+ ty_auto_deref_stability(
+ cx,
+ cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i + 1)),
+ precedence,
+ )
+ .position_for_arg()
+ }
+ })
+ },
+ ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess {
+ name: name.name,
+ of_union: is_union(cx.typeck_results(), child),
+ }),
+ ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref),
+ ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+ | ExprKind::Index(child, _)
+ if child.hir_id == e.hir_id =>
+ {
+ Some(Position::Postfix)
+ },
+ _ if child_id == e.hir_id => {
+ precedence = parent.precedence().order();
+ None
+ },
+ _ => None,
+ },
+ _ => None,
+ }
+ })
+ .unwrap_or(Position::Other(precedence));
+ (position, adjustments)
+}
+
+fn is_union<'tcx>(typeck: &'tcx TypeckResults<'_>, path_expr: &'tcx Expr<'_>) -> bool {
+ typeck
+ .expr_ty_adjusted(path_expr)
+ .ty_adt_def()
+ .map_or(false, rustc_middle::ty::AdtDef::is_union)
+}
+
+fn closure_result_position<'tcx>(
+ cx: &LateContext<'tcx>,
+ closure: &'tcx Closure<'_>,
+ ty: Ty<'tcx>,
+ precedence: i8,
+) -> Position {
+ match closure.fn_decl.output {
+ FnRetTy::Return(hir_ty) => {
+ if let Some(sig) = ty_sig(cx, ty)
+ && let Some(output) = sig.output()
+ {
+ binding_ty_auto_deref_stability(cx, hir_ty, precedence, output.bound_vars())
+ } else {
+ Position::Other(precedence)
+ }
+ },
+ FnRetTy::DefaultReturn(_) => Position::Other(precedence),
+ }
+}
+
+// Checks the stability of auto-deref when assigned to a binding with the given explicit type.
+//
+// e.g.
+// let x = Box::new(Box::new(0u32));
+// let y1: &Box<_> = x.deref();
+// let y2: &Box<_> = &x;
+//
+// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
+// switching to auto-dereferencing.
+fn binding_ty_auto_deref_stability<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: &'tcx hir::Ty<'_>,
+ precedence: i8,
+ binder_args: &'tcx List<BoundVariableKind>,
+) -> Position {
+ let TyKind::Rptr(_, ty) = &ty.kind else {
+ return Position::Other(precedence);
+ };
+ let mut ty = ty;
+
+ loop {
+ break match ty.ty.kind {
+ TyKind::Rptr(_, ref ref_ty) => {
+ ty = ref_ty;
+ continue;
+ },
+ TyKind::Path(
+ QPath::TypeRelative(_, path)
+ | QPath::Resolved(
+ _,
+ Path {
+ segments: [.., path], ..
+ },
+ ),
+ ) => {
+ if let Some(args) = path.args
+ && args.args.iter().any(|arg| match arg {
+ GenericArg::Infer(_) => true,
+ GenericArg::Type(ty) => ty_contains_infer(ty),
+ _ => false,
+ })
+ {
+ Position::ReborrowStable(precedence)
+ } else {
+ Position::DerefStable(
+ precedence,
+ cx.tcx
+ .erase_late_bound_regions(Binder::bind_with_vars(
+ cx.typeck_results().node_type(ty.ty.hir_id),
+ binder_args,
+ ))
+ .is_sized(cx.tcx, cx.param_env.without_caller_bounds()),
+ )
+ }
+ },
+ TyKind::Slice(_) => Position::DerefStable(precedence, false),
+ TyKind::Array(..) | TyKind::Ptr(_) | TyKind::BareFn(_) => Position::DerefStable(precedence, true),
+ TyKind::Never
+ | TyKind::Tup(_)
+ | TyKind::Path(_) => Position::DerefStable(
+ precedence,
+ cx.tcx
+ .erase_late_bound_regions(Binder::bind_with_vars(
+ cx.typeck_results().node_type(ty.ty.hir_id),
+ binder_args,
+ ))
+ .is_sized(cx.tcx, cx.param_env.without_caller_bounds()),
+ ),
+ TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err => {
+ Position::ReborrowStable(precedence)
+ },
+ };
+ }
+}
+
+// Checks whether a type is inferred at some point.
+// e.g. `_`, `Box<_>`, `[_]`
+fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
+ struct V(bool);
+ impl Visitor<'_> for V {
+ fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
+ if self.0
+ || matches!(
+ ty.kind,
+ TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err
+ )
+ {
+ self.0 = true;
+ } else {
+ walk_ty(self, ty);
+ }
+ }
+
+ fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) {
+ if self.0 || matches!(arg, GenericArg::Infer(_)) {
+ self.0 = true;
+ } else if let GenericArg::Type(ty) = arg {
+ self.visit_ty(ty);
+ }
+ }
+ }
+ let mut v = V(false);
+ v.visit_ty(ty);
+ v.0
+}
+
++fn call_is_qualified(expr: &Expr<'_>) -> bool {
++ if let ExprKind::Path(path) = &expr.kind {
++ match path {
++ QPath::Resolved(_, path) => path.segments.last().map_or(false, |segment| segment.args.is_some()),
++ QPath::TypeRelative(_, segment) => segment.args.is_some(),
++ QPath::LangItem(..) => false,
++ }
++ } else {
++ false
++ }
++}
++
+// Checks whether:
+// * child is an expression of the form `&e` in an argument position requiring an `impl Trait`
+// * `e`'s type implements `Trait` and is copyable
+// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
+// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
+// be moved, but it cannot be.
- | ty::Projection(_) => Position::DerefStable(
- precedence,
- ty.is_sized(cx.tcx, cx.param_env.without_caller_bounds()),
- )
- .into(),
++#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
+fn needless_borrow_impl_arg_position<'tcx>(
+ cx: &LateContext<'tcx>,
+ possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+ parent: &Expr<'tcx>,
+ arg_index: usize,
+ param_ty: ParamTy,
+ mut expr: &Expr<'tcx>,
+ precedence: i8,
+ msrv: Option<RustcVersion>,
+) -> Position {
+ let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
+ let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
+
+ let Some(callee_def_id) = fn_def_id(cx, parent) else { return Position::Other(precedence) };
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ let substs_with_expr_ty = cx
+ .typeck_results()
+ .node_substs(if let ExprKind::Call(callee, _) = parent.kind {
+ callee.hir_id
+ } else {
+ parent.hir_id
+ });
+
+ let predicates = cx.tcx.param_env(callee_def_id).caller_bounds();
+ let projection_predicates = predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ Some(projection_predicate)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let mut trait_with_ref_mut_self_method = false;
+
+ // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
+ if predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
+ {
+ Some(trait_predicate.trait_ref.def_id)
+ } else {
+ None
+ }
+ })
+ .inspect(|trait_def_id| {
+ trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
+ })
+ .all(|trait_def_id| {
+ Some(trait_def_id) == destruct_trait_def_id
+ || Some(trait_def_id) == sized_trait_def_id
+ || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
+ })
+ {
+ return Position::Other(precedence);
+ }
+
++ // See:
++ // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
++ // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
++ if projection_predicates
++ .iter()
++ .any(|projection_predicate| is_mixed_projection_predicate(cx, callee_def_id, projection_predicate))
++ {
++ return Position::Other(precedence);
++ }
++
+ // `substs_with_referent_ty` can be constructed outside of `check_referent` because the same
+ // elements are modified each time `check_referent` is called.
+ let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
+
+ let mut check_reference_and_referent = |reference, referent| {
+ let referent_ty = cx.typeck_results().expr_ty(referent);
+
+ if !is_copy(cx, referent_ty)
+ && (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
+ || !referent_used_exactly_once(cx, possible_borrowers, reference))
+ {
+ return false;
+ }
+
+ // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
+ if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ return false;
+ }
+
+ if !replace_types(
+ cx,
+ param_ty,
+ referent_ty,
+ fn_sig,
+ arg_index,
+ &projection_predicates,
+ &mut substs_with_referent_ty,
+ ) {
+ return false;
+ }
+
+ predicates.iter().all(|predicate| {
+ if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
+ && let ty::Param(param_ty) = trait_predicate.self_ty().kind()
+ && let GenericArgKind::Type(ty) = substs_with_referent_ty[param_ty.index as usize].unpack()
+ && ty.is_array()
+ && !meets_msrv(msrv, msrvs::ARRAY_INTO_ITERATOR)
+ {
+ return false;
+ }
+
+ let predicate = EarlyBinder(predicate).subst(cx.tcx, &substs_with_referent_ty);
+ let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
+ let infcx = cx.tcx.infer_ctxt().build();
+ infcx.predicate_must_hold_modulo_regions(&obligation)
+ })
+ };
+
+ let mut needless_borrow = false;
+ while let ExprKind::AddrOf(_, _, referent) = expr.kind {
+ if !check_reference_and_referent(expr, referent) {
+ break;
+ }
+ expr = referent;
+ needless_borrow = true;
+ }
+
+ if needless_borrow {
+ Position::ImplArg(expr.hir_id)
+ } else {
+ Position::Other(precedence)
+ }
+}
+
+fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
+ cx.tcx
+ .associated_items(trait_def_id)
+ .in_definition_order()
+ .any(|assoc_item| {
+ if assoc_item.fn_has_self_parameter {
+ let self_ty = cx.tcx.fn_sig(assoc_item.def_id).skip_binder().inputs()[0];
+ matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
+ } else {
+ false
+ }
+ })
+}
+
++fn is_mixed_projection_predicate<'tcx>(
++ cx: &LateContext<'tcx>,
++ callee_def_id: DefId,
++ projection_predicate: &ProjectionPredicate<'tcx>,
++) -> bool {
++ let generics = cx.tcx.generics_of(callee_def_id);
++ // The predicate requires the projected type to equal a type parameter from the parent context.
++ if let Some(term_ty) = projection_predicate.term.ty()
++ && let ty::Param(term_param_ty) = term_ty.kind()
++ && (term_param_ty.index as usize) < generics.parent_count
++ {
++ // The inner-most self type is a type parameter from the current function.
++ let mut projection_ty = projection_predicate.projection_ty;
++ loop {
++ match projection_ty.self_ty().kind() {
++ ty::Projection(inner_projection_ty) => {
++ projection_ty = *inner_projection_ty;
++ }
++ ty::Param(param_ty) => {
++ return (param_ty.index as usize) >= generics.parent_count;
++ }
++ _ => {
++ return false;
++ }
++ }
++ }
++ } else {
++ false
++ }
++}
++
+fn referent_used_exactly_once<'tcx>(
+ cx: &LateContext<'tcx>,
+ possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+ reference: &Expr<'tcx>,
+) -> bool {
+ let mir = enclosing_mir(cx.tcx, reference.hir_id);
+ if let Some(local) = expr_local(cx.tcx, reference)
+ && let [location] = *local_assignments(mir, local).as_slice()
+ && let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index)
+ && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
+ && !place.has_deref()
++ // Ensure not in a loop (https://github.com/rust-lang/rust-clippy/issues/9710)
++ && TriColorDepthFirstSearch::new(&mir.basic_blocks).run_from(location.block, &mut CycleDetector).is_none()
+ {
+ let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
+ if possible_borrowers
+ .last()
+ .map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
+ {
+ possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
+ }
+ let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
+ // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
+ // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
+ // itself. See the comment in that method for an explanation as to why.
+ possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
+ && used_exactly_once(mir, place.local).unwrap_or(false)
+ } else {
+ false
+ }
+}
+
+// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
+// projected type that is a type parameter. Returns `false` if replacing the types would have an
+// effect on the function signature beyond substituting `new_ty` for `param_ty`.
+// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
+fn replace_types<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_ty: ParamTy,
+ new_ty: Ty<'tcx>,
+ fn_sig: FnSig<'tcx>,
+ arg_index: usize,
+ projection_predicates: &[ProjectionPredicate<'tcx>],
+ substs: &mut [ty::GenericArg<'tcx>],
+) -> bool {
+ let mut replaced = BitSet::new_empty(substs.len());
+
+ let mut deque = VecDeque::with_capacity(substs.len());
+ deque.push_back((param_ty, new_ty));
+
+ while let Some((param_ty, new_ty)) = deque.pop_front() {
+ // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
+ if !fn_sig
+ .inputs_and_output
+ .iter()
+ .enumerate()
+ .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
+ {
+ return false;
+ }
+
+ substs[param_ty.index as usize] = ty::GenericArg::from(new_ty);
+
+ // The `replaced.insert(...)` check provides some protection against infinite loops.
+ if replaced.insert(param_ty.index) {
+ for projection_predicate in projection_predicates {
+ if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
+ && let Some(term_ty) = projection_predicate.term.ty()
+ && let ty::Param(term_param_ty) = term_ty.kind()
+ {
+ let item_def_id = projection_predicate.projection_ty.item_def_id;
+ let assoc_item = cx.tcx.associated_item(item_def_id);
+ let projection = cx.tcx
+ .mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, &[]));
+
+ if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
+ && substs[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty)
+ {
+ deque.push_back((*term_param_ty, projected_ty));
+ }
+ }
+ }
+ }
+ }
+
+ true
+}
+
+struct TyPosition<'tcx> {
+ position: Position,
+ ty: Option<Ty<'tcx>>,
+}
+impl From<Position> for TyPosition<'_> {
+ fn from(position: Position) -> Self {
+ Self { position, ty: None }
+ }
+}
+impl<'tcx> TyPosition<'tcx> {
+ fn new_deref_stable_for_result(precedence: i8, ty: Ty<'tcx>) -> Self {
+ Self {
+ position: Position::ReborrowStable(precedence),
+ ty: Some(ty),
+ }
+ }
+ fn position_for_result(self, cx: &LateContext<'tcx>) -> Position {
+ match (self.position, self.ty) {
+ (Position::ReborrowStable(precedence), Some(ty)) => {
+ Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env))
+ },
+ (position, _) => position,
+ }
+ }
+ fn position_for_arg(self) -> Position {
+ self.position
+ }
+}
+
+// Checks whether a type is stable when switching to auto dereferencing,
+fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedence: i8) -> TyPosition<'tcx> {
+ let ty::Ref(_, mut ty, _) = *ty.kind() else {
+ return Position::Other(precedence).into();
+ };
+
+ loop {
+ break match *ty.kind() {
+ ty::Ref(_, ref_ty, _) => {
+ ty = ref_ty;
+ continue;
+ },
+ ty::Param(_) => TyPosition::new_deref_stable_for_result(precedence, ty),
++ ty::Projection(_) if ty.has_non_region_param() => TyPosition::new_deref_stable_for_result(precedence, ty),
+ ty::Infer(_) | ty::Error(_) | ty::Bound(..) | ty::Opaque(..) | ty::Placeholder(_) | ty::Dynamic(..) => {
+ Position::ReborrowStable(precedence).into()
+ },
+ ty::Adt(..) if ty.has_placeholders() || ty.has_opaque_types() => {
+ Position::ReborrowStable(precedence).into()
+ },
+ ty::Adt(_, substs) if substs.has_non_region_param() => {
+ TyPosition::new_deref_stable_for_result(precedence, ty)
+ },
+ ty::Bool
+ | ty::Char
+ | ty::Int(_)
+ | ty::Uint(_)
+ | ty::Array(..)
+ | ty::Float(_)
+ | ty::RawPtr(..)
+ | ty::FnPtr(_) => Position::DerefStable(precedence, true).into(),
+ ty::Str | ty::Slice(..) => Position::DerefStable(precedence, false).into(),
+ ty::Adt(..)
+ | ty::Foreign(_)
+ | ty::FnDef(..)
+ | ty::Generator(..)
+ | ty::GeneratorWitness(..)
+ | ty::Closure(..)
+ | ty::Never
+ | ty::Tuple(_)
++ | ty::Projection(_) => {
++ Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env.without_caller_bounds())).into()
++ },
+ };
+ }
+}
+
+fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
+ if let ty::Adt(adt, _) = *ty.kind() {
+ adt.is_struct() && adt.all_fields().any(|f| f.name == name)
+ } else {
+ false
+ }
+}
+
+#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
+fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
+ match state {
+ State::DerefMethod {
+ ty_changed_count,
+ is_final_ufcs,
+ target_mut,
+ } => {
+ let mut app = Applicability::MachineApplicable;
+ let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let (_, ref_count) = peel_mid_ty_refs(ty);
+ let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
+ // a deref call changing &T -> &U requires two deref operators the first time
+ // this occurs. One to remove the reference, a second to call the deref impl.
+ "*".repeat(ty_changed_count + 1)
+ } else {
+ "*".repeat(ty_changed_count)
+ };
+ let addr_of_str = if ty_changed_count < ref_count {
+ // Check if a reborrow from &mut T -> &T is required.
+ if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ "&*"
+ } else {
+ ""
+ }
+ } else if target_mut == Mutability::Mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
+ format!("({expr_str})")
+ } else {
+ expr_str.into_owned()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_DEREF_METHODS,
+ data.span,
+ match target_mut {
+ Mutability::Not => "explicit `deref` method call",
+ Mutability::Mut => "explicit `deref_mut` method call",
+ },
+ "try this",
+ format!("{addr_of_str}{deref_str}{expr_str}"),
+ app,
+ );
+ },
+ State::DerefedBorrow(state) => {
+ let mut app = Applicability::MachineApplicable;
+ let snip_expr = state.snip_expr.map_or(expr, |hir_id| cx.tcx.hir().expect_expr(hir_id));
+ let (snip, snip_is_macro) = snippet_with_context(cx, snip_expr.span, data.span.ctxt(), "..", &mut app);
+ span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
+ let calls_field = matches!(expr.kind, ExprKind::Field(..)) && matches!(data.position, Position::Callee);
+ let sugg = if !snip_is_macro
+ && !has_enclosing_paren(&snip)
+ && (expr.precedence().order() < data.position.precedence() || calls_field)
+ {
+ format!("({snip})")
+ } else {
+ snip.into()
+ };
+ diag.span_suggestion(data.span, "change this to", sugg, app);
+ });
+ },
+ State::ExplicitDeref { mutability } => {
+ if matches!(
+ expr.kind,
+ ExprKind::Block(..)
+ | ExprKind::ConstBlock(_)
+ | ExprKind::If(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Match(..)
+ ) && matches!(data.position, Position::DerefStable(_, true))
+ {
+ // Rustc bug: auto deref doesn't work on block expression when targeting sized types.
+ return;
+ }
+
+ let (prefix, precedence) = if let Some(mutability) = mutability
+ && !cx.typeck_results().expr_ty(expr).is_ref()
+ {
+ let prefix = match mutability {
+ Mutability::Not => "&",
+ Mutability::Mut => "&mut ",
+ };
+ (prefix, 0)
+ } else {
+ ("", data.position.precedence())
+ };
+ span_lint_hir_and_then(
+ cx,
+ EXPLICIT_AUTO_DEREF,
+ data.hir_id,
+ data.span,
+ "deref which would be done by auto-deref",
+ |diag| {
+ let mut app = Applicability::MachineApplicable;
+ let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
+ let sugg =
+ if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
+ format!("{prefix}({snip})")
+ } else {
+ format!("{prefix}{snip}")
+ };
+ diag.span_suggestion(data.span, "try this", sugg, app);
+ },
+ );
+ },
+ State::ExplicitDerefField { .. } => {
+ if matches!(
+ expr.kind,
+ ExprKind::Block(..)
+ | ExprKind::ConstBlock(_)
+ | ExprKind::If(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Match(..)
+ ) && matches!(data.position, Position::DerefStable(_, true))
+ {
+ // Rustc bug: auto deref doesn't work on block expression when targeting sized types.
+ return;
+ }
+
+ span_lint_hir_and_then(
+ cx,
+ EXPLICIT_AUTO_DEREF,
+ data.hir_id,
+ data.span,
+ "deref which would be done by auto-deref",
+ |diag| {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
+ diag.span_suggestion(data.span, "try this", snip.into_owned(), app);
+ },
+ );
+ },
+ State::Borrow { .. } | State::Reborrow { .. } => (),
+ }
+}
+
+impl<'tcx> Dereferencing<'tcx> {
+ fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
+ if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
+ if let Some(pat) = outer_pat {
+ // Check for auto-deref
+ if !matches!(
+ cx.typeck_results().expr_adjustments(e),
+ [
+ Adjustment {
+ kind: Adjust::Deref(_),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Deref(_),
+ ..
+ },
+ ..
+ ]
+ ) {
+ match get_parent_expr(cx, e) {
+ // Field accesses are the same no matter the number of references.
+ Some(Expr {
+ kind: ExprKind::Field(..),
+ ..
+ }) => (),
+ Some(&Expr {
+ span,
+ kind: ExprKind::Unary(UnOp::Deref, _),
+ ..
+ }) if !span.from_expansion() => {
+ // Remove explicit deref.
+ let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
+ pat.replacements.push((span, snip.into()));
+ },
+ Some(parent) if !parent.span.from_expansion() => {
+ // Double reference might be needed at this point.
+ if parent.precedence().order() == PREC_POSTFIX {
+ // Parentheses would be needed here, don't lint.
+ *outer_pat = None;
+ } else {
+ pat.always_deref = false;
+ let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
+ pat.replacements.push((e.span, format!("&{snip}")));
+ }
+ },
+ _ if !e.span.from_expansion() => {
+ // Double reference might be needed at this point.
+ pat.always_deref = false;
+ let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
+ pat.replacements.push((e.span, format!("&{snip}")));
+ },
+ // Edge case for macros. The span of the identifier will usually match the context of the
+ // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
+ // macros
+ _ => *outer_pat = None,
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir::def::{Namespace, Res};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::macro_backtrace;
+use rustc_data_structures::fx::FxHashSet;
- diag.note(&format!("{reason} (from clippy.toml)"));
+use rustc_hir::def_id::DefIdMap;
+use rustc_hir::{Expr, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{ExpnId, Span};
+
+use crate::utils::conf;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Denies the configured macros in clippy.toml
+ ///
+ /// Note: Even though this lint is warn-by-default, it will only trigger if
+ /// macros are defined in the clippy.toml file.
+ ///
+ /// ### Why is this bad?
+ /// Some macros are undesirable in certain contexts, and it's beneficial to
+ /// lint for them as needed.
+ ///
+ /// ### Example
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// disallowed-macros = [
+ /// # Can use a string as the path of the disallowed macro.
+ /// "std::print",
+ /// # Can also use an inline table with a `path` key.
+ /// { path = "std::println" },
+ /// # When using an inline table, can add a `reason` for why the macro
+ /// # is disallowed.
+ /// { path = "serde::Serialize", reason = "no serializing" },
+ /// ]
+ /// ```
+ /// ```
+ /// use serde::Serialize;
+ ///
+ /// // Example code where clippy issues a warning
+ /// println!("warns");
+ ///
+ /// // The diagnostic will contain the message "no serializing"
+ /// #[derive(Serialize)]
+ /// struct Data {
+ /// name: String,
+ /// value: usize,
+ /// }
+ /// ```
+ #[clippy::version = "1.65.0"]
+ pub DISALLOWED_MACROS,
+ style,
+ "use of a disallowed macro"
+}
+
+pub struct DisallowedMacros {
+ conf_disallowed: Vec<conf::DisallowedPath>,
+ disallowed: DefIdMap<usize>,
+ seen: FxHashSet<ExpnId>,
+}
+
+impl DisallowedMacros {
+ pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
+ Self {
+ conf_disallowed,
+ disallowed: DefIdMap::default(),
+ seen: FxHashSet::default(),
+ }
+ }
+
+ fn check(&mut self, cx: &LateContext<'_>, span: Span) {
+ if self.conf_disallowed.is_empty() {
+ return;
+ }
+
+ for mac in macro_backtrace(span) {
+ if !self.seen.insert(mac.expn) {
+ return;
+ }
+
+ if let Some(&index) = self.disallowed.get(&mac.def_id) {
+ let conf = &self.conf_disallowed[index];
+
+ span_lint_and_then(
+ cx,
+ DISALLOWED_MACROS,
+ mac.span,
+ &format!("use of a disallowed macro `{}`", conf.path()),
+ |diag| {
+ if let Some(reason) = conf.reason() {
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::MacroNS)) {
++ diag.note(reason);
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
+
+impl LateLintPass<'_> for DisallowedMacros {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for (index, conf) in self.conf_disallowed.iter().enumerate() {
+ let segs: Vec<_> = conf.path().split("::").collect();
++ for id in clippy_utils::def_path_def_ids(cx, &segs) {
+ self.disallowed.insert(id, index);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ self.check(cx, expr.span);
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
+ self.check(cx, stmt.span);
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) {
+ self.check(cx, ty.span);
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
+ self.check(cx, pat.span);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ self.check(cx, item.span);
+ self.check(cx, item.vis_span);
+ }
+
+ fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) {
+ self.check(cx, item.span);
+ self.check(cx, item.vis_span);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
+ self.check(cx, item.span);
+ self.check(cx, item.vis_span);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
+ self.check(cx, item.span);
+ }
+
+ fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
+ self.check(cx, path.span);
+ }
+}
--- /dev/null
- use rustc_hir::def::{Namespace, Res};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{fn_def_id, get_parent_expr, path_def_id};
+
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::ValueNS)) {
+use rustc_hir::def_id::DefIdMap;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+use crate::utils::conf;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Denies the configured methods and functions in clippy.toml
+ ///
+ /// Note: Even though this lint is warn-by-default, it will only trigger if
+ /// methods are defined in the clippy.toml file.
+ ///
+ /// ### Why is this bad?
+ /// Some methods are undesirable in certain contexts, and it's beneficial to
+ /// lint for them as needed.
+ ///
+ /// ### Example
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// disallowed-methods = [
+ /// # Can use a string as the path of the disallowed method.
+ /// "std::boxed::Box::new",
+ /// # Can also use an inline table with a `path` key.
+ /// { path = "std::time::Instant::now" },
+ /// # When using an inline table, can add a `reason` for why the method
+ /// # is disallowed.
+ /// { path = "std::vec::Vec::leak", reason = "no leaking memory" },
+ /// ]
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// // Example code where clippy issues a warning
+ /// let xs = vec![1, 2, 3, 4];
+ /// xs.leak(); // Vec::leak is disallowed in the config.
+ /// // The diagnostic contains the message "no leaking memory".
+ ///
+ /// let _now = Instant::now(); // Instant::now is disallowed in the config.
+ ///
+ /// let _box = Box::new(3); // Box::new is disallowed in the config.
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// // Example code which does not raise clippy warning
+ /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.
+ /// xs.push(123); // Vec::push is _not_ disallowed in the config.
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub DISALLOWED_METHODS,
+ style,
+ "use of a disallowed method call"
+}
+
+#[derive(Clone, Debug)]
+pub struct DisallowedMethods {
+ conf_disallowed: Vec<conf::DisallowedPath>,
+ disallowed: DefIdMap<usize>,
+}
+
+impl DisallowedMethods {
+ pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
+ Self {
+ conf_disallowed,
+ disallowed: DefIdMap::default(),
+ }
+ }
+}
+
+impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
+
+impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for (index, conf) in self.conf_disallowed.iter().enumerate() {
+ let segs: Vec<_> = conf.path().split("::").collect();
- diag.note(&format!("{reason} (from clippy.toml)"));
++ for id in clippy_utils::def_path_def_ids(cx, &segs) {
+ self.disallowed.insert(id, index);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr)
+ && let ExprKind::Call(receiver, _) = parent.kind
+ && receiver.hir_id == expr.hir_id
+ {
+ None
+ } else {
+ path_def_id(cx, expr)
+ };
+ let Some(def_id) = uncalled_path.or_else(|| fn_def_id(cx, expr)) else {
+ return
+ };
+ let conf = match self.disallowed.get(&def_id) {
+ Some(&index) => &self.conf_disallowed[index],
+ None => return,
+ };
+ let msg = format!("use of a disallowed method `{}`", conf.path());
+ span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| {
+ if let Some(reason) = conf.reason() {
++ diag.note(reason);
+ }
+ });
+ }
+}
--- /dev/null
- use rustc_hir::def::{Namespace, Res};
+use clippy_utils::diagnostics::span_lint_and_then;
+
+use rustc_data_structures::fx::FxHashMap;
- def_ids: FxHashMap<DefId, Option<String>>,
- prim_tys: FxHashMap<PrimTy, Option<String>>,
++use rustc_hir::def::Res;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+use crate::utils::conf;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Denies the configured types in clippy.toml.
+ ///
+ /// Note: Even though this lint is warn-by-default, it will only trigger if
+ /// types are defined in the clippy.toml file.
+ ///
+ /// ### Why is this bad?
+ /// Some types are undesirable in certain contexts.
+ ///
+ /// ### Example:
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// disallowed-types = [
+ /// # Can use a string as the path of the disallowed type.
+ /// "std::collections::BTreeMap",
+ /// # Can also use an inline table with a `path` key.
+ /// { path = "std::net::TcpListener" },
+ /// # When using an inline table, can add a `reason` for why the type
+ /// # is disallowed.
+ /// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
+ /// ]
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// use std::collections::BTreeMap;
+ /// // or its use
+ /// let x = std::collections::BTreeMap::new();
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// // A similar type that is allowed by the config
+ /// use std::collections::HashMap;
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub DISALLOWED_TYPES,
+ style,
+ "use of disallowed types"
+}
+#[derive(Clone, Debug)]
+pub struct DisallowedTypes {
+ conf_disallowed: Vec<conf::DisallowedPath>,
- if let Some(reason) = self.def_ids.get(did) {
- emit(cx, &cx.tcx.def_path_str(*did), span, reason.as_deref());
++ def_ids: FxHashMap<DefId, usize>,
++ prim_tys: FxHashMap<PrimTy, usize>,
+}
+
+impl DisallowedTypes {
+ pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
+ Self {
+ conf_disallowed,
+ def_ids: FxHashMap::default(),
+ prim_tys: FxHashMap::default(),
+ }
+ }
+
+ fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
+ match res {
+ Res::Def(_, did) => {
- if let Some(reason) = self.prim_tys.get(prim) {
- emit(cx, prim.name_str(), span, reason.as_deref());
++ if let Some(&index) = self.def_ids.get(did) {
++ emit(cx, &cx.tcx.def_path_str(*did), span, &self.conf_disallowed[index]);
+ }
+ },
+ Res::PrimTy(prim) => {
- for conf in &self.conf_disallowed {
++ if let Some(&index) = self.prim_tys.get(prim) {
++ emit(cx, prim.name_str(), span, &self.conf_disallowed[index]);
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
+
+impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
- let reason = conf.reason().map(|reason| format!("{reason} (from clippy.toml)"));
- match clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
- Res::Def(_, id) => {
- self.def_ids.insert(id, reason);
- },
- Res::PrimTy(ty) => {
- self.prim_tys.insert(ty, reason);
- },
- _ => {},
++ for (index, conf) in self.conf_disallowed.iter().enumerate() {
+ let segs: Vec<_> = conf.path().split("::").collect();
- fn emit(cx: &LateContext<'_>, name: &str, span: Span, reason: Option<&str>) {
++
++ for res in clippy_utils::def_path_res(cx, &segs) {
++ match res {
++ Res::Def(_, id) => {
++ self.def_ids.insert(id, index);
++ },
++ Res::PrimTy(ty) => {
++ self.prim_tys.insert(ty, index);
++ },
++ _ => {},
++ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if let ItemKind::Use(path, UseKind::Single) = &item.kind {
+ self.check_res_emit(cx, &path.res, item.span);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
+ if let TyKind::Path(path) = &ty.kind {
+ self.check_res_emit(cx, &cx.qpath_res(path, ty.hir_id), ty.span);
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) {
+ self.check_res_emit(cx, &poly.trait_ref.path.res, poly.trait_ref.path.span);
+ }
+}
+
- if let Some(reason) = reason {
++fn emit(cx: &LateContext<'_>, name: &str, span: Span, conf: &conf::DisallowedPath) {
+ span_lint_and_then(
+ cx,
+ DISALLOWED_TYPES,
+ span,
+ &format!("`{name}` is not allowed according to config"),
+ |diag| {
++ if let Some(reason) = conf.reason() {
+ diag.note(reason);
+ }
+ },
+ );
+}
--- /dev/null
- use rustc_errors::{Applicability, Handler, MultiSpan, SuggestionStyle};
+use clippy_utils::attrs::is_doc_hidden;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then};
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::source::{first_line_of_span, snippet_with_applicability};
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind};
+use rustc_ast::token::CommentKind;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::emitter::EmitterWriter;
- NEEDLESS_DOCTEST_MAIN
++use rustc_errors::{Applicability, Handler, SuggestionStyle};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{AnonConst, Expr};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_parse::maybe_new_parser_from_source_str;
+use rustc_parse::parser::ForceCollect;
+use rustc_session::parse::ParseSess;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::{BytePos, FilePathMapping, SourceMap, Span};
+use rustc_span::{sym, FileName, Pos};
+use std::io;
+use std::ops::Range;
+use std::thread;
+use url::Url;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the presence of `_`, `::` or camel-case words
+ /// outside ticks in documentation.
+ ///
+ /// ### Why is this bad?
+ /// *Rustdoc* supports markdown formatting, `_`, `::` and
+ /// camel-case probably indicates some code which should be included between
+ /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
+ /// consider that.
+ ///
+ /// ### Known problems
+ /// Lots of bad docs won’t be fixed, what the lint checks
+ /// for is limited, and there are still false positives. HTML elements and their
+ /// content are not linted.
+ ///
+ /// In addition, when writing documentation comments, including `[]` brackets
+ /// inside a link text would trip the parser. Therefore, documenting link with
+ /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
+ /// would fail.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// /// Do something with the foo_bar parameter. See also
+ /// /// that::other::module::foo.
+ /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
+ /// fn doit(foo_bar: usize) {}
+ /// ```
+ ///
+ /// ```rust
+ /// // Link text with `[]` brackets should be written as following:
+ /// /// Consume the array and return the inner
+ /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
+ /// /// [SmallVec]: SmallVec
+ /// fn main() {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "presence of `_`, `::` or camel-case outside backticks in documentation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the doc comments of publicly visible
+ /// unsafe functions and warns if there is no `# Safety` section.
+ ///
+ /// ### Why is this bad?
+ /// Unsafe functions should document their safety
+ /// preconditions, so that users can be sure they are using them safely.
+ ///
+ /// ### Examples
+ /// ```rust
+ ///# type Universe = ();
+ /// /// This function should really be documented
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
+ ///
+ /// At least write a line about safety:
+ ///
+ /// ```rust
+ ///# type Universe = ();
+ /// /// # Safety
+ /// ///
+ /// /// This function should not be called before the horsemen are ready.
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub MISSING_SAFETY_DOC,
+ style,
+ "`pub unsafe fn` without `# Safety` docs"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// return a `Result` type and warns if there is no `# Errors` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the type of errors that can be returned from a
+ /// function can help callers write code to handle the errors appropriately.
+ ///
+ /// ### Examples
+ /// Since the following function returns a `Result` it has an `# Errors` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ ///# use std::io;
+ /// /// # Errors
+ /// ///
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
+ /// /// permission to read it.
+ /// pub fn read(filename: String) -> io::Result<String> {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub MISSING_ERRORS_DOC,
+ pedantic,
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// may panic and warns if there is no `# Panics` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the scenarios in which panicking occurs
+ /// can help callers who do not want to panic to avoid those situations.
+ ///
+ /// ### Examples
+ /// Since the following function may panic it has a `# Panics` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ /// /// # Panics
+ /// ///
+ /// /// Will panic if y is 0
+ /// pub fn divide_by(x: i32, y: i32) -> i32 {
+ /// if y == 0 {
+ /// panic!("Cannot divide by 0")
+ /// } else {
+ /// x / y
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MISSING_PANICS_DOC,
+ pedantic,
+ "`pub fn` may panic without `# Panics` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `fn main() { .. }` in doctests
+ ///
+ /// ### Why is this bad?
+ /// The test can be shorter (and likely more readable)
+ /// if the `fn main()` is left implicit.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// /// An example of a doctest with a `main()` function
+ /// ///
+ /// /// # Examples
+ /// ///
+ /// /// ```
+ /// /// fn main() {
+ /// /// // this needs not be in an `fn`
+ /// /// }
+ /// /// ```
+ /// fn needless_main() {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub NEEDLESS_DOCTEST_MAIN,
+ style,
+ "presence of `fn main() {` in code examples"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects the syntax `['foo']` in documentation comments (notice quotes instead of backticks)
+ /// outside of code blocks
+ /// ### Why is this bad?
+ /// It is likely a typo when defining an intra-doc link
+ ///
+ /// ### Example
+ /// ```rust
+ /// /// See also: ['foo']
+ /// fn bar() {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// /// See also: [`foo`]
+ /// fn bar() {}
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub DOC_LINK_WITH_QUOTES,
+ pedantic,
+ "possible typo for an intra-doc link"
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for the doc comments of publicly visible
++ /// safe functions and traits and warns if there is a `# Safety` section.
++ ///
++ /// ### Why is this bad?
++ /// Safe functions and traits are safe to implement and therefore do not
++ /// need to describe safety preconditions that users are required to uphold.
++ ///
++ /// ### Examples
++ /// ```rust
++ ///# type Universe = ();
++ /// /// # Safety
++ /// ///
++ /// /// This function should not be called before the horsemen are ready.
++ /// pub fn start_apocalypse_but_safely(u: &mut Universe) {
++ /// unimplemented!();
++ /// }
++ /// ```
++ ///
++ /// The function is safe, so there shouldn't be any preconditions
++ /// that have to be explained for safety reasons.
++ ///
++ /// ```rust
++ ///# type Universe = ();
++ /// /// This function should really be documented
++ /// pub fn start_apocalypse(u: &mut Universe) {
++ /// unimplemented!();
++ /// }
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub UNNECESSARY_SAFETY_DOC,
++ style,
++ "`pub fn` or `pub trait` with `# Safety` docs"
++}
++
+#[expect(clippy::module_name_repetitions)]
+#[derive(Clone)]
+pub struct DocMarkdown {
+ valid_idents: FxHashSet<String>,
+ in_trait_impl: bool,
+}
+
+impl DocMarkdown {
+ pub fn new(valid_idents: FxHashSet<String>) -> Self {
+ Self {
+ valid_idents,
+ in_trait_impl: false,
+ }
+ }
+}
+
+impl_lint_pass!(DocMarkdown => [
+ DOC_LINK_WITH_QUOTES,
+ DOC_MARKDOWN,
+ MISSING_SAFETY_DOC,
+ MISSING_ERRORS_DOC,
+ MISSING_PANICS_DOC,
- let headers = check_attrs(cx, &self.valid_idents, attrs);
++ NEEDLESS_DOCTEST_MAIN,
++ UNNECESSARY_SAFETY_DOC,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
+ check_attrs(cx, &self.valid_idents, attrs);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
- lint_for_missing_headers(
- cx,
- item.owner_id.def_id,
- item.span,
- sig,
- headers,
- Some(body_id),
- fpu.panic_span,
- );
++ let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
+ match item.kind {
+ hir::ItemKind::Fn(ref sig, _, body_id) => {
+ if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.owner_id.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(body.value);
- hir::ItemKind::Trait(_, unsafety, ..) => {
- if !headers.safety && unsafety == hir::Unsafety::Unsafe {
- span_lint(
- cx,
- MISSING_SAFETY_DOC,
- item.span,
- "docs for unsafe trait missing `# Safety` section",
- );
- }
++ lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ },
+ hir::ItemKind::Impl(impl_) => {
+ self.in_trait_impl = impl_.of_trait.is_some();
+ },
- let headers = check_attrs(cx, &self.valid_idents, attrs);
++ hir::ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) {
++ (false, hir::Unsafety::Unsafe) => span_lint(
++ cx,
++ MISSING_SAFETY_DOC,
++ cx.tcx.def_span(item.owner_id),
++ "docs for unsafe trait missing `# Safety` section",
++ ),
++ (true, hir::Unsafety::Normal) => span_lint(
++ cx,
++ UNNECESSARY_SAFETY_DOC,
++ cx.tcx.def_span(item.owner_id),
++ "docs for safe trait have unnecessary `# Safety` section",
++ ),
++ _ => (),
+ },
+ _ => (),
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if let hir::ItemKind::Impl { .. } = item.kind {
+ self.in_trait_impl = false;
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
- lint_for_missing_headers(cx, item.owner_id.def_id, item.span, sig, headers, None, None);
++ let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
+ if !in_external_macro(cx.tcx.sess, item.span) {
- let headers = check_attrs(cx, &self.valid_idents, attrs);
++ lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, None, None);
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
- lint_for_missing_headers(
- cx,
- item.owner_id.def_id,
- item.span,
- sig,
- headers,
- Some(body_id),
- fpu.panic_span,
- );
++ let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
+ if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+ if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.owner_id.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(body.value);
- fn lint_for_missing_headers<'tcx>(
- cx: &LateContext<'tcx>,
++ lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ }
+}
+
- span: impl Into<MultiSpan> + Copy,
++fn lint_for_missing_headers(
++ cx: &LateContext<'_>,
+ def_id: LocalDefId,
- if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
- span_lint(
+ sig: &hir::FnSig<'_>,
+ headers: DocHeaders,
+ body_id: Option<hir::BodyId>,
+ panic_span: Option<Span>,
+) {
+ if !cx.effective_visibilities.is_exported(def_id) {
+ return; // Private functions do not require doc comments
+ }
+
+ // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
+ if cx
+ .tcx
+ .hir()
+ .parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
+ .any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
+ {
+ return;
+ }
+
- );
++ let span = cx.tcx.def_span(def_id);
++ match (headers.safety, sig.header.unsafety) {
++ (false, hir::Unsafety::Unsafe) => span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ span,
+ "unsafe function's docs miss `# Safety` section",
- fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
++ ),
++ (true, hir::Unsafety::Normal) => span_lint(
++ cx,
++ UNNECESSARY_SAFETY_DOC,
++ span,
++ "safe function's docs have unnecessary `# Safety` section",
++ ),
++ _ => (),
+ }
+ if !headers.panics && panic_span.is_some() {
+ span_lint_and_note(
+ cx,
+ MISSING_PANICS_DOC,
+ span,
+ "docs for function which may panic missing `# Panics` section",
+ panic_span,
+ "first possible panic found here",
+ );
+ }
+ if !headers.errors {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+ if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ } else {
+ if_chain! {
+ if let Some(body_id) = body_id;
+ if let Some(future) = cx.tcx.lang_items().future_trait();
+ let typeck = cx.tcx.typeck_body(body_id);
+ let body = cx.tcx.hir().body(body_id);
+ let ret_ty = typeck.expr_ty(body.value);
+ if implements_trait(cx, ret_ty, future, &[]);
+ if let ty::Opaque(_, subs) = ret_ty.kind();
+ if let Some(gen) = subs.types().next();
+ if let ty::Generator(_, subs, _) = gen.kind();
+ if is_type_diagnostic_item(cx, subs.as_generator().return_ty(), sym::Result);
+ then {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Cleanup documentation decoration.
+///
+/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
+/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
+/// need to keep track of
+/// the spans but this function is inspired from the later.
+#[expect(clippy::cast_possible_truncation)]
+#[must_use]
+pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
+ // one-line comments lose their prefix
+ if comment_kind == CommentKind::Line {
+ let mut doc = doc.to_owned();
+ doc.push('\n');
+ let len = doc.len();
+ // +3 skips the opening delimiter
+ return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]);
+ }
+
+ let mut sizes = vec![];
+ let mut contains_initial_stars = false;
+ for line in doc.lines() {
+ let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
+ debug_assert_eq!(offset as u32 as usize, offset);
+ contains_initial_stars |= line.trim_start().starts_with('*');
+ // +1 adds the newline, +3 skips the opening delimiter
+ sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
+ }
+ if !contains_initial_stars {
+ return (doc.to_string(), sizes);
+ }
+ // remove the initial '*'s if any
+ let mut no_stars = String::with_capacity(doc.len());
+ for line in doc.lines() {
+ let mut chars = line.chars();
+ for c in &mut chars {
+ if c.is_whitespace() {
+ no_stars.push(c);
+ } else {
+ no_stars.push(if c == '*' { ' ' } else { c });
+ break;
+ }
+ }
+ no_stars.push_str(chars.as_str());
+ no_stars.push('\n');
+ }
+
+ (no_stars, sizes)
+}
+
+#[derive(Copy, Clone, Default)]
+struct DocHeaders {
+ safety: bool,
+ errors: bool,
+ panics: bool,
+}
+
- return DocHeaders {
- safety: true,
- errors: true,
- panics: true,
- };
++fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
+ use pulldown_cmark::{BrokenLink, CowStr, Options};
+ /// We don't want the parser to choke on intra doc links. Since we don't
+ /// actually care about rendering them, just pretend that all broken links are
+ /// point to a fake address.
+ #[expect(clippy::unnecessary_wraps)] // we're following a type signature
+ fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+ Some(("fake".into(), "fake".into()))
+ }
+
+ let mut doc = String::new();
+ let mut spans = vec![];
+
+ for attr in attrs {
+ if let AttrKind::DocComment(comment_kind, comment) = attr.kind {
+ let (comment, current_spans) = strip_doc_comment_decoration(comment.as_str(), comment_kind, attr.span);
+ spans.extend_from_slice(¤t_spans);
+ doc.push_str(&comment);
+ } else if attr.has_name(sym::doc) {
+ // ignore mix of sugared and non-sugared doc
+ // don't trigger the safety or errors check
- return DocHeaders::default();
++ return None;
+ }
+ }
+
+ let mut current = 0;
+ for &mut (ref mut offset, _) in &mut spans {
+ let offset_copy = *offset;
+ *offset = current;
+ current += offset_copy;
+ }
+
+ if doc.is_empty() {
- check_doc(cx, valid_idents, events, &spans)
++ return Some(DocHeaders::default());
+ }
+
+ let mut cb = fake_broken_link_callback;
+
+ let parser =
+ pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter();
+ // Iterate over all `Events` and combine consecutive events into one
+ let events = parser.coalesce(|previous, current| {
+ use pulldown_cmark::Event::Text;
+
+ let previous_range = previous.1;
+ let current_range = current.1;
+
+ match (previous.0, current.0) {
+ (Text(previous), Text(current)) => {
+ let mut previous = previous.to_string();
+ previous.push_str(¤t);
+ Ok((Text(previous.into()), previous_range))
+ },
+ (previous, current) => Err(((previous, previous_range), (current, current_range))),
+ }
+ });
++ Some(check_doc(cx, valid_idents, events, &spans))
+}
+
+const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
+
+fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
+ cx: &LateContext<'_>,
+ valid_idents: &FxHashSet<String>,
+ events: Events,
+ spans: &[(usize, Span)],
+) -> DocHeaders {
+ // true if a safety header was found
+ use pulldown_cmark::Event::{
+ Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
+ };
+ use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
+ use pulldown_cmark::{CodeBlockKind, CowStr};
+
+ let mut headers = DocHeaders::default();
+ let mut in_code = false;
+ let mut in_link = None;
+ let mut in_heading = false;
+ let mut is_rust = false;
+ let mut edition = None;
+ let mut ticks_unbalanced = false;
+ let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
+ let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
+ for (event, range) in events {
+ match event {
+ Start(CodeBlock(ref kind)) => {
+ in_code = true;
+ if let CodeBlockKind::Fenced(lang) = kind {
+ for item in lang.split(',') {
+ if item == "ignore" {
+ is_rust = false;
+ break;
+ }
+ if let Some(stripped) = item.strip_prefix("edition") {
+ is_rust = true;
+ edition = stripped.parse::<Edition>().ok();
+ } else if item.is_empty() || RUST_CODE.contains(&item) {
+ is_rust = true;
+ }
+ }
+ }
+ },
+ End(CodeBlock(_)) => {
+ in_code = false;
+ is_rust = false;
+ },
+ Start(Link(_, url, _)) => in_link = Some(url),
+ End(Link(..)) => in_link = None,
+ Start(Heading(_, _, _) | Paragraph | Item) => {
+ if let Start(Heading(_, _, _)) = event {
+ in_heading = true;
+ }
+ ticks_unbalanced = false;
+ let (_, span) = get_current_span(spans, range.start);
+ paragraph_span = first_line_of_span(cx, span);
+ },
+ End(Heading(_, _, _) | Paragraph | Item) => {
+ if let End(Heading(_, _, _)) = event {
+ in_heading = false;
+ }
+ if ticks_unbalanced {
+ span_lint_and_help(
+ cx,
+ DOC_MARKDOWN,
+ paragraph_span,
+ "backticks are unbalanced",
+ None,
+ "a backtick may be missing a pair",
+ );
+ } else {
+ for (text, span) in text_to_check {
+ check_text(cx, valid_idents, &text, span);
+ }
+ }
+ text_to_check = Vec::new();
+ },
+ Start(_tag) | End(_tag) => (), // We don't care about other tags
+ Html(_html) => (), // HTML is weird, just ignore it
+ SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
+ FootnoteReference(text) | Text(text) => {
+ let (begin, span) = get_current_span(spans, range.start);
+ paragraph_span = paragraph_span.with_hi(span.hi());
+ ticks_unbalanced |= text.contains('`') && !in_code;
+ if Some(&text) == in_link.as_ref() || ticks_unbalanced {
+ // Probably a link of the form `<http://example.com>`
+ // Which are represented as a link to "http://example.com" with
+ // text "http://example.com" by pulldown-cmark
+ continue;
+ }
+ let trimmed_text = text.trim();
+ headers.safety |= in_heading && trimmed_text == "Safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation Safety";
+ headers.errors |= in_heading && trimmed_text == "Errors";
+ headers.panics |= in_heading && trimmed_text == "Panics";
+ if in_code {
+ if is_rust {
+ let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
+ check_code(cx, &text, edition, span);
+ }
+ } else {
+ check_link_quotes(cx, in_link.is_some(), trimmed_text, span, &range, begin, text.len());
+ // Adjust for the beginning of the current `Event`
+ let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
+ text_to_check.push((text, span));
+ }
+ },
+ }
+ }
+ headers
+}
+
+fn check_link_quotes(
+ cx: &LateContext<'_>,
+ in_link: bool,
+ trimmed_text: &str,
+ span: Span,
+ range: &Range<usize>,
+ begin: usize,
+ text_len: usize,
+) {
+ if in_link && trimmed_text.starts_with('\'') && trimmed_text.ends_with('\'') {
+ // fix the span to only point at the text within the link
+ let lo = span.lo() + BytePos::from_usize(range.start - begin);
+ span_lint(
+ cx,
+ DOC_LINK_WITH_QUOTES,
+ span.with_lo(lo).with_hi(lo + BytePos::from_usize(text_len)),
+ "possible intra-doc link using quotes instead of backticks",
+ );
+ }
+}
+
+fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
+ let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
+ Ok(o) => o,
+ Err(e) => e - 1,
+ };
+ spans[index]
+}
+
+fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
+ fn has_needless_main(code: String, edition: Edition) -> bool {
+ rustc_driver::catch_fatal_errors(|| {
+ rustc_span::create_session_globals_then(edition, || {
+ let filename = FileName::anon_source_code(&code);
+
+ let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let fallback_bundle =
+ rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
+ let emitter = EmitterWriter::new(
+ Box::new(io::sink()),
+ None,
+ None,
+ fallback_bundle,
+ false,
+ false,
+ false,
+ None,
+ false,
+ false,
+ );
+ let handler = Handler::with_emitter(false, None, Box::new(emitter));
+ let sess = ParseSess::with_span_handler(handler, sm);
+
+ let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
+ Ok(p) => p,
+ Err(errs) => {
+ drop(errs);
+ return false;
+ },
+ };
+
+ let mut relevant_main_found = false;
+ loop {
+ match parser.parse_item(ForceCollect::No) {
+ Ok(Some(item)) => match &item.kind {
+ ItemKind::Fn(box Fn {
+ sig, body: Some(block), ..
+ }) if item.ident.name == sym::main => {
+ let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
+ let returns_nothing = match &sig.decl.output {
+ FnRetTy::Default(..) => true,
+ FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
+ FnRetTy::Ty(_) => false,
+ };
+
+ if returns_nothing && !is_async && !block.stmts.is_empty() {
+ // This main function should be linted, but only if there are no other functions
+ relevant_main_found = true;
+ } else {
+ // This main function should not be linted, we're done
+ 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(e) => {
+ e.cancel();
+ return false;
+ },
+ }
+ }
+
+ relevant_main_found
+ })
+ })
+ .ok()
+ .unwrap_or_default()
+ }
+
+ // Because of the global session, we need to create a new session in a different thread with
+ // the edition we need.
+ let text = text.to_owned();
+ if thread::spawn(move || has_needless_main(text, edition))
+ .join()
+ .expect("thread::spawn failed")
+ {
+ span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
+ }
+}
+
+fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
+ for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
+ // Trim punctuation as in `some comment (see foo::bar).`
+ // ^^
+ // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
+ let mut word = word.trim_matches(|c: char| !c.is_alphanumeric() && c != ':');
+
+ // Remove leading or trailing single `:` which may be part of a sentence.
+ if word.starts_with(':') && !word.starts_with("::") {
+ word = word.trim_start_matches(':');
+ }
+ if word.ends_with(':') && !word.ends_with("::") {
+ word = word.trim_end_matches(':');
+ }
+
+ if valid_idents.contains(word) || word.chars().all(|c| c == ':') {
+ continue;
+ }
+
+ // Adjust for the current word
+ let offset = word.as_ptr() as usize - text.as_ptr() as usize;
+ let span = Span::new(
+ span.lo() + BytePos::from_usize(offset),
+ span.lo() + BytePos::from_usize(offset + word.len()),
+ span.ctxt(),
+ span.parent(),
+ );
+
+ check_word(cx, word, span);
+ }
+}
+
+fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
+ /// Checks if a string is camel-case, i.e., contains at least two uppercase
+ /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
+ /// Plurals are also excluded (`IDs` is ok).
+ fn is_camel_case(s: &str) -> bool {
+ if s.starts_with(|c: char| c.is_ascii_digit()) {
+ return false;
+ }
+
+ let s = s.strip_suffix('s').unwrap_or(s);
+
+ s.chars().all(char::is_alphanumeric)
+ && s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
+ && s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
+ }
+
+ fn has_underscore(s: &str) -> bool {
+ s != "_" && !s.contains("\\_") && s.contains('_')
+ }
+
+ fn has_hyphen(s: &str) -> bool {
+ s != "-" && s.contains('-')
+ }
+
+ if let Ok(url) = Url::parse(word) {
+ // try to get around the fact that `foo::bar` parses as a valid URL
+ if !url.cannot_be_a_base() {
+ span_lint(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "you should put bare URLs between `<`/`>` or make a proper Markdown link",
+ );
+
+ return;
+ }
+ }
+
+ // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
+ if has_underscore(word) && has_hyphen(word) {
+ return;
+ }
+
+ if has_underscore(word) || word.contains("::") || is_camel_case(word) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_then(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "item in documentation is missing backticks",
+ |diag| {
+ let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
+ diag.span_suggestion_with_style(
+ span,
+ "try",
+ format!("`{snippet}`"),
+ applicability,
+ // always show the suggestion in a separate line, since the
+ // inline presentation adds another pair of backticks
+ SuggestionStyle::ShowAlways,
+ );
+ },
+ );
+ }
+}
+
+struct FindPanicUnwrap<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ panic_span: Option<Span>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.panic_span.is_some() {
+ return;
+ }
+
+ if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
+ if is_panic(self.cx, macro_call.def_id)
+ || matches!(
+ self.cx.tcx.item_name(macro_call.def_id).as_str(),
+ "assert" | "assert_eq" | "assert_ne" | "todo"
+ )
+ {
+ self.panic_span = Some(macro_call.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
+ if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
+ {
+ self.panic_span = Some(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ // Panics in const blocks will cause compilation to fail.
+ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- if let Some(&(ref mod_name, ref mod_camel)) = self.modules.last() {
+//! lint on enum variants that are prefixed or suffixed by the same characters
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::source::is_present_in_source;
+use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start};
+use rustc_hir::{EnumDef, Item, ItemKind, Variant};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects enumeration variants that are prefixed or suffixed
+ /// by the same characters.
+ ///
+ /// ### Why is this bad?
+ /// Enumeration variant names should specify their variant,
+ /// not repeat the enumeration name.
+ ///
+ /// ### Limitations
+ /// Characters with no casing will be considered when comparing prefixes/suffixes
+ /// This applies to numbers and non-ascii characters without casing
+ /// e.g. `Foo1` and `Foo2` is considered to have different prefixes
+ /// (the prefixes are `Foo1` and `Foo2` respectively), as also `Bar螃`, `Bar蟹`
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Cake {
+ /// BlackForestCake,
+ /// HummingbirdCake,
+ /// BattenbergCake,
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// enum Cake {
+ /// BlackForest,
+ /// Hummingbird,
+ /// Battenberg,
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_VARIANT_NAMES,
+ style,
+ "enums where all variants share a prefix/postfix"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects type names that are prefixed or suffixed by the
+ /// containing module's name.
+ ///
+ /// ### Why is this bad?
+ /// It requires the user to type the module name twice.
+ ///
+ /// ### Example
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForestCake;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForest;
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub MODULE_NAME_REPETITIONS,
+ pedantic,
+ "type names prefixed/postfixed with their containing module's name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for modules that have the same name as their
+ /// parent module
+ ///
+ /// ### Why is this bad?
+ /// A typical beginner mistake is to have `mod foo;` and
+ /// again `mod foo { ..
+ /// }` in `foo.rs`.
+ /// The expectation is that items inside the inner `mod foo { .. }` are then
+ /// available
+ /// through `foo::x`, but they are only available through
+ /// `foo::foo::x`.
+ /// If this is done on purpose, it would be better to choose a more
+ /// representative module name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // lib.rs
+ /// mod foo;
+ /// // foo.rs
+ /// mod foo {
+ /// ...
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MODULE_INCEPTION,
+ style,
+ "modules that have the same name as their parent module"
+}
+
+pub struct EnumVariantNames {
+ modules: Vec<(Symbol, String)>,
+ threshold: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl EnumVariantNames {
+ #[must_use]
+ pub fn new(threshold: u64, avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ modules: Vec::new(),
+ threshold,
+ avoid_breaking_exported_api,
+ }
+ }
+}
+
+impl_lint_pass!(EnumVariantNames => [
+ ENUM_VARIANT_NAMES,
+ MODULE_NAME_REPETITIONS,
+ MODULE_INCEPTION
+]);
+
+fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
+ let name = variant.ident.name.as_str();
+ let item_name_chars = item_name.chars().count();
+
+ if count_match_start(item_name, name).char_count == item_name_chars
+ && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase())
+ && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric())
+ {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ variant.span,
+ "variant name starts with the enum's name",
+ );
+ }
+}
+
+fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
+ let name = variant.ident.name.as_str();
+ let item_name_chars = item_name.chars().count();
+
+ if count_match_end(item_name, name).char_count == item_name_chars {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ variant.span,
+ "variant name ends with the enum's name",
+ );
+ }
+}
+
+fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_name: &str, span: Span) {
+ if (def.variants.len() as u64) < threshold {
+ return;
+ }
+
+ let first = &def.variants[0].ident.name.as_str();
+ let mut pre = camel_case_split(first);
+ let mut post = pre.clone();
+ post.reverse();
+ for var in def.variants {
+ check_enum_start(cx, item_name, var);
+ check_enum_end(cx, item_name, var);
+ let name = var.ident.name.as_str();
+
+ let variant_split = camel_case_split(name);
+ if variant_split.len() == 1 {
+ return;
+ }
+
+ pre = pre
+ .iter()
+ .zip(variant_split.iter())
+ .take_while(|(a, b)| a == b)
+ .map(|e| *e.0)
+ .collect();
+ post = post
+ .iter()
+ .zip(variant_split.iter().rev())
+ .take_while(|(a, b)| a == b)
+ .map(|e| *e.0)
+ .collect();
+ }
+ let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) {
+ (true, true) => return,
+ (false, _) => ("pre", pre.join("")),
+ (true, false) => {
+ post.reverse();
+ ("post", post.join(""))
+ },
+ };
+ span_lint_and_help(
+ cx,
+ ENUM_VARIANT_NAMES,
+ span,
+ &format!("all variants have the same {what}fix: `{value}`"),
+ None,
+ &format!(
+ "remove the {what}fixes and use full paths to \
+ the variants instead of glob imports"
+ ),
+ );
+}
+
+#[must_use]
+fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
+ prefixes.iter().all(|p| p == &"" || p == &"_")
+}
+
+#[must_use]
+fn to_camel_case(item_name: &str) -> String {
+ let mut s = String::new();
+ let mut up = true;
+ for c in item_name.chars() {
+ if c.is_uppercase() {
+ // we only turn snake case text into CamelCase
+ return item_name.to_string();
+ }
+ if c == '_' {
+ up = true;
+ continue;
+ }
+ if up {
+ up = false;
+ s.extend(c.to_uppercase());
+ } else {
+ s.push(c);
+ }
+ }
+ s
+}
+
+impl LateLintPass<'_> for EnumVariantNames {
+ fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
+ let last = self.modules.pop();
+ assert!(last.is_some());
+ }
+
+ #[expect(clippy::similar_names)]
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ let item_name = item.ident.name.as_str();
+ let item_camel = to_camel_case(item_name);
+ if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
++ if let Some((mod_name, mod_camel)) = self.modules.last() {
+ // constants don't have surrounding modules
+ if !mod_camel.is_empty() {
+ if mod_name == &item.ident.name {
+ if let ItemKind::Mod(..) = item.kind {
+ span_lint(
+ cx,
+ MODULE_INCEPTION,
+ item.span,
+ "module has the same name as its containing module",
+ );
+ }
+ }
+ // The `module_name_repetitions` lint should only trigger if the item has the module in its
+ // name. Having the same name is accepted.
+ if cx.tcx.visibility(item.owner_id).is_public() && item_camel.len() > mod_camel.len() {
+ let matching = count_match_start(mod_camel, &item_camel);
+ let rmatching = count_match_end(mod_camel, &item_camel);
+ let nchars = mod_camel.chars().count();
+
+ let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
+
+ if matching.char_count == nchars {
+ match item_camel.chars().nth(nchars) {
+ Some(c) if is_word_beginning(c) => span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name starts with its containing module's name",
+ ),
+ _ => (),
+ }
+ }
+ if rmatching.char_count == nchars {
+ span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name ends with its containing module's name",
+ );
+ }
+ }
+ }
+ }
+ }
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id)) {
+ check_variant(cx, self.threshold, def, item_name, item.span);
+ }
+ }
+ self.modules.push((item.ident.name, item_camel));
+ }
+}
--- /dev/null
- use if_chain::if_chain;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::implements_trait;
- if_chain! {
- if !in_external_macro(cx.sess(), expr.span);
- if let ExprKind::Let(let_expr) = expr.kind;
- if unary_pattern(let_expr.pat);
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for pattern matchings that can be expressed using equality.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// * It reads better and has less cognitive load because equality won't cause binding.
+ /// * It is a [Yoda condition](https://en.wikipedia.org/wiki/Yoda_conditions). Yoda conditions are widely
+ /// criticized for increasing the cognitive load of reading the code.
+ /// * Equality is a simple bool expression and can be merged with `&&` and `||` and
+ /// reuse if blocks
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if let Some(2) = x {
+ /// do_thing();
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// if x == Some(2) {
+ /// do_thing();
+ /// }
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub EQUATABLE_IF_LET,
+ nursery,
+ "using pattern matching instead of equality"
+}
+
+declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]);
+
+/// detects if pattern matches just one thing
+fn unary_pattern(pat: &Pat<'_>) -> bool {
+ fn array_rec(pats: &[Pat<'_>]) -> bool {
+ pats.iter().all(unary_pattern)
+ }
+ match &pat.kind {
+ PatKind::Slice(_, _, _) | PatKind::Range(_, _, _) | PatKind::Binding(..) | PatKind::Wild | PatKind::Or(_) => {
+ false
+ },
+ PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)),
+ PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a),
+ PatKind::Ref(x, _) | PatKind::Box(x) => unary_pattern(x),
+ PatKind::Path(_) | PatKind::Lit(_) => true,
+ }
+}
+
+fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> bool {
+ if let Some(def_id) = cx.tcx.lang_items().eq_trait() {
+ implements_trait(cx, ty, def_id, &[other.into()])
+ } else {
+ false
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for PatternEquality {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
- if is_structural_partial_eq(cx, exp_ty, pat_ty);
- then {
++ if !in_external_macro(cx.sess(), expr.span)
++ && let ExprKind::Let(let_expr) = expr.kind
++ && unary_pattern(let_expr.pat) {
+ let exp_ty = cx.typeck_results().expr_ty(let_expr.init);
+ let pat_ty = cx.typeck_results().pat_ty(let_expr.pat);
- let mut applicability = Applicability::MachineApplicable;
++ let mut applicability = Applicability::MachineApplicable;
+
++ if is_structural_partial_eq(cx, exp_ty, pat_ty) {
+ let pat_str = match let_expr.pat.kind {
+ PatKind::Struct(..) => format!(
+ "({})",
+ snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
+ ),
+ _ => snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0.to_string(),
+ };
+ span_lint_and_sugg(
+ cx,
+ EQUATABLE_IF_LET,
+ expr.span,
+ "this pattern matching can be expressed using equality",
+ "try",
+ format!(
+ "{} == {pat_str}",
+ snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
+ ),
+ applicability,
+ );
++ } else {
++ span_lint_and_sugg(
++ cx,
++ EQUATABLE_IF_LET,
++ expr.span,
++ "this pattern matching can be expressed using `matches!`",
++ "try",
++ format!(
++ "matches!({}, {})",
++ snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
++ snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
++ ),
++ applicability,
++ );
+ }
+ }
+ }
+}
--- /dev/null
- fn fake_read(
- &mut self,
- _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
- _: FakeReadCause,
- _: HirId,
- ) {
- }
+use clippy_utils::diagnostics::span_lint_hir;
+use rustc_hir::intravisit;
+use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node, Pat, PatKind};
+use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, TraitRef, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::kw;
+use rustc_target::spec::abi::Abi;
+
+#[derive(Copy, Clone)]
+pub struct BoxedLocal {
+ pub too_large_for_stack: u64,
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `Box<T>` where an unboxed `T` would
+ /// work fine.
+ ///
+ /// ### Why is this bad?
+ /// This is an unnecessary allocation, and bad for
+ /// performance. It is only necessary to allocate if you wish to move the box
+ /// into something.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: Box<u32>) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn foo(x: u32) {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BOXED_LOCAL,
+ perf,
+ "using `Box<T>` where unnecessary"
+}
+
+fn is_non_trait_box(ty: Ty<'_>) -> bool {
+ ty.is_box() && !ty.boxed_ty().is_trait()
+}
+
+struct EscapeDelegate<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ set: HirIdSet,
+ trait_self_ty: Option<Ty<'tcx>>,
+ too_large_for_stack: u64,
+}
+
+impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]);
+
+impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ hir_id: HirId,
+ ) {
+ if let Some(header) = fn_kind.header() {
+ if header.abi != Abi::Rust {
+ return;
+ }
+ }
+
+ let parent_id = cx.tcx.hir().get_parent_item(hir_id).def_id;
+ let parent_node = cx.tcx.hir().find_by_def_id(parent_id);
+
+ let mut trait_self_ty = None;
+ if let Some(Node::Item(item)) = parent_node {
+ // If the method is an impl for a trait, don't warn.
+ if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = item.kind {
+ return;
+ }
+
+ // find `self` ty for this trait if relevant
+ if let ItemKind::Trait(_, _, _, _, items) = item.kind {
+ for trait_item in items {
+ if trait_item.id.hir_id() == hir_id {
+ // be sure we have `self` parameter in this function
+ if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
+ trait_self_ty = Some(
+ TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id())
+ .self_ty()
+ .skip_binder(),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ let mut v = EscapeDelegate {
+ cx,
+ set: HirIdSet::default(),
+ trait_self_ty,
+ too_large_for_stack: self.too_large_for_stack,
+ };
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+ let infcx = cx.tcx.infer_ctxt().build();
+ ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
+
+ for node in v.set {
+ span_lint_hir(
+ cx,
+ BOXED_LOCAL,
+ node,
+ cx.tcx.hir().span(node),
+ "local variable doesn't need to be boxed here",
+ );
+ }
+ }
+}
+
+// TODO: Replace with Map::is_argument(..) when it's fixed
+fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool {
+ match map.find(id) {
+ Some(Node::Pat(Pat {
+ kind: PatKind::Binding(..),
+ ..
+ })) => (),
+ _ => return false,
+ }
+
+ matches!(map.find(map.get_parent_node(id)), Some(Node::Param(_)))
+}
+
+impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
+ fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ if cmt.place.projections.is_empty() {
+ if let PlaceBase::Local(lid) = cmt.place.base {
+ self.set.remove(&lid);
+ }
+ }
+ }
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
+ if cmt.place.projections.is_empty() {
+ if let PlaceBase::Local(lid) = cmt.place.base {
+ self.set.remove(&lid);
+ }
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ if cmt.place.projections.is_empty() {
+ let map = &self.cx.tcx.hir();
+ if is_argument(*map, cmt.hir_id) {
+ // Skip closure arguments
+ let parent_id = map.get_parent_node(cmt.hir_id);
+ if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) {
+ return;
+ }
+
+ // skip if there is a `self` parameter binding to a type
+ // that contains `Self` (i.e.: `self: Box<Self>`), see #4804
+ if let Some(trait_self_ty) = self.trait_self_ty {
+ if map.name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) {
+ return;
+ }
+ }
+
+ if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
+ self.set.insert(cmt.hir_id);
+ }
+ }
+ }
+ }
+
++ fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
+ fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
+ // Large types need to be boxed to avoid stack overflows.
+ if ty.is_box() {
+ self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind};
- use rustc_lint::{EarlyContext, EarlyLintPass};
+use clippy_utils::diagnostics::span_lint_and_help;
- use rustc_span::{sym, Span};
++use clippy_utils::{get_parent_as_impl, has_repr_attr, is_bool};
++use rustc_hir::intravisit::FnKind;
++use rustc_hir::{Body, FnDecl, HirId, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, Ty};
++use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
- fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
- match fn_sig.header.ext {
- Extern::Implicit(_) | Extern::Explicit(_, _) => return,
- Extern::None => (),
++use rustc_span::Span;
++use rustc_target::spec::abi::Abi;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive
+ /// use of bools in structs.
+ ///
+ /// ### Why is this bad?
+ /// Excessive bools in a struct
+ /// is often a sign that it's used as a state machine,
+ /// which is much better implemented as an enum.
+ /// If it's not the case, excessive bools usually benefit
+ /// from refactoring into two-variant enums for better
+ /// readability and API.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S {
+ /// is_pending: bool,
+ /// is_processing: bool,
+ /// is_finished: bool,
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// enum S {
+ /// Pending,
+ /// Processing,
+ /// Finished,
+ /// }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub STRUCT_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in a struct"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive use of
+ /// bools in function definitions.
+ ///
+ /// ### Why is this bad?
+ /// Calls to such functions
+ /// are confusing and error prone, because it's
+ /// hard to remember argument order and you have
+ /// no type system support to back you up. Using
+ /// two-variant enums instead of bools often makes
+ /// API easier to use.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn f(is_round: bool, is_hot: bool) { ... }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// enum Shape {
+ /// Round,
+ /// Spiky,
+ /// }
+ ///
+ /// enum Temperature {
+ /// Hot,
+ /// IceCold,
+ /// }
+ ///
+ /// fn f(shape: Shape, temperature: Temperature) { ... }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub FN_PARAMS_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in function parameters"
+}
+
+pub struct ExcessiveBools {
+ max_struct_bools: u64,
+ max_fn_params_bools: u64,
+}
+
++#[derive(Eq, PartialEq, Debug, Copy, Clone)]
++enum Kind {
++ Struct,
++ Fn,
++}
++
+impl ExcessiveBools {
+ #[must_use]
+ pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
+ Self {
+ max_struct_bools,
+ max_fn_params_bools,
+ }
+ }
+
- let fn_sig_bools = fn_sig
- .decl
- .inputs
- .iter()
- .filter(|param| is_bool_ty(¶m.ty))
- .count()
- .try_into()
- .unwrap();
- if self.max_fn_params_bools < fn_sig_bools {
++ fn too_many_bools<'tcx>(&self, tys: impl Iterator<Item = &'tcx Ty<'tcx>>, kind: Kind) -> bool {
++ if let Ok(bools) = tys.filter(|ty| is_bool(ty)).count().try_into() {
++ (if Kind::Fn == kind {
++ self.max_fn_params_bools
++ } else {
++ self.max_struct_bools
++ }) < bools
++ } else {
++ false
+ }
++ }
+
- fn is_bool_ty(ty: &Ty) -> bool {
- if let TyKind::Path(None, path) = &ty.kind {
- if let [name] = path.segments.as_slice() {
- return name.ident.name == sym::bool;
++ fn check_fn_sig(&self, cx: &LateContext<'_>, fn_decl: &FnDecl<'_>, span: Span) {
++ if !span.from_expansion() && self.too_many_bools(fn_decl.inputs.iter(), Kind::Fn) {
+ span_lint_and_help(
+ cx,
+ FN_PARAMS_EXCESSIVE_BOOLS,
+ span,
+ &format!("more than {} bools in function parameters", self.max_fn_params_bools),
+ None,
+ "consider refactoring bools into two-variant enums",
+ );
+ }
+ }
+}
+
+impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
+
- false
- }
++impl<'tcx> LateLintPass<'tcx> for ExcessiveBools {
++ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
++ if item.span.from_expansion() {
++ return;
++ }
++ if let ItemKind::Struct(variant_data, _) = &item.kind {
++ if has_repr_attr(cx, item.hir_id()) {
++ return;
++ }
++
++ if self.too_many_bools(variant_data.fields().iter().map(|field| field.ty), Kind::Struct) {
++ span_lint_and_help(
++ cx,
++ STRUCT_EXCESSIVE_BOOLS,
++ item.span,
++ &format!("more than {} bools in a struct", self.max_struct_bools),
++ None,
++ "consider using a state machine or refactoring bools into two-variant enums",
++ );
++ }
+ }
+ }
- impl EarlyLintPass for ExcessiveBools {
- fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
- if item.span.from_expansion() {
- return;
+
- match &item.kind {
- ItemKind::Struct(variant_data, _) => {
- if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
- return;
- }
++ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) {
++ // functions with a body are already checked by `check_fn`
++ if let TraitItemKind::Fn(fn_sig, TraitFn::Required(_)) = &trait_item.kind
++ && fn_sig.header.abi == Abi::Rust
++ {
++ self.check_fn_sig(cx, fn_sig.decl, fn_sig.span);
+ }
- let struct_bools = variant_data
- .fields()
- .iter()
- .filter(|field| is_bool_ty(&field.ty))
- .count()
- .try_into()
- .unwrap();
- if self.max_struct_bools < struct_bools {
- span_lint_and_help(
- cx,
- STRUCT_EXCESSIVE_BOOLS,
- item.span,
- &format!("more than {} bools in a struct", self.max_struct_bools),
- None,
- "consider using a state machine or refactoring bools into two-variant enums",
- );
- }
- },
- ItemKind::Impl(box Impl {
- of_trait: None, items, ..
- })
- | ItemKind::Trait(box Trait { items, .. }) => {
- for item in items {
- if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
- self.check_fn_sig(cx, sig, item.span);
- }
- }
- },
- ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
- _ => (),
++ }
+
++ fn check_fn(
++ &mut self,
++ cx: &LateContext<'tcx>,
++ fn_kind: FnKind<'tcx>,
++ fn_decl: &'tcx FnDecl<'tcx>,
++ _: &'tcx Body<'tcx>,
++ span: Span,
++ hir_id: HirId,
++ ) {
++ if let Some(fn_header) = fn_kind.header()
++ && fn_header.abi == Abi::Rust
++ && get_parent_as_impl(cx.tcx, hir_id)
++ .map_or(true,
++ |impl_item| impl_item.of_trait.is_none()
++ )
++ {
++ self.check_fn_sig(cx, fn_decl, span);
+ }
+ }
+}
--- /dev/null
- fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::method_chain_args;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
+ ///
+ /// ### Why is this bad?
+ /// `TryFrom` should be used if there's a possibility of failure.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo(i32);
+ ///
+ /// impl From<String> for Foo {
+ /// fn from(s: String) -> Self {
+ /// Foo(s.parse().unwrap())
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// struct Foo(i32);
+ ///
+ /// impl TryFrom<String> for Foo {
+ /// type Error = ();
+ /// fn try_from(s: String) -> Result<Self, Self::Error> {
+ /// if let Ok(parsed) = s.parse() {
+ /// Ok(Foo(parsed))
+ /// } else {
+ /// Err(())
+ /// }
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FALLIBLE_IMPL_FROM,
+ nursery,
+ "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
+}
+
+declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
+
+impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ // check for `impl From<???> for ..`
+ if_chain! {
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id);
+ if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.def_id);
+ then {
+ lint_impl_body(cx, item.span, impl_.items);
+ }
+ }
+ }
+}
+
++fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
+ use rustc_hir::intravisit::{self, Visitor};
+ use rustc_hir::{Expr, ImplItemKind};
+
+ struct FindPanicUnwrap<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ result: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
+ if is_panic(self.lcx, macro_call.def_id) {
+ self.result.push(expr.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ 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);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ for impl_item in impl_items {
+ if_chain! {
+ if impl_item.ident.name == sym::from;
+ if let ImplItemKind::Fn(_, body_id) =
+ cx.tcx.hir().impl_item(impl_item.id).kind;
+ then {
+ // check the body for `begin_panic` or `unwrap`
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ lcx: cx,
+ typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
+ result: Vec::new(),
+ };
+ fpu.visit_expr(body.value);
+
+ // if we've found one, lint
+ if !fpu.result.is_empty() {
+ span_lint_and_then(
+ cx,
+ FALLIBLE_IMPL_FROM,
+ impl_span,
+ "consider implementing `TryFrom` instead",
+ move |diag| {
+ diag.help(
+ "`From` is intended for infallible conversions only. \
+ Use `TryFrom` if there's a possibility for the conversion to fail");
+ diag.span_note(fpu.result, "potential failure(s)");
+ });
+ }
+ }
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_help;
++use clippy_utils::ty::is_c_void;
++use clippy_utils::{match_def_path, path_def_id, paths};
++use rustc_hir::def_id::DefId;
++use rustc_hir::{Expr, ExprKind, QPath};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::RawPtr;
++use rustc_middle::ty::TypeAndMut;
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::sym;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks if we're passing a `c_void` raw pointer to `{Box,Rc,Arc,Weak}::from_raw(_)`
++ ///
++ /// ### Why is this bad?
++ /// When dealing with `c_void` raw pointers in FFI, it is easy to run into the pitfall of calling `from_raw` with the `c_void` pointer.
++ /// The type signature of `Box::from_raw` is `fn from_raw(raw: *mut T) -> Box<T>`, so if you pass a `*mut c_void` you will get a `Box<c_void>` (and similarly for `Rc`, `Arc` and `Weak`).
++ /// For this to be safe, `c_void` would need to have the same memory layout as the original type, which is often not the case.
++ ///
++ /// ### Example
++ /// ```rust
++ /// # use std::ffi::c_void;
++ /// let ptr = Box::into_raw(Box::new(42usize)) as *mut c_void;
++ /// let _ = unsafe { Box::from_raw(ptr) };
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// # use std::ffi::c_void;
++ /// # let ptr = Box::into_raw(Box::new(42usize)) as *mut c_void;
++ /// let _ = unsafe { Box::from_raw(ptr as *mut usize) };
++ /// ```
++ ///
++ #[clippy::version = "1.66.0"]
++ pub FROM_RAW_WITH_VOID_PTR,
++ suspicious,
++ "creating a `Box` from a void raw pointer"
++}
++declare_lint_pass!(FromRawWithVoidPtr => [FROM_RAW_WITH_VOID_PTR]);
++
++impl LateLintPass<'_> for FromRawWithVoidPtr {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
++ if let ExprKind::Call(box_from_raw, [arg]) = expr.kind
++ && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind
++ && seg.ident.name == sym!(from_raw)
++ && let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id))
++ && let arg_kind = cx.typeck_results().expr_ty(arg).kind()
++ && let RawPtr(TypeAndMut { ty, .. }) = arg_kind
++ && is_c_void(cx, *ty) {
++ let msg = format!("creating a `{type_str}` from a void raw pointer");
++ span_lint_and_help(cx, FROM_RAW_WITH_VOID_PTR, expr.span, &msg, Some(arg.span), "cast this to a pointer of the appropriate type");
++ }
++ }
++}
++
++/// Checks whether a `DefId` matches `Box`, `Rc`, `Arc`, or one of the `Weak` types.
++/// Returns a static string slice with the name of the type, if one was found.
++fn def_id_matches_type(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> {
++ // Box
++ if Some(def_id) == cx.tcx.lang_items().owned_box() {
++ return Some("Box");
++ }
++
++ if let Some(symbol) = cx.tcx.get_diagnostic_name(def_id) {
++ if symbol == sym::Arc {
++ return Some("Arc");
++ } else if symbol == sym::Rc {
++ return Some("Rc");
++ }
++ }
++
++ if match_def_path(cx, def_id, &paths::WEAK_RC) || match_def_path(cx, def_id, &paths::WEAK_ARC) {
++ Some("Weak")
++ } else {
++ None
++ }
++}
--- /dev/null
- #[clippy::version = "1.64.0"]
+mod must_use;
+mod not_unsafe_ptr_arg_deref;
+mod result;
+mod too_many_arguments;
+mod too_many_lines;
+
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions with too many parameters.
+ ///
+ /// ### Why is this bad?
+ /// Functions with lots of parameters are considered bad
+ /// style and reduce readability (“what does the 5th parameter mean?”). Consider
+ /// grouping some parameters into a new type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Color;
+ /// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TOO_MANY_ARGUMENTS,
+ complexity,
+ "functions with too many arguments"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions with a large amount of lines.
+ ///
+ /// ### Why is this bad?
+ /// Functions with a lot of lines are harder to understand
+ /// due to having to look at a larger amount of code to understand what the
+ /// function is doing. Consider splitting the body of the function into
+ /// multiple functions.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn im_too_long() {
+ /// println!("");
+ /// // ... 100 more LoC
+ /// println!("");
+ /// }
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub TOO_MANY_LINES,
+ pedantic,
+ "functions with too many lines"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public functions that dereference raw pointer
+ /// arguments but are not marked `unsafe`.
+ ///
+ /// ### Why is this bad?
+ /// The function should probably be marked `unsafe`, since
+ /// for an arbitrary raw pointer, there is no way of telling for sure if it is
+ /// valid.
+ ///
+ /// ### Known problems
+ /// * It does not check functions recursively so if the pointer is passed to a
+ /// private non-`unsafe` function which does the dereferencing, the lint won't
+ /// trigger.
+ /// * It only checks for arguments whose type are raw pointers, not raw pointers
+ /// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
+ /// `some_argument.get_raw_ptr()`).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// pub fn foo(x: *const u8) {
+ /// println!("{}", unsafe { *x });
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// pub unsafe fn foo(x: *const u8) {
+ /// println!("{}", unsafe { *x });
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NOT_UNSAFE_PTR_ARG_DEREF,
+ correctness,
+ "public functions dereferencing raw pointer arguments but not marked `unsafe`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a `#[must_use]` attribute on
+ /// unit-returning functions and methods.
+ ///
+ /// ### Why is this bad?
+ /// Unit values are useless. The attribute is likely
+ /// a remnant of a refactoring that removed the return type.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// #[must_use]
+ /// fn useless() { }
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub MUST_USE_UNIT,
+ style,
+ "`#[must_use]` attribute on a unit-returning function / method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a `#[must_use]` attribute without
+ /// further information on functions and methods that return a type already
+ /// marked as `#[must_use]`.
+ ///
+ /// ### Why is this bad?
+ /// The attribute isn't needed. Not using the result
+ /// will already be reported. Alternatively, one can add some text to the
+ /// attribute to improve the lint message.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// #[must_use]
+ /// fn double_must_use() -> Result<(), ()> {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub DOUBLE_MUST_USE,
+ style,
+ "`#[must_use]` attribute on a `#[must_use]`-returning function / method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public functions that have no
+ /// `#[must_use]` attribute, but return something not already marked
+ /// must-use, have no mutable arg and mutate no statics.
+ ///
+ /// ### Why is this bad?
+ /// Not bad at all, this lint just shows places where
+ /// you could add the attribute.
+ ///
+ /// ### Known problems
+ /// The lint only checks the arguments for mutable
+ /// types without looking if they are actually changed. On the other hand,
+ /// it also ignores a broad range of potentially interesting side effects,
+ /// because we cannot decide whether the programmer intends the function to
+ /// be called for the side effect or the result. Expect many false
+ /// positives. At least we don't lint if the result type is unit or already
+ /// `#[must_use]`.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// // this could be annotated with `#[must_use]`.
+ /// fn id<T>(t: T) -> T { t }
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub MUST_USE_CANDIDATE,
+ pedantic,
+ "function or method that could take a `#[must_use]` attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public functions that return a `Result`
+ /// with an `Err` type of `()`. It suggests using a custom type that
+ /// implements `std::error::Error`.
+ ///
+ /// ### Why is this bad?
+ /// Unit does not implement `Error` and carries no
+ /// further information about what went wrong.
+ ///
+ /// ### Known problems
+ /// Of course, this lint assumes that `Result` is used
+ /// for a fallible operation (which is after all the intended use). However
+ /// code may opt to (mis)use it as a basic two-variant-enum. In that case,
+ /// the suggestion is misguided, and the code should use a custom enum
+ /// instead.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// pub fn read_u8() -> Result<u8, ()> { Err(()) }
+ /// ```
+ /// should become
+ /// ```rust,should_panic
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// pub struct EndOfStream;
+ ///
+ /// impl fmt::Display for EndOfStream {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// write!(f, "End of Stream")
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for EndOfStream { }
+ ///
+ /// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
+ ///# fn main() {
+ ///# read_u8().unwrap();
+ ///# }
+ /// ```
+ ///
+ /// Note that there are crates that simplify creating the error type, e.g.
+ /// [`thiserror`](https://docs.rs/thiserror).
+ #[clippy::version = "1.49.0"]
+ pub RESULT_UNIT_ERR,
+ style,
+ "public function returning `Result` with an `Err` type of `()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions that return `Result` with an unusually large
+ /// `Err`-variant.
+ ///
+ /// ### Why is this bad?
+ /// A `Result` is at least as large as the `Err`-variant. While we
+ /// expect that variant to be seldomly used, the compiler needs to reserve
+ /// and move that much memory every single time.
+ ///
+ /// ### Known problems
+ /// The size determined by Clippy is platform-dependent.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// pub enum ParseError {
+ /// UnparsedBytes([u8; 512]),
+ /// UnexpectedEof,
+ /// }
+ ///
+ /// // The `Result` has at least 512 bytes, even in the `Ok`-case
+ /// pub fn parse() -> Result<(), ParseError> {
+ /// Ok(())
+ /// }
+ /// ```
+ /// should be
+ /// ```
+ /// pub enum ParseError {
+ /// UnparsedBytes(Box<[u8; 512]>),
+ /// UnexpectedEof,
+ /// }
+ ///
+ /// // The `Result` is slightly larger than a pointer
+ /// pub fn parse() -> Result<(), ParseError> {
+ /// Ok(())
+ /// }
+ /// ```
++ #[clippy::version = "1.65.0"]
+ pub RESULT_LARGE_ERR,
+ perf,
+ "function returning `Result` with large `Err` type"
+}
+
+#[derive(Copy, Clone)]
+pub struct Functions {
+ too_many_arguments_threshold: u64,
+ too_many_lines_threshold: u64,
+ large_error_threshold: u64,
+}
+
+impl Functions {
+ pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self {
+ Self {
+ too_many_arguments_threshold,
+ too_many_lines_threshold,
+ large_error_threshold,
+ }
+ }
+}
+
+impl_lint_pass!(Functions => [
+ TOO_MANY_ARGUMENTS,
+ TOO_MANY_LINES,
+ NOT_UNSAFE_PTR_ARG_DEREF,
+ MUST_USE_UNIT,
+ DOUBLE_MUST_USE,
+ MUST_USE_CANDIDATE,
+ RESULT_UNIT_ERR,
+ RESULT_LARGE_ERR,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Functions {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: intravisit::FnKind<'tcx>,
+ decl: &'tcx hir::FnDecl<'_>,
+ body: &'tcx hir::Body<'_>,
+ span: Span,
+ hir_id: hir::HirId,
+ ) {
+ too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
+ too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
+ not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ must_use::check_item(cx, item);
+ result::check_item(cx, item, self.large_error_threshold);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ must_use::check_impl_item(cx, item);
+ result::check_impl_item(cx, item, self.large_error_threshold);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
+ not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
+ must_use::check_trait_item(cx, item);
+ result::check_trait_item(cx, item, self.large_error_threshold);
+ }
+}
--- /dev/null
- } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none()
+use rustc_ast::ast::Attribute;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::{DefIdSet, LocalDefId};
+use rustc_hir::{self as hir, def::Res, QPath};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::{
+ lint::in_external_macro,
+ ty::{self, Ty},
+};
+use rustc_span::{sym, Span, Symbol};
+
+use clippy_utils::attrs::is_proc_macro;
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_must_use_ty;
+use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{return_ty, trait_ref_of_method};
+
+use core::ops::ControlFlow;
+
+use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
+
+pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
+ let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ if let Some(attr) = attr {
+ check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
+ } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
+ check_must_use_candidate(
+ cx,
+ sig.decl,
+ cx.tcx.hir().body(*body_id),
+ item.span,
+ item.owner_id.def_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this function could have a `#[must_use]` attribute",
+ );
+ }
+ }
+}
+
+pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
+ let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ if let Some(attr) = attr {
+ check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
- is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner.def_id).pat_ty(pat), pat.span, tys)
++ } else if is_public
++ && !is_proc_macro(cx.sess(), attrs)
++ && trait_ref_of_method(cx, item.owner_id.def_id).is_none()
+ {
+ check_must_use_candidate(
+ cx,
+ sig.decl,
+ cx.tcx.hir().body(*body_id),
+ item.span,
+ item.owner_id.def_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this method could have a `#[must_use]` attribute",
+ );
+ }
+ }
+}
+
+pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
+ let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ if let Some(attr) = attr {
+ check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
+ } else if let hir::TraitFn::Provided(eid) = *eid {
+ let body = cx.tcx.hir().body(eid);
+ if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
+ check_must_use_candidate(
+ cx,
+ sig.decl,
+ body,
+ item.span,
+ item.owner_id.def_id,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ "this method could have a `#[must_use]` attribute",
+ );
+ }
+ }
+ }
+}
+
+fn check_needless_must_use(
+ cx: &LateContext<'_>,
+ decl: &hir::FnDecl<'_>,
+ item_id: hir::HirId,
+ item_span: Span,
+ fn_header_span: Span,
+ attr: &Attribute,
+) {
+ if in_external_macro(cx.sess(), item_span) {
+ return;
+ }
+ if returns_unit(decl) {
+ span_lint_and_then(
+ cx,
+ MUST_USE_UNIT,
+ fn_header_span,
+ "this unit-returning function has a `#[must_use]` attribute",
+ |diag| {
+ diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
+ },
+ );
+ } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
+ span_lint_and_help(
+ cx,
+ DOUBLE_MUST_USE,
+ fn_header_span,
+ "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
+ None,
+ "either add some descriptive text or remove the attribute",
+ );
+ }
+}
+
+fn check_must_use_candidate<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: &'tcx hir::FnDecl<'_>,
+ body: &'tcx hir::Body<'_>,
+ item_span: Span,
+ item_id: LocalDefId,
+ fn_span: Span,
+ msg: &str,
+) {
+ if has_mutable_arg(cx, body)
+ || mutates_static(cx, body)
+ || in_external_macro(cx.sess(), item_span)
+ || returns_unit(decl)
+ || !cx.effective_visibilities.is_exported(item_id)
+ || is_must_use_ty(cx, return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(item_id)))
+ {
+ return;
+ }
+ span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
+ if let Some(snippet) = snippet_opt(cx, fn_span) {
+ diag.span_suggestion(
+ fn_span,
+ "add the attribute",
+ format!("#[must_use] {snippet}"),
+ Applicability::MachineApplicable,
+ );
+ }
+ });
+}
+
+fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
+ match decl.output {
+ hir::FnRetTy::DefaultReturn(_) => true,
+ hir::FnRetTy::Return(ty) => match ty.kind {
+ hir::TyKind::Tup(tys) => tys.is_empty(),
+ hir::TyKind::Never => true,
+ _ => false,
+ },
+ }
+}
+
+fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
+ let mut tys = DefIdSet::default();
+ body.params.iter().any(|param| is_mutable_pat(cx, param.pat, &mut tys))
+}
+
+fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet) -> bool {
+ if let hir::PatKind::Wild = pat.kind {
+ return false; // ignore `_` patterns
+ }
+ if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
- fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool {
++ is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner.def_id).pat_ty(pat), tys)
+ } else {
+ false
+ }
+}
+
+static KNOWN_WRAPPER_TYS: &[Symbol] = &[sym::Rc, sym::Arc];
+
- && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
++fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, tys: &mut DefIdSet) -> bool {
+ match *ty.kind() {
+ // 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, cx.param_env)
+ || KNOWN_WRAPPER_TYS
+ .iter()
+ .any(|&sym| cx.tcx.is_diagnostic_item(sym, adt.did()))
- ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),
- ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
++ && substs.types().any(|ty| is_mutable_ty(cx, ty, tys))
+ },
- mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
++ ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, tys)),
++ ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, tys),
+ ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
- && is_mutable_ty(
- cx,
- cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
- arg.span,
- &mut tys,
- )
++ mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, tys)
+ },
+ // calling something constitutes a side effect, so return true on all callables
+ // also never calls need not be used, so return true for them, too
+ _ => true,
+ }
+}
+
+fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
+ use hir::ExprKind::{Field, Index, Path};
+
+ match e.kind {
+ Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
+ Path(_) => true,
+ Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
+ _ => false,
+ }
+}
+
+fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
+ for_each_expr(body.value, |e| {
+ use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
+
+ match e.kind {
+ Call(_, args) => {
+ let mut tys = DefIdSet::default();
+ for arg in args {
+ if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
- && is_mutable_ty(
- cx,
- cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
- arg.span,
- &mut tys,
- )
++ && is_mutable_ty(cx, cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg), &mut tys)
+ && is_mutated_static(arg)
+ {
+ return ControlFlow::Break(());
+ }
+ tys.clear();
+ }
+ ControlFlow::Continue(())
+ },
+ MethodCall(_, receiver, args, _) => {
+ let mut tys = DefIdSet::default();
+ for arg in std::iter::once(receiver).chain(args.iter()) {
+ if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
++ && is_mutable_ty(cx, cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg), &mut tys)
+ && is_mutated_static(arg)
+ {
+ return ControlFlow::Break(());
+ }
+ tys.clear();
+ }
+ ControlFlow::Continue(())
+ },
+ Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target)
+ if is_mutated_static(target) =>
+ {
+ ControlFlow::Break(())
+ },
+ _ => ControlFlow::Continue(()),
+ }
+ })
+ .is_some()
+}
--- /dev/null
- use rustc_middle::ty::{self, Ty};
+use rustc_errors::Diagnostic;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::lint::in_external_macro;
- use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item};
++use rustc_middle::ty::{self, Adt, Ty};
+use rustc_span::{sym, Span};
+
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
+use clippy_utils::trait_ref_of_method;
- let ty_size = approx_ty_size(cx, err_ty);
- if ty_size >= large_err_threshold {
- span_lint_and_then(
- cx,
- RESULT_LARGE_ERR,
- hir_ty_span,
- "the `Err`-variant returned from this function is very large",
- |diag: &mut Diagnostic| {
- diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
- diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
- },
- );
++use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item, AdtVariantInfo};
+
+use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR};
+
+/// The type of the `Err`-variant in a `std::result::Result` returned by the
+/// given `FnDecl`
+fn result_err_ty<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: &hir::FnDecl<'tcx>,
+ id: hir::def_id::LocalDefId,
+ item_span: Span,
+) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> {
+ if !in_external_macro(cx.sess(), item_span)
+ && let hir::FnRetTy::Return(hir_ty) = decl.output
+ && let ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).output())
+ && is_type_diagnostic_item(cx, ty, sym::Result)
+ && let ty::Adt(_, substs) = ty.kind()
+ {
+ let err_ty = substs.type_at(1);
+ Some((hir_ty, err_ty))
+ } else {
+ None
+ }
+}
+
+pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) {
+ if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind
+ && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
+ {
+ if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ check_result_unit_err(cx, err_ty, fn_header_span);
+ }
+ check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
+ }
+}
+
+pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) {
+ // Don't lint if method is a trait's implementation, we can't do anything about those
+ if let hir::ImplItemKind::Fn(ref sig, _) = item.kind
+ && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
+ && trait_ref_of_method(cx, item.owner_id.def_id).is_none()
+ {
+ if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ check_result_unit_err(cx, err_ty, fn_header_span);
+ }
+ check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
+ }
+}
+
+pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) {
+ if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span) {
+ if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
+ check_result_unit_err(cx, err_ty, fn_header_span);
+ }
+ check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
+ }
+ }
+}
+
+fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) {
+ if err_ty.is_unit() {
+ span_lint_and_help(
+ cx,
+ RESULT_UNIT_ERR,
+ fn_header_span,
+ "this returns a `Result<_, ()>`",
+ None,
+ "use a custom `Error` type instead",
+ );
+ }
+}
+
+fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
++ if_chain! {
++ if let Adt(adt, subst) = err_ty.kind();
++ if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local();
++ if let Some(hir::Node::Item(item)) = cx
++ .tcx
++ .hir()
++ .find_by_def_id(local_def_id);
++ if let hir::ItemKind::Enum(ref def, _) = item.kind;
++ then {
++ let variants_size = AdtVariantInfo::new(cx, *adt, subst);
++ if variants_size[0].size >= large_err_threshold {
++ span_lint_and_then(
++ cx,
++ RESULT_LARGE_ERR,
++ hir_ty_span,
++ "the `Err`-variant returned from this function is very large",
++ |diag| {
++ diag.span_label(
++ def.variants[variants_size[0].ind].span,
++ format!("the largest variant contains at least {} bytes", variants_size[0].size),
++ );
++
++ for variant in &variants_size[1..] {
++ if variant.size >= large_err_threshold {
++ let variant_def = &def.variants[variant.ind];
++ diag.span_label(
++ variant_def.span,
++ format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size),
++ );
++ }
++ }
++
++ diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
++ }
++ );
++ }
++ }
++ else {
++ let ty_size = approx_ty_size(cx, err_ty);
++ if ty_size >= large_err_threshold {
++ span_lint_and_then(
++ cx,
++ RESULT_LARGE_ERR,
++ hir_ty_span,
++ "the `Err`-variant returned from this function is very large",
++ |diag: &mut Diagnostic| {
++ diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
++ diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
++ },
++ );
++ }
++ }
+ }
+}
--- /dev/null
- fn suggestion<'tcx>(
- cx: &LateContext<'tcx>,
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+
+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};
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{Ty, TypeckResults};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+use if_chain::if_chain;
+
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public `impl` or `fn` missing generalization
+ /// over different hashers and implicitly defaulting to the default hashing
+ /// algorithm (`SipHash`).
+ ///
+ /// ### Why is this bad?
+ /// `HashMap` or `HashSet` with custom hashers cannot be
+ /// used with them.
+ ///
+ /// ### Known problems
+ /// Suggestions for replacing constructors can contain
+ /// false-positives. Also applying suggestions can require modification of other
+ /// pieces of code, possibly including external crates.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # use std::hash::{Hash, BuildHasher};
+ /// # trait Serialize {};
+ /// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
+ ///
+ /// pub fn foo(map: &mut HashMap<i32, i32>) { }
+ /// ```
+ /// could be rewritten as
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # use std::hash::{Hash, BuildHasher};
+ /// # trait Serialize {};
+ /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
+ ///
+ /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub IMPLICIT_HASHER,
+ pedantic,
+ "missing generalization over different hashers"
+}
+
+declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
+ #[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ use rustc_span::BytePos;
+
++ fn suggestion(
++ cx: &LateContext<'_>,
+ diag: &mut Diagnostic,
+ generics_span: Span,
+ generics_suggestion_span: Span,
+ target: &ImplicitHasherType<'_>,
+ vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
+ ) {
+ let generics_snip = snippet(cx, generics_span, "");
+ // trim `<` `>`
+ let generics_snip = if generics_snip.is_empty() {
+ ""
+ } else {
+ &generics_snip[1..generics_snip.len() - 1]
+ };
+
+ multispan_sugg(
+ diag,
+ "consider adding a type parameter",
+ vec![
+ (
+ generics_suggestion_span,
+ format!(
+ "<{generics_snip}{}S: ::std::hash::BuildHasher{}>",
+ if generics_snip.is_empty() { "" } else { ", " },
+ if vis.suggestions.is_empty() {
+ ""
+ } else {
+ // request users to add `Default` bound so that generic constructors can be used
+ " + Default"
+ },
+ ),
+ ),
+ (
+ target.span(),
+ format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
+ ),
+ ],
+ );
+
+ if !vis.suggestions.is_empty() {
+ multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
+ }
+ }
+
+ if !cx.effective_visibilities.is_exported(item.owner_id.def_id) {
+ return;
+ }
+
+ match item.kind {
+ ItemKind::Impl(impl_) => {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(impl_.self_ty);
+
+ for target in &vis.found {
+ if item.span.ctxt() != target.span().ctxt() {
+ return;
+ }
+
+ let generics_suggestion_span = impl_.generics.span.substitute_dummy({
+ let pos = snippet_opt(cx, item.span.until(target.span()))
+ .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
+ if let Some(pos) = pos {
+ Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+ } else {
+ return;
+ }
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
+ ctr_vis.visit_impl_item(item);
+ }
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "impl for `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ },
+ ItemKind::Fn(ref sig, generics, body_id) => {
+ let body = cx.tcx.hir().body(body_id);
+
+ for ty in sig.decl.inputs {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(ty);
+
+ for target in &vis.found {
+ if in_external_macro(cx.sess(), generics.span) {
+ continue;
+ }
+ let generics_suggestion_span = generics.span.substitute_dummy({
+ let pos = snippet_opt(
+ cx,
+ Span::new(
+ item.span.lo(),
+ body.params[0].pat.span.lo(),
+ item.span.ctxt(),
+ item.span.parent(),
+ ),
+ )
+ .and_then(|snip| {
+ let i = snip.find("fn")?;
+ Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32))
+ })
+ .expect("failed to create span for type parameters");
+ Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ ctr_vis.visit_body(body);
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "parameter of type `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+enum ImplicitHasherType<'tcx> {
+ HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
+ HashSet(Span, Ty<'tcx>, Cow<'static, str>),
+}
+
+impl<'tcx> ImplicitHasherType<'tcx> {
+ /// Checks that `ty` is a target type without a `BuildHasher`.
+ fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
+ if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
+ let params: Vec<_> = path
+ .segments
+ .last()
+ .as_ref()?
+ .args
+ .as_ref()?
+ .args
+ .iter()
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ .collect();
+ let params_len = params.len();
+
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) && params_len == 2 {
+ Some(ImplicitHasherType::HashMap(
+ hir_ty.span,
+ ty,
+ snippet(cx, params[0].span, "K"),
+ snippet(cx, params[1].span, "V"),
+ ))
+ } else if is_type_diagnostic_item(cx, ty, sym::HashSet) && params_len == 1 {
+ Some(ImplicitHasherType::HashSet(
+ hir_ty.span,
+ ty,
+ snippet(cx, params[0].span, "T"),
+ ))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ fn type_name(&self) -> &'static str {
+ match *self {
+ ImplicitHasherType::HashMap(..) => "HashMap",
+ ImplicitHasherType::HashSet(..) => "HashSet",
+ }
+ }
+
+ fn type_arguments(&self) -> String {
+ match *self {
+ ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{k}, {v}"),
+ ImplicitHasherType::HashSet(.., ref t) => format!("{t}"),
+ }
+ }
+
+ fn ty(&self) -> Ty<'tcx> {
+ match *self {
+ ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
+ }
+ }
+
+ fn span(&self) -> Span {
+ match *self {
+ ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
+ }
+ }
+}
+
+struct ImplicitHasherTypeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ found: Vec<ImplicitHasherType<'tcx>>,
+}
+
+impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self { cx, found: vec![] }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
+ fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
+ if let Some(target) = ImplicitHasherType::new(self.cx, t) {
+ self.found.push(target);
+ }
+
+ walk_ty(self, t);
+ }
+
+ fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
+ if let Some(target) = ImplicitHasherType::new(self.cx, &inf.to_ty()) {
+ self.found.push(target);
+ }
+
+ walk_inf(self, inf);
+ }
+}
+
+/// Looks for default-hasher-dependent constructors like `HashMap::new`.
+struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ target: &'b ImplicitHasherType<'tcx>,
+ suggestions: BTreeMap<Span, String>,
+}
+
+impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ target,
+ suggestions: BTreeMap::new(),
+ }
+ }
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_body(&mut self, body: &'tcx Body<'_>) {
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
+ walk_body(self, body);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = e.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
+ if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
+ if let Some(ty_did) = ty_path.res.opt_def_id();
+ then {
+ if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
+ return;
+ }
+
+ if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
+ if method.ident.name == sym::new {
+ self.suggestions
+ .insert(e.span, "HashMap::default()".to_string());
+ } else if method.ident.name == sym!(with_capacity) {
+ self.suggestions.insert(
+ e.span,
+ format!(
+ "HashMap::with_capacity_and_hasher({}, Default::default())",
+ snippet(self.cx, args[0].span, "capacity"),
+ ),
+ );
+ }
+ } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
+ if method.ident.name == sym::new {
+ self.suggestions
+ .insert(e.span, "HashSet::default()".to_string());
+ } else if method.ident.name == sym!(with_capacity) {
+ self.suggestions.insert(
+ e.span,
+ format!(
+ "HashSet::with_capacity_and_hasher({}, Default::default())",
+ snippet(self.cx, args[0].span, "capacity"),
+ ),
+ );
+ }
+ }
+ }
+ }
+
+ walk_expr(self, e);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- fn filter_lintable_slices<'a, 'tcx>(
- cx: &'a LateContext<'tcx>,
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::IfLet;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local};
+use if_chain::if_chain;
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::Ident, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// The lint checks for slice bindings in patterns that are only used to
+ /// access individual slice values.
+ ///
+ /// ### Why is this bad?
+ /// Accessing slice values using indices can lead to panics. Using refutable
+ /// patterns can avoid these. Binding to individual values also improves the
+ /// readability as they can be named.
+ ///
+ /// ### Limitations
+ /// This lint currently only checks for immutable access inside `if let`
+ /// patterns.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ ///
+ /// if let Some(slice) = slice {
+ /// println!("{}", slice[0]);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ ///
+ /// if let Some(&[first, ..]) = slice {
+ /// println!("{}", first);
+ /// }
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub INDEX_REFUTABLE_SLICE,
+ nursery,
+ "avoid indexing on slices which could be destructed"
+}
+
+#[derive(Copy, Clone)]
+pub struct IndexRefutableSlice {
+ max_suggested_slice: u64,
+ msrv: Option<RustcVersion>,
+}
+
+impl IndexRefutableSlice {
+ pub fn new(max_suggested_slice_pattern_length: u64, msrv: Option<RustcVersion>) -> Self {
+ Self {
+ max_suggested_slice: max_suggested_slice_pattern_length,
+ msrv,
+ }
+ }
+}
+
+impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
+
+impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
+ if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
+ if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
+ if meets_msrv(self.msrv, msrvs::SLICE_PATTERNS);
+
+ let found_slices = find_slice_values(cx, let_pat);
+ if !found_slices.is_empty();
+ let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
+ if !filtered_slices.is_empty();
+ then {
+ for slice in filtered_slices.values() {
+ lint_slice(cx, slice);
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap<hir::HirId, SliceLintInformation> {
+ let mut removed_pat: FxHashSet<hir::HirId> = FxHashSet::default();
+ let mut slices: FxIndexMap<hir::HirId, SliceLintInformation> = FxIndexMap::default();
+ pat.walk_always(|pat| {
+ // We'll just ignore mut and ref mut for simplicity sake right now
+ if let hir::PatKind::Binding(
+ hir::BindingAnnotation(by_ref, hir::Mutability::Not),
+ value_hir_id,
+ ident,
+ sub_pat,
+ ) = pat.kind
+ {
+ // This block catches bindings with sub patterns. It would be hard to build a correct suggestion
+ // for them and it's likely that the user knows what they are doing in such a case.
+ if removed_pat.contains(&value_hir_id) {
+ return;
+ }
+ if sub_pat.is_some() {
+ removed_pat.insert(value_hir_id);
+ slices.remove(&value_hir_id);
+ return;
+ }
+
+ let bound_ty = cx.typeck_results().node_type(pat.hir_id);
+ if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() {
+ // The values need to use the `ref` keyword if they can't be copied.
+ // This will need to be adjusted if the lint want to support mutable access in the future
+ let src_is_ref = bound_ty.is_ref() && by_ref != hir::ByRef::Yes;
+ let needs_ref = !(src_is_ref || is_copy(cx, *inner_ty));
+
+ let slice_info = slices
+ .entry(value_hir_id)
+ .or_insert_with(|| SliceLintInformation::new(ident, needs_ref));
+ slice_info.pattern_spans.push(pat.span);
+ }
+ }
+ });
+
+ slices
+}
+
+fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
+ let used_indices = slice
+ .index_use
+ .iter()
+ .map(|(index, _)| *index)
+ .collect::<FxHashSet<_>>();
+
+ let value_name = |index| format!("{}_{index}", slice.ident.name);
+
+ if let Some(max_index) = used_indices.iter().max() {
+ let opt_ref = if slice.needs_ref { "ref " } else { "" };
+ let pat_sugg_idents = (0..=*max_index)
+ .map(|index| {
+ if used_indices.contains(&index) {
+ format!("{opt_ref}{}", value_name(index))
+ } else {
+ "_".to_string()
+ }
+ })
+ .collect::<Vec<_>>();
+ let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", "));
+
+ span_lint_and_then(
+ cx,
+ INDEX_REFUTABLE_SLICE,
+ slice.ident.span,
+ "this binding can be a slice pattern to avoid indexing",
+ |diag| {
+ diag.multipart_suggestion(
+ "try using a slice pattern here",
+ slice
+ .pattern_spans
+ .iter()
+ .map(|span| (*span, pat_sugg.clone()))
+ .collect(),
+ Applicability::MaybeIncorrect,
+ );
+
+ diag.multipart_suggestion(
+ "and replace the index expressions here",
+ slice
+ .index_use
+ .iter()
+ .map(|(index, span)| (*span, value_name(*index)))
+ .collect(),
+ Applicability::MaybeIncorrect,
+ );
+
+ // The lint message doesn't contain a warning about the removed index expression,
+ // since `filter_lintable_slices` will only return slices where all access indices
+ // are known at compile time. Therefore, they can be removed without side effects.
+ },
+ );
+ }
+}
+
+#[derive(Debug)]
+struct SliceLintInformation {
+ ident: Ident,
+ needs_ref: bool,
+ pattern_spans: Vec<Span>,
+ index_use: Vec<(u64, Span)>,
+}
+
+impl SliceLintInformation {
+ fn new(ident: Ident, needs_ref: bool) -> Self {
+ Self {
+ ident,
+ needs_ref,
+ pattern_spans: Vec::new(),
+ index_use: Vec::new(),
+ }
+ }
+}
+
++fn filter_lintable_slices<'tcx>(
++ cx: &LateContext<'tcx>,
+ slice_lint_info: FxIndexMap<hir::HirId, SliceLintInformation>,
+ max_suggested_slice: u64,
+ scope: &'tcx hir::Expr<'tcx>,
+) -> FxIndexMap<hir::HirId, SliceLintInformation> {
+ let mut visitor = SliceIndexLintingVisitor {
+ cx,
+ slice_lint_info,
+ max_suggested_slice,
+ };
+
+ intravisit::walk_expr(&mut visitor, scope);
+
+ visitor.slice_lint_info
+}
+
+struct SliceIndexLintingVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ slice_lint_info: FxIndexMap<hir::HirId, SliceLintInformation>,
+ max_suggested_slice: u64,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if let Some(local_id) = path_to_local(expr) {
+ let Self {
+ cx,
+ ref mut slice_lint_info,
+ max_suggested_slice,
+ } = *self;
+
+ if_chain! {
+ // Check if this is even a local we're interested in
+ if let Some(use_info) = slice_lint_info.get_mut(&local_id);
+
+ let map = cx.tcx.hir();
+
+ // Checking for slice indexing
+ let parent_id = map.get_parent_node(expr.hir_id);
+ if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
+ if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind;
+ if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr);
+ if let Ok(index_value) = index_value.try_into();
+ if index_value < max_suggested_slice;
+
+ // Make sure that this slice index is read only
+ let maybe_addrof_id = map.get_parent_node(parent_id);
+ if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
+ if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
+ then {
+ use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
+ return;
+ }
+ }
+
+ // The slice was used for something other than indexing
+ self.slice_lint_info.remove(&local_id);
+ }
+ intravisit::walk_expr(self, expr);
+ }
+}
--- /dev/null
- fn to_const_range<'tcx>(
- cx: &LateContext<'tcx>,
- range: higher::Range<'_>,
- array_size: u128,
- ) -> (Option<u128>, Option<u128>) {
+//! lint on indexing and slicing operations
+
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::higher;
+use rustc_ast::ast::RangeLimits;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for out of bounds array indexing with a constant
+ /// index.
+ ///
+ /// ### Why is this bad?
+ /// This will always panic at runtime.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// let x = [1, 2, 3, 4];
+ ///
+ /// x[9];
+ /// &x[2..9];
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = [1, 2, 3, 4];
+ /// // Index within bounds
+ ///
+ /// x[0];
+ /// x[3];
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OUT_OF_BOUNDS_INDEXING,
+ correctness,
+ "out of bounds constant indexing"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of indexing or slicing. Arrays are special cases, this lint
+ /// does report on arrays if we can tell that slicing operations are in bounds and does not
+ /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint.
+ ///
+ /// ### Why is this bad?
+ /// Indexing and slicing can panic at runtime and there are
+ /// safe alternatives.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// // Vector
+ /// let x = vec![0; 5];
+ ///
+ /// x[2];
+ /// &x[2..100];
+ ///
+ /// // Array
+ /// let y = [0, 1, 2, 3];
+ ///
+ /// &y[10..100];
+ /// &y[10..];
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #![allow(unused)]
+ ///
+ /// # let x = vec![0; 5];
+ /// # let y = [0, 1, 2, 3];
+ /// x.get(2);
+ /// x.get(2..100);
+ ///
+ /// y.get(10);
+ /// y.get(10..100);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INDEXING_SLICING,
+ restriction,
+ "indexing/slicing usage"
+}
+
+declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]);
+
+impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if cx.tcx.hir().is_inside_const_context(expr.hir_id) {
+ return;
+ }
+
+ if let ExprKind::Index(array, index) = &expr.kind {
+ let ty = cx.typeck_results().expr_ty(array).peel_refs();
+ if let Some(range) = higher::Range::hir(index) {
+ // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
+ if let ty::Array(_, s) = ty.kind() {
+ let size: u128 = if let Some(size) = s.try_eval_usize(cx.tcx, cx.param_env) {
+ size.into()
+ } else {
+ return;
+ };
+
+ let const_range = to_const_range(cx, range, size);
+
+ if let (Some(start), _) = const_range {
+ if start > size {
+ span_lint(
+ cx,
+ OUT_OF_BOUNDS_INDEXING,
+ range.start.map_or(expr.span, |start| start.span),
+ "range is out of bounds",
+ );
+ return;
+ }
+ }
+
+ if let (_, Some(end)) = const_range {
+ if end > size {
+ span_lint(
+ cx,
+ OUT_OF_BOUNDS_INDEXING,
+ range.end.map_or(expr.span, |end| end.span),
+ "range is out of bounds",
+ );
+ return;
+ }
+ }
+
+ if let (Some(_), Some(_)) = const_range {
+ // early return because both start and end are constants
+ // and we have proven above that they are in bounds
+ return;
+ }
+ }
+
+ let help_msg = match (range.start, range.end) {
+ (None, Some(_)) => "consider using `.get(..n)`or `.get_mut(..n)` instead",
+ (Some(_), None) => "consider using `.get(n..)` or .get_mut(n..)` instead",
+ (Some(_), Some(_)) => "consider using `.get(n..m)` or `.get_mut(n..m)` instead",
+ (None, None) => return, // [..] is ok.
+ };
+
+ span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic", None, help_msg);
+ } else {
+ // Catchall non-range index, i.e., [n] or [n << m]
+ if let ty::Array(..) = ty.kind() {
+ // Index is a const block.
+ if let ExprKind::ConstBlock(..) = index.kind {
+ return;
+ }
+ // Index is a constant uint.
+ if let Some(..) = constant(cx, cx.typeck_results(), index) {
+ // Let rustc's `const_err` lint handle constant `usize` indexing on arrays.
+ return;
+ }
+ }
+
+ span_lint_and_help(
+ cx,
+ INDEXING_SLICING,
+ expr.span,
+ "indexing may panic",
+ None,
+ "consider using `.get(n)` or `.get_mut(n)` instead",
+ );
+ }
+ }
+ }
+}
+
+/// Returns a tuple of options with the start and end (exclusive) values of
+/// the range. If the start or end is not constant, None is returned.
++fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) {
+ let s = range
+ .start
+ .map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
+ let start = match s {
+ Some(Some(Constant::Int(x))) => Some(x),
+ Some(_) => None,
+ None => Some(0),
+ };
+
+ let e = range
+ .end
+ .map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
+ let end = match e {
+ Some(Some(Constant::Int(x))) => {
+ if range.limits == RangeLimits::Closed {
+ Some(x + 1)
+ } else {
+ Some(x)
+ }
+ },
+ Some(_) => None,
+ None => Some(array_size),
+ };
+
+ (start, end)
+}
--- /dev/null
--- /dev/null
++use clippy_utils::{
++ diagnostics::{self, span_lint_and_sugg},
++ meets_msrv, msrvs, source,
++ sugg::Sugg,
++ ty,
++};
++use rustc_errors::Applicability;
++use rustc_hir::{BinOpKind, Expr, ExprKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{source_map::Spanned, sym};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Lints subtraction between `Instant::now()` and another `Instant`.
++ ///
++ /// ### Why is this bad?
++ /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
++ /// as `Instant` subtraction saturates.
++ ///
++ /// `prev_instant.elapsed()` also more clearly signals intention.
++ ///
++ /// ### Example
++ /// ```rust
++ /// use std::time::Instant;
++ /// let prev_instant = Instant::now();
++ /// let duration = Instant::now() - prev_instant;
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// use std::time::Instant;
++ /// let prev_instant = Instant::now();
++ /// let duration = prev_instant.elapsed();
++ /// ```
++ #[clippy::version = "1.65.0"]
++ pub MANUAL_INSTANT_ELAPSED,
++ pedantic,
++ "subtraction between `Instant::now()` and previous `Instant`"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Lints subtraction between an [`Instant`] and a [`Duration`].
++ ///
++ /// ### Why is this bad?
++ /// Unchecked subtraction could cause underflow on certain platforms, leading to
++ /// unintentional panics.
++ ///
++ /// ### Example
++ /// ```rust
++ /// # use std::time::{Instant, Duration};
++ /// let time_passed = Instant::now() - Duration::from_secs(5);
++ /// ```
++ ///
++ /// Use instead:
++ /// ```rust
++ /// # use std::time::{Instant, Duration};
++ /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
++ /// ```
++ ///
++ /// [`Duration`]: std::time::Duration
++ /// [`Instant::now()`]: std::time::Instant::now;
++ #[clippy::version = "1.65.0"]
++ pub UNCHECKED_DURATION_SUBTRACTION,
++ suspicious,
++ "finds unchecked subtraction of a 'Duration' from an 'Instant'"
++}
++
++pub struct InstantSubtraction {
++ msrv: Option<RustcVersion>,
++}
++
++impl InstantSubtraction {
++ #[must_use]
++ pub fn new(msrv: Option<RustcVersion>) -> Self {
++ Self { msrv }
++ }
++}
++
++impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]);
++
++impl LateLintPass<'_> for InstantSubtraction {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
++ if let ExprKind::Binary(
++ Spanned {
++ node: BinOpKind::Sub, ..
++ },
++ lhs,
++ rhs,
++ ) = expr.kind
++ {
++ if_chain! {
++ if is_instant_now_call(cx, lhs);
++
++ if is_an_instant(cx, rhs);
++ if let Some(sugg) = Sugg::hir_opt(cx, rhs);
++
++ then {
++ print_manual_instant_elapsed_sugg(cx, expr, sugg)
++ } else {
++ if_chain! {
++ if !expr.span.from_expansion();
++ if meets_msrv(self.msrv, msrvs::TRY_FROM);
++
++ if is_an_instant(cx, lhs);
++ if is_a_duration(cx, rhs);
++
++ then {
++ print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
++ }
++ }
++ }
++ }
++ }
++ }
++
++ extract_msrv_attr!(LateContext);
++}
++
++fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
++ if let ExprKind::Call(fn_expr, []) = expr_block.kind
++ && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
++ && clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
++ {
++ true
++ } else {
++ false
++ }
++}
++
++fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
++ let expr_ty = cx.typeck_results().expr_ty(expr);
++
++ match expr_ty.kind() {
++ rustc_middle::ty::Adt(def, _) => clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT),
++ _ => false,
++ }
++}
++
++fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
++ let expr_ty = cx.typeck_results().expr_ty(expr);
++ ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration)
++}
++
++fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
++ span_lint_and_sugg(
++ cx,
++ MANUAL_INSTANT_ELAPSED,
++ expr.span,
++ "manual implementation of `Instant::elapsed`",
++ "try",
++ format!("{}.elapsed()", sugg.maybe_par()),
++ Applicability::MachineApplicable,
++ );
++}
++
++fn print_unchecked_duration_subtraction_sugg(
++ cx: &LateContext<'_>,
++ left_expr: &Expr<'_>,
++ right_expr: &Expr<'_>,
++ expr: &Expr<'_>,
++) {
++ let mut applicability = Applicability::MachineApplicable;
++
++ let left_expr =
++ source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability);
++ let right_expr = source::snippet_with_applicability(
++ cx,
++ right_expr.span,
++ "std::time::Duration::from_secs(1)",
++ &mut applicability,
++ );
++
++ diagnostics::span_lint_and_sugg(
++ cx,
++ UNCHECKED_DURATION_SUBTRACTION,
++ expr.span,
++ "unchecked subtraction of a 'Duration' from an 'Instant'",
++ "try",
++ format!("{left_expr}.checked_sub({right_expr}).unwrap()"),
++ applicability,
++ );
++}
--- /dev/null
- (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
+//! lint on blocks unnecessarily using >= with a + 1 or - 1
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind};
+use rustc_ast::token;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block
+ ///
+ /// ### Why is this bad?
+ /// Readability -- better to use `> y` instead of `>= y + 1`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x >= y + 1 {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x > y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INT_PLUS_ONE,
+ complexity,
+ "instead of using `x >= y + 1`, use `x > y`"
+}
+
+declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]);
+
+// cases:
+// BinOpKind::Ge
+// x >= y + 1
+// x - 1 >= y
+//
+// BinOpKind::Le
+// x + 1 <= y
+// x <= y - 1
+
+#[derive(Copy, Clone)]
+enum Side {
+ Lhs,
+ Rhs,
+}
+
+impl IntPlusOne {
+ #[expect(clippy::cast_sign_loss)]
+ fn check_lit(token_lit: token::Lit, target_value: i128) -> bool {
+ if let Ok(LitKind::Int(value, ..)) = LitKind::from_token_lit(token_lit) {
+ return value == (target_value as u128);
+ }
+ false
+ }
+
+ fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option<String> {
+ match (binop, &lhs.kind, &rhs.kind) {
+ // case where `x - 1 >= ...` or `-1 + x >= ...`
- (BinOpKind::Add, &ExprKind::Lit(lit), _) if Self::check_lit(lit, -1) => {
++ (BinOpKind::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => {
+ match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
+ // `-1 + x`
- (BinOpKind::Sub, _, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
++ (BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ // `x - 1`
- (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs))
- if rhskind.node == BinOpKind::Add =>
- {
++ (BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y + 1` or `... >= 1 + y`
- (&ExprKind::Lit(lit), _) if Self::check_lit(lit, 1) => {
++ (BinOpKind::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => {
+ match (&rhslhs.kind, &rhsrhs.kind) {
+ // `y + 1` and `1 + y`
- (_, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
++ (ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
- (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _)
- if lhskind.node == BinOpKind::Add =>
- {
++ (_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `x + 1 <= ...` or `1 + x <= ...`
- (&ExprKind::Lit(lit), _) if Self::check_lit(lit, 1) => {
++ (BinOpKind::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => {
+ match (&lhslhs.kind, &lhsrhs.kind) {
+ // `1 + x` and `x + 1`
- (_, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
++ (ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
- (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
++ (_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y - 1` or `... >= -1 + y`
- (BinOpKind::Add, &ExprKind::Lit(lit), _) if Self::check_lit(lit, -1) => {
++ (BinOpKind::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => {
+ match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
+ // `-1 + y`
- (BinOpKind::Sub, _, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
++ (BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ // `y - 1`
++ (BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+ }
+
+ fn generate_recommendation(
+ cx: &EarlyContext<'_>,
+ binop: BinOpKind,
+ node: &Expr,
+ other_side: &Expr,
+ side: Side,
+ ) -> Option<String> {
+ let binop_string = match binop {
+ BinOpKind::Ge => ">",
+ BinOpKind::Le => "<",
+ _ => return None,
+ };
+ if let Some(snippet) = snippet_opt(cx, node.span) {
+ if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) {
+ let rec = match side {
+ Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
+ Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
+ };
+ return rec;
+ }
+ }
+ None
+ }
+
+ fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
+ span_lint_and_sugg(
+ cx,
+ INT_PLUS_ONE,
+ block.span,
+ "unnecessary `>= y + 1` or `x - 1 >=`",
+ "change it to",
+ recommendation,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+}
+
+impl EarlyLintPass for IntPlusOne {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
+ if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
+ if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
+ Self::emit_warning(cx, item, rec);
+ }
+ }
+ }
+}
--- /dev/null
- fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> {
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, IntTy, UintTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+use clippy_utils::comparisons;
+use clippy_utils::comparisons::Rel;
+use clippy_utils::consts::{constant_full_int, FullInt};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparisons where the relation is always either
+ /// true or false, but where one side has been upcast so that the comparison is
+ /// necessary. Only integer types are checked.
+ ///
+ /// ### Why is this bad?
+ /// An expression like `let x : u8 = ...; (x as u32) > 300`
+ /// will mistakenly imply that it is possible for `x` to be outside the range of
+ /// `u8`.
+ ///
+ /// ### Known problems
+ /// https://github.com/rust-lang/rust-clippy/issues/886
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u8 = 1;
+ /// (x as u32) > 300;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INVALID_UPCAST_COMPARISONS,
+ pedantic,
+ "a comparison involving an upcast which is always true or false"
+}
+
+declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]);
+
++fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(FullInt, FullInt)> {
+ if let ExprKind::Cast(cast_exp, _) = expr.kind {
+ let pre_cast_ty = cx.typeck_results().expr_ty(cast_exp);
+ let cast_ty = cx.typeck_results().expr_ty(expr);
+ // if it's a cast from i32 to u32 wrapping will invalidate all these checks
+ if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) {
+ return None;
+ }
+ match pre_cast_ty.kind() {
+ ty::Int(int_ty) => Some(match int_ty {
+ IntTy::I8 => (FullInt::S(i128::from(i8::MIN)), FullInt::S(i128::from(i8::MAX))),
+ IntTy::I16 => (FullInt::S(i128::from(i16::MIN)), FullInt::S(i128::from(i16::MAX))),
+ IntTy::I32 => (FullInt::S(i128::from(i32::MIN)), FullInt::S(i128::from(i32::MAX))),
+ IntTy::I64 => (FullInt::S(i128::from(i64::MIN)), FullInt::S(i128::from(i64::MAX))),
+ IntTy::I128 => (FullInt::S(i128::MIN), FullInt::S(i128::MAX)),
+ IntTy::Isize => (FullInt::S(isize::MIN as i128), FullInt::S(isize::MAX as i128)),
+ }),
+ ty::Uint(uint_ty) => Some(match uint_ty {
+ UintTy::U8 => (FullInt::U(u128::from(u8::MIN)), FullInt::U(u128::from(u8::MAX))),
+ UintTy::U16 => (FullInt::U(u128::from(u16::MIN)), FullInt::U(u128::from(u16::MAX))),
+ UintTy::U32 => (FullInt::U(u128::from(u32::MIN)), FullInt::U(u128::from(u32::MAX))),
+ UintTy::U64 => (FullInt::U(u128::from(u64::MIN)), FullInt::U(u128::from(u64::MAX))),
+ UintTy::U128 => (FullInt::U(u128::MIN), FullInt::U(u128::MAX)),
+ UintTy::Usize => (FullInt::U(usize::MIN as u128), FullInt::U(usize::MAX as u128)),
+ }),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
+
+fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
+ if let ExprKind::Cast(cast_val, _) = expr.kind {
+ span_lint(
+ cx,
+ INVALID_UPCAST_COMPARISONS,
+ span,
+ &format!(
+ "because of the numeric bounds on `{}` prior to casting, this expression is always {}",
+ snippet(cx, cast_val.span, "the expression"),
+ if always { "true" } else { "false" },
+ ),
+ );
+ }
+}
+
+fn upcast_comparison_bounds_err<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ rel: comparisons::Rel,
+ lhs_bounds: Option<(FullInt, FullInt)>,
+ lhs: &'tcx Expr<'_>,
+ rhs: &'tcx Expr<'_>,
+ invert: bool,
+) {
+ if let Some((lb, ub)) = lhs_bounds {
+ if let Some(norm_rhs_val) = constant_full_int(cx, cx.typeck_results(), rhs) {
+ if rel == Rel::Eq || rel == Rel::Ne {
+ if norm_rhs_val < lb || norm_rhs_val > ub {
+ err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
+ }
+ } else if match rel {
+ Rel::Lt => {
+ if invert {
+ norm_rhs_val < lb
+ } else {
+ ub < norm_rhs_val
+ }
+ },
+ Rel::Le => {
+ if invert {
+ norm_rhs_val <= lb
+ } else {
+ ub <= norm_rhs_val
+ }
+ },
+ Rel::Eq | Rel::Ne => unreachable!(),
+ } {
+ err_upcast_comparison(cx, span, lhs, true);
+ } else if match rel {
+ Rel::Lt => {
+ if invert {
+ norm_rhs_val >= ub
+ } else {
+ lb >= norm_rhs_val
+ }
+ },
+ Rel::Le => {
+ if invert {
+ norm_rhs_val > ub
+ } else {
+ lb > norm_rhs_val
+ }
+ },
+ Rel::Eq | Rel::Ne => unreachable!(),
+ } {
+ err_upcast_comparison(cx, span, lhs, false);
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
+ let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs);
+ let Some((rel, normalized_lhs, normalized_rhs)) = normalized else {
+ return;
+ };
+
+ let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs);
+ let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs);
+
+ upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false);
+ upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true);
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{diagnostics::span_lint_and_then, ty::approx_ty_size, ty::is_copy};
+//! lint when there is a large size difference between variants on an enum
+
+use clippy_utils::source::snippet_with_applicability;
- use rustc_middle::ty::{Adt, AdtDef, GenericArg, List, Ty};
++use clippy_utils::{
++ diagnostics::span_lint_and_then,
++ ty::{approx_ty_size, is_copy, AdtVariantInfo},
++};
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
- struct FieldInfo {
- ind: usize,
- size: u64,
- }
-
- struct VariantInfo {
- ind: usize,
- size: u64,
- fields_size: Vec<FieldInfo>,
- }
-
- fn variants_size<'tcx>(
- cx: &LateContext<'tcx>,
- adt: AdtDef<'tcx>,
- subst: &'tcx List<GenericArg<'tcx>>,
- ) -> Vec<VariantInfo> {
- let mut variants_size = adt
- .variants()
- .iter()
- .enumerate()
- .map(|(i, variant)| {
- let mut fields_size = variant
- .fields
- .iter()
- .enumerate()
- .map(|(i, f)| FieldInfo {
- ind: i,
- size: approx_ty_size(cx, f.ty(cx.tcx, subst)),
- })
- .collect::<Vec<_>>();
- fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
-
- VariantInfo {
- ind: i,
- size: fields_size.iter().map(|info| info.size).sum(),
- fields_size,
- }
- })
- .collect::<Vec<_>>();
- variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
- variants_size
- }
-
++use rustc_middle::ty::{Adt, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for large size differences between variants on
+ /// `enum`s.
+ ///
+ /// ### Why is this bad?
+ /// Enum size is bounded by the largest variant. Having one
+ /// large variant can penalize the memory layout of that enum.
+ ///
+ /// ### Known problems
+ /// This lint obviously cannot take the distribution of
+ /// variants in your running program into account. It is possible that the
+ /// smaller variants make up less than 1% of all instances, in which case
+ /// the overhead is negligible and the boxing is counter-productive. Always
+ /// measure the change this lint suggests.
+ ///
+ /// For types that implement `Copy`, the suggestion to `Box` a variant's
+ /// data would require removing the trait impl. The types can of course
+ /// still be `Clone`, but that is worse ergonomically. Depending on the
+ /// use case it may be possible to store the large data in an auxiliary
+ /// structure (e.g. Arena or ECS).
+ ///
+ /// The lint will ignore the impact of generic types to the type layout by
+ /// assuming every type parameter is zero-sized. Depending on your use case,
+ /// this may lead to a false positive.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Test {
+ /// A(i32),
+ /// B([i32; 8000]),
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// // Possibly better
+ /// enum Test2 {
+ /// A(i32),
+ /// B(Box<[i32; 8000]>),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LARGE_ENUM_VARIANT,
+ perf,
+ "large size difference between variants on an enum"
+}
+
+#[derive(Copy, Clone)]
+pub struct LargeEnumVariant {
+ maximum_size_difference_allowed: u64,
+}
+
+impl LargeEnumVariant {
+ #[must_use]
+ pub fn new(maximum_size_difference_allowed: u64) -> Self {
+ Self {
+ maximum_size_difference_allowed,
+ }
+ }
+}
+
- let variants_size = variants_size(cx, *adt, subst);
+impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ let ty = cx.tcx.type_of(item.owner_id);
+ let Adt(adt, subst) = ty.kind() else {
+ panic!("already checked whether this is an enum")
+ };
+ if adt.variants().len() <= 1 {
+ return;
+ }
- .map_while(|val| {
++ let variants_size = AdtVariantInfo::new(cx, *adt, subst);
+
+ let mut difference = variants_size[0].size - variants_size[1].size;
+ if difference > self.maximum_size_difference_allowed {
+ let help_text = "consider boxing the large fields to reduce the total size of the enum";
+ span_lint_and_then(
+ cx,
+ LARGE_ENUM_VARIANT,
+ item.span,
+ "large size difference between variants",
+ |diag| {
+ diag.span_label(
+ item.span,
+ format!("the entire enum is at least {} bytes", approx_ty_size(cx, ty)),
+ );
+ diag.span_label(
+ def.variants[variants_size[0].ind].span,
+ format!("the largest variant contains at least {} bytes", variants_size[0].size),
+ );
+ diag.span_label(
+ def.variants[variants_size[1].ind].span,
+ &if variants_size[1].fields_size.is_empty() {
+ "the second-largest variant carries no data at all".to_owned()
+ } else {
+ format!(
+ "the second-largest variant contains at least {} bytes",
+ variants_size[1].size
+ )
+ },
+ );
+
+ let fields = def.variants[variants_size[0].ind].data.fields();
+ let mut applicability = Applicability::MaybeIncorrect;
+ if is_copy(cx, ty) || maybe_copy(cx, ty) {
+ diag.span_note(
+ item.ident.span,
+ "boxing a variant would require the type no longer be `Copy`",
+ );
+ } else {
+ let sugg: Vec<(Span, String)> = variants_size[0]
+ .fields_size
+ .iter()
+ .rev()
- difference = difference.saturating_sub(val.size);
++ .map_while(|&(ind, size)| {
+ if difference > self.maximum_size_difference_allowed {
- fields[val.ind].ty.span,
++ difference = difference.saturating_sub(size);
+ Some((
- fields[val.ind].ty.span,
++ fields[ind].ty.span,
+ format!(
+ "Box<{}>",
+ snippet_with_applicability(
+ cx,
++ fields[ind].ty.span,
+ "..",
+ &mut applicability
+ )
+ .into_owned()
+ ),
+ ))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if !sugg.is_empty() {
+ diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
+ return;
+ }
+ }
+ diag.span_help(def.variants[variants_size[0].ind].span, help_text);
+ },
+ );
+ }
+ }
+ }
+}
+
+fn maybe_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if let Adt(_def, substs) = ty.kind()
+ && substs.types().next().is_some()
+ && let Some(copy_trait) = cx.tcx.lang_items().copy_trait()
+ {
+ return cx.tcx.non_blanket_impls_for_ty(copy_trait, ty).next().is_some();
+ }
+ false
+}
--- /dev/null
- if let (&ExprKind::MethodCall(method_path, receiver, args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind)
- {
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefIdSet;
+use rustc_hir::{
+ def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
+ ItemKind, Mutability, Node, TraitItemRef, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{
+ source_map::{Span, Spanned, Symbol},
+ symbol::sym,
+};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for getting the length of something via `.len()`
+ /// just to compare to zero, and suggests using `.is_empty()` where applicable.
+ ///
+ /// ### Why is this bad?
+ /// Some structures can answer `.is_empty()` much faster
+ /// than calculating their length. So it is good to get into the habit of using
+ /// `.is_empty()`, and having it is cheap.
+ /// Besides, it makes the intent clearer than a manual comparison in some contexts.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if x.len() == 0 {
+ /// ..
+ /// }
+ /// if y.len() != 0 {
+ /// ..
+ /// }
+ /// ```
+ /// instead use
+ /// ```ignore
+ /// if x.is_empty() {
+ /// ..
+ /// }
+ /// if !y.is_empty() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LEN_ZERO,
+ style,
+ "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items that implement `.len()` but not
+ /// `.is_empty()`.
+ ///
+ /// ### Why is this bad?
+ /// It is good custom to have both methods, because for
+ /// some data structures, asking about the length will be a costly operation,
+ /// whereas `.is_empty()` can usually answer in constant time. Also it used to
+ /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that
+ /// lint will ignore such entities.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl X {
+ /// pub fn len(&self) -> usize {
+ /// ..
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LEN_WITHOUT_IS_EMPTY,
+ style,
+ "traits or impls with a public `len` method but no corresponding `is_empty` method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparing to an empty slice such as `""` or `[]`,
+ /// and suggests using `.is_empty()` where applicable.
+ ///
+ /// ### Why is this bad?
+ /// Some structures can answer `.is_empty()` much faster
+ /// than checking for equality. So it is good to get into the habit of using
+ /// `.is_empty()`, and having it is cheap.
+ /// Besides, it makes the intent clearer than a manual comparison in some contexts.
+ ///
+ /// ### Example
+ ///
+ /// ```ignore
+ /// if s == "" {
+ /// ..
+ /// }
+ ///
+ /// if arr == [] {
+ /// ..
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// if s.is_empty() {
+ /// ..
+ /// }
+ ///
+ /// if arr.is_empty() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub COMPARISON_TO_EMPTY,
+ style,
+ "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
+}
+
+declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
+
+impl<'tcx> LateLintPass<'tcx> for LenZero {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind {
+ check_trait_items(cx, item, trait_items);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if_chain! {
+ if item.ident.name == sym::len;
+ if let ImplItemKind::Fn(sig, _) = &item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if cx.effective_visibilities.is_exported(item.owner_id.def_id);
+ if matches!(sig.decl.output, FnRetTy::Return(_));
+ if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
+ if imp.of_trait.is_none();
+ if let TyKind::Path(ty_path) = &imp.self_ty.kind;
+ if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
+ if let Some(local_id) = ty_id.as_local();
+ let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
+ if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
+ if let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).skip_binder());
+ then {
+ let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
+ Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
+ Some(Node::Item(x)) => match x.kind {
+ ItemKind::Struct(..) => (x.ident.name, "struct"),
+ ItemKind::Enum(..) => (x.ident.name, "enum"),
+ ItemKind::Union(..) => (x.ident.name, "union"),
+ _ => (x.ident.name, "type"),
+ }
+ _ => return,
+ };
+ check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind {
+ match cmp {
+ BinOpKind::Eq => {
+ check_cmp(cx, expr.span, left, right, "", 0); // len == 0
+ check_cmp(cx, expr.span, right, left, "", 0); // 0 == len
+ },
+ BinOpKind::Ne => {
+ check_cmp(cx, expr.span, left, right, "!", 0); // len != 0
+ check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len
+ },
+ BinOpKind::Gt => {
+ check_cmp(cx, expr.span, left, right, "!", 0); // len > 0
+ check_cmp(cx, expr.span, right, left, "", 1); // 1 > len
+ },
+ BinOpKind::Lt => {
+ check_cmp(cx, expr.span, left, right, "", 1); // len < 1
+ check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len
+ },
+ BinOpKind::Ge => check_cmp(cx, expr.span, left, right, "!", 1), // len >= 1
+ BinOpKind::Le => check_cmp(cx, expr.span, right, left, "!", 1), // 1 <= len
+ _ => (),
+ }
+ }
+ }
+}
+
+fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) {
+ fn is_named_self(cx: &LateContext<'_>, item: &TraitItemRef, name: Symbol) -> bool {
+ item.ident.name == name
+ && if let AssocItemKind::Fn { has_self } = item.kind {
+ has_self && { cx.tcx.fn_sig(item.id.owner_id).inputs().skip_binder().len() == 1 }
+ } else {
+ false
+ }
+ }
+
+ // fill the set with current and super traits
+ fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
+ if set.insert(traitt) {
+ for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) {
+ fill_trait_set(supertrait, set, cx);
+ }
+ }
+ }
+
+ if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
+ && trait_items.iter().any(|i| is_named_self(cx, i, sym::len))
+ {
+ let mut current_and_super_traits = DefIdSet::default();
+ fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
+ let is_empty = sym!(is_empty);
+
+ let is_empty_method_found = current_and_super_traits
+ .iter()
+ .flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(is_empty))
+ .any(|i| {
+ i.kind == ty::AssocKind::Fn
+ && i.fn_has_self_parameter
+ && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1
+ });
+
+ if !is_empty_method_found {
+ span_lint(
+ cx,
+ LEN_WITHOUT_IS_EMPTY,
+ visited_trait.span,
+ &format!(
+ "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
+ visited_trait.ident.name
+ ),
+ );
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum LenOutput<'tcx> {
+ Integral,
+ Option(DefId),
+ Result(DefId, Ty<'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::Result, adt.did()) => subs
+ .type_at(0)
+ .is_integral()
+ .then(|| LenOutput::Result(adt.did(), subs.type_at(1))),
+ _ => None,
+ }
+}
+
+impl<'tcx> LenOutput<'tcx> {
+ fn matches_is_empty_output(self, ty: Ty<'tcx>) -> 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() => {
+ subs.type_at(0).is_bool() && subs.type_at(1) == err_ty
+ },
+ _ => false,
+ }
+ }
+
+ fn expected_sig(self, self_kind: ImplicitSelfKind) -> String {
+ let self_ref = match self_kind {
+ ImplicitSelfKind::ImmRef => "&",
+ ImplicitSelfKind::MutRef => "&mut ",
+ _ => "",
+ };
+ match self {
+ Self::Integral => format!("expected signature: `({self_ref}self) -> bool`"),
+ Self::Option(_) => {
+ format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
+ },
+ Self::Result(..) => {
+ format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
+ },
+ }
+ }
+}
+
+/// Checks if the given signature matches the expectations for `is_empty`
+fn check_is_empty_sig<'tcx>(sig: FnSig<'tcx>, self_kind: ImplicitSelfKind, len_output: LenOutput<'tcx>) -> bool {
+ match &**sig.inputs_and_output {
+ [arg, res] if len_output.matches_is_empty_output(*res) => {
+ matches!(
+ (arg.kind(), self_kind),
+ (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
+ | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef)
+ ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
+ },
+ _ => false,
+ }
+}
+
+/// Checks if the given type has an `is_empty` method with the appropriate signature.
+fn check_for_is_empty<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ self_kind: ImplicitSelfKind,
+ output: LenOutput<'tcx>,
+ impl_ty: DefId,
+ item_name: Symbol,
+ item_kind: &str,
+) {
+ let is_empty = Symbol::intern("is_empty");
+ let is_empty = cx
+ .tcx
+ .inherent_impls(impl_ty)
+ .iter()
+ .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
+ .find(|item| item.kind == AssocKind::Fn);
+
+ let (msg, is_empty_span, self_kind) = match is_empty {
+ None => (
+ format!(
+ "{item_kind} `{}` has a public `len` method, but no `is_empty` method",
+ item_name.as_str(),
+ ),
+ None,
+ None,
+ ),
+ Some(is_empty) if !cx.effective_visibilities.is_exported(is_empty.def_id.expect_local()) => (
+ format!(
+ "{item_kind} `{}` has a public `len` method, but a private `is_empty` method",
+ item_name.as_str(),
+ ),
+ Some(cx.tcx.def_span(is_empty.def_id)),
+ None,
+ ),
+ Some(is_empty)
+ if !(is_empty.fn_has_self_parameter
+ && check_is_empty_sig(cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind, output)) =>
+ {
+ (
+ format!(
+ "{item_kind} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
+ item_name.as_str(),
+ ),
+ Some(cx.tcx.def_span(is_empty.def_id)),
+ Some(self_kind),
+ )
+ },
+ Some(_) => return,
+ };
+
+ span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| {
+ if let Some(span) = is_empty_span {
+ db.span_note(span, "`is_empty` defined here");
+ }
+ if let Some(self_kind) = self_kind {
+ db.note(&output.expected_sig(self_kind));
+ }
+ });
+}
+
+fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
++ if let (&ExprKind::MethodCall(method_path, receiver, args, _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
+ // check if we are in an is_empty() method
+ if let Some(name) = get_item_name(cx, method) {
+ if name.as_str() == "is_empty" {
+ return;
+ }
+ }
+
+ check_len(
+ cx,
+ span,
+ method_path.ident.name,
+ receiver,
+ args,
+ &lit.node,
+ op,
+ compare_to,
+ );
+ } else {
+ check_empty_expr(cx, span, method, lit, op);
+ }
+}
+
+// FIXME(flip1995): Figure out how to reduce the number of arguments
+#[allow(clippy::too_many_arguments)]
+fn check_len(
+ cx: &LateContext<'_>,
+ span: Span,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ args: &[Expr<'_>],
+ lit: &LitKind,
+ op: &str,
+ compare_to: u32,
+) {
+ if let LitKind::Int(lit, _) = *lit {
+ // check if length is compared to the specified number
+ if lit != u128::from(compare_to) {
+ return;
+ }
+
+ if method_name == sym::len && args.is_empty() && has_is_empty(cx, receiver) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ LEN_ZERO,
+ span,
+ &format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }),
+ &format!("using `{op}is_empty` is clearer and more explicit"),
+ format!(
+ "{op}{}.is_empty()",
+ snippet_with_applicability(cx, receiver.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
+ if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ COMPARISON_TO_EMPTY,
+ span,
+ "comparison to empty slice",
+ &format!("using `{op}is_empty` is clearer and more explicit"),
+ format!(
+ "{op}{}.is_empty()",
+ snippet_with_applicability(cx, lit1.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
+
+fn is_empty_string(expr: &Expr<'_>) -> bool {
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(lit, _) = lit.node {
+ let lit = lit.as_str();
+ return lit.is_empty();
+ }
+ }
+ false
+}
+
+fn is_empty_array(expr: &Expr<'_>) -> bool {
+ if let ExprKind::Array(arr) = expr.kind {
+ return arr.is_empty();
+ }
+ false
+}
+
+/// Checks if this type has an `is_empty` method.
+fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
+ fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
+ if item.kind == ty::AssocKind::Fn {
+ let sig = cx.tcx.fn_sig(item.def_id);
+ let ty = sig.skip_binder();
+ ty.inputs().len() == 1
+ } else {
+ false
+ }
+ }
+
+ /// Checks the inherent impl's items for an `is_empty(self)` method.
+ fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
+ let is_empty = sym!(is_empty);
+ cx.tcx.inherent_impls(id).iter().any(|imp| {
+ cx.tcx
+ .associated_items(*imp)
+ .filter_by_name_unhygienic(is_empty)
+ .any(|item| is_is_empty(cx, item))
+ })
+ }
+
+ let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
+ match ty.kind() {
+ ty::Dynamic(tt, ..) => tt.principal().map_or(false, |principal| {
+ let is_empty = sym!(is_empty);
+ cx.tcx
+ .associated_items(principal.def_id())
+ .filter_by_name_unhygienic(is_empty)
+ .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::Array(..) | ty::Slice(..) | ty::Str => true,
+ _ => false,
+ }
+}
--- /dev/null
- use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item, match_type};
+use clippy_utils::diagnostics::span_lint_and_help;
- use if_chain::if_chain;
++use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
+use clippy_utils::{is_must_use_func_call, paths};
- use rustc_span::{sym, Symbol};
+use rustc_hir::{Local, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- "non-binding let on a `#[must_use]` expression"
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `let _ = <expr>` where expr is `#[must_use]`
+ ///
+ /// ### Why is this bad?
+ /// It's better to explicitly handle the value of a `#[must_use]`
+ /// expr
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn f() -> Result<u32, u32> {
+ /// Ok(0)
+ /// }
+ ///
+ /// let _ = f();
+ /// // is_ok() is marked #[must_use]
+ /// let _ = f().is_ok();
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub LET_UNDERSCORE_MUST_USE,
+ restriction,
- /// Checks for `let _ = sync_lock`.
- /// This supports `mutex` and `rwlock` in `std::sync` and `parking_lot`.
++ "non-binding `let` on a `#[must_use]` expression"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- "non-binding let on a synchronization lock"
++ /// Checks for `let _ = sync_lock`. This supports `mutex` and `rwlock` in
++ /// `parking_lot`. For `std` locks see the `rustc` lint
++ /// [`let_underscore_lock`](https://doc.rust-lang.org/nightly/rustc/lints/listing/deny-by-default.html#let-underscore-lock)
+ ///
+ /// ### Why is this bad?
+ /// This statement immediately drops the lock instead of
+ /// extending its lifetime to the end of the scope, which is often not intended.
+ /// To extend lock lifetime to the end of the scope, use an underscore-prefixed
+ /// name instead (i.e. _lock). If you want to explicitly drop the lock,
+ /// `std::mem::drop` conveys your intention better and is less error-prone.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let _ = mutex.lock();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// let _lock = mutex.lock();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub LET_UNDERSCORE_LOCK,
+ correctness,
- /// Checks for `let _ = <expr>`
- /// where expr has a type that implements `Drop`
++ "non-binding `let` on a synchronization lock"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- /// This statement immediately drops the initializer
- /// expression instead of extending its lifetime to the end of the scope, which
- /// is often not intended. To extend the expression's lifetime to the end of the
- /// scope, use an underscore-prefixed name instead (i.e. _var). If you want to
- /// explicitly drop the expression, `std::mem::drop` conveys your intention
- /// better and is less error-prone.
++ /// Checks for `let _ = <expr>` where the resulting type of expr implements `Future`
+ ///
+ /// ### Why is this bad?
- /// # struct DroppableItem;
- /// {
- /// let _ = DroppableItem;
- /// // ^ dropped here
- /// /* more code */
++ /// Futures must be polled for work to be done. The original intention was most likely to await the future
++ /// and ignore the resulting value.
+ ///
+ /// ### Example
+ /// ```rust
- /// # struct DroppableItem;
- /// {
- /// let _droppable = DroppableItem;
- /// /* more code */
- /// // dropped at end of scope
++ /// async fn foo() -> Result<(), ()> {
++ /// Ok(())
+ /// }
++ /// let _ = foo();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
- #[clippy::version = "1.50.0"]
- pub LET_UNDERSCORE_DROP,
- pedantic,
- "non-binding let on a type that implements `Drop`"
++ /// # async fn context() {
++ /// async fn foo() -> Result<(), ()> {
++ /// Ok(())
+ /// }
++ /// let _ = foo().await;
++ /// # }
+ /// ```
- declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]);
-
- const SYNC_GUARD_SYMS: [Symbol; 3] = [sym::MutexGuard, sym::RwLockReadGuard, sym::RwLockWriteGuard];
++ #[clippy::version = "1.66"]
++ pub LET_UNDERSCORE_FUTURE,
++ suspicious,
++ "non-binding `let` on a future"
+}
+
- if in_external_macro(cx.tcx.sess, local.span) {
- return;
- }
-
- if_chain! {
- if let PatKind::Wild = local.pat.kind;
- if let Some(init) = local.init;
- then {
- let init_ty = cx.typeck_results().expr_ty(init);
- let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
- GenericArgKind::Type(inner_ty) => {
- SYNC_GUARD_SYMS
- .iter()
- .any(|&sym| is_type_diagnostic_item(cx, inner_ty, sym))
- || SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path))
- },
-
- GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
- });
- if contains_sync_guard {
- span_lint_and_help(
- cx,
- LET_UNDERSCORE_LOCK,
- local.span,
- "non-binding let on a synchronization lock",
- None,
- "consider using an underscore-prefixed named \
- binding or dropping explicitly with `std::mem::drop`",
- );
- } else if init_ty.needs_drop(cx.tcx, cx.param_env) {
- span_lint_and_help(
- cx,
- LET_UNDERSCORE_DROP,
- local.span,
- "non-binding `let` on a type that implements `Drop`",
- None,
- "consider using an underscore-prefixed named \
++declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]);
+
+const SYNC_GUARD_PATHS: [&[&str]; 3] = [
+ &paths::PARKING_LOT_MUTEX_GUARD,
+ &paths::PARKING_LOT_RWLOCK_READ_GUARD,
+ &paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
+];
+
+impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
- );
- } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
- span_lint_and_help(
- cx,
- LET_UNDERSCORE_MUST_USE,
- local.span,
- "non-binding let on an expression with `#[must_use]` type",
- None,
- "consider explicitly using expression value",
- );
- } else if is_must_use_func_call(cx, init) {
- span_lint_and_help(
- cx,
- LET_UNDERSCORE_MUST_USE,
- local.span,
- "non-binding let on a result of a `#[must_use]` function",
- None,
- "consider explicitly using function result",
- );
- }
++ if !in_external_macro(cx.tcx.sess, local.span)
++ && let PatKind::Wild = local.pat.kind
++ && let Some(init) = local.init
++ {
++ let init_ty = cx.typeck_results().expr_ty(init);
++ let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
++ GenericArgKind::Type(inner_ty) => SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)),
++ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
++ });
++ if contains_sync_guard {
++ span_lint_and_help(
++ cx,
++ LET_UNDERSCORE_LOCK,
++ local.span,
++ "non-binding `let` on a synchronization lock",
++ None,
++ "consider using an underscore-prefixed named \
+ binding or dropping explicitly with `std::mem::drop`",
++ );
++ } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
++ && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) {
++ span_lint_and_help(
++ cx,
++ LET_UNDERSCORE_FUTURE,
++ local.span,
++ "non-binding `let` on a future",
++ None,
++ "consider awaiting the future or dropping explicitly with `std::mem::drop`"
++ );
++ } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
++ span_lint_and_help(
++ cx,
++ LET_UNDERSCORE_MUST_USE,
++ local.span,
++ "non-binding `let` on an expression with `#[must_use]` type",
++ None,
++ "consider explicitly using expression value",
++ );
++ } else if is_must_use_func_call(cx, init) {
++ span_lint_and_help(
++ cx,
++ LET_UNDERSCORE_MUST_USE,
++ local.span,
++ "non-binding `let` on a result of a `#[must_use]` function",
++ None,
++ "consider explicitly using function result",
++ );
+ }
+ }
+ }
+}
--- /dev/null
- extern crate rustc_hir_typeck;
+#![feature(array_windows)]
+#![feature(binary_heap_into_iter_sorted)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(drain_filter)]
+#![feature(iter_intersperse)]
+#![feature(let_chains)]
+#![feature(lint_reasons)]
+#![feature(never_type)]
+#![feature(once_cell)]
+#![feature(rustc_private)]
+#![feature(stmt_expr_attributes)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)]
+#![warn(trivial_casts, trivial_numeric_casts)]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+// Disable this rustc lint for now, as it was also done in rustc
+#![allow(rustc::potential_query_instability)]
+
+// 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;
+extern crate rustc_data_structures;
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_analysis;
- use rustc_lint::LintId;
+extern crate rustc_hir_pretty;
++extern crate rustc_hir_typeck;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_parse;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+
+#[macro_use]
+extern crate clippy_utils;
++#[macro_use]
++extern crate declare_clippy_lint;
++
++use std::io;
++use std::path::PathBuf;
+
+use clippy_utils::parse_msrv;
+use rustc_data_structures::fx::FxHashSet;
- /// Macro used to declare a Clippy lint.
- ///
- /// Every lint declaration consists of 4 parts:
- ///
- /// 1. The documentation, which is used for the website
- /// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
- /// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
- /// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
- /// 4. The `description` that contains a short explanation on what's wrong with code where the
- /// lint is triggered.
- ///
- /// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are
- /// enabled by default. As said in the README.md of this repository, if the lint level mapping
- /// changes, please update README.md.
- ///
- /// # Example
- ///
- /// ```
- /// #![feature(rustc_private)]
- /// extern crate rustc_session;
- /// use rustc_session::declare_tool_lint;
- /// use clippy_lints::declare_clippy_lint;
- ///
- /// declare_clippy_lint! {
- /// /// ### What it does
- /// /// Checks for ... (describe what the lint matches).
- /// ///
- /// /// ### Why is this bad?
- /// /// Supply the reason for linting the code.
- /// ///
- /// /// ### Example
- /// /// ```rust
- /// /// Insert a short example of code that triggers the lint
- /// /// ```
- /// ///
- /// /// Use instead:
- /// /// ```rust
- /// /// Insert a short example of improved code that doesn't trigger the lint
- /// /// ```
- /// pub LINT_NAME,
- /// pedantic,
- /// "description"
- /// }
- /// ```
- /// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
- #[macro_export]
- macro_rules! declare_clippy_lint {
- { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, suspicious, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
- }
- };
- { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => {
- declare_tool_lint! {
- $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
- }
- };
- }
-
++use rustc_lint::{Lint, LintId};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+
- mod manual_instant_elapsed;
+#[cfg(feature = "internal")]
+pub mod deprecated_lints;
+#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
+mod utils;
+
++mod declared_lints;
+mod renamed_lints;
+
+// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod almost_complete_letter_range;
+mod approx_const;
+mod as_conversions;
+mod asm_syntax;
+mod assertions_on_constants;
+mod assertions_on_result_states;
+mod async_yields_async;
+mod attrs;
+mod await_holding_invalid;
+mod blocks_in_if_conditions;
+mod bool_assert_comparison;
+mod bool_to_int_with_if;
+mod booleans;
+mod borrow_deref_ref;
+mod box_default;
+mod cargo;
+mod casts;
+mod checked_conversions;
+mod cognitive_complexity;
+mod collapsible_if;
+mod comparison_chain;
+mod copies;
+mod copy_iterator;
+mod crate_in_macro_def;
+mod create_dir;
+mod dbg_macro;
+mod default;
+mod default_instead_of_iter_empty;
+mod default_numeric_fallback;
+mod default_union_representation;
+mod dereference;
+mod derivable_impls;
+mod derive;
+mod disallowed_macros;
+mod disallowed_methods;
+mod disallowed_names;
+mod disallowed_script_idents;
+mod disallowed_types;
+mod doc;
+mod double_parens;
+mod drop_forget_ref;
+mod duplicate_mod;
+mod else_if_without_else;
+mod empty_drop;
+mod empty_enum;
+mod empty_structs_with_brackets;
+mod entry;
+mod enum_clike;
+mod enum_variants;
+mod equatable_if_let;
+mod escape;
+mod eta_reduction;
+mod excessive_bools;
+mod exhaustive_items;
+mod exit;
+mod explicit_write;
+mod fallible_impl_from;
+mod float_literal;
+mod floating_point_arithmetic;
+mod format;
+mod format_args;
+mod format_impl;
+mod format_push_string;
+mod formatting;
+mod from_over_into;
++mod from_raw_with_void_ptr;
+mod from_str_radix_10;
+mod functions;
+mod future_not_send;
+mod if_let_mutex;
+mod if_not_else;
+mod if_then_some_else_none;
+mod implicit_hasher;
+mod implicit_return;
+mod implicit_saturating_add;
+mod implicit_saturating_sub;
+mod inconsistent_struct_constructor;
+mod index_refutable_slice;
+mod indexing_slicing;
+mod infinite_iter;
+mod inherent_impl;
+mod inherent_to_string;
+mod init_numbered_fields;
+mod inline_fn_without_body;
++mod instant_subtraction;
+mod int_plus_one;
+mod invalid_upcast_comparisons;
+mod invalid_utf8_in_unchecked;
+mod items_after_statements;
+mod iter_not_returning_iterator;
+mod large_const_arrays;
+mod large_enum_variant;
+mod large_include_file;
+mod large_stack_arrays;
+mod len_zero;
+mod let_if_seq;
+mod let_underscore;
+mod lifetimes;
+mod literal_representation;
+mod loops;
+mod macro_use;
+mod main_recursion;
+mod manual_assert;
+mod manual_async_fn;
+mod manual_bits;
+mod manual_clamp;
- pub use crate::utils::conf::Conf;
++mod manual_is_ascii_check;
++mod manual_let_else;
+mod manual_non_exhaustive;
+mod manual_rem_euclid;
+mod manual_retain;
+mod manual_string_new;
+mod manual_strip;
+mod map_unit_fn;
+mod match_result_ok;
+mod matches;
+mod mem_forget;
+mod mem_replace;
+mod methods;
+mod minmax;
+mod misc;
+mod misc_early;
+mod mismatching_type_param_order;
+mod missing_const_for_fn;
+mod missing_doc;
+mod missing_enforced_import_rename;
+mod missing_inline;
+mod missing_trait_methods;
+mod mixed_read_write_in_expression;
+mod module_style;
+mod multi_assignments;
+mod mut_key;
+mod mut_mut;
+mod mut_reference;
+mod mutable_debug_assertion;
+mod mutex_atomic;
+mod needless_arbitrary_self_type;
+mod needless_bool;
+mod needless_borrowed_ref;
+mod needless_continue;
+mod needless_for_each;
+mod needless_late_init;
+mod needless_parens_on_range_literals;
+mod needless_pass_by_value;
+mod needless_question_mark;
+mod needless_update;
+mod neg_cmp_op_on_partial_ord;
+mod neg_multiply;
+mod new_without_default;
+mod no_effect;
+mod non_copy_const;
+mod non_expressive_names;
+mod non_octal_unix_permissions;
+mod non_send_fields_in_send_ty;
+mod nonstandard_macro_braces;
+mod octal_escapes;
+mod only_used_in_recursion;
+mod operators;
+mod option_env_unwrap;
+mod option_if_let_else;
+mod overflow_check_conditional;
+mod panic_in_result_fn;
+mod panic_unimplemented;
+mod partial_pub_fields;
+mod partialeq_ne_impl;
+mod partialeq_to_none;
+mod pass_by_ref_or_value;
+mod pattern_type_mismatch;
+mod precedence;
+mod ptr;
+mod ptr_offset_with_cast;
+mod pub_use;
+mod question_mark;
+mod ranges;
+mod rc_clone_in_vec_init;
+mod read_zero_byte_vec;
+mod redundant_clone;
+mod redundant_closure_call;
+mod redundant_else;
+mod redundant_field_names;
+mod redundant_pub_crate;
+mod redundant_slicing;
+mod redundant_static_lifetimes;
+mod ref_option_ref;
+mod reference;
+mod regex;
+mod return_self_not_must_use;
+mod returns;
+mod same_name_method;
+mod self_named_constructors;
+mod semicolon_if_nothing_returned;
+mod serde_api;
+mod shadow;
+mod single_char_lifetime_names;
+mod single_component_path_imports;
+mod size_of_in_element_count;
+mod slow_vector_initialization;
+mod std_instead_of_core;
+mod strings;
+mod strlen_on_c_strings;
+mod suspicious_operation_groupings;
+mod suspicious_trait_impl;
++mod suspicious_xor_used_as_pow;
+mod swap;
+mod swap_ptr_to_ref;
+mod tabs_in_doc_comments;
+mod temporary_assignment;
+mod to_digit_is_some;
+mod trailing_empty_array;
+mod trait_bounds;
+mod transmute;
+mod types;
+mod undocumented_unsafe_blocks;
+mod unicode;
+mod uninit_vec;
+mod unit_return_expecting_ord;
+mod unit_types;
+mod unnamed_address;
+mod unnecessary_owned_empty_strings;
+mod unnecessary_self_imports;
+mod unnecessary_wraps;
+mod unnested_or_patterns;
+mod unsafe_removed_from_name;
+mod unused_async;
+mod unused_io_amount;
+mod unused_peekable;
+mod unused_rounding;
+mod unused_self;
+mod unused_unit;
+mod unwrap;
+mod unwrap_in_result;
+mod upper_case_acronyms;
+mod use_self;
+mod useless_conversion;
+mod vec;
+mod vec_init_then_push;
+mod wildcard_imports;
+mod write;
+mod zero_div_zero;
+mod zero_sized_map_values;
+// end lints modules, do not remove this comment, it’s used in `update_lints`
+
- pub fn read_conf(sess: &Session) -> Conf {
- let file_name = match utils::conf::lookup_conf_file() {
+use crate::utils::conf::{format_error, TryConf};
++pub use crate::utils::conf::{lookup_conf_file, Conf};
+
+/// Register all pre expansion lints
+///
+/// Pre-expansion lints run before any macro expansion has happened.
+///
+/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate
+/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
+
+ let msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(format!(
+ "error reading Clippy's configuration file. `{s}` is not a valid Rust version"
+ ));
+ None
+ })
+ });
+
+ store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
+}
+
+fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
+ let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
+ .ok()
+ .and_then(|v| parse_msrv(&v, None, None));
+ let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(format!(
+ "error reading Clippy's configuration file. `{s}` is not a valid Rust version"
+ ));
+ None
+ })
+ });
+
+ if let Some(cargo_msrv) = cargo_msrv {
+ if let Some(clippy_msrv) = clippy_msrv {
+ // if both files have an msrv, let's compare them and emit a warning if they differ
+ if clippy_msrv != cargo_msrv {
+ sess.warn(format!(
+ "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
+ ));
+ }
+
+ Some(clippy_msrv)
+ } else {
+ Some(cargo_msrv)
+ }
+ } else {
+ clippy_msrv
+ }
+}
+
+#[doc(hidden)]
- let TryConf { conf, errors, warnings } = utils::conf::read(&file_name);
++pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
++ let file_name = match path {
+ Ok(Some(path)) => path,
+ Ok(None) => return Conf::default(),
+ Err(error) => {
+ sess.struct_err(&format!("error finding Clippy's configuration file: {error}"))
+ .emit();
+ return Conf::default();
+ },
+ };
+
- include!("lib.register_lints.rs");
- include!("lib.register_restriction.rs");
- include!("lib.register_pedantic.rs");
-
- #[cfg(feature = "internal")]
- include!("lib.register_internal.rs");
-
- include!("lib.register_all.rs");
- include!("lib.register_style.rs");
- include!("lib.register_complexity.rs");
- include!("lib.register_correctness.rs");
- include!("lib.register_suspicious.rs");
- include!("lib.register_perf.rs");
- include!("lib.register_cargo.rs");
- include!("lib.register_nursery.rs");
-
++ let TryConf { conf, errors, warnings } = utils::conf::read(file_name);
+ // all conf errors are non-fatal, we just use the default conf in case of error
+ for error in errors {
+ sess.err(format!(
+ "error reading Clippy's configuration file `{}`: {}",
+ file_name.display(),
+ format_error(error)
+ ));
+ }
+
+ for warning in warnings {
+ sess.struct_warn(format!(
+ "error reading Clippy's configuration file `{}`: {}",
+ file_name.display(),
+ format_error(warning)
+ ))
+ .emit();
+ }
+
+ conf
+}
+
++#[derive(Default)]
++struct RegistrationGroups {
++ all: Vec<LintId>,
++ cargo: Vec<LintId>,
++ complexity: Vec<LintId>,
++ correctness: Vec<LintId>,
++ nursery: Vec<LintId>,
++ pedantic: Vec<LintId>,
++ perf: Vec<LintId>,
++ restriction: Vec<LintId>,
++ style: Vec<LintId>,
++ suspicious: Vec<LintId>,
++ #[cfg(feature = "internal")]
++ internal: Vec<LintId>,
++}
++
++impl RegistrationGroups {
++ #[rustfmt::skip]
++ fn register(self, store: &mut rustc_lint::LintStore) {
++ store.register_group(true, "clippy::all", Some("clippy_all"), self.all);
++ store.register_group(true, "clippy::cargo", Some("clippy_cargo"), self.cargo);
++ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), self.complexity);
++ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), self.correctness);
++ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), self.nursery);
++ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), self.pedantic);
++ store.register_group(true, "clippy::perf", Some("clippy_perf"), self.perf);
++ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), self.restriction);
++ store.register_group(true, "clippy::style", Some("clippy_style"), self.style);
++ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), self.suspicious);
++ #[cfg(feature = "internal")]
++ store.register_group(true, "clippy::internal", Some("clippy_internal"), self.internal);
++ }
++}
++
++#[derive(Copy, Clone)]
++pub(crate) enum LintCategory {
++ Cargo,
++ Complexity,
++ Correctness,
++ Nursery,
++ Pedantic,
++ Perf,
++ Restriction,
++ Style,
++ Suspicious,
++ #[cfg(feature = "internal")]
++ Internal,
++}
++#[allow(clippy::enum_glob_use)]
++use LintCategory::*;
++
++impl LintCategory {
++ fn is_all(self) -> bool {
++ matches!(self, Correctness | Suspicious | Style | Complexity | Perf)
++ }
++
++ fn group(self, groups: &mut RegistrationGroups) -> &mut Vec<LintId> {
++ match self {
++ Cargo => &mut groups.cargo,
++ Complexity => &mut groups.complexity,
++ Correctness => &mut groups.correctness,
++ Nursery => &mut groups.nursery,
++ Pedantic => &mut groups.pedantic,
++ Perf => &mut groups.perf,
++ Restriction => &mut groups.restriction,
++ Style => &mut groups.style,
++ Suspicious => &mut groups.suspicious,
++ #[cfg(feature = "internal")]
++ Internal => &mut groups.internal,
++ }
++ }
++}
++
++pub(crate) struct LintInfo {
++ /// Double reference to maintain pointer equality
++ lint: &'static &'static Lint,
++ category: LintCategory,
++ explanation: &'static str,
++}
++
++pub fn explain(name: &str) {
++ let target = format!("clippy::{}", name.to_ascii_uppercase());
++ match declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
++ Some(info) => print!("{}", info.explanation),
++ None => println!("unknown lint: {name}"),
++ }
++}
++
++fn register_categories(store: &mut rustc_lint::LintStore) {
++ let mut groups = RegistrationGroups::default();
++
++ for LintInfo { lint, category, .. } in declared_lints::LINTS {
++ if category.is_all() {
++ groups.all.push(LintId::of(lint));
++ }
++
++ category.group(&mut groups).push(LintId::of(lint));
++ }
++
++ let lints: Vec<&'static Lint> = declared_lints::LINTS.iter().map(|info| *info.lint).collect();
++
++ store.register_lints(&lints);
++ groups.register(store);
++}
++
+/// Register all lints and lint groups with the rustc plugin registry
+///
+/// Used in `./src/driver.rs`.
+#[expect(clippy::too_many_lines)]
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ register_removed_non_tool_lints(store);
++ register_categories(store);
+
+ include!("lib.deprecated.rs");
+
- store.register_late_pass(|_| Box::new(mut_key::MutableKeyType));
+ #[cfg(feature = "internal")]
+ {
+ if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
+ store.register_late_pass(|_| Box::new(utils::internal_lints::metadata_collector::MetadataCollector::new()));
+ return;
+ }
+ }
+
+ // all the internal lints
+ #[cfg(feature = "internal")]
+ {
+ store.register_early_pass(|| Box::new(utils::internal_lints::clippy_lints_internal::ClippyLintsInternal));
+ store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls));
+ store.register_late_pass(|_| {
+ Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new())
+ });
+ store.register_late_pass(|_| Box::new(utils::internal_lints::if_chain_style::IfChainStyle));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths));
+ store.register_late_pass(|_| {
+ Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default()
+ });
+ store.register_late_pass(|_| {
+ Box::<utils::internal_lints::lint_without_lint_pass::LintWithoutLintPass>::default()
+ });
+ store.register_late_pass(|_| Box::<utils::internal_lints::unnecessary_def_path::UnnecessaryDefPath>::default());
+ store.register_late_pass(|_| Box::new(utils::internal_lints::outer_expn_data_pass::OuterExpnDataPass));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::msrv_attr_impl::MsrvAttrImpl));
+ }
+
+ let arithmetic_side_effects_allowed = conf.arithmetic_side_effects_allowed.clone();
+ store.register_late_pass(move |_| {
+ Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(
+ arithmetic_side_effects_allowed.clone(),
+ ))
+ });
+ store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
+ store.register_late_pass(|_| Box::new(utils::author::Author));
+ let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
+ store.register_late_pass(move |_| {
+ Box::new(await_holding_invalid::AwaitHolding::new(
+ await_holding_invalid_types.clone(),
+ ))
+ });
+ store.register_late_pass(|_| Box::new(serde_api::SerdeApi));
+ let vec_box_size_threshold = conf.vec_box_size_threshold;
+ let type_complexity_threshold = conf.type_complexity_threshold;
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
+ store.register_late_pass(move |_| {
+ Box::new(types::Types::new(
+ vec_box_size_threshold,
+ type_complexity_threshold,
+ avoid_breaking_exported_api,
+ ))
+ });
+ store.register_late_pass(|_| Box::new(booleans::NonminimalBool));
+ store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant));
+ store.register_late_pass(|_| Box::new(float_literal::FloatLiteral));
+ store.register_late_pass(|_| Box::new(ptr::Ptr));
+ store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool));
+ store.register_late_pass(|_| Box::new(needless_bool::BoolComparison));
+ store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
+ store.register_late_pass(|_| Box::new(misc::MiscLints));
+ store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
+ store.register_late_pass(|_| Box::new(mut_mut::MutMut));
+ store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed));
+ store.register_late_pass(|_| Box::new(len_zero::LenZero));
+ store.register_late_pass(|_| Box::new(attrs::Attributes));
+ store.register_late_pass(|_| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
+ store.register_late_pass(|_| Box::new(unicode::Unicode));
+ store.register_late_pass(|_| Box::new(uninit_vec::UninitVec));
+ store.register_late_pass(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
+ store.register_late_pass(|_| Box::new(strings::StringAdd));
+ store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn));
+ store.register_late_pass(|_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub));
+ store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
+ store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
+ store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
+ store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
+
+ let msrv = read_msrv(conf, sess);
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
+ let allow_expect_in_tests = conf.allow_expect_in_tests;
+ let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
+ store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv)));
+ store.register_late_pass(move |_| {
+ Box::new(methods::Methods::new(
+ avoid_breaking_exported_api,
+ msrv,
+ allow_expect_in_tests,
+ allow_unwrap_in_tests,
+ ))
+ });
+ store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv)));
++ let matches_for_let_else = conf.matches_for_let_else;
++ store.register_late_pass(move |_| Box::new(manual_let_else::ManualLetElse::new(msrv, matches_for_let_else)));
+ store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
+ store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
+ store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
+ store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(msrv)));
+ store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(msrv)));
+ store.register_late_pass(move |_| Box::new(ranges::Ranges::new(msrv)));
+ store.register_late_pass(move |_| Box::new(from_over_into::FromOverInto::new(msrv)));
+ store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(msrv)));
+ store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(msrv)));
+ store.register_late_pass(move |_| Box::new(needless_question_mark::NeedlessQuestionMark));
+ store.register_late_pass(move |_| Box::new(casts::Casts::new(msrv)));
+ store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv)));
+ store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount));
+ store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod));
+ let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
+ store.register_late_pass(move |_| {
+ Box::new(index_refutable_slice::IndexRefutableSlice::new(
+ max_suggested_slice_pattern_length,
+ msrv,
+ ))
+ });
+ store.register_late_pass(|_| Box::<shadow::Shadow>::default());
+ store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
+ store.register_late_pass(|_| Box::new(loops::Loops));
+ store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
+ store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
+ store.register_late_pass(|_| Box::new(entry::HashMapPass));
+ store.register_late_pass(|_| Box::new(minmax::MinMaxPass));
+ store.register_late_pass(|_| Box::new(zero_div_zero::ZeroDiv));
+ store.register_late_pass(|_| Box::new(mutex_atomic::Mutex));
+ store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate));
+ store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
+ store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef));
+ store.register_late_pass(|_| Box::new(no_effect::NoEffect));
+ store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
+ store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv)));
+ let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
+ store.register_late_pass(move |_| {
+ Box::new(cognitive_complexity::CognitiveComplexity::new(
+ cognitive_complexity_threshold,
+ ))
+ });
+ let too_large_for_stack = conf.too_large_for_stack;
+ store.register_late_pass(move |_| Box::new(escape::BoxedLocal { too_large_for_stack }));
+ store.register_late_pass(move |_| Box::new(vec::UselessVec { too_large_for_stack }));
+ store.register_late_pass(|_| Box::new(panic_unimplemented::PanicUnimplemented));
+ store.register_late_pass(|_| Box::new(strings::StringLitAsBytes));
+ store.register_late_pass(|_| Box::new(derive::Derive));
+ store.register_late_pass(|_| Box::new(derivable_impls::DerivableImpls));
+ store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef));
+ store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
+ store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
+ store.register_late_pass(|_| Box::new(regex::Regex));
+ store.register_late_pass(|_| Box::new(copies::CopyAndPaste));
+ store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
+ store.register_late_pass(|_| Box::new(format::UselessFormat));
+ store.register_late_pass(|_| Box::new(swap::Swap));
+ store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
+ store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
+ let disallowed_names = conf.disallowed_names.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone())));
+ let too_many_arguments_threshold = conf.too_many_arguments_threshold;
+ let too_many_lines_threshold = conf.too_many_lines_threshold;
+ let large_error_threshold = conf.large_error_threshold;
+ store.register_late_pass(move |_| {
+ Box::new(functions::Functions::new(
+ too_many_arguments_threshold,
+ too_many_lines_threshold,
+ large_error_threshold,
+ ))
+ });
+ let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
+ store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply));
+ store.register_late_pass(|_| Box::new(mem_forget::MemForget));
+ store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq));
+ store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
+ store.register_late_pass(|_| Box::new(missing_doc::MissingDoc::new()));
+ store.register_late_pass(|_| Box::new(missing_inline::MissingInline));
+ store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems));
+ store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk));
+ store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
+ store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
+ let enum_variant_size_threshold = conf.enum_variant_size_threshold;
+ store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
+ store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite));
+ store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
+ let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
+ conf.trivial_copy_size_limit,
+ conf.pass_by_value_size_limit,
+ conf.avoid_breaking_exported_api,
+ &sess.target,
+ );
+ store.register_late_pass(move |_| Box::new(pass_by_ref_or_value));
+ store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef));
+ store.register_late_pass(|_| Box::new(infinite_iter::InfiniteIter));
+ store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody));
+ store.register_late_pass(|_| Box::<useless_conversion::UselessConversion>::default());
+ store.register_late_pass(|_| Box::new(implicit_hasher::ImplicitHasher));
+ store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom));
+ store.register_late_pass(|_| Box::new(question_mark::QuestionMark));
+ store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
+ store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl));
+ store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit));
+ store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl));
+ store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
+ store.register_late_pass(|_| Box::new(unwrap::Unwrap));
+ store.register_late_pass(|_| Box::new(indexing_slicing::IndexingSlicing));
+ store.register_late_pass(|_| Box::new(non_copy_const::NonCopyConst));
+ store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
+ store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
+ store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit));
+ store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api)));
+ store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants));
+ store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
+ store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString));
+ let max_trait_bounds = conf.max_trait_bounds;
+ store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
+ store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
- store.register_early_pass(|| Box::new(single_component_path_imports::SingleComponentPathImports));
++ let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
++ store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
+ store.register_early_pass(|| Box::new(reference::DerefAddrOf));
+ store.register_early_pass(|| Box::new(double_parens::DoubleParens));
+ store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new()));
+ store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
+ store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
+ store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
+ store.register_early_pass(|| Box::new(formatting::Formatting));
+ store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
+ store.register_early_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
+ store.register_late_pass(|_| Box::new(returns::Return));
+ store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
+ store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
+ store.register_early_pass(|| Box::new(precedence::Precedence));
+ store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
+ store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
+ store.register_early_pass(|| Box::new(redundant_else::RedundantElse));
+ store.register_late_pass(|_| Box::new(create_dir::CreateDir));
+ store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType));
+ let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
+ store.register_early_pass(move || {
+ Box::new(literal_representation::LiteralDigitGrouping::new(
+ literal_representation_lint_fraction_readability,
+ ))
+ });
+ let literal_representation_threshold = conf.literal_representation_threshold;
+ store.register_early_pass(move || {
+ Box::new(literal_representation::DecimalLiteralRepresentation::new(
+ literal_representation_threshold,
+ ))
+ });
+ let enum_variant_name_threshold = conf.enum_variant_name_threshold;
+ store.register_late_pass(move |_| {
+ Box::new(enum_variants::EnumVariantNames::new(
+ enum_variant_name_threshold,
+ avoid_breaking_exported_api,
+ ))
+ });
+ store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
+ let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
+ store.register_late_pass(move |_| {
+ Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
+ avoid_breaking_exported_api,
+ upper_case_acronyms_aggressive,
+ ))
+ });
+ store.register_late_pass(|_| Box::<default::Default>::default());
+ store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(avoid_breaking_exported_api)));
+ store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
+ store.register_late_pass(|_| Box::new(exit::Exit));
+ store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome));
+ let array_size_threshold = conf.array_size_threshold;
+ store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
+ store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
+ store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
+ store.register_early_pass(|| Box::new(as_conversions::AsConversions));
+ store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
- store.register_early_pass(move || {
++ store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
+ let max_fn_params_bools = conf.max_fn_params_bools;
+ let max_struct_bools = conf.max_struct_bools;
- store.register_late_pass(|_| Box::<write::Write>::default());
++ store.register_late_pass(move |_| {
+ Box::new(excessive_bools::ExcessiveBools::new(
+ max_struct_bools,
+ max_fn_params_bools,
+ ))
+ });
+ store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
+ let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
+ store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
+ store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
+ store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
+ store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv)));
+ store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
+ store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
+ store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
+ store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
+ store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
+ store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn));
+ store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn));
+ let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
+ store.register_early_pass(move || {
+ Box::new(non_expressive_names::NonExpressiveNames {
+ single_char_binding_names_threshold,
+ })
+ });
+ let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher)));
+ store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default());
+ store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch));
+ store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
+ store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
+ store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
+ let disallowed_macros = conf.disallowed_macros.clone();
+ store.register_late_pass(move |_| Box::new(disallowed_macros::DisallowedMacros::new(disallowed_macros.clone())));
+ let disallowed_methods = conf.disallowed_methods.clone();
+ store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
+ store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop));
+ store.register_late_pass(|_| Box::new(strings::StrToString));
+ store.register_late_pass(|_| Box::new(strings::StringToString));
+ store.register_late_pass(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues));
+ store.register_late_pass(|_| Box::<vec_init_then_push::VecInitThenPush>::default());
+ store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing));
+ store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10));
+ store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
+ store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
+ store.register_early_pass(move || Box::new(module_style::ModStyle));
+ store.register_late_pass(|_| Box::new(unused_async::UnusedAsync));
+ let disallowed_types = conf.disallowed_types.clone();
+ store.register_late_pass(move |_| Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone())));
+ let import_renames = conf.enforced_import_renames.clone();
+ store.register_late_pass(move |_| {
+ Box::new(missing_enforced_import_rename::ImportRename::new(
+ import_renames.clone(),
+ ))
+ });
+ let scripts = conf.allowed_scripts.clone();
+ store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
+ store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings));
+ store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors));
+ store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator));
+ store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert));
+ let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
+ store.register_late_pass(move |_| {
+ Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
+ enable_raw_pointer_heuristic_for_send,
+ ))
+ });
+ store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
+ store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv)));
+ store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
+ store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
+ store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
+ store.register_late_pass(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse));
+ store.register_late_pass(|_| Box::new(init_numbered_fields::NumberedFields));
+ store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames));
+ store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(msrv)));
+ store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
+ store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
+ let allow_dbg_in_tests = conf.allow_dbg_in_tests;
+ store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
++ let allow_print_in_tests = conf.allow_print_in_tests;
++ store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests)));
+ let cargo_ignore_publish = conf.cargo_ignore_publish;
+ store.register_late_pass(move |_| {
+ Box::new(cargo::Cargo {
+ ignore_publish: cargo_ignore_publish,
+ })
+ });
- store.register_late_pass(|_| Box::new(manual_instant_elapsed::ManualInstantElapsed));
+ store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
+ store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
+ store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
+ store.register_early_pass(|| Box::new(pub_use::PubUse));
+ store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
+ let max_include_file_size = conf.max_include_file_size;
+ store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
+ store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace));
+ store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
+ store.register_early_pass(|| Box::<duplicate_mod::DuplicateMod>::default());
+ store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding));
+ store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv)));
+ store.register_late_pass(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef));
+ store.register_late_pass(|_| Box::new(mismatching_type_param_order::TypeParamMismatch));
+ store.register_late_pass(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec));
+ store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
+ store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv)));
+ store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv)));
+ let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
+ store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
+ store.register_late_pass(|_| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
+ store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
++ store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv)));
+ store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
+ store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(msrv)));
+ store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew));
+ store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable));
+ store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments));
+ store.register_late_pass(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf));
+ store.register_late_pass(|_| Box::new(box_default::BoxDefault));
+ store.register_late_pass(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd));
+ store.register_early_pass(|| Box::new(partial_pub_fields::PartialPubFields));
+ store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods));
++ store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr));
++ store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
++ store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv)));
+ // add lints here, do not remove this comment, it's used in `new_lint`
+}
+
+#[rustfmt::skip]
+fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
+ store.register_removed(
+ "should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "reverse_range_loop",
+ "this lint is now included in reversed_empty_ranges",
+ );
+}
+
+/// Register renamed lints.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
+ for (old_name, new_name) in renamed_lints::RENAMED_LINTS {
+ ls.register_renamed(old_name, new_name);
+ }
+}
+
+// only exists to let the dogfood integration test works.
+// Don't run clippy as an executable directly
+#[allow(dead_code)]
+fn main() {
+ panic!("Please use the cargo-clippy executable");
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint;
++use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::trait_ref_of_method;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
+use rustc_hir::intravisit::{
+ walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
+ walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor,
+};
+use rustc_hir::lang_items;
+use rustc_hir::FnRetTy::Return;
+use rustc_hir::{
+ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem,
+ ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, TraitFn, TraitItem,
+ TraitItemKind, Ty, TyKind, WherePredicate,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter as middle_nested_filter;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{kw, Ident, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for lifetime annotations which can be removed by
+ /// relying on lifetime elision.
+ ///
+ /// ### Why is this bad?
+ /// The additional lifetimes make the code look more
+ /// complicated, while there is nothing out of the ordinary going on. Removing
+ /// them leads to more readable code.
+ ///
+ /// ### Known problems
+ /// - We bail out if the function has a `where` clause where lifetimes
+ /// are mentioned due to potential false positives.
+ /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the
+ /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Unnecessary lifetime annotations
+ /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 {
+ /// x
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn elided(x: &u8, y: u8) -> &u8 {
+ /// x
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_LIFETIMES,
+ complexity,
+ "using explicit lifetimes for references in function arguments when elision rules \
+ would allow omitting them"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for lifetimes in generics that are never used
+ /// anywhere else.
+ ///
+ /// ### Why is this bad?
+ /// The additional lifetimes make the code look more
+ /// complicated, while there is nothing out of the ordinary going on. Removing
+ /// them leads to more readable code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // unnecessary lifetimes
+ /// fn unused_lifetime<'a>(x: u8) {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn no_lifetime(x: u8) {
+ /// // ...
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXTRA_UNUSED_LIFETIMES,
+ complexity,
+ "unused lifetimes in function definitions"
+}
+
+declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
+
+impl<'tcx> LateLintPass<'tcx> for Lifetimes {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Fn(ref sig, generics, id) = item.kind {
+ check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
+ } else if let ItemKind::Impl(impl_) = item.kind {
+ if !item.span.from_expansion() {
+ report_extra_impl_lifetimes(cx, impl_);
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Fn(ref sig, id) = item.kind {
+ let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id.def_id).is_none();
+ check_fn_inner(
+ cx,
+ sig.decl,
+ Some(id),
+ None,
+ item.generics,
+ item.span,
+ report_extra_lifetimes,
+ );
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(ref sig, ref body) = item.kind {
+ let (body, trait_sig) = match *body {
+ TraitFn::Required(sig) => (None, Some(sig)),
+ TraitFn::Provided(id) => (Some(id), None),
+ };
+ check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true);
+ }
+ }
+}
+
+/// The lifetime of a &-reference.
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+enum RefLt {
+ Unnamed,
+ Static,
+ Named(LocalDefId),
+}
+
+fn check_fn_inner<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ trait_sig: Option<&[Ident]>,
+ generics: &'tcx Generics<'_>,
+ span: Span,
+ report_extra_lifetimes: bool,
+) {
+ if span.from_expansion() || has_where_lifetimes(cx, generics) {
+ return;
+ }
+
+ let types = generics
+ .params
+ .iter()
+ .filter(|param| matches!(param.kind, GenericParamKind::Type { .. }));
++
+ for typ in types {
+ for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) {
+ if pred.origin == PredicateOrigin::WhereClause {
+ // has_where_lifetimes checked that this predicate contains no lifetime.
+ continue;
+ }
+
+ for bound in pred.bounds {
+ let mut visitor = RefVisitor::new(cx);
+ walk_param_bound(&mut visitor, bound);
+ if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
+ return;
+ }
+ if let GenericBound::Trait(ref trait_ref, _) = *bound {
+ let params = &trait_ref
+ .trait_ref
+ .path
+ .segments
+ .last()
+ .expect("a path must have at least one segment")
+ .args;
+ if let Some(params) = *params {
+ let lifetimes = params.args.iter().filter_map(|arg| match arg {
+ GenericArg::Lifetime(lt) => Some(lt),
+ _ => None,
+ });
+ for bound in lifetimes {
+ if bound.name != LifetimeName::Static && !bound.is_elided() {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
- if could_use_elision(cx, decl, body, trait_sig, generics.params) {
- span_lint(
++
++ if let Some(elidable_lts) = could_use_elision(cx, decl, body, trait_sig, generics.params) {
++ let lts = elidable_lts
++ .iter()
++ // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
++ // `Node::GenericParam`.
++ .filter_map(|&(def_id, _)| cx.tcx.hir().get_by_def_id(def_id).ident())
++ .map(|ident| ident.to_string())
++ .collect::<Vec<_>>()
++ .join(", ");
++
++ span_lint_and_then(
+ cx,
+ NEEDLESS_LIFETIMES,
+ span.with_hi(decl.output.span().hi()),
- "explicit lifetimes given in parameter types where they could be elided \
- (or replaced with `'_` if needed by type declaration)",
++ &format!("the following explicit lifetimes could be elided: {lts}"),
++ |diag| {
++ if let Some(span) = elidable_lts.iter().find_map(|&(_, span)| span) {
++ diag.span_help(span, "replace with `'_` in generic arguments such as here");
++ }
++ },
+ );
+ }
++
+ if report_extra_lifetimes {
+ self::report_extra_lifetimes(cx, decl, generics);
+ }
+}
+
+// elision doesn't work for explicit self types, see rust-lang/rust#69064
+fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
+ if_chain! {
+ if let Some(ident) = ident;
+ if ident.name == kw::SelfLower;
+ if !func.implicit_self.has_implicit_self();
+
+ if let Some(self_ty) = func.inputs.first();
+ then {
+ let mut visitor = RefVisitor::new(cx);
+ visitor.visit_ty(self_ty);
+
+ !visitor.all_lts().is_empty()
+ } else {
+ false
+ }
+ }
+}
+
+fn could_use_elision<'tcx>(
+ cx: &LateContext<'tcx>,
+ func: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ trait_sig: Option<&[Ident]>,
+ named_generics: &'tcx [GenericParam<'_>],
- ) -> bool {
++) -> Option<Vec<(LocalDefId, Option<Span>)>> {
+ // There are two scenarios where elision works:
+ // * no output references, all input references have different LT
+ // * output references, exactly one input reference with same LT
+ // All lifetimes must be unnamed, 'static or defined without bounds on the
+ // level of the current item.
+
+ // check named LTs
+ let allowed_lts = allowed_lts_from(cx.tcx, named_generics);
+
+ // these will collect all the lifetimes for references in arg/return types
+ let mut input_visitor = RefVisitor::new(cx);
+ let mut output_visitor = RefVisitor::new(cx);
+
+ // extract lifetimes in input argument types
+ for arg in func.inputs {
+ input_visitor.visit_ty(arg);
+ }
+ // extract lifetimes in output type
+ if let Return(ty) = func.output {
+ output_visitor.visit_ty(ty);
+ }
+ for lt in named_generics {
+ input_visitor.visit_generic_param(lt);
+ }
+
+ if input_visitor.abort() || output_visitor.abort() {
- return false;
++ return None;
+ }
+
+ let input_lts = input_visitor.lts;
+ let output_lts = output_visitor.lts;
+
+ if let Some(trait_sig) = trait_sig {
+ if explicit_self_type(cx, func, trait_sig.first().copied()) {
- return false;
++ return None;
+ }
+ }
+
+ if let Some(body_id) = body {
+ let body = cx.tcx.hir().body(body_id);
+
+ let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
+ if explicit_self_type(cx, func, first_ident) {
- return false;
++ return None;
+ }
+
+ let mut checker = BodyLifetimeChecker {
+ lifetimes_used_in_body: false,
+ };
+ checker.visit_expr(body.value);
+ if checker.lifetimes_used_in_body {
- return false;
++ return None;
+ }
+ }
+
+ // check for lifetimes from higher scopes
+ for lt in input_lts.iter().chain(output_lts.iter()) {
+ if !allowed_lts.contains(lt) {
- return false;
++ return None;
+ }
+ }
+
+ // check for higher-ranked trait bounds
+ if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() {
+ let allowed_lts: FxHashSet<_> = allowed_lts
+ .iter()
+ .filter_map(|lt| match lt {
+ RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())),
+ _ => None,
+ })
+ .collect();
+ for lt in input_visitor.nested_elision_site_lts {
+ if let RefLt::Named(def_id) = lt {
+ if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
- return false;
++ return None;
+ }
+ }
+ }
+ for lt in output_visitor.nested_elision_site_lts {
+ if let RefLt::Named(def_id) = lt {
+ if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
- return false;
++ return None;
+ }
+ }
+ }
+ }
+
- // no input lifetimes? easy case!
- if input_lts.is_empty() {
- false
- } else if output_lts.is_empty() {
- // no output lifetimes, check distinctness of input lifetimes
++ // A lifetime can be newly elided if:
++ // - It occurs only once among the inputs.
++ // - If there are multiple input lifetimes, then the newly elided lifetime does not occur among the
++ // outputs (because eliding such an lifetime would create an ambiguity).
++ let elidable_lts = named_lifetime_occurrences(&input_lts)
++ .into_iter()
++ .filter_map(|(def_id, occurrences)| {
++ if occurrences == 1 && (input_lts.len() == 1 || !output_lts.contains(&RefLt::Named(def_id))) {
++ Some((
++ def_id,
++ input_visitor
++ .lifetime_generic_arg_spans
++ .get(&def_id)
++ .or_else(|| output_visitor.lifetime_generic_arg_spans.get(&def_id))
++ .copied(),
++ ))
++ } else {
++ None
++ }
++ })
++ .collect::<Vec<_>>();
+
- // only unnamed and static, ok
- let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
- if unnamed_and_static {
- return false;
- }
- // we have no output reference, so we only need all distinct lifetimes
- input_lts.len() == unique_lifetimes(&input_lts)
++ if elidable_lts.is_empty() {
++ None
+ } else {
- // we have output references, so we need one input reference,
- // and all output lifetimes must be the same
- if unique_lifetimes(&output_lts) > 1 {
- return false;
- }
- if input_lts.len() == 1 {
- match (&input_lts[0], &output_lts[0]) {
- (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
- (&RefLt::Named(_), &RefLt::Unnamed) => true,
- _ => false, /* already elided, different named lifetimes
- * or something static going on */
- }
- } else {
- false
- }
++ Some(elidable_lts)
+ }
+}
+
+fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
+ let mut allowed_lts = FxHashSet::default();
+ for par in named_generics.iter() {
+ if let GenericParamKind::Lifetime { .. } = par.kind {
+ allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id)));
+ }
+ }
+ allowed_lts.insert(RefLt::Unnamed);
+ allowed_lts.insert(RefLt::Static);
+ allowed_lts
+}
+
- /// Number of unique lifetimes in the given vector.
++/// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
++/// relative order.
+#[must_use]
- fn unique_lifetimes(lts: &[RefLt]) -> usize {
- lts.iter().collect::<FxHashSet<_>>().len()
++fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> {
++ let mut occurrences = Vec::new();
++ for lt in lts {
++ if let &RefLt::Named(curr_def_id) = lt {
++ if let Some(pair) = occurrences
++ .iter_mut()
++ .find(|(prev_def_id, _)| *prev_def_id == curr_def_id)
++ {
++ pair.1 += 1;
++ } else {
++ occurrences.push((curr_def_id, 1));
++ }
++ }
++ }
++ occurrences
+}
+
+/// A visitor usable for `rustc_front::visit::walk_ty()`.
+struct RefVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ lts: Vec<RefLt>,
++ lifetime_generic_arg_spans: FxHashMap<LocalDefId, Span>,
+ nested_elision_site_lts: Vec<RefLt>,
+ unelided_trait_object_lifetime: bool,
+}
+
+impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ lts: Vec::new(),
++ lifetime_generic_arg_spans: FxHashMap::default(),
+ nested_elision_site_lts: Vec::new(),
+ unelided_trait_object_lifetime: false,
+ }
+ }
+
+ fn record(&mut self, lifetime: &Option<Lifetime>) {
+ if let Some(ref lt) = *lifetime {
+ if lt.name == LifetimeName::Static {
+ self.lts.push(RefLt::Static);
+ } else if let LifetimeName::Param(_, ParamName::Fresh) = lt.name {
+ // Fresh lifetimes generated should be ignored.
+ self.lts.push(RefLt::Unnamed);
+ } else if lt.is_elided() {
+ self.lts.push(RefLt::Unnamed);
+ } else if let LifetimeName::Param(def_id, _) = lt.name {
+ self.lts.push(RefLt::Named(def_id));
+ } else {
+ self.lts.push(RefLt::Unnamed);
+ }
+ } else {
+ self.lts.push(RefLt::Unnamed);
+ }
+ }
+
+ fn all_lts(&self) -> Vec<RefLt> {
+ self.lts
+ .iter()
+ .chain(self.nested_elision_site_lts.iter())
+ .cloned()
+ .collect::<Vec<_>>()
+ }
+
+ fn abort(&self) -> bool {
+ self.unelided_trait_object_lifetime
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
+ // for lifetimes as parameters of generics
+ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+ self.record(&Some(*lifetime));
+ }
+
+ fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>) {
+ let trait_ref = &poly_tref.trait_ref;
+ if let Some(id) = trait_ref.trait_def_id() && lang_items::FN_TRAITS.iter().any(|&item| {
+ self.cx.tcx.lang_items().get(item) == Some(id)
+ }) {
+ let mut sub_visitor = RefVisitor::new(self.cx);
+ sub_visitor.visit_trait_ref(trait_ref);
+ self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
+ } else {
+ walk_poly_trait_ref(self, poly_tref);
+ }
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
+ match ty.kind {
+ TyKind::OpaqueDef(item, bounds, _) => {
+ let map = self.cx.tcx.hir();
+ let item = map.item(item);
+ let len = self.lts.len();
+ walk_item(self, item);
+ self.lts.truncate(len);
+ self.lts.extend(bounds.iter().filter_map(|bound| match bound {
+ GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id, _) = l.name {
+ RefLt::Named(def_id)
+ } else {
+ RefLt::Unnamed
+ }),
+ _ => None,
+ }));
+ },
+ TyKind::BareFn(&BareFnTy { decl, .. }) => {
+ let mut sub_visitor = RefVisitor::new(self.cx);
+ sub_visitor.visit_fn_decl(decl);
+ self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
+ },
+ TyKind::TraitObject(bounds, ref lt, _) => {
+ if !lt.is_elided() {
+ self.unelided_trait_object_lifetime = true;
+ }
+ for bound in bounds {
+ self.visit_poly_trait_ref(bound);
+ }
+ },
+ _ => walk_ty(self, ty),
+ }
+ }
++
++ fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) {
++ if let GenericArg::Lifetime(l) = generic_arg
++ && let LifetimeName::Param(def_id, _) = l.name
++ {
++ self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.span);
++ }
++ // Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
++ // walk_generic_arg(self, generic_arg);
++ match generic_arg {
++ GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
++ GenericArg::Type(ty) => self.visit_ty(ty),
++ GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
++ GenericArg::Infer(inf) => self.visit_infer(inf),
++ }
++ }
+}
+
+/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
+/// reason about elision.
+fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_>) -> bool {
+ for predicate in generics.predicates {
+ match *predicate {
+ WherePredicate::RegionPredicate(..) => return true,
+ WherePredicate::BoundPredicate(ref pred) => {
+ // a predicate like F: Trait or F: for<'a> Trait<'a>
+ let mut visitor = RefVisitor::new(cx);
+ // walk the type F, it may not contain LT refs
+ walk_ty(&mut visitor, pred.bounded_ty);
+ if !visitor.all_lts().is_empty() {
+ return true;
+ }
+ // if the bounds define new lifetimes, they are fine to occur
+ let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params);
+ // now walk the bounds
+ for bound in pred.bounds.iter() {
+ walk_param_bound(&mut visitor, bound);
+ }
+ // and check that all lifetimes are allowed
+ if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) {
+ return true;
+ }
+ },
+ WherePredicate::EqPredicate(ref pred) => {
+ let mut visitor = RefVisitor::new(cx);
+ walk_ty(&mut visitor, pred.lhs_ty);
+ walk_ty(&mut visitor, pred.rhs_ty);
+ if !visitor.lts.is_empty() {
+ return true;
+ }
+ },
+ }
+ }
+ false
+}
+
+struct LifetimeChecker<'cx, 'tcx, F> {
+ cx: &'cx LateContext<'tcx>,
+ map: FxHashMap<Symbol, Span>,
+ phantom: std::marker::PhantomData<F>,
+}
+
+impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> {
+ fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap<Symbol, Span>) -> LifetimeChecker<'cx, 'tcx, F> {
+ Self {
+ cx,
+ map,
+ phantom: std::marker::PhantomData,
+ }
+ }
+}
+
+impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
+where
+ F: NestedFilter<'tcx>,
+{
+ type Map = rustc_middle::hir::map::Map<'tcx>;
+ type NestedFilter = F;
+
+ // for lifetimes as parameters of generics
+ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+ self.map.remove(&lifetime.name.ident().name);
+ }
+
+ fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) {
+ // don't actually visit `<'a>` or `<'a: 'b>`
+ // we've already visited the `'a` declarations and
+ // don't want to spuriously remove them
+ // `'b` in `'a: 'b` is useless unless used elsewhere in
+ // a non-lifetime bound
+ if let GenericParamKind::Type { .. } = param.kind {
+ walk_generic_param(self, param);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
+ let hs = generics
+ .params
+ .iter()
+ .filter_map(|par| match par.kind {
+ GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
+ _ => None,
+ })
+ .collect();
+ let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, hs);
+
+ walk_generics(&mut checker, generics);
+ walk_fn_decl(&mut checker, func);
+
+ for &v in checker.map.values() {
+ span_lint(
+ cx,
+ EXTRA_UNUSED_LIFETIMES,
+ v,
+ "this lifetime isn't used in the function definition",
+ );
+ }
+}
+
+fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) {
+ let hs = impl_
+ .generics
+ .params
+ .iter()
+ .filter_map(|par| match par.kind {
+ GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
+ _ => None,
+ })
+ .collect();
+ let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, hs);
+
+ walk_generics(&mut checker, impl_.generics);
+ if let Some(ref trait_ref) = impl_.of_trait {
+ walk_trait_ref(&mut checker, trait_ref);
+ }
+ walk_ty(&mut checker, impl_.self_ty);
+ for item in impl_.items {
+ walk_impl_item_ref(&mut checker, item);
+ }
+
+ for &v in checker.map.values() {
+ span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl");
+ }
+}
+
+struct BodyLifetimeChecker {
+ lifetimes_used_in_body: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
+ // for lifetimes as parameters of generics
+ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+ if lifetime.name.ident().name != kw::UnderscoreLifetime && lifetime.name.ident().name != kw::StaticLifetime {
+ self.lifetimes_used_in_body = true;
+ }
+ }
+}
--- /dev/null
- mod needless_collect;
+mod empty_loop;
+mod explicit_counter_loop;
+mod explicit_into_iter_loop;
+mod explicit_iter_loop;
+mod for_kv_map;
+mod iter_next_loop;
+mod manual_find;
+mod manual_flatten;
+mod manual_memcpy;
+mod missing_spin_loop;
+mod mut_range_bound;
- declare_clippy_lint! {
- /// ### What it does
- /// Checks for functions collecting an iterator when collect
- /// is not needed.
- ///
- /// ### Why is this bad?
- /// `collect` causes the allocation of a new data structure,
- /// when this allocation may not be needed.
- ///
- /// ### Example
- /// ```rust
- /// # let iterator = vec![1].into_iter();
- /// let len = iterator.clone().collect::<Vec<_>>().len();
- /// // should be
- /// let len = iterator.count();
- /// ```
- #[clippy::version = "1.30.0"]
- pub NEEDLESS_COLLECT,
- perf,
- "collecting an iterator when collect is not needed"
- }
-
+mod needless_range_loop;
+mod never_loop;
+mod same_item_push;
+mod single_element_loop;
+mod utils;
+mod while_immutable_condition;
+mod while_let_loop;
+mod while_let_on_iterator;
+
+use clippy_utils::higher;
+use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for for-loops that manually copy items between
+ /// slices that could be optimized by having a memcpy.
+ ///
+ /// ### Why is this bad?
+ /// It is not as fast as a memcpy.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// for i in 0..src.len() {
+ /// dst[i + 64] = src[i];
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MANUAL_MEMCPY,
+ perf,
+ "manually copying items between slices"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for looping over the range of `0..len` of some
+ /// collection just to get the values by index.
+ ///
+ /// ### Why is this bad?
+ /// Just iterating the collection itself makes the intent
+ /// more clear and is probably faster.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in 0..vec.len() {
+ /// println!("{}", vec[i]);
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in vec {
+ /// println!("{}", i);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_RANGE_LOOP,
+ style,
+ "for-looping over a range of indices where an iterator over items would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `x.iter()` where `&x` will do, and
+ /// suggests the latter.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Known problems
+ /// False negatives. We currently only warn on some known
+ /// types.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // with `y` a `Vec` or slice:
+ /// # let y = vec![1];
+ /// for x in y.iter() {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in &y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `y.into_iter()` where `y` will do, and
+ /// suggests the latter.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y = vec![1];
+ /// // with `y` a `Vec` or slice:
+ /// for x in y.into_iter() {
+ /// // ..
+ /// }
+ /// ```
+ /// can be rewritten to
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_INTO_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.into_iter()` when `_` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `x.next()`.
+ ///
+ /// ### Why is this bad?
+ /// `next()` returns either `Some(value)` if there was a
+ /// value, or `None` otherwise. The insidious thing is that `Option<_>`
+ /// implements `IntoIterator`, so that possibly one value will be iterated,
+ /// leading to some hard to find bugs. No one will want to write such code
+ /// [except to win an Underhanded Rust
+ /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr).
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for x in y.next() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_NEXT_LOOP,
+ correctness,
+ "for-looping over `_.next()` which is probably not intended"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `loop + match` combinations that are easier
+ /// written as a `while let` loop.
+ ///
+ /// ### Why is this bad?
+ /// The `while let` loop is usually shorter and more
+ /// readable.
+ ///
+ /// ### Known problems
+ /// Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)).
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # let y = Some(1);
+ /// loop {
+ /// let x = match y {
+ /// Some(x) => x,
+ /// None => break,
+ /// };
+ /// // .. do something with x
+ /// }
+ /// // is easier written as
+ /// while let Some(x) = y {
+ /// // .. do something with x
+ /// };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_LET_LOOP,
+ complexity,
+ "`loop { if let { ... } else break }`, which can be written as a `while let` loop"
+}
+
- NEEDLESS_COLLECT,
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks `for` loops over slices with an explicit counter
+ /// and suggests the use of `.enumerate()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `.enumerate()` makes the intent more clear,
+ /// declutters the code and may be faster in some instances.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = vec![1];
+ /// # fn bar(bar: usize, baz: usize) {}
+ /// let mut i = 0;
+ /// for item in &v {
+ /// bar(i, *item);
+ /// i += 1;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let v = vec![1];
+ /// # fn bar(bar: usize, baz: usize) {}
+ /// for (i, item) in v.iter().enumerate() { bar(i, *item); }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_COUNTER_LOOP,
+ complexity,
+ "for-looping with an explicit counter when `_.enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty `loop` expressions.
+ ///
+ /// ### Why is this bad?
+ /// These busy loops burn CPU cycles without doing
+ /// anything. It is _almost always_ a better idea to `panic!` than to have
+ /// a busy loop.
+ ///
+ /// If panicking isn't possible, think of the environment and either:
+ /// - block on something
+ /// - sleep the thread for some microseconds
+ /// - yield or pause the thread
+ ///
+ /// For `std` targets, this can be done with
+ /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html)
+ /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html).
+ ///
+ /// For `no_std` targets, doing this is more complicated, especially because
+ /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will
+ /// probably need to invoke some target-specific intrinsic. Examples include:
+ /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html)
+ /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html)
+ ///
+ /// ### Example
+ /// ```no_run
+ /// loop {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LOOP,
+ suspicious,
+ "empty `loop {}`, which should block or sleep"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `while let` expressions on iterators.
+ ///
+ /// ### Why is this bad?
+ /// Readability. A simple `for` loop is shorter and conveys
+ /// the intent better.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// while let Some(val) = iter.next() {
+ /// ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```ignore
+ /// for val in &mut iter {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_LET_ON_ITERATOR,
+ style,
+ "using a `while let` loop instead of a for loop on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iterating a map (`HashMap` or `BTreeMap`) and
+ /// ignoring either the keys or values.
+ ///
+ /// ### Why is this bad?
+ /// Readability. There are `keys` and `values` methods that
+ /// can be used to express that don't need the values or keys.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for (k, _) in &map {
+ /// ..
+ /// }
+ /// ```
+ ///
+ /// could be replaced by
+ ///
+ /// ```ignore
+ /// for k in map.keys() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FOR_KV_MAP,
+ style,
+ "looping on a map using `iter` when `keys` or `values` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops that will always `break`, `return` or
+ /// `continue` an outer loop.
+ ///
+ /// ### Why is this bad?
+ /// This loop never loops, all it does is obfuscating the
+ /// code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// loop {
+ /// ..;
+ /// break;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEVER_LOOP,
+ correctness,
+ "any loop that will always `break` or `return`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops which have a range bound that is a mutable variable
+ ///
+ /// ### Why is this bad?
+ /// One might think that modifying the mutable variable changes the loop bounds
+ ///
+ /// ### Known problems
+ /// False positive when mutation is followed by a `break`, but the `break` is not immediately
+ /// after the mutation:
+ ///
+ /// ```rust
+ /// let mut x = 5;
+ /// for _ in 0..x {
+ /// x += 1; // x is a range bound that is mutated
+ /// ..; // some other expression
+ /// break; // leaves the loop, so mutation is not an issue
+ /// }
+ /// ```
+ ///
+ /// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut foo = 42;
+ /// for i in 0..foo {
+ /// foo -= 1;
+ /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUT_RANGE_BOUND,
+ suspicious,
+ "for loop over a range where one of the bounds is a mutable variable"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether variables used within while loop condition
+ /// can be (and are) mutated in the body.
+ ///
+ /// ### Why is this bad?
+ /// If the condition is unchanged, entering the body of the loop
+ /// will lead to an infinite loop.
+ ///
+ /// ### Known problems
+ /// If the `while`-loop is in a closure, the check for mutation of the
+ /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is
+ /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let i = 0;
+ /// while i > 10 {
+ /// println!("let me loop forever!");
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_IMMUTABLE_CONDITION,
+ correctness,
+ "variables used within while expression are not mutated in the body"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop is being used to push a constant
+ /// value into a Vec.
+ ///
+ /// ### Why is this bad?
+ /// This kind of operation can be expressed more succinctly with
+ /// `vec![item; SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
+ /// have better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = Vec::new();
+ /// for _ in 0..20 {
+ /// vec.push(item1);
+ /// }
+ /// for _ in 0..30 {
+ /// vec.push(item2);
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = vec![item1; 20];
+ /// vec.resize(20 + 30, item2);
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub SAME_ITEM_PUSH,
+ style,
+ "the same item is pushed inside of a for loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop has a single element.
+ ///
+ /// ### Why is this bad?
+ /// There is no reason to have a loop of a
+ /// single element.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// for item in &[item1] {
+ /// println!("{}", item);
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let item1 = 2;
+ /// let item = &item1;
+ /// println!("{}", item);
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub SINGLE_ELEMENT_LOOP,
+ complexity,
+ "there is no reason to have a single element loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for unnecessary `if let` usage in a for loop
+ /// where only the `Some` or `Ok` variant of the iterator element is used.
+ ///
+ /// ### Why is this bad?
+ /// It is verbose and can be simplified
+ /// by first calling the `flatten` method on the `Iterator`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x {
+ /// if let Some(n) = n {
+ /// println!("{}", n);
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x.into_iter().flatten() {
+ /// println!("{}", n);
+ /// }
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub MANUAL_FLATTEN,
+ complexity,
+ "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.61.0"]
+ pub MISSING_SPIN_LOOP,
+ perf,
+ "An empty busy waiting loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for manual implementations of Iterator::find
+ ///
+ /// ### Why is this bad?
+ /// It doesn't affect performance, but using `find` is shorter and easier to read.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn example(arr: Vec<i32>) -> Option<i32> {
+ /// for el in arr {
+ /// if el == 1 {
+ /// return Some(el);
+ /// }
+ /// }
+ /// None
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn example(arr: Vec<i32>) -> Option<i32> {
+ /// arr.into_iter().find(|&el| el == 1)
+ /// }
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub MANUAL_FIND,
+ complexity,
+ "manual implementation of `Iterator::find`"
+}
+
+declare_lint_pass!(Loops => [
+ MANUAL_MEMCPY,
+ MANUAL_FLATTEN,
+ NEEDLESS_RANGE_LOOP,
+ EXPLICIT_ITER_LOOP,
+ EXPLICIT_INTO_ITER_LOOP,
+ ITER_NEXT_LOOP,
+ WHILE_LET_LOOP,
-
- needless_collect::check(expr, cx);
+ EXPLICIT_COUNTER_LOOP,
+ EMPTY_LOOP,
+ WHILE_LET_ON_ITERATOR,
+ FOR_KV_MAP,
+ NEVER_LOOP,
+ MUT_RANGE_BOUND,
+ WHILE_IMMUTABLE_CONDITION,
+ SAME_ITEM_PUSH,
+ SINGLE_ELEMENT_LOOP,
+ MISSING_SPIN_LOOP,
+ MANUAL_FIND,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Loops {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let for_loop = higher::ForLoop::hir(expr);
+ if let Some(higher::ForLoop {
+ pat,
+ arg,
+ body,
+ loop_id,
+ span,
+ }) = for_loop
+ {
+ // we don't want to check expanded macros
+ // this check is not at the top of the function
+ // since higher::for_loop expressions are marked as expansions
+ if body.span.from_expansion() {
+ return;
+ }
+ check_for_loop(cx, pat, arg, body, expr, span);
+ if let ExprKind::Block(block, _) = body.kind {
+ never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
+ }
+ }
+
+ // we don't want to check expanded macros
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // check for never_loop
+ if let ExprKind::Loop(block, ..) = expr.kind {
+ never_loop::check(cx, block, expr.hir_id, expr.span, None);
+ }
+
+ // check for `loop { if let {} else break }` that could be `while let`
+ // (also matches an explicit "match" instead of "if let")
+ // (even if the "match" or "if let" is used for declaration)
+ if let ExprKind::Loop(block, _, LoopSource::Loop, _) = expr.kind {
+ // also check for empty `loop {}` statements, skipping those in #[panic_handler]
+ empty_loop::check(cx, expr, block);
+ while_let_loop::check(cx, expr, block);
+ }
+
+ while_let_on_iterator::check(cx, 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);
+ }
+ }
+}
+
+fn check_for_loop<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+ span: Span,
+) {
+ let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
+ if !is_manual_memcpy_triggered {
+ needless_range_loop::check(cx, pat, arg, body, expr);
+ explicit_counter_loop::check(cx, pat, arg, body, expr);
+ }
+ check_for_loop_arg(cx, pat, arg);
+ for_kv_map::check(cx, pat, arg, body);
+ mut_range_bound::check(cx, arg, body);
+ single_element_loop::check(cx, pat, arg, body, expr);
+ same_item_push::check(cx, pat, arg, body, expr);
+ manual_flatten::check(cx, pat, arg, body, span);
+ manual_find::check(cx, pat, arg, body, span, expr);
+}
+
+fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
+ if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
+ let method_name = method.ident.as_str();
+ // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
+ match method_name {
+ "iter" | "iter_mut" => {
+ explicit_iter_loop::check(cx, self_arg, arg, method_name);
+ },
+ "into_iter" => {
+ explicit_iter_loop::check(cx, self_arg, arg, method_name);
+ explicit_into_iter_loop::check(cx, self_arg, arg);
+ },
+ "next" => {
+ iter_next_loop::check(cx, arg);
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
- fn check_for_mutation<'tcx>(
- cx: &LateContext<'tcx>,
+use super::MUT_RANGE_BOUND;
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::{get_enclosing_block, higher, path_to_local};
+use if_chain::if_chain;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
+use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::{mir::FakeReadCause, ty};
+use rustc_span::source_map::Span;
+
+pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range {
+ start: Some(start),
+ end: Some(end),
+ ..
+ }) = higher::Range::hir(arg);
+ let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
+ if mut_id_start.is_some() || mut_id_end.is_some();
+ then {
+ let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
+ mut_warn_with_span(cx, span_low);
+ mut_warn_with_span(cx, span_high);
+ }
+ }
+}
+
+fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
+ if let Some(sp) = span {
+ span_lint_and_note(
+ cx,
+ MUT_RANGE_BOUND,
+ sp,
+ "attempt to mutate range bound within loop",
+ None,
+ "the range of the loop is unchanged",
+ );
+ }
+}
+
+fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
+ if_chain! {
+ if let Some(hir_id) = path_to_local(bound);
+ if let Node::Pat(pat) = cx.tcx.hir().get(hir_id);
+ if let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind;
+ then {
+ return Some(hir_id);
+ }
+ }
+ None
+}
+
- fn fake_read(
- &mut self,
- _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
- _: FakeReadCause,
- _: HirId,
- ) {
- }
++fn check_for_mutation(
++ cx: &LateContext<'_>,
+ body: &Expr<'_>,
+ bound_id_start: Option<HirId>,
+ bound_id_end: Option<HirId>,
+) -> (Option<Span>, Option<Span>) {
+ let mut delegate = MutatePairDelegate {
+ cx,
+ hir_id_low: bound_id_start,
+ hir_id_high: bound_id_end,
+ span_low: None,
+ span_high: None,
+ };
+ let infcx = cx.tcx.infer_ctxt().build();
+ ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ body.hir_id.owner.def_id,
+ cx.param_env,
+ cx.typeck_results(),
+ )
+ .walk_expr(body);
+
+ delegate.mutation_span()
+}
+
+struct MutatePairDelegate<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ hir_id_low: Option<HirId>,
+ hir_id_high: Option<HirId>,
+ span_low: Option<Span>,
+ span_high: Option<Span>,
+}
+
+impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
+ if bk == ty::BorrowKind::MutBorrow {
+ if let PlaceBase::Local(id) = cmt.place.base {
+ if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
+ self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
+ }
+ if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
+ self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
+ }
+ }
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
+ if let PlaceBase::Local(id) = cmt.place.base {
+ if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
+ self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
+ }
+ if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
+ self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
+ }
+ }
+ }
+
++ fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+impl MutatePairDelegate<'_, '_> {
+ fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
+ (self.span_low, self.span_high)
+ }
+}
+
+struct BreakAfterExprVisitor {
+ hir_id: HirId,
+ past_expr: bool,
+ past_candidate: bool,
+ break_after_expr: bool,
+}
+
+impl BreakAfterExprVisitor {
+ pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ let mut visitor = BreakAfterExprVisitor {
+ hir_id,
+ past_expr: false,
+ past_candidate: false,
+ break_after_expr: false,
+ };
+
+ get_enclosing_block(cx, hir_id).map_or(false, |block| {
+ visitor.visit_block(block);
+ visitor.break_after_expr
+ })
+ }
+}
+
+impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ if self.past_candidate {
+ return;
+ }
+
+ if expr.hir_id == self.hir_id {
+ self.past_expr = true;
+ } else if self.past_expr {
+ if matches!(&expr.kind, ExprKind::Break(..)) {
+ self.break_after_expr = true;
+ }
+
+ self.past_candidate = true;
+ } else {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+}
--- /dev/null
- use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
+use super::utils::make_iterator_snippet;
+use super::NEVER_LOOP;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::ForLoop;
+use clippy_utils::source::snippet;
+use rustc_errors::Applicability;
- match never_loop_block(block, loop_id) {
++use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+use std::iter::{once, Iterator};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ block: &Block<'_>,
+ loop_id: HirId,
+ span: Span,
+ for_loop: Option<&ForLoop<'_>>,
+) {
- fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
- let mut iter = block
++ match never_loop_block(block, &mut Vec::new(), loop_id) {
+ NeverLoopResult::AlwaysBreak => {
+ span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
+ if let Some(ForLoop {
+ arg: iterator,
+ pat,
+ span: for_span,
+ ..
+ }) = for_loop
+ {
+ // Suggests using an `if let` instead. This is `Unspecified` because the
+ // loop may (probably) contain `break` statements which would be invalid
+ // in an `if let`.
+ diag.span_suggestion_verbose(
+ for_span.with_hi(iterator.span.hi()),
+ "if you need the first element of the iterator, try writing",
+ for_to_if_let_sugg(cx, iterator, pat),
+ Applicability::Unspecified,
+ );
+ }
+ });
+ },
+ NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
+ }
+}
+
+#[derive(Copy, Clone)]
+enum NeverLoopResult {
+ // A break/return always get triggered but not necessarily for the main loop.
+ AlwaysBreak,
+ // A continue may occur for the main loop.
+ MayContinueMainLoop,
+ Otherwise,
+}
+
+#[must_use]
+fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
+ match arg {
+ NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
+ NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
+ }
+}
+
+// Combine two results for parts that are called in order.
+#[must_use]
+fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
+ match first {
+ NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first,
+ NeverLoopResult::Otherwise => second,
+ }
+}
+
+// Combine two results where both parts are called but not necessarily in order.
+#[must_use]
+fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult {
+ match (left, right) {
+ (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
+ NeverLoopResult::MayContinueMainLoop
+ },
+ (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
+ (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
+ }
+}
+
+// Combine two results where only one of the part may have been executed.
+#[must_use]
+fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
+ match (b1, b2) {
+ (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
+ (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
+ NeverLoopResult::MayContinueMainLoop
+ },
+ (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
+ }
+}
+
- never_loop_expr_seq(&mut iter, main_loop_id)
- }
++fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
++ let iter = block
+ .stmts
+ .iter()
+ .filter_map(stmt_to_expr)
+ .chain(block.expr.map(|expr| (expr, None)));
- fn never_loop_expr_seq<'a, T: Iterator<Item = (&'a Expr<'a>, Option<&'a Block<'a>>)>>(
- es: &mut T,
- main_loop_id: HirId,
- ) -> NeverLoopResult {
- es.map(|(e, els)| {
- let e = never_loop_expr(e, main_loop_id);
- els.map_or(e, |els| combine_branches(e, never_loop_block(els, main_loop_id)))
+
- StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some((e, None)),
++ iter.map(|(e, els)| {
++ let e = never_loop_expr(e, ignore_ids, main_loop_id);
++ // els is an else block in a let...else binding
++ els.map_or(e, |els| {
++ combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
++ })
+ })
+ .fold(NeverLoopResult::Otherwise, combine_seq)
+}
+
+fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
+ match stmt.kind {
- fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
++ StmtKind::Semi(e) | StmtKind::Expr(e) => Some((e, None)),
++ // add the let...else expression (if present)
+ StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
+ StmtKind::Item(..) => None,
+ }
+}
+
- | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id),
- ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id),
- ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), main_loop_id),
- ExprKind::MethodCall(_, receiver, es, _) => {
- never_loop_expr_all(&mut std::iter::once(receiver).chain(es.iter()), main_loop_id)
- },
++#[allow(clippy::too_many_lines)]
++fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
+ match expr.kind {
+ ExprKind::Box(e)
+ | ExprKind::Unary(_, e)
+ | ExprKind::Cast(e, _)
+ | ExprKind::Type(e, _)
+ | ExprKind::Field(e, _)
+ | ExprKind::AddrOf(_, _, e)
+ | ExprKind::Repeat(e, _)
- let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id);
++ | ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, main_loop_id),
++ ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, ignore_ids, main_loop_id),
++ ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), ignore_ids, main_loop_id),
++ ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
++ &mut std::iter::once(receiver).chain(es.iter()),
++ ignore_ids,
++ main_loop_id,
++ ),
+ ExprKind::Struct(_, fields, base) => {
- combine_both(fields, never_loop_expr(base, main_loop_id))
++ let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
+ if let Some(base) = base {
- ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
++ combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id))
+ } else {
+ fields
+ }
+ },
- | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), main_loop_id),
++ ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
+ ExprKind::Binary(_, e1, e2)
+ | ExprKind::Assign(e1, e2, _)
+ | ExprKind::AssignOp(_, e1, e2)
- absorb_break(never_loop_block(b, main_loop_id))
++ | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
+ ExprKind::Loop(b, _, _, _) => {
+ // Break can come from the inner loop so remove them.
- let e1 = never_loop_expr(e, main_loop_id);
- let e2 = never_loop_expr(e2, main_loop_id);
- let e3 = e3
- .as_ref()
- .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id));
++ absorb_break(never_loop_block(b, ignore_ids, main_loop_id))
+ },
+ ExprKind::If(e, e2, e3) => {
- let e = never_loop_expr(e, main_loop_id);
++ let e1 = never_loop_expr(e, ignore_ids, main_loop_id);
++ let e2 = never_loop_expr(e2, ignore_ids, main_loop_id);
++ let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
++ never_loop_expr(e, ignore_ids, main_loop_id)
++ });
+ combine_seq(e1, combine_branches(e2, e3))
+ },
+ ExprKind::Match(e, arms, _) => {
- let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), main_loop_id);
++ let e = never_loop_expr(e, ignore_ids, main_loop_id);
+ if arms.is_empty() {
+ e
+ } else {
- ExprKind::Block(b, _) => never_loop_block(b, main_loop_id),
++ let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
+ combine_seq(e, arms)
+ }
+ },
- combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
++ ExprKind::Block(b, l) => {
++ if l.is_some() {
++ ignore_ids.push(b.hir_id);
++ }
++ let ret = never_loop_block(b, ignore_ids, main_loop_id);
++ ignore_ids.pop();
++ ret
++ },
+ ExprKind::Continue(d) => {
+ let id = d
+ .target_id
+ .expect("target ID can only be missing in the presence of compilation errors");
+ if id == main_loop_id {
+ NeverLoopResult::MayContinueMainLoop
+ } else {
+ NeverLoopResult::AlwaysBreak
+ }
+ },
++ // checks if break targets a block instead of a loop
++ ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
++ .map_or(NeverLoopResult::Otherwise, |e| {
++ combine_seq(never_loop_expr(e, ignore_ids, main_loop_id), NeverLoopResult::Otherwise)
++ }),
+ ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
- never_loop_expr(expr, main_loop_id)
++ combine_seq(
++ never_loop_expr(e, ignore_ids, main_loop_id),
++ NeverLoopResult::AlwaysBreak,
++ )
+ }),
+ ExprKind::InlineAsm(asm) => asm
+ .operands
+ .iter()
+ .map(|(o, _)| match o {
+ InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
- InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter().copied(), main_loop_id),
- InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
- never_loop_expr_all(&mut once(*in_expr).chain(out_expr.iter().copied()), main_loop_id)
++ never_loop_expr(expr, ignore_ids, main_loop_id)
+ },
- fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
- es.map(|e| never_loop_expr(e, main_loop_id))
++ InlineAsmOperand::Out { expr, .. } => {
++ never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id)
+ },
++ InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
++ &mut once(*in_expr).chain(out_expr.iter().copied()),
++ ignore_ids,
++ main_loop_id,
++ ),
+ InlineAsmOperand::Const { .. }
+ | InlineAsmOperand::SymFn { .. }
+ | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
+ })
+ .fold(NeverLoopResult::Otherwise, combine_both),
+ ExprKind::Yield(_, _)
+ | ExprKind::Closure { .. }
+ | ExprKind::Path(_)
+ | ExprKind::ConstBlock(_)
+ | ExprKind::Lit(_)
+ | ExprKind::Err => NeverLoopResult::Otherwise,
+ }
+}
+
- fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult {
- e.map(|e| never_loop_expr(e, main_loop_id))
++fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
++ es: &mut T,
++ ignore_ids: &mut Vec<HirId>,
++ main_loop_id: HirId,
++) -> NeverLoopResult {
++ es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
+ .fold(NeverLoopResult::Otherwise, combine_both)
+}
+
++fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
++ e: &mut T,
++ ignore_ids: &mut Vec<HirId>,
++ main_loop_id: HirId,
++) -> NeverLoopResult {
++ e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
+ .fold(NeverLoopResult::AlwaysBreak, combine_branches)
+}
+
+fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
+ let pat_snippet = snippet(cx, pat.span, "_");
+ let iter_snippet = make_iterator_snippet(cx, iterator, &mut Applicability::Unspecified);
+
+ format!("if let Some({pat_snippet}) = {iter_snippet}.next()")
+}
--- /dev/null
--- /dev/null
++use rustc_ast::LitKind::{Byte, Char};
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{def_id::DefId, sym};
++
++use clippy_utils::{
++ diagnostics::span_lint_and_sugg, in_constant, macros::root_macro_call, meets_msrv, msrvs, source::snippet,
++};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Suggests to use dedicated built-in methods,
++ /// `is_ascii_(lowercase|uppercase|digit)` for checking on corresponding ascii range
++ ///
++ /// ### Why is this bad?
++ /// Using the built-in functions is more readable and makes it
++ /// clear that it's not a specific subset of characters, but all
++ /// ASCII (lowercase|uppercase|digit) characters.
++ /// ### Example
++ /// ```rust
++ /// fn main() {
++ /// assert!(matches!('x', 'a'..='z'));
++ /// assert!(matches!(b'X', b'A'..=b'Z'));
++ /// assert!(matches!('2', '0'..='9'));
++ /// assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// fn main() {
++ /// assert!('x'.is_ascii_lowercase());
++ /// assert!(b'X'.is_ascii_uppercase());
++ /// assert!('2'.is_ascii_digit());
++ /// assert!('x'.is_ascii_alphabetic());
++ /// }
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub MANUAL_IS_ASCII_CHECK,
++ style,
++ "use dedicated method to check ascii range"
++}
++impl_lint_pass!(ManualIsAsciiCheck => [MANUAL_IS_ASCII_CHECK]);
++
++pub struct ManualIsAsciiCheck {
++ msrv: Option<RustcVersion>,
++}
++
++impl ManualIsAsciiCheck {
++ #[must_use]
++ pub fn new(msrv: Option<RustcVersion>) -> Self {
++ Self { msrv }
++ }
++}
++
++#[derive(Debug, PartialEq)]
++enum CharRange {
++ /// 'a'..='z' | b'a'..=b'z'
++ LowerChar,
++ /// 'A'..='Z' | b'A'..=b'Z'
++ UpperChar,
++ /// AsciiLower | AsciiUpper
++ FullChar,
++ /// '0..=9'
++ Digit,
++ Otherwise,
++}
++
++impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if !meets_msrv(self.msrv, msrvs::IS_ASCII_DIGIT) {
++ return;
++ }
++
++ if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::IS_ASCII_DIGIT_CONST) {
++ return;
++ }
++
++ let Some(macro_call) = root_macro_call(expr.span) else { return };
++
++ if is_matches_macro(cx, macro_call.def_id) {
++ if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
++ let range = check_pat(&arm.pat.kind);
++
++ if let Some(sugg) = match range {
++ CharRange::UpperChar => Some("is_ascii_uppercase"),
++ CharRange::LowerChar => Some("is_ascii_lowercase"),
++ CharRange::FullChar => Some("is_ascii_alphabetic"),
++ CharRange::Digit => Some("is_ascii_digit"),
++ CharRange::Otherwise => None,
++ } {
++ let default_snip = "..";
++ // `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
++ // macro span, so we check applicability manually by comparing `recv` is not default.
++ let recv = snippet(cx, recv.span, default_snip);
++
++ let applicability = if recv == default_snip {
++ Applicability::HasPlaceholders
++ } else {
++ Applicability::MachineApplicable
++ };
++
++ span_lint_and_sugg(
++ cx,
++ MANUAL_IS_ASCII_CHECK,
++ macro_call.span,
++ "manual check for common ascii range",
++ "try",
++ format!("{recv}.{sugg}()"),
++ applicability,
++ );
++ }
++ }
++ }
++ }
++
++ extract_msrv_attr!(LateContext);
++}
++
++fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
++ match pat_kind {
++ PatKind::Or(pats) => {
++ let ranges = pats.iter().map(|p| check_pat(&p.kind)).collect::<Vec<_>>();
++
++ if ranges.len() == 2 && ranges.contains(&CharRange::UpperChar) && ranges.contains(&CharRange::LowerChar) {
++ CharRange::FullChar
++ } else {
++ CharRange::Otherwise
++ }
++ },
++ PatKind::Range(Some(start), Some(end), kind) if *kind == RangeEnd::Included => check_range(start, end),
++ _ => CharRange::Otherwise,
++ }
++}
++
++fn check_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange {
++ if let ExprKind::Lit(start_lit) = &start.kind
++ && let ExprKind::Lit(end_lit) = &end.kind {
++ match (&start_lit.node, &end_lit.node) {
++ (Char('a'), Char('z')) | (Byte(b'a'), Byte(b'z')) => CharRange::LowerChar,
++ (Char('A'), Char('Z')) | (Byte(b'A'), Byte(b'Z')) => CharRange::UpperChar,
++ (Char('0'), Char('9')) | (Byte(b'0'), Byte(b'9')) => CharRange::Digit,
++ _ => CharRange::Otherwise,
++ }
++ } else {
++ CharRange::Otherwise
++ }
++}
++
++fn is_matches_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
++ if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
++ return sym::matches_macro == name;
++ }
++
++ false
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::higher::IfLetOrMatch;
++use clippy_utils::source::snippet_opt;
++use clippy_utils::ty::is_type_diagnostic_item;
++use clippy_utils::visitors::{for_each_expr, Descend};
++use clippy_utils::{meets_msrv, msrvs, peel_blocks};
++use if_chain::if_chain;
++use rustc_data_structures::fx::FxHashSet;
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
++use rustc_lint::{LateContext, LateLintPass, LintContext};
++use rustc_middle::lint::in_external_macro;
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::symbol::sym;
++use rustc_span::Span;
++use serde::Deserialize;
++use std::ops::ControlFlow;
++
++declare_clippy_lint! {
++ /// ### What it does
++ ///
++ /// Warn of cases where `let...else` could be used
++ ///
++ /// ### Why is this bad?
++ ///
++ /// `let...else` provides a standard construct for this pattern
++ /// that people can easily recognize. It's also more compact.
++ ///
++ /// ### Example
++ ///
++ /// ```rust
++ /// # let w = Some(0);
++ /// let v = if let Some(v) = w { v } else { return };
++ /// ```
++ ///
++ /// Could be written:
++ ///
++ /// ```rust
++ /// # #![feature(let_else)]
++ /// # fn main () {
++ /// # let w = Some(0);
++ /// let Some(v) = w else { return };
++ /// # }
++ /// ```
++ #[clippy::version = "1.67.0"]
++ pub MANUAL_LET_ELSE,
++ pedantic,
++ "manual implementation of a let...else statement"
++}
++
++pub struct ManualLetElse {
++ msrv: Option<RustcVersion>,
++ matches_behaviour: MatchLintBehaviour,
++}
++
++impl ManualLetElse {
++ #[must_use]
++ pub fn new(msrv: Option<RustcVersion>, matches_behaviour: MatchLintBehaviour) -> Self {
++ Self {
++ msrv,
++ matches_behaviour,
++ }
++ }
++}
++
++impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
++
++impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
++ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
++ let if_let_or_match = if_chain! {
++ if meets_msrv(self.msrv, msrvs::LET_ELSE);
++ if !in_external_macro(cx.sess(), stmt.span);
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if local.els.is_none();
++ if local.ty.is_none();
++ if init.span.ctxt() == stmt.span.ctxt();
++ if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init);
++ then {
++ if_let_or_match
++ } else {
++ return;
++ }
++ };
++
++ match if_let_or_match {
++ IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => if_chain! {
++ if expr_is_simple_identity(let_pat, if_then);
++ if let Some(if_else) = if_else;
++ if expr_diverges(cx, if_else);
++ then {
++ emit_manual_let_else(cx, stmt.span, if_let_expr, let_pat, if_else);
++ }
++ },
++ IfLetOrMatch::Match(match_expr, arms, source) => {
++ if self.matches_behaviour == MatchLintBehaviour::Never {
++ return;
++ }
++ if source != MatchSource::Normal {
++ return;
++ }
++ // Any other number than two arms doesn't (neccessarily)
++ // have a trivial mapping to let else.
++ if arms.len() != 2 {
++ return;
++ }
++ // Guards don't give us an easy mapping either
++ if arms.iter().any(|arm| arm.guard.is_some()) {
++ return;
++ }
++ let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
++ let diverging_arm_opt = arms
++ .iter()
++ .enumerate()
++ .find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
++ let Some((idx, diverging_arm)) = diverging_arm_opt else { return; };
++ let pat_arm = &arms[1 - idx];
++ if !expr_is_simple_identity(pat_arm.pat, pat_arm.body) {
++ return;
++ }
++
++ emit_manual_let_else(cx, stmt.span, match_expr, pat_arm.pat, diverging_arm.body);
++ },
++ }
++ }
++
++ extract_msrv_attr!(LateContext);
++}
++
++fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat: &Pat<'_>, else_body: &Expr<'_>) {
++ span_lint_and_then(
++ cx,
++ MANUAL_LET_ELSE,
++ span,
++ "this could be rewritten as `let...else`",
++ |diag| {
++ // This is far from perfect, for example there needs to be:
++ // * mut additions for the bindings
++ // * renamings of the bindings
++ // * unused binding collision detection with existing ones
++ // * putting patterns with at the top level | inside ()
++ // for this to be machine applicable.
++ let app = Applicability::HasPlaceholders;
++
++ if let Some(sn_pat) = snippet_opt(cx, pat.span) &&
++ let Some(sn_expr) = snippet_opt(cx, expr.span) &&
++ let Some(sn_else) = snippet_opt(cx, else_body.span)
++ {
++ let else_bl = if matches!(else_body.kind, ExprKind::Block(..)) {
++ sn_else
++ } else {
++ format!("{{ {sn_else} }}")
++ };
++ let sugg = format!("let {sn_pat} = {sn_expr} else {else_bl};");
++ diag.span_suggestion(span, "consider writing", sugg, app);
++ }
++ },
++ );
++}
++
++fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
++ fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
++ if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
++ return ty.is_never();
++ }
++ false
++ }
++ // We can't just call is_never on expr and be done, because the type system
++ // sometimes coerces the ! type to something different before we can get
++ // our hands on it. So instead, we do a manual search. We do fall back to
++ // is_never in some places when there is no better alternative.
++ for_each_expr(expr, |ex| {
++ match ex.kind {
++ ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
++ ExprKind::Call(call, _) => {
++ if is_never(cx, ex) || is_never(cx, call) {
++ return ControlFlow::Break(());
++ }
++ ControlFlow::Continue(Descend::Yes)
++ },
++ ExprKind::MethodCall(..) => {
++ if is_never(cx, ex) {
++ return ControlFlow::Break(());
++ }
++ ControlFlow::Continue(Descend::Yes)
++ },
++ ExprKind::If(if_expr, if_then, if_else) => {
++ let else_diverges = if_else.map_or(false, |ex| expr_diverges(cx, ex));
++ let diverges = expr_diverges(cx, if_expr) || (else_diverges && expr_diverges(cx, if_then));
++ if diverges {
++ return ControlFlow::Break(());
++ }
++ ControlFlow::Continue(Descend::No)
++ },
++ ExprKind::Match(match_expr, match_arms, _) => {
++ let diverges = expr_diverges(cx, match_expr)
++ || match_arms.iter().all(|arm| {
++ let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(cx, g.body()));
++ guard_diverges || expr_diverges(cx, arm.body)
++ });
++ if diverges {
++ return ControlFlow::Break(());
++ }
++ ControlFlow::Continue(Descend::No)
++ },
++
++ // Don't continue into loops or labeled blocks, as they are breakable,
++ // and we'd have to start checking labels.
++ ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
++
++ // Default: descend
++ _ => ControlFlow::Continue(Descend::Yes),
++ }
++ })
++ .is_some()
++}
++
++fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
++ // Check whether the pattern contains any bindings, as the
++ // binding might potentially be used in the body.
++ // TODO: only look for *used* bindings.
++ let mut has_bindings = false;
++ pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true);
++ if has_bindings {
++ return false;
++ }
++
++ // If we shouldn't check the types, exit early.
++ if !check_types {
++ return true;
++ }
++
++ // Check whether any possibly "unknown" patterns are included,
++ // because users might not know which values some enum has.
++ // Well-known enums are excepted, as we assume people know them.
++ // We do a deep check, to be able to disallow Err(En::Foo(_))
++ // for usage of the En::Foo variant, as we disallow En::Foo(_),
++ // but we allow Err(_).
++ let typeck_results = cx.typeck_results();
++ let mut has_disallowed = false;
++ pat.walk_always(|pat| {
++ // Only do the check if the type is "spelled out" in the pattern
++ if !matches!(
++ pat.kind,
++ PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..)
++ ) {
++ return;
++ };
++ let ty = typeck_results.pat_ty(pat);
++ // Option and Result are allowed, everything else isn't.
++ if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) {
++ has_disallowed = true;
++ }
++ });
++ !has_disallowed
++}
++
++/// Checks if the passed block is a simple identity referring to bindings created by the pattern
++fn expr_is_simple_identity(pat: &'_ Pat<'_>, expr: &'_ Expr<'_>) -> bool {
++ // We support patterns with multiple bindings and tuples, like:
++ // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
++ let peeled = peel_blocks(expr);
++ let paths = match peeled.kind {
++ ExprKind::Tup(exprs) | ExprKind::Array(exprs) => exprs,
++ ExprKind::Path(_) => std::slice::from_ref(peeled),
++ _ => return false,
++ };
++ let mut pat_bindings = FxHashSet::default();
++ pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| {
++ pat_bindings.insert(ident);
++ });
++ if pat_bindings.len() < paths.len() {
++ return false;
++ }
++ for path in paths {
++ if_chain! {
++ if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind;
++ if let [path_seg] = path.segments;
++ then {
++ if !pat_bindings.remove(&path_seg.ident) {
++ return false;
++ }
++ } else {
++ return false;
++ }
++ }
++ }
++ true
++}
++
++#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]
++pub enum MatchLintBehaviour {
++ AllTypes,
++ WellKnownTypes,
++ Never,
++}
--- /dev/null
- fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<Span> {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{iter_input_pats, method_chain_args};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option.map(f)` where f is a function
+ /// or closure that returns the unit type `()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more clearly with
+ /// an if let statement
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff() -> Option<String> { Some(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Option<String> = do_stuff();
+ /// x.map(log_err_msg);
+ /// # let x: Option<String> = do_stuff();
+ /// x.map(|msg| log_err_msg(format_msg(msg)));
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn do_stuff() -> Option<String> { Some(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Option<String> = do_stuff();
+ /// if let Some(msg) = x {
+ /// log_err_msg(msg);
+ /// }
+ ///
+ /// # let x: Option<String> = do_stuff();
+ /// if let Some(msg) = x {
+ /// log_err_msg(format_msg(msg));
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_MAP_UNIT_FN,
+ complexity,
+ "using `option.map(f)`, where `f` is a function or closure that returns `()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `result.map(f)` where f is a function
+ /// or closure that returns the unit type `()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more clearly with
+ /// an if let statement
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Result<String, String> = do_stuff();
+ /// x.map(log_err_msg);
+ /// # let x: Result<String, String> = do_stuff();
+ /// x.map(|msg| log_err_msg(format_msg(msg)));
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Result<String, String> = do_stuff();
+ /// if let Ok(msg) = x {
+ /// log_err_msg(msg);
+ /// };
+ /// # let x: Result<String, String> = do_stuff();
+ /// if let Ok(msg) = x {
+ /// log_err_msg(format_msg(msg));
+ /// };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RESULT_MAP_UNIT_FN,
+ complexity,
+ "using `result.map(f)`, where `f` is a function or closure that returns `()`"
+}
+
+declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]);
+
+fn is_unit_type(ty: Ty<'_>) -> bool {
+ ty.is_unit() || ty.is_never()
+}
+
+fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ if let ty::FnDef(id, _) = *ty.kind() {
+ if let Some(fn_type) = cx.tcx.fn_sig(id).no_bound_vars() {
+ return is_unit_type(fn_type.output());
+ }
+ }
+ false
+}
+
+fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+ is_unit_type(cx.typeck_results().expr_ty(expr))
+}
+
+/// The expression inside a closure may or may not have surrounding braces and
+/// semicolons, which causes problems when generating a suggestion. Given an
+/// expression that evaluates to '()' or '!', recursively remove useless braces
+/// and semi-colons until is suitable for including in the suggestion template
++fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Span> {
+ if !is_unit_expression(cx, expr) {
+ return None;
+ }
+
+ match expr.kind {
+ hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(..) => {
+ // Calls can't be reduced any more
+ Some(expr.span)
+ },
+ hir::ExprKind::Block(block, _) => {
+ match (block.stmts, block.expr.as_ref()) {
+ ([], Some(inner_expr)) => {
+ // If block only contains an expression,
+ // reduce `{ X }` to `X`
+ reduce_unit_expression(cx, inner_expr)
+ },
+ ([inner_stmt], None) => {
+ // If block only contains statements,
+ // reduce `{ X; }` to `X` or `X;`
+ match inner_stmt.kind {
+ hir::StmtKind::Local(local) => Some(local.span),
+ hir::StmtKind::Expr(e) => Some(e.span),
+ hir::StmtKind::Semi(..) => Some(inner_stmt.span),
+ hir::StmtKind::Item(..) => None,
+ }
+ },
+ _ => {
+ // For closures that contain multiple statements
+ // it's difficult to get a correct suggestion span
+ // for all cases (multi-line closures specifically)
+ //
+ // We do not attempt to build a suggestion for those right now.
+ None
+ },
+ }
+ },
+ _ => None,
+ }
+}
+
+fn unit_closure<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
+ if_chain! {
+ if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind;
+ let body = cx.tcx.hir().body(body);
+ let body_expr = &body.value;
+ if fn_decl.inputs.len() == 1;
+ if is_unit_expression(cx, body_expr);
+ if let Some(binding) = iter_input_pats(fn_decl, body).next();
+ then {
+ return Some((binding, body_expr));
+ }
+ }
+ None
+}
+
+/// Builds a name for the let binding variable (`var_arg`)
+///
+/// `x.field` => `x_field`
+/// `y` => `_y`
+///
+/// Anything else will return `a`.
+fn let_binding_name(cx: &LateContext<'_>, var_arg: &hir::Expr<'_>) -> String {
+ match &var_arg.kind {
+ hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace('.', "_"),
+ hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")),
+ _ => "a".to_string(),
+ }
+}
+
+#[must_use]
+fn suggestion_msg(function_type: &str, map_type: &str) -> String {
+ format!("called `map(f)` on an `{map_type}` value where `f` is a {function_type} that returns the unit type `()`")
+}
+
+fn lint_map_unit_fn(
+ cx: &LateContext<'_>,
+ stmt: &hir::Stmt<'_>,
+ expr: &hir::Expr<'_>,
+ map_args: (&hir::Expr<'_>, &[hir::Expr<'_>]),
+) {
+ let var_arg = &map_args.0;
+
+ let (map_type, variant, lint) = if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Option) {
+ ("Option", "Some", OPTION_MAP_UNIT_FN)
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Result) {
+ ("Result", "Ok", RESULT_MAP_UNIT_FN)
+ } else {
+ return;
+ };
+ let fn_arg = &map_args.1[0];
+
+ if is_unit_function(cx, fn_arg) {
+ let mut applicability = Applicability::MachineApplicable;
+ let msg = suggestion_msg("function", map_type);
+ let suggestion = format!(
+ "if let {0}({binding}) = {1} {{ {2}({binding}) }}",
+ variant,
+ snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
+ snippet_with_applicability(cx, fn_arg.span, "_", &mut applicability),
+ binding = let_binding_name(cx, var_arg)
+ );
+
+ span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
+ diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
+ });
+ } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
+ let msg = suggestion_msg("closure", map_type);
+
+ span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
+ if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = format!(
+ "if let {0}({1}) = {2} {{ {3} }}",
+ variant,
+ snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability),
+ snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
+ snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0,
+ );
+ diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
+ } else {
+ let suggestion = format!(
+ "if let {0}({1}) = {2} {{ ... }}",
+ variant,
+ snippet(cx, binding.pat.span, "_"),
+ snippet(cx, var_arg.span, "_"),
+ );
+ diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::HasPlaceholders);
+ }
+ });
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MapUnit {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
+ if stmt.span.from_expansion() {
+ return;
+ }
+
+ if let hir::StmtKind::Semi(expr) = stmt.kind {
+ if let Some(arglists) = method_chain_args(expr, &["map"]) {
+ lint_map_unit_fn(cx, stmt, expr, arglists[0]);
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs};
+use rustc_errors::Applicability;
- if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
++use rustc_hir::{ByRef, ExprKind, Local, MatchSource, PatKind, QPath};
+use rustc_lint::LateContext;
+
+use super::INFALLIBLE_DESTRUCTURING_MATCH;
+
+pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
+ if_chain! {
+ if !local.span.from_expansion();
+ if let Some(expr) = local.init;
+ if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
+ if arms.len() == 1 && arms[0].guard.is_none();
+ if let PatKind::TupleStruct(
+ QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
+ if args.len() == 1;
- "let {}({}) = {};",
++ if let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind;
+ let body = peel_blocks(arms[0].body);
+ if path_to_local_id(body, arg);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ local.span,
+ "you seem to be trying to use `match` to destructure a single infallible pattern. \
+ Consider using `let`",
+ "try this",
+ format!(
++ "let {}({}{}) = {};",
+ snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
++ if binding.0 == ByRef::Yes { "ref " } else { "" },
+ snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- fn is_some_expr<'tcx>(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &'tcx Expr<'_>) -> bool {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::contains_unsafe_block;
+use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id};
+
+use rustc_hir::LangItem::OptionSome;
+use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::{sym, SyntaxContext};
+
+use super::manual_utils::{check_with, SomeExpr};
+use super::MANUAL_FILTER;
+
+// Function called on the <expr> of `[&+]Some((ref | ref mut) x) => <expr>`
+// Need to check if it's of the form `<expr>=if <cond> {<then_expr>} else {<else_expr>}`
+// AND that only one `then/else_expr` resolves to `Some(x)` while the other resolves to `None`
+// return the `cond` expression if so.
+fn get_cond_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &Pat<'_>,
+ expr: &'tcx Expr<'_>,
+ ctxt: SyntaxContext,
+) -> Option<SomeExpr<'tcx>> {
+ if_chain! {
+ if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
+ if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
+ if let PatKind::Binding(_,target, ..) = pat.kind;
+ if let (then_visitor, else_visitor)
+ = (is_some_expr(cx, target, ctxt, then_expr),
+ is_some_expr(cx, target, ctxt, else_expr));
+ if then_visitor != else_visitor; // check that one expr resolves to `Some(x)`, the other to `None`
+ then {
+ return Some(SomeExpr {
+ expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
+ needs_unsafe_block: contains_unsafe_block(cx, expr),
+ needs_negated: !then_visitor // if the `then_expr` resolves to `None`, need to negate the cond
+ })
+ }
+ };
+ None
+}
+
+fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
+ // we don't want to use `peel_blocks` here because we don't care if the block is unsafe, it's
+ // checked by `contains_unsafe_block`
+ if let ExprKind::Block(block, None) = expr.kind {
+ if block.stmts.is_empty() {
+ return block.expr;
+ }
+ };
+ None
+}
+
+fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
+ peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr)
+}
+
+// function called for each <expr> expression:
+// Some(x) => if <cond> {
+// <expr>
+// } else {
+// <expr>
+// }
+// Returns true if <expr> resolves to `Some(x)`, `false` otherwise
++fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool {
+ if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) {
+ // there can be not statements in the block as they would be removed when switching to `.filter`
+ if let ExprKind::Call(callee, [arg]) = inner_expr.kind {
+ return ctxt == expr.span.ctxt()
+ && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome)
+ && path_to_local_id(arg, target);
+ }
+ };
+ false
+}
+
+// given the closure: `|<pattern>| <expr>`
+// returns `|&<pattern>| <expr>`
+fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String {
+ if has_copy_trait {
+ let mut with_ampersand = body_str;
+ with_ampersand.insert(1, '&');
+ with_ampersand
+ } else {
+ body_str
+ }
+}
+
+pub(super) fn check_match<'tcx>(
+ cx: &LateContext<'tcx>,
+ scrutinee: &'tcx Expr<'_>,
+ arms: &'tcx [Arm<'_>],
+ expr: &'tcx Expr<'_>,
+) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if is_type_diagnostic_item(cx, ty, sym::Option)
+ && let [first_arm, second_arm] = arms
+ && first_arm.guard.is_none()
+ && second_arm.guard.is_none()
+ {
+ check(cx, expr, scrutinee, first_arm.pat, first_arm.body, Some(second_arm.pat), second_arm.body);
+ }
+}
+
+pub(super) fn check_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ let_pat: &'tcx Pat<'_>,
+ let_expr: &'tcx Expr<'_>,
+ then_expr: &'tcx Expr<'_>,
+ else_expr: &'tcx Expr<'_>,
+) {
+ check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
+}
+
+fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ scrutinee: &'tcx Expr<'_>,
+ then_pat: &'tcx Pat<'_>,
+ then_body: &'tcx Expr<'_>,
+ else_pat: Option<&'tcx Pat<'_>>,
+ else_body: &'tcx Expr<'_>,
+) {
+ if let Some(sugg_info) = check_with(
+ cx,
+ expr,
+ scrutinee,
+ then_pat,
+ then_body,
+ else_pat,
+ else_body,
+ get_cond_expr,
+ ) {
+ let body_str = add_ampersand_if_copy(sugg_info.body_str, sugg_info.scrutinee_impl_copy);
+ span_lint_and_sugg(
+ cx,
+ MANUAL_FILTER,
+ expr.span,
+ "manual implementation of `Option::filter`",
+ "try this",
+ if sugg_info.needs_brackets {
+ format!(
+ "{{ {}{}.filter({body_str}) }}",
+ sugg_info.scrutinee_str, sugg_info.as_ref_str
+ )
+ } else {
+ format!("{}{}.filter({body_str})", sugg_info.scrutinee_str, sugg_info.as_ref_str)
+ },
+ sugg_info.app,
+ );
+ }
+}
--- /dev/null
- fn has_significant_drop_in_scrutinee<'tcx, 'a>(
- cx: &'a LateContext<'tcx>,
+use crate::FxHashSet;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::{indent_of, snippet};
+use clippy_utils::{get_attr, is_lint_allowed};
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{Ty, TypeAndMut};
+use rustc_span::Span;
+
+use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ scrutinee: &'tcx Expr<'_>,
+ arms: &'tcx [Arm<'_>],
+ source: MatchSource,
+) {
+ if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
+ return;
+ }
+
+ if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
+ for found in suggestions {
+ span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
+ set_diagnostic(diag, cx, expr, found);
+ let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
+ diag.span_label(s, "temporary lives until here");
+ for span in has_significant_drop_in_arms(cx, arms) {
+ diag.span_label(span, "another value with significant `Drop` created here");
+ }
+ diag.note("this might lead to deadlocks or other unexpected behavior");
+ });
+ }
+ }
+}
+
+fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
+ if found.lint_suggestion == LintSuggestion::MoveAndClone {
+ // If our suggestion is to move and clone, then we want to leave it to the user to
+ // decide how to address this lint, since it may be that cloning is inappropriate.
+ // Therefore, we won't to emit a suggestion.
+ return;
+ }
+
+ let original = snippet(cx, found.found_span, "..");
+ let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0));
+
+ let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy {
+ format!("let value = *{original};\n{trailing_indent}")
+ } else if found.is_unit_return_val {
+ // If the return value of the expression to be moved is unit, then we don't need to
+ // capture the result in a temporary -- we can just replace it completely with `()`.
+ format!("{original};\n{trailing_indent}")
+ } else {
+ format!("let value = {original};\n{trailing_indent}")
+ };
+
+ let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly {
+ "try moving the temporary above the match"
+ } else {
+ "try moving the temporary above the match and create a copy"
+ };
+
+ let scrutinee_replacement = if found.is_unit_return_val {
+ "()".to_owned()
+ } else {
+ "value".to_owned()
+ };
+
+ diag.multipart_suggestion(
+ suggestion_message,
+ vec![
+ (expr.span.shrink_to_lo(), replacement),
+ (found.found_span, scrutinee_replacement),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+}
+
+/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
+/// may have a surprising lifetime.
- /// However, if we are at the the end of the chain, we want to accept whatever is there. (The
++fn has_significant_drop_in_scrutinee<'tcx>(
++ cx: &LateContext<'tcx>,
+ scrutinee: &'tcx Expr<'tcx>,
+ source: MatchSource,
+) -> Option<(Vec<FoundSigDrop>, &'static str)> {
+ let mut helper = SigDropHelper::new(cx);
+ let scrutinee = match (source, &scrutinee.kind) {
+ (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
+ _ => scrutinee,
+ };
+ helper.find_sig_drop(scrutinee).map(|drops| {
+ let message = if source == MatchSource::Normal {
+ "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
+ } else {
+ "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
+ };
+ (drops, message)
+ })
+}
+
+struct SigDropChecker<'a, 'tcx> {
+ seen_types: FxHashSet<Ty<'tcx>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
+ SigDropChecker {
+ seen_types: FxHashSet::default(),
+ cx,
+ }
+ }
+
+ fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
+ self.cx.typeck_results().expr_ty(ex)
+ }
+
+ fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
+ !self.seen_types.insert(ty)
+ }
+
+ fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if let Some(adt) = ty.ty_adt_def() {
+ if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
+ return true;
+ }
+ }
+
+ match ty.kind() {
+ rustc_middle::ty::Adt(a, b) => {
+ for f in a.all_fields() {
+ let ty = f.ty(cx.tcx, b);
+ if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
+ return true;
+ }
+ }
+
+ for generic_arg in b.iter() {
+ if let GenericArgKind::Type(ty) = generic_arg.unpack() {
+ if self.has_sig_drop_attr(cx, ty) {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ rustc_middle::ty::Array(ty, _)
+ | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
+ | rustc_middle::ty::Ref(_, ty, _)
+ | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
+ _ => false,
+ }
+ }
+}
+
+struct SigDropHelper<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_chain_end: bool,
+ has_significant_drop: bool,
+ current_sig_drop: Option<FoundSigDrop>,
+ sig_drop_spans: Option<Vec<FoundSigDrop>>,
+ special_handling_for_binary_op: bool,
+ sig_drop_checker: SigDropChecker<'a, 'tcx>,
+}
+
+#[expect(clippy::enum_variant_names)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum LintSuggestion {
+ MoveOnly,
+ MoveAndDerefToCopy,
+ MoveAndClone,
+}
+
+#[derive(Clone, Copy)]
+struct FoundSigDrop {
+ found_span: Span,
+ is_unit_return_val: bool,
+ lint_suggestion: LintSuggestion,
+}
+
+impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> {
+ SigDropHelper {
+ cx,
+ is_chain_end: true,
+ has_significant_drop: false,
+ current_sig_drop: None,
+ sig_drop_spans: None,
+ special_handling_for_binary_op: false,
+ sig_drop_checker: SigDropChecker::new(cx),
+ }
+ }
+
+ fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option<Vec<FoundSigDrop>> {
+ self.visit_expr(match_expr);
+
+ // If sig drop spans is empty but we found a significant drop, it means that we didn't find
+ // a type that was trivially copyable as we moved up the chain after finding a significant
+ // drop, so move the entire scrutinee.
+ if self.has_significant_drop && self.sig_drop_spans.is_none() {
+ self.try_setting_current_suggestion(match_expr, true);
+ self.move_current_suggestion();
+ }
+
+ self.sig_drop_spans.take()
+ }
+
+ fn replace_current_sig_drop(
+ &mut self,
+ found_span: Span,
+ is_unit_return_val: bool,
+ lint_suggestion: LintSuggestion,
+ ) {
+ self.current_sig_drop.replace(FoundSigDrop {
+ found_span,
+ is_unit_return_val,
+ lint_suggestion,
+ });
+ }
+
+ /// This will try to set the current suggestion (so it can be moved into the suggestions vec
+ /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us
+ /// an opportunity to look for another type in the chain that will be trivially copyable.
- fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
++ /// However, if we are at the end of the chain, we want to accept whatever is there. (The
+ /// suggestion won't actually be output, but the diagnostic message will be output, so the user
+ /// can determine the best way to handle the lint.)
+ fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) {
+ if self.current_sig_drop.is_some() {
+ return;
+ }
+ let ty = self.sig_drop_checker.get_type(expr);
+ if ty.is_ref() {
+ // We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
+ // but let's avoid any chance of an ICE
+ if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) {
+ if ty.is_trivially_pure_clone_copy() {
+ self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy);
+ } else if allow_move_and_clone {
+ self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
+ }
+ }
+ } else if ty.is_trivially_pure_clone_copy() {
+ self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly);
+ } else if allow_move_and_clone {
+ self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
+ }
+ }
+
+ fn move_current_suggestion(&mut self) {
+ if let Some(current) = self.current_sig_drop.take() {
+ self.sig_drop_spans.get_or_insert_with(Vec::new).push(current);
+ }
+ }
+
+ fn visit_exprs_for_binary_ops(
+ &mut self,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+ is_unit_return_val: bool,
+ span: Span,
+ ) {
+ self.special_handling_for_binary_op = true;
+ self.visit_expr(left);
+ self.visit_expr(right);
+
+ // If either side had a significant drop, suggest moving the entire scrutinee to avoid
+ // unnecessary copies and to simplify cases where both sides have significant drops.
+ if self.has_significant_drop {
+ self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly);
+ }
+
+ self.special_handling_for_binary_op = false;
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ if !self.is_chain_end
+ && self
+ .sig_drop_checker
+ .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
+ {
+ self.has_significant_drop = true;
+ return;
+ }
+ self.is_chain_end = false;
+
+ match ex.kind {
+ ExprKind::MethodCall(_, expr, ..) => {
+ self.visit_expr(expr);
+ }
+ ExprKind::Binary(_, left, right) => {
+ self.visit_exprs_for_binary_ops(left, right, false, ex.span);
+ }
+ ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => {
+ self.visit_exprs_for_binary_ops(left, right, true, ex.span);
+ }
+ ExprKind::Tup(exprs) => {
+ for expr in exprs {
+ self.visit_expr(expr);
+ if self.has_significant_drop {
+ // We may have not have set current_sig_drop if all the suggestions were
+ // MoveAndClone, so add this tuple item's full expression in that case.
+ if self.current_sig_drop.is_none() {
+ self.try_setting_current_suggestion(expr, true);
+ }
+
+ // Now we are guaranteed to have something, so add it to the final vec.
+ self.move_current_suggestion();
+ }
+ // Reset `has_significant_drop` after each tuple expression so we can look for
+ // additional cases.
+ self.has_significant_drop = false;
+ }
+ if self.sig_drop_spans.is_some() {
+ self.has_significant_drop = true;
+ }
+ }
+ ExprKind::Box(..) |
+ ExprKind::Array(..) |
+ ExprKind::Call(..) |
+ ExprKind::Unary(..) |
+ ExprKind::If(..) |
+ ExprKind::Match(..) |
+ ExprKind::Field(..) |
+ ExprKind::Index(..) |
+ ExprKind::Ret(..) |
+ ExprKind::Repeat(..) |
+ ExprKind::Yield(..) => walk_expr(self, ex),
+ ExprKind::AddrOf(_, _, _) |
+ ExprKind::Block(_, _) |
+ ExprKind::Break(_, _) |
+ ExprKind::Cast(_, _) |
+ // Don't want to check the closure itself, only invocation, which is covered by MethodCall
+ ExprKind::Closure { .. } |
+ ExprKind::ConstBlock(_) |
+ ExprKind::Continue(_) |
+ ExprKind::DropTemps(_) |
+ ExprKind::Err |
+ ExprKind::InlineAsm(_) |
+ ExprKind::Let(_) |
+ ExprKind::Lit(_) |
+ ExprKind::Loop(_, _, _, _) |
+ ExprKind::Path(_) |
+ ExprKind::Struct(_, _, _) |
+ ExprKind::Type(_, _) => {
+ return;
+ }
+ }
+
+ // Once a significant temporary has been found, we need to go back up at least 1 level to
+ // find the span to extract for replacement, so the temporary gets dropped. However, for
+ // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to
+ // simplify cases where both sides have significant drops.
+ if self.has_significant_drop && !self.special_handling_for_binary_op {
+ self.try_setting_current_suggestion(ex, false);
+ }
+ }
+}
+
+struct ArmSigDropHelper<'a, 'tcx> {
+ sig_drop_checker: SigDropChecker<'a, 'tcx>,
+ found_sig_drop_spans: FxHashSet<Span>,
+}
+
+impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
+ ArmSigDropHelper {
+ sig_drop_checker: SigDropChecker::new(cx),
+ found_sig_drop_spans: FxHashSet::<Span>::default(),
+ }
+ }
+}
+
++fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
+ let mut helper = ArmSigDropHelper::new(cx);
+ for arm in arms {
+ helper.visit_expr(arm.body);
+ }
+ helper.found_sig_drop_spans
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+ if self
+ .sig_drop_checker
+ .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
+ {
+ self.found_sig_drop_spans.insert(ex.span);
+ return;
+ }
+ walk_expr(self, ex);
+ }
+}
--- /dev/null
- fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{expr_block, snippet};
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs};
+use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs};
+use core::cmp::max;
+use rustc_errors::Applicability;
+use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::sym;
+
+use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
+
+#[rustfmt::skip]
+pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
+ if expr.span.from_expansion() {
+ // Don't lint match expressions present in
+ // macro_rules! block
+ return;
+ }
+ if let PatKind::Or(..) = arms[0].pat.kind {
+ // don't lint for or patterns for now, this makes
+ // the lint noisy in unnecessary situations
+ return;
+ }
+ let els = arms[1].body;
+ let els = if is_unit_expr(peel_blocks(els)) {
+ None
+ } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind {
+ if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
+ // single statement/expr "else" block, don't lint
+ return;
+ }
+ // block with 2+ statements or 1 expr and 1+ statement
+ Some(els)
+ } else {
+ // not a block, don't lint
+ return;
+ };
+
+ let ty = cx.typeck_results().expr_ty(ex);
+ if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
+ check_single_pattern(cx, ex, arms, expr, els);
+ check_opt_like(cx, ex, arms, expr, ty, els);
+ }
+ }
+}
+
+fn check_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ if is_wild(arms[1].pat) {
+ report_single_pattern(cx, ex, arms, expr, els);
+ }
+}
+
+fn report_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
+ let els_str = els.map_or(String::new(), |els| {
+ format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
+ });
+
+ let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
+ let (msg, sugg) = if_chain! {
+ if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
+ let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
+ if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
+ if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
+ if ty.is_integral() || ty.is_char() || ty.is_str()
+ || (implements_trait(cx, ty, spe_trait_id, &[])
+ && implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
+ then {
+ // scrutinee derives PartialEq and the pattern is a constant.
+ let pat_ref_count = match pat.kind {
+ // string literals are already a reference.
+ PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
+ _ => pat_ref_count,
+ };
+ // References are only implicitly added to the pattern, so no overflow here.
+ // e.g. will work: match &Some(_) { Some(_) => () }
+ // will not: match Some(_) { &Some(_) => () }
+ let ref_count_diff = ty_ref_count - pat_ref_count;
+
+ // Try to remove address of expressions first.
+ let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
+ let ref_count_diff = ref_count_diff - removed;
+
+ let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
+ let sugg = format!(
+ "if {} == {}{} {}{els_str}",
+ snippet(cx, ex.span, ".."),
+ // PartialEq for different reference counts may not exist.
+ "&".repeat(ref_count_diff),
+ snippet(cx, arms[0].pat.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ );
+ (msg, sugg)
+ } else {
+ let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
+ let sugg = format!(
+ "if let {} = {} {}{els_str}",
+ snippet(cx, arms[0].pat.span, ".."),
+ snippet(cx, ex.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ );
+ (msg, sugg)
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ "try this",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+}
+
+fn check_opt_like<'a>(
+ cx: &LateContext<'a>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ ty: Ty<'a>,
+ els: Option<&Expr<'_>>,
+) {
+ // We don't want to lint if the second arm contains an enum which could
+ // have more variants in the future.
+ if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
+ report_single_pattern(cx, ex, arms, expr, els);
+ }
+}
+
+/// Returns `true` if all of the types in the pattern are enums which we know
+/// won't be expanded in the future
+fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool {
+ let mut paths_and_types = Vec::new();
+ collect_pat_paths(&mut paths_and_types, cx, pat, ty);
+ paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty))
+}
+
+/// Returns `true` if the given type is an enum we know won't be expanded in the future
++fn in_candidate_enum(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ // list of candidate `Enum`s we know will never get any more members
+ let candidates = [sym::Cow, sym::Option, sym::Result];
+
+ for candidate_ty in candidates {
+ if is_type_diagnostic_item(cx, ty, candidate_ty) {
+ return true;
+ }
+ }
+ false
+}
+
+/// Collects types from the given pattern
+fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) {
+ match pat.kind {
+ PatKind::Tuple(inner, _) => inner.iter().for_each(|p| {
+ let p_ty = cx.typeck_results().pat_ty(p);
+ collect_pat_paths(acc, cx, p, p_ty);
+ }),
+ PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::NONE, .., None) | PatKind::Path(_) => {
+ acc.push(ty);
+ },
+ _ => {},
+ }
+}
+
+/// Returns true if the given arm of pattern matching contains wildcard patterns.
+fn contains_only_wilds(pat: &Pat<'_>) -> bool {
+ match pat.kind {
+ PatKind::Wild => true,
+ PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds),
+ _ => false,
+ }
+}
+
+/// Returns true if the given patterns forms only exhaustive matches that don't contain enum
+/// patterns without a wildcard.
+fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (PatKind::Wild, _) | (_, PatKind::Wild) => true,
+ (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
+ // We don't actually know the position and the presence of the `..` (dotdot) operator
+ // in the arms, so we need to evaluate the correct offsets here in order to iterate in
+ // both arms at the same time.
+ let left_pos = left_pos.as_opt_usize();
+ let right_pos = right_pos.as_opt_usize();
+ let len = max(
+ left_in.len() + usize::from(left_pos.is_some()),
+ right_in.len() + usize::from(right_pos.is_some()),
+ );
+ let mut left_pos = left_pos.unwrap_or(usize::MAX);
+ let mut right_pos = right_pos.unwrap_or(usize::MAX);
+ let mut left_dot_space = 0;
+ let mut right_dot_space = 0;
+ for i in 0..len {
+ let mut found_dotdot = false;
+ if i == left_pos {
+ left_dot_space += 1;
+ if left_dot_space < len - left_in.len() {
+ left_pos += 1;
+ }
+ found_dotdot = true;
+ }
+ if i == right_pos {
+ right_dot_space += 1;
+ if right_dot_space < len - right_in.len() {
+ right_pos += 1;
+ }
+ found_dotdot = true;
+ }
+ if found_dotdot {
+ continue;
+ }
+ if !contains_only_wilds(&left_in[i - left_dot_space])
+ && !contains_only_wilds(&right_in[i - right_dot_space])
+ {
+ return false;
+ }
+ }
+ true
+ },
+ (PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right),
+ (PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => {
+ pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds)
+ },
+ _ => false,
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(
- cx: &LateContext<'tcx>,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_lint::Lint;
+
+/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`.
++pub(super) fn check(
++ cx: &LateContext<'_>,
+ info: &crate::methods::BinaryExprInfo<'_>,
+ chain_methods: &[&str],
+ lint: &'static Lint,
+ suggest: &str,
+) -> bool {
+ if_chain! {
+ if let Some(args) = method_chain_args(info.chain, chain_methods);
+ if let hir::ExprKind::Lit(ref lit) = info.other.kind;
+ if let ast::LitKind::Char(c) = lit.node;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ lint,
+ info.expr.span,
+ &format!("you should use the `{suggest}` method"),
+ "like this",
+ format!("{}{}.{suggest}('{}')",
+ if info.eq { "" } else { "!" },
+ snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
+ c.escape_default()),
+ applicability,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+use crate::methods::chars_cmp;
+use rustc_lint::LateContext;
+
+use super::CHARS_LAST_CMP;
+
+/// Checks for the `CHARS_LAST_CMP` lint.
++pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ if chars_cmp::check(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") {
+ true
+ } else {
+ chars_cmp::check(cx, info, &["chars", "next_back"], CHARS_LAST_CMP, "ends_with")
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+use crate::methods::chars_cmp_with_unwrap;
+use rustc_lint::LateContext;
+
+use super::CHARS_LAST_CMP;
+
+/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`.
++pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ if chars_cmp_with_unwrap::check(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") {
+ true
+ } else {
+ chars_cmp_with_unwrap::check(cx, info, &["chars", "next_back", "unwrap"], CHARS_LAST_CMP, "ends_with")
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+use rustc_lint::LateContext;
+
+use super::CHARS_NEXT_CMP;
+
+/// Checks for the `CHARS_NEXT_CMP` lint.
++pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ crate::methods::chars_cmp::check(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with")
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+use rustc_lint::LateContext;
+
+use super::CHARS_NEXT_CMP;
+
+/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`.
++pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ crate::methods::chars_cmp_with_unwrap::check(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with")
+}
--- /dev/null
- && let Some(("replace", _, [current_from, current_to], _)) = method_call(parent)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{eq_expr_value, get_parent_expr};
+use core::ops::ControlFlow;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use std::collections::VecDeque;
+
+use super::method_call;
+use super::COLLAPSIBLE_STR_REPLACE;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ from: &'tcx hir::Expr<'tcx>,
+ to: &'tcx hir::Expr<'tcx>,
+) {
+ let replace_methods = collect_replace_calls(cx, expr, to);
+ if replace_methods.methods.len() > 1 {
+ let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
+ // If the parent node's `to` argument is the same as the `to` argument
+ // of the last replace call in the current chain, don't lint as it was already linted
+ if let Some(parent) = get_parent_expr(cx, expr)
- if let Some(("replace", _, [from, to], _)) = method_call(e) {
++ && let Some(("replace", _, [current_from, current_to], _, _)) = method_call(parent)
+ && eq_expr_value(cx, to, current_to)
+ && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
+ {
+ return;
+ }
+
+ check_consecutive_replace_calls(cx, expr, &replace_methods, to);
+ }
+}
+
+struct ReplaceMethods<'tcx> {
+ methods: VecDeque<&'tcx hir::Expr<'tcx>>,
+ from_args: VecDeque<&'tcx hir::Expr<'tcx>>,
+}
+
+fn collect_replace_calls<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ to_arg: &'tcx hir::Expr<'tcx>,
+) -> ReplaceMethods<'tcx> {
+ let mut methods = VecDeque::new();
+ let mut from_args = VecDeque::new();
+
+ let _: Option<()> = for_each_expr(expr, |e| {
- if let Some((_, _, [..], span_lo)) = method_call(earliest_replace_call) {
++ if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
+ if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
+ methods.push_front(e);
+ from_args.push_front(from);
+ ControlFlow::Continue(())
+ } else {
+ ControlFlow::BREAK
+ }
+ } else {
+ ControlFlow::Continue(())
+ }
+ });
+
+ ReplaceMethods { methods, from_args }
+}
+
+/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
+fn check_consecutive_replace_calls<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ replace_methods: &ReplaceMethods<'tcx>,
+ to_arg: &'tcx hir::Expr<'tcx>,
+) {
+ let from_args = &replace_methods.from_args;
+ let from_arg_reprs: Vec<String> = from_args
+ .iter()
+ .map(|from_arg| snippet(cx, from_arg.span, "..").to_string())
+ .collect();
+ let app = Applicability::MachineApplicable;
+ let earliest_replace_call = replace_methods.methods.front().unwrap();
++ if let Some((_, _, [..], span_lo, _)) = method_call(earliest_replace_call) {
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_STR_REPLACE,
+ expr.span.with_lo(span_lo.lo()),
+ "used consecutive `str::replace` call",
+ "replace with",
+ format!(
+ "replace([{}], {})",
+ from_arg_reprs.join(", "),
+ snippet(cx, to_arg.span, ".."),
+ ),
+ app,
+ );
+ }
+}
--- /dev/null
- use clippy_utils::is_in_test_function;
+use clippy_utils::diagnostics::span_lint_and_help;
- Some((EXPECT_USED, "an Option", "None", ""))
++use clippy_utils::is_in_cfg_test;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::EXPECT_USED;
+
+/// lint use of `expect()` or `expect_err` for `Result` and `expect()` for `Option`.
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ is_err: bool,
+ allow_expect_in_tests: bool,
+) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
- Some((EXPECT_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an "))
++ Some((EXPECT_USED, "an `Option`", "None", ""))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
- if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
++ Some((EXPECT_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
+ } else {
+ None
+ };
+
+ let method = if is_err { "expect_err" } else { "expect" };
+
- &format!("used `{method}()` on `{kind}` value"),
++ if allow_expect_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) {
+ return;
+ }
+
+ if let Some((lint, kind, none_value, none_prefix)) = mess {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
++ &format!("used `{method}()` on {kind} value"),
+ None,
+ &format!("if this value is {none_prefix}`{none_value}`, it will panic"),
+ );
+ }
+}
--- /dev/null
- fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{sym, Symbol};
+use std::borrow::Cow;
+
+use super::MANUAL_FILTER_MAP;
+use super::MANUAL_FIND_MAP;
+use super::OPTION_FILTER_MAP;
+
- fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool {
++fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
+ match &expr.kind {
+ hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
+ hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
+ segments.segments.last().unwrap().ident.name == method_name
+ },
+ hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+ let body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(body.value);
+ let arg_id = body.params[0].pat.hir_id;
+ match closure_expr.kind {
+ hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
+ if_chain! {
+ if ident.name == method_name;
+ if let hir::ExprKind::Path(path) = &receiver.kind;
+ if let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id);
+ then {
+ return arg_id == *local
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+}
+
- pub(super) fn check<'tcx>(
- cx: &LateContext<'tcx>,
++fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool {
+ is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
+}
+
+/// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
+fn is_filter_some_map_unwrap(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ filter_recv: &hir::Expr<'_>,
+ filter_arg: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+) -> bool {
+ let iterator = is_trait_method(cx, expr, sym::Iterator);
+ let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option);
+
+ (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg)
+}
+
+/// lint use of `filter().map()` or `find().map()` for `Iterators`
+#[allow(clippy::too_many_arguments)]
++pub(super) fn check(
++ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ filter_recv: &hir::Expr<'_>,
+ filter_arg: &hir::Expr<'_>,
+ filter_span: Span,
+ map_recv: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ map_span: Span,
+ is_find: bool,
+) {
+ if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) {
+ span_lint_and_sugg(
+ cx,
+ OPTION_FILTER_MAP,
+ filter_span.with_hi(expr.span.hi()),
+ "`filter` for `Some` followed by `unwrap`",
+ "consider using `flatten` instead",
+ reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(),
+ Applicability::MachineApplicable,
+ );
+
+ return;
+ }
+
+ if_chain! {
+ if is_trait_method(cx, map_recv, sym::Iterator);
+
+ // filter(|x| ...is_some())...
+ if let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind;
+ let filter_body = cx.tcx.hir().body(filter_body_id);
+ if let [filter_param] = filter_body.params;
+ // optional ref pattern: `filter(|&x| ..)`
+ let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
+ (ref_pat, true)
+ } else {
+ (filter_param.pat, false)
+ };
+ // closure ends with is_some() or is_ok()
+ 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).peel_refs().ty_adt_def();
+ 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()) {
+ Some(true)
+ } else {
+ None
+ };
+ if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
+
+ // ...map(|x| ...unwrap())
+ if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind;
+ let map_body = cx.tcx.hir().body(map_body_id);
+ if let [map_param] = map_body.params;
+ if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
+ // closure ends with expect() or unwrap()
+ if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind;
+ if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
+
+ // .filter(..).map(|y| f(y).copied().unwrap())
+ // ~~~~
+ let map_arg_peeled = match map_arg.kind {
+ ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => {
+ original_arg
+ },
+ _ => map_arg,
+ };
+
+ // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
+ let simple_equal = path_to_local_id(filter_arg, filter_param_id)
+ && path_to_local_id(map_arg_peeled, map_param_id);
+
+ let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
+ // in `filter(|x| ..)`, replace `*x` with `x`
+ let a_path = if_chain! {
+ if !is_filter_param_ref;
+ if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
+ then { expr_path } else { a }
+ };
+ // let the filter closure arg and the map closure arg be equal
+ path_to_local_id(a_path, filter_param_id)
+ && path_to_local_id(b, map_param_id)
+ && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
+ };
+
+ if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
+ then {
+ let span = filter_span.with_hi(expr.span.hi());
+ let (filter_name, lint) = if is_find {
+ ("find", MANUAL_FIND_MAP)
+ } else {
+ ("filter", MANUAL_FILTER_MAP)
+ };
+ let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
+ let (to_opt, deref) = if is_result {
+ (".ok()", String::new())
+ } else {
+ let derefs = cx.typeck_results()
+ .expr_adjustments(map_arg)
+ .iter()
+ .filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ .count();
+
+ ("", "*".repeat(derefs))
+ };
+ let sugg = format!(
+ "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
+ snippet(cx, map_arg.span, ".."),
+ );
+ span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
+ }
+ }
+}
+
+fn acceptable_methods(method: &PathSegment<'_>) -> bool {
+ let methods: [Symbol; 8] = [
+ sym::clone,
+ sym::as_ref,
+ sym!(copied),
+ sym!(cloned),
+ sym!(as_deref),
+ sym!(as_mut),
+ sym!(as_deref_mut),
+ sym!(to_owned),
+ ];
+
+ methods.contains(&method.ident.name)
+}
--- /dev/null
- pub fn check<'tcx>(
- cx: &LateContext<'tcx>,
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_type_lang_item, walk_ptrs_ty_depth};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::{Symbol, sym};
+
+use super::INEFFICIENT_TO_STRING;
+
+/// Checks for the `INEFFICIENT_TO_STRING` lint
++pub fn check(
++ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ method_name: Symbol,
+ receiver: &hir::Expr<'_>,
+ args: &[hir::Expr<'_>],
+) {
+ if_chain! {
+ if args.is_empty() && method_name == sym::to_string;
+ if let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD);
+ if let Some(substs) = cx.typeck_results().node_substs_opt(expr.hir_id);
+ let arg_ty = cx.typeck_results().expr_ty_adjusted(receiver);
+ let self_ty = substs.type_at(0);
+ let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty);
+ if deref_count >= 1;
+ if specializes_tostring(cx, deref_self_ty);
+ then {
+ span_lint_and_then(
+ cx,
+ INEFFICIENT_TO_STRING,
+ expr.span,
+ &format!("calling `to_string` on `{arg_ty}`"),
+ |diag| {
+ diag.help(&format!(
+ "`{self_ty}` implements `ToString` through a slower blanket impl, but `{deref_self_ty}` has a fast specialization of `ToString`"
+ ));
+ let mut applicability = Applicability::MachineApplicable;
+ let arg_snippet = snippet_with_applicability(cx, receiver.span, "..", &mut applicability);
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing the receiver",
+ format!("({}{arg_snippet}).to_string()", "*".repeat(deref_count)),
+ applicability,
+ );
+ },
+ );
+ }
+ }
+}
+
+/// Returns whether `ty` specializes `ToString`.
+/// Currently, these are `str`, `String`, and `Cow<'_, str>`.
+fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ if let ty::Str = ty.kind() {
+ return true;
+ }
+
+ if is_type_lang_item(cx, ty, hir::LangItem::String) {
+ return true;
+ }
+
+ if let ty::Adt(adt, substs) = ty.kind() {
+ cx.tcx.is_diagnostic_item(sym::Cow, adt.did()) && substs.type_at(1).is_str()
+ } else {
+ false
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_NTH_ZERO;
+
++pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ if_chain! {
+ if is_trait_method(cx, expr, sym::Iterator);
+ if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_NTH_ZERO,
+ expr.span,
+ "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent",
+ "try calling `.next()` instead of `.nth(0)`",
+ format!("{}.next()", snippet_with_applicability(cx, recv.span, "..", &mut applicability)),
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::{get_expr_use_or_unification_node, is_no_std_crate, is_res_lang_ctor, path_res};
+
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{Expr, ExprKind, Node};
+use rustc_lint::LateContext;
+
+use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
+
+enum IterType {
+ Iter,
+ IterMut,
+ IntoIter,
+}
+
+impl IterType {
+ fn ref_prefix(&self) -> &'static str {
+ match self {
+ Self::Iter => "&",
+ Self::IterMut => "&mut ",
+ Self::IntoIter => "",
+ }
+ }
+}
+
++pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
+ let item = match recv.kind {
+ ExprKind::Array([]) => None,
+ ExprKind::Array([e]) => Some(e),
+ ExprKind::Path(ref p) if is_res_lang_ctor(cx, cx.qpath_res(p, recv.hir_id), OptionNone) => None,
+ ExprKind::Call(f, [arg]) if is_res_lang_ctor(cx, path_res(cx, f), OptionSome) => Some(arg),
+ _ => return,
+ };
+ let iter_type = match method_name {
+ "iter" => IterType::Iter,
+ "iter_mut" => IterType::IterMut,
+ "into_iter" => IterType::IntoIter,
+ _ => return,
+ };
+
+ let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
+ Some((Node::Expr(parent), child_id)) => match parent.kind {
+ ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
+ ExprKind::If(_, _, _)
+ | ExprKind::Match(_, _, _)
+ | ExprKind::Closure(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Break(_, _) => true,
+ _ => false,
+ },
+ Some((Node::Stmt(_) | Node::Local(_), _)) => false,
+ _ => true,
+ };
+
+ if is_unified {
+ return;
+ }
+
+ if let Some(i) = item {
+ let sugg = format!(
+ "{}::iter::once({}{})",
+ if is_no_std_crate(cx) { "core" } else { "std" },
+ iter_type.ref_prefix(),
+ snippet(cx, i.span, "...")
+ );
+ span_lint_and_sugg(
+ cx,
+ ITER_ON_SINGLE_ITEMS,
+ expr.span,
+ &format!("`{method_name}` call on a collection with only one item"),
+ "try",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ ITER_ON_EMPTY_COLLECTIONS,
+ expr.span,
+ &format!("`{method_name}` call on an empty collection"),
+ "try",
+ if is_no_std_crate(cx) {
+ "core::iter::empty()".to_string()
+ } else {
+ "std::iter::empty()".to_string()
+ },
+ Applicability::MaybeIncorrect,
+ );
+ }
+}
--- /dev/null
- fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<MinMax> {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{match_def_path, path_def_id};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::layout::LayoutOf;
+
+pub fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ arith_lhs: &hir::Expr<'_>,
+ arith_rhs: &hir::Expr<'_>,
+ unwrap_arg: &hir::Expr<'_>,
+ arith: &str,
+) {
+ let ty = cx.typeck_results().expr_ty(arith_lhs);
+ if !ty.is_integral() {
+ return;
+ }
+
+ let Some(mm) = is_min_or_max(cx, unwrap_arg) else { return };
+
+ if ty.is_signed() {
+ use self::{
+ MinMax::{Max, Min},
+ Sign::{Neg, Pos},
+ };
+
+ let Some(sign) = lit_sign(arith_rhs) else {
+ return;
+ };
+
+ match (arith, sign, mm) {
+ ("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
+ // "mul" is omitted because lhs can be negative.
+ _ => return,
+ }
+ } else {
+ match (mm, arith) {
+ (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
+ _ => return,
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ super::MANUAL_SATURATING_ARITHMETIC,
+ expr.span,
+ "manual saturating arithmetic",
+ &format!("try using `saturating_{arith}`"),
+ format!(
+ "{}.saturating_{arith}({})",
+ snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
+ snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
+
+#[derive(PartialEq, Eq)]
+enum MinMax {
+ Min,
+ Max,
+}
+
++fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
+ // `T::max_value()` `T::min_value()` inherent methods
+ if_chain! {
+ if let hir::ExprKind::Call(func, args) = &expr.kind;
+ if args.is_empty();
+ if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind;
+ then {
+ match segment.ident.as_str() {
+ "max_value" => return Some(MinMax::Max),
+ "min_value" => return Some(MinMax::Min),
+ _ => {}
+ }
+ }
+ }
+
+ let ty = cx.typeck_results().expr_ty(expr);
+ let ty_str = ty.to_string();
+
+ // `std::T::MAX` `std::T::MIN` constants
+ if let Some(id) = path_def_id(cx, expr) {
+ if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
+ return Some(MinMax::Max);
+ }
+
+ if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
+ return Some(MinMax::Min);
+ }
+ }
+
+ // Literals
+ let bits = cx.layout_of(ty).unwrap().size.bits();
+ let (minval, maxval): (u128, u128) = if ty.is_signed() {
+ let minval = 1 << (bits - 1);
+ let mut maxval = !(1 << (bits - 1));
+ if bits != 128 {
+ maxval &= (1 << bits) - 1;
+ }
+ (minval, maxval)
+ } else {
+ (0, if bits == 128 { !0 } else { (1 << bits) - 1 })
+ };
+
+ let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
+ if let hir::ExprKind::Lit(lit) = &expr.kind {
+ if let ast::LitKind::Int(value, _) = lit.node {
+ if value == maxval {
+ return Some(MinMax::Max);
+ }
+
+ if check_min && value == minval {
+ return Some(MinMax::Min);
+ }
+ }
+ }
+
+ None
+ };
+
+ if let r @ Some(_) = check_lit(expr, !ty.is_signed()) {
+ return r;
+ }
+
+ if ty.is_signed() {
+ if let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind {
+ return check_lit(val, true);
+ }
+ }
+
+ None
+}
+
+#[derive(PartialEq, Eq)]
+enum Sign {
+ Pos,
+ Neg,
+}
+
+fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
+ if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
+ if let hir::ExprKind::Lit(..) = &inner.kind {
+ return Some(Sign::Neg);
+ }
+ } else if let hir::ExprKind::Lit(..) = &expr.kind {
+ return Some(Sign::Pos);
+ }
+
+ None
+}
--- /dev/null
- if let Some(collect_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id);
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_path_diagnostic_item;
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+use std::borrow::Cow;
+
+use super::MANUAL_STR_REPEAT;
+
+enum RepeatKind {
+ String,
+ Char(char),
+}
+
+fn get_ty_param(ty: Ty<'_>) -> Option<Ty<'_>> {
+ if let ty::Adt(_, subs) = ty.kind() {
+ subs.types().next()
+ } else {
+ None
+ }
+}
+
+fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<RepeatKind> {
+ if let ExprKind::Lit(lit) = &e.kind {
+ match lit.node {
+ LitKind::Str(..) => Some(RepeatKind::String),
+ LitKind::Char(c) => Some(RepeatKind::Char(c)),
+ _ => None,
+ }
+ } else {
+ let ty = cx.typeck_results().expr_ty(e);
+ if is_type_lang_item(cx, ty, LangItem::String)
+ || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, Ty::is_str))
+ || (is_type_diagnostic_item(cx, ty, sym::Cow) && get_ty_param(ty).map_or(false, Ty::is_str))
+ {
+ Some(RepeatKind::String)
+ } else {
+ let ty = ty.peel_refs();
+ (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String)).then_some(RepeatKind::String)
+ }
+ }
+}
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ collect_expr: &Expr<'_>,
+ take_expr: &Expr<'_>,
+ take_self_arg: &Expr<'_>,
+ take_arg: &Expr<'_>,
+) {
+ if_chain! {
+ if let ExprKind::Call(repeat_fn, [repeat_arg]) = take_self_arg.kind;
+ if is_path_diagnostic_item(cx, repeat_fn, sym::iter_repeat);
+ if is_type_lang_item(cx, cx.typeck_results().expr_ty(collect_expr), LangItem::String);
- if cx.tcx.trait_of_item(collect_id) == Some(iter_trait_id);
+ if let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id);
+ if let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ if cx.tcx.trait_of_item(take_id) == Some(iter_trait_id);
+ if let Some(repeat_kind) = parse_repeat_arg(cx, repeat_arg);
+ let ctxt = collect_expr.span.ctxt();
+ if ctxt == take_expr.span.ctxt();
+ if ctxt == take_self_arg.span.ctxt();
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let count_snip = snippet_with_context(cx, take_arg.span, ctxt, "..", &mut app).0;
+
+ let val_str = match repeat_kind {
+ RepeatKind::Char(_) if repeat_arg.span.ctxt() != ctxt => return,
+ RepeatKind::Char('\'') => r#""'""#.into(),
+ RepeatKind::Char('"') => r#""\"""#.into(),
+ RepeatKind::Char(_) =>
+ match snippet_with_applicability(cx, repeat_arg.span, "..", &mut app) {
+ Cow::Owned(s) => Cow::Owned(format!("\"{}\"", &s[1..s.len() - 1])),
+ s @ Cow::Borrowed(_) => s,
+ },
+ RepeatKind::String =>
+ Sugg::hir_with_context(cx, repeat_arg, ctxt, "..", &mut app).maybe_par().to_string().into(),
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_STR_REPEAT,
+ collect_expr.span,
+ "manual implementation of `str::repeat` using iterators",
+ "try this",
+ format!("{val_str}.repeat({count_snip})"),
+ app
+ )
+ }
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
+use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span};
+
+use super::MAP_CLONE;
+
- arg: &'tcx hir::Expr<'_>,
++pub(super) fn check(
+ cx: &LateContext<'_>,
+ e: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
++ arg: &hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if_chain! {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id);
+ if cx.tcx.impl_of_method(method_id)
+ .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id), sym::Option))
+ || is_diag_trait_item(cx, method_id, sym::Iterator);
+ if let hir::ExprKind::Closure(&hir::Closure{ body, .. }) = arg.kind;
+ then {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(closure_body.value);
+ match closure_body.params[0].pat.kind {
+ hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
+ hir::BindingAnnotation::NONE, .., name, None
+ ) = inner.kind {
+ if ident_eq(name, closure_expr) {
+ lint_explicit_closure(cx, e.span, recv.span, true, msrv);
+ }
+ },
+ hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
+ match closure_expr.kind {
+ hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
+ if ident_eq(name, inner) {
+ if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
+ lint_explicit_closure(cx, e.span, recv.span, true, msrv);
+ }
+ }
+ },
+ hir::ExprKind::MethodCall(method, obj, [], _) => if_chain! {
+ if ident_eq(name, obj) && method.ident.name == sym::clone;
+ if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id);
+ if let Some(trait_id) = cx.tcx.trait_of_item(fn_id);
+ if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id);
+ // no autoderefs
+ if !cx.typeck_results().expr_adjustments(obj).iter()
+ .any(|a| matches!(a.kind, Adjust::Deref(Some(..))));
+ then {
+ let obj_ty = cx.typeck_results().expr_ty(obj);
+ if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
+ if matches!(mutability, Mutability::Not) {
+ let copy = is_copy(cx, *ty);
+ lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
+ }
+ } else {
+ lint_needless_cloning(cx, e.span, recv.span);
+ }
+ }
+ },
+ _ => {},
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+}
+
+fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
+ path.segments.len() == 1 && path.segments[0].ident == name
+ } else {
+ false
+ }
+}
+
+fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ root.trim_start(receiver).unwrap(),
+ "you are needlessly cloning iterator elements",
+ "remove the `map` call",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: Option<RustcVersion>) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ let (message, sugg_method) = if is_copy && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
+ ("you are using an explicit closure for copying elements", "copied")
+ } else {
+ ("you are using an explicit closure for cloning elements", "cloned")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ replace,
+ message,
+ &format!("consider calling the dedicated `{sugg_method}` method"),
+ format!(
+ "{}.{sugg_method}()",
+ snippet_with_applicability(cx, root, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
--- /dev/null
- use clippy_utils::is_trait_method;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- pub(super) fn check(
- cx: &LateContext<'_>,
- expr: &hir::Expr<'_>,
- iter: &hir::Expr<'_>,
- map_fn: &hir::Expr<'_>,
- collect_recv: &hir::Expr<'_>,
- ) {
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+use super::MAP_COLLECT_RESULT_UNIT;
+
- // called on Iterator
- if is_trait_method(cx, collect_recv, sym::Iterator);
- // return of collect `Result<(),_>`
- let collect_ret_ty = cx.typeck_results().expr_ty(expr);
++pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, iter: &hir::Expr<'_>, map_fn: &hir::Expr<'_>) {
++ // return of collect `Result<(),_>`
++ let collect_ret_ty = cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if is_type_diagnostic_item(cx, collect_ret_ty, sym::Result);
+ if let ty::Adt(_, substs) = collect_ret_ty.kind();
+ if let Some(result_t) = substs.types().next();
+ if result_t.is_unit();
+ // get parts for snippet
+ then {
+ span_lint_and_sugg(
+ cx,
+ MAP_COLLECT_RESULT_UNIT,
+ expr.span,
+ "`.map().collect()` can be replaced with `.try_for_each()`",
+ "try this",
+ format!(
+ "{}.try_for_each({})",
+ snippet(cx, iter.span, ".."),
+ snippet(cx, map_fn.span, "..")
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'_>, e: &Expr<'_>, arg: &'tcx Expr<'_>) {
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::MAP_ERR_IGNORE;
+
++pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+ && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+ && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Result)
+ && let ExprKind::Closure(&Closure {
+ capture_clause: CaptureBy::Ref,
+ body,
+ fn_decl_span,
+ ..
+ }) = arg.kind
+ && let closure_body = cx.tcx.hir().body(body)
+ && let [param] = closure_body.params
+ && let PatKind::Wild = param.pat.kind
+ {
+ // span the area of the closure capture and warn that the
+ // original error will be thrown away
+ span_lint_and_help(
+ cx,
+ MAP_ERR_IGNORE,
+ fn_decl_span,
+ "`map_err(|_|...` wildcard pattern discards the original error",
+ None,
+ "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)",
+ );
+ }
+}
--- /dev/null
- use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item};
- use clippy_utils::{contains_return, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
+mod bind_instead_of_map;
+mod bytecount;
+mod bytes_count_to_len;
+mod bytes_nth;
+mod case_sensitive_file_extension_comparisons;
+mod chars_cmp;
+mod chars_cmp_with_unwrap;
+mod chars_last_cmp;
+mod chars_last_cmp_with_unwrap;
+mod chars_next_cmp;
+mod chars_next_cmp_with_unwrap;
+mod clone_on_copy;
+mod clone_on_ref_ptr;
+mod cloned_instead_of_copied;
+mod collapsible_str_replace;
+mod err_expect;
+mod expect_fun_call;
+mod expect_used;
+mod extend_with_drain;
+mod filetype_is_file;
+mod filter_map;
+mod filter_map_identity;
+mod filter_map_next;
+mod filter_next;
+mod flat_map_identity;
+mod flat_map_option;
+mod from_iter_instead_of_collect;
+mod get_first;
+mod get_last_with_len;
+mod get_unwrap;
+mod implicit_clone;
+mod inefficient_to_string;
+mod inspect_for_each;
+mod into_iter_on_ref;
+mod is_digit_ascii_radix;
+mod iter_cloned_collect;
+mod iter_count;
+mod iter_kv_map;
+mod iter_next_slice;
+mod iter_nth;
+mod iter_nth_zero;
+mod iter_on_single_or_empty_collections;
+mod iter_overeager_cloned;
+mod iter_skip_next;
+mod iter_with_drain;
+mod iterator_step_by_zero;
+mod manual_ok_or;
+mod manual_saturating_arithmetic;
+mod manual_str_repeat;
+mod map_clone;
+mod map_collect_result_unit;
+mod map_err_ignore;
+mod map_flatten;
+mod map_identity;
+mod map_unwrap_or;
+mod mut_mutex_lock;
++mod needless_collect;
+mod needless_option_as_deref;
+mod needless_option_take;
+mod no_effect_replace;
+mod obfuscated_if_else;
+mod ok_expect;
+mod open_options;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod or_then_unwrap;
+mod path_buf_push_overwrite;
+mod range_zip_with_len;
+mod repeat_once;
+mod search_is_some;
++mod seek_from_current;
++mod seek_to_start_instead_of_rewind;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
+mod stable_sort_primitive;
+mod str_splitn;
+mod string_extend_chars;
+mod suspicious_map;
+mod suspicious_splitn;
+mod suspicious_to_owned;
+mod uninit_assumed_init;
+mod unit_hash;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_iter_cloned;
+mod unnecessary_join;
+mod unnecessary_lazy_eval;
+mod unnecessary_sort_by;
+mod unnecessary_to_owned;
+mod unwrap_or_else_default;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
- use rustc_hir::def::Res;
- use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
++use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
++use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
- /// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l");
++use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, TraitRef, Ty};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `cloned()` on an `Iterator` or `Option` where
+ /// `copied()` could be used instead.
+ ///
+ /// ### Why is this bad?
+ /// `copied()` is better because it guarantees that the type being cloned
+ /// implements `Copy`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1, 2, 3].iter().cloned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// [1, 2, 3].iter().copied();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub CLONED_INSTEAD_OF_COPIED,
+ pedantic,
+ "used `cloned` where `copied` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for consecutive calls to `str::replace` (2 or more)
+ /// that can be collapsed into a single call.
+ ///
+ /// ### Why is this bad?
+ /// Consecutive `str::replace` calls scan the string multiple times
+ /// with repetitive code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let hello = "hesuo worpd"
+ /// .replace('s', "l")
+ /// .replace("u", "l")
+ /// .replace('p', "l");
+ /// ```
+ /// Use instead:
+ /// ```rust
- #[clippy::version = "1.64.0"]
++ /// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l");
+ /// ```
- /// The function will always be called and potentially
- /// allocate an object acting as the default.
++ #[clippy::version = "1.65.0"]
+ pub COLLAPSIBLE_STR_REPLACE,
+ perf,
+ "collapse consecutive calls to str::replace (2 or more) into a single call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
+ ///
+ /// ### Why is this bad?
+ /// It's often inefficient to clone all elements of an iterator, when eventually, only some
+ /// of them will be consumed.
+ ///
+ /// ### Known Problems
+ /// This `lint` removes the side of effect of cloning items in the iterator.
+ /// A code that relies on that side-effect could fail.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ /// vec.iter().cloned().take(10);
+ /// vec.iter().cloned().last();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ /// vec.iter().take(10).cloned();
+ /// vec.iter().last().cloned();
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub ITER_OVEREAGER_CLONED,
+ perf,
+ "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+ /// used instead.
+ ///
+ /// ### Why is this bad?
+ /// When applicable, `filter_map()` is more clear since it shows that
+ /// `Option` is used to produce 0 or 1 items.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub FLAT_MAP_OPTION,
+ pedantic,
+ "used `flat_map` where `filter_map` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s.
+ ///
+ /// ### Why is this bad?
+ /// It is better to handle the `None` or `Err` case,
+ /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of
+ /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is
+ /// `Allow` by default.
+ ///
+ /// `result.unwrap()` will let the thread panic on `Err` values.
+ /// Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// Even if you want to panic on errors, not all `Error`s implement good
+ /// messages on display. Therefore, it may be beneficial to look at the places
+ /// where they may get displayed. Activate this lint to do just that.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.unwrap();
+ /// result.unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.expect("more helpful message");
+ /// result.expect("more helpful message");
+ /// ```
+ ///
+ /// If [expect_used](#expect_used) is enabled, instead:
+ /// ```rust,ignore
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option?;
+ ///
+ /// // or
+ ///
+ /// result?;
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub UNWRAP_USED,
+ restriction,
+ "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s.
+ ///
+ /// ### Why is this bad?
+ /// Usually it is better to handle the `None` or `Err` case.
+ /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why
+ /// this lint is `Allow` by default.
+ ///
+ /// `result.expect()` will let the thread panic on `Err`
+ /// values. Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// ### Examples
+ /// ```rust,ignore
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.expect("one");
+ /// result.expect("one");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option?;
+ ///
+ /// // or
+ ///
+ /// result?;
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub EXPECT_USED,
+ restriction,
+ "using `.expect()` on `Result` or `Option`, which might be better handled"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods that should live in a trait
+ /// implementation of a `std` trait (see [llogiq's blog
+ /// post](http://llogiq.github.io/2015/07/30/traits.html) for further
+ /// information) instead of an inherent implementation.
+ ///
+ /// ### Why is this bad?
+ /// Implementing the traits improve ergonomics for users of
+ /// the code, often with very little cost. Also people seeing a `mul(...)`
+ /// method
+ /// may expect `*` to work equally, so you should have good reason to disappoint
+ /// them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn add(&self, other: &X) -> X {
+ /// // ..
+ /// # X
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SHOULD_IMPLEMENT_TRAIT,
+ style,
+ "defining a method that should be implementing a std trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods with certain name prefixes and which
+ /// doesn't match how self is taken. The actual rules are:
+ ///
+ /// |Prefix |Postfix |`self` taken | `self` type |
+ /// |-------|------------|-------------------------------|--------------|
+ /// |`as_` | none |`&self` or `&mut self` | any |
+ /// |`from_`| none | none | any |
+ /// |`into_`| none |`self` | any |
+ /// |`is_` | none |`&mut self` or `&self` or none | any |
+ /// |`to_` | `_mut` |`&mut self` | any |
+ /// |`to_` | not `_mut` |`self` | `Copy` |
+ /// |`to_` | not `_mut` |`&self` | not `Copy` |
+ ///
+ /// Note: Clippy doesn't trigger methods with `to_` prefix in:
+ /// - Traits definition.
+ /// Clippy can not tell if a type that implements a trait is `Copy` or not.
+ /// - Traits implementation, when `&self` is taken.
+ /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+ /// (see e.g. the `std::string::ToString` trait).
+ ///
+ /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required.
+ ///
+ /// Please find more info here:
+ /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ ///
+ /// ### Why is this bad?
+ /// Consistency breeds readability. If you follow the
+ /// conventions, your users won't be surprised that they, e.g., need to supply a
+ /// mutable reference to a `as_..` function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct X;
+ /// impl X {
+ /// fn as_str(self) -> &'static str {
+ /// // ..
+ /// # ""
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRONG_SELF_CONVENTION,
+ style,
+ "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `ok().expect(..)`.
+ ///
+ /// ### Why is this bad?
+ /// Because you usually call `expect()` on the `Result`
+ /// directly to get a better error message.
+ ///
+ /// ### Known problems
+ /// The error type needs to implement `Debug`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ /// x.ok().expect("why did I do this again?");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ /// x.expect("why did I do this again?");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OK_EXPECT,
+ style,
+ "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.err().expect()` calls on the `Result` type.
+ ///
+ /// ### Why is this bad?
+ /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`.
+ ///
+ /// ### Example
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.err().expect("Testing err().expect()");
+ /// ```
+ /// Use instead:
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.expect_err("Testing expect_err");
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub ERR_EXPECT,
+ style,
+ r#"using `.err().expect("")` when `.expect_err("")` can be used"#
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
+ /// `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written as `_.unwrap_or_default`, which is
+ /// simpler and more concise.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let x = Some(1);
+ /// x.unwrap_or_else(Default::default);
+ /// x.unwrap_or_else(u32::default);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = Some(1);
+ /// x.unwrap_or_default();
+ /// ```
+ #[clippy::version = "1.56.0"]
+ pub UNWRAP_OR_ELSE_DEFAULT,
+ style,
+ "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
+ /// `result.map(_).unwrap_or_else(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written more concisely (resp.) as
+ /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ /// option.map(|a| a + 1).unwrap_or(0);
+ /// result.map(|a| a + 1).unwrap_or_else(some_function);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ /// option.map_or(0, |a| a + 1);
+ /// result.map_or_else(some_function, |a| a + 1);
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MAP_UNWRAP_OR,
+ pedantic,
+ "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, _)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.and_then(_)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some(1);
+ /// opt.map_or(None, |a| Some(a + 1));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let opt = Some(1);
+ /// opt.and_then(|a| Some(a + 1));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_MAP_OR_NONE,
+ style,
+ "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, Some)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ok()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.map_or(None, Some));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.ok());
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub RESULT_MAP_OR_INTO_OPTION,
+ style,
+ "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
+ /// `_.or_else(|x| Err(y))`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.map(|x| y)` or `_.map_err(|x| y)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().and_then(|s| Some(s.len()));
+ /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) });
+ /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) });
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().map(|s| s.len());
+ /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });
+ /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub BIND_INSTEAD_OF_MAP,
+ complexity,
+ "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().filter(|x| **x == 0).next();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FILTER_NEXT,
+ complexity,
+ "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.skip_while(condition).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(!condition)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().skip_while(|x| **x == 0).next();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x != 0);
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub SKIP_WHILE_NEXT,
+ complexity,
+ "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![vec![1]];
+ /// let opt = Some(5);
+ ///
+ /// vec.iter().map(|x| x.iter()).flatten();
+ /// opt.map(|x| Some(x * 2)).flatten();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![vec![1]];
+ /// # let opt = Some(5);
+ /// vec.iter().flat_map(|x| x.iter());
+ /// opt.and_then(|x| Some(x * 2));
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub MAP_FLATTEN,
+ complexity,
+ "using combinations of `flatten` and `map` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).map(_)` that can be written more simply
+ /// as `filter_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `filter` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// (0_i32..10)
+ /// .filter(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #[allow(unused)]
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FILTER_MAP,
+ complexity,
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.find(_).map(_)` that can be written more simply
+ /// as `find_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `find` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0_i32..10)
+ /// .find(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// (0_i32..10).find_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FIND_MAP,
+ complexity,
+ "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter_map(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find_map(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
+ /// ```
+ /// Can be written as
+ ///
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
+ /// ```
+ #[clippy::version = "1.36.0"]
+ pub FILTER_MAP_NEXT,
+ pedantic,
+ "using combination of `filter_map` and `next` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `flat_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flat_map(|x| x);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for an iterator or string search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as:
+ /// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
+ /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ ///
+ /// "hello world".find("world").is_none();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ ///
+ /// # #[allow(unused)]
+ /// !"hello world".contains("world");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.chars().next()` on a `str` to check
+ /// if it starts with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.starts_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_NEXT_CMP,
+ style,
+ "using `.chars().next()` to check if a string starts with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+ /// `.or_insert(foo(..))` etc., and suggests to use `.or_else(|| foo(..))`,
+ /// `.unwrap_or_else(|| foo(..))`, `.unwrap_or_default()` or `.or_default()`
+ /// etc. instead.
+ ///
+ /// ### Why is this bad?
- /// If the function has side-effects, not calling it will
- /// change the semantic of the program, but you shouldn't rely on that anyway.
++ /// The function will always be called. This is only bad if it allocates or
++ /// does some non-trivial amount of work.
+ ///
+ /// ### Known problems
- /// foo.unwrap_or(String::new());
++ /// If the function has side-effects, not calling it will change the
++ /// semantic of the program, but you shouldn't rely on that.
++ ///
++ /// The lint also cannot figure out whether the function you call is
++ /// actually expensive to call or not.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
- /// foo.unwrap_or_else(String::new);
- ///
- /// // or
- ///
- /// # let foo = Some(String::new());
- /// foo.unwrap_or_default();
++ /// foo.unwrap_or(String::from("empty"));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = Some(String::new());
- perf,
++ /// foo.unwrap_or_else(|| String::from("empty"));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OR_FUN_CALL,
- /// Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
++ nursery,
+ "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(..))`,
+ /// etc., and suggests to use `unwrap_or_else` instead
+ ///
+ /// ### Why is this bad?
+ /// The function will always be called.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantics of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(&format!("Err {}: {}", err_code, err_msg));
+ ///
+ /// // or
+ ///
+ /// # let foo = Some(String::new());
+ /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPECT_FUN_CALL,
+ perf,
+ "using any `expect` method with a function call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a `Copy` type.
+ ///
+ /// ### Why is this bad?
+ /// The only reason `Copy` types implement `Clone` is for
+ /// generics, not for using the `clone` method on a concrete type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_COPY,
+ complexity,
+ "using `clone` on a `Copy` type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a ref-counted pointer,
+ /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+ /// function syntax instead (e.g., `Rc::clone(foo)`).
+ ///
+ /// ### Why is this bad?
+ /// Calling '.clone()' on an Rc, Arc, or Weak
+ /// can obscure the fact that only the pointer is being cloned, not the underlying
+ /// data.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// let x = Rc::new(1);
+ ///
+ /// x.clone();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// # let x = Rc::new(1);
+ /// Rc::clone(&x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_REF_PTR,
+ restriction,
+ "using 'clone' on a ref-counted pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on an `&&T`.
+ ///
+ /// ### Why is this bad?
+ /// Cloning an `&&T` copies the inner `&T`, instead of
+ /// cloning the underlying `T`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = vec![1];
+ /// let y = &&x;
+ /// let z = y.clone();
+ /// println!("{:p} {:p}", *y, z); // prints out the same pointer
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_DOUBLE_REF,
+ correctness,
+ "using `clone` on `&&T`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.to_string()` on an `&&T` where
+ /// `T` implements `ToString` directly (like `&&str` or `&&String`).
+ ///
+ /// ### Why is this bad?
+ /// This bypasses the specialized implementation of
+ /// `ToString` and instead goes through the more expensive string formatting
+ /// facilities.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Generic implementation for `T: Display` is used (slow)
+ /// ["foo", "bar"].iter().map(|s| s.to_string());
+ ///
+ /// // OK, the specialized impl is used
+ /// ["foo", "bar"].iter().map(|&s| s.to_string());
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub INEFFICIENT_TO_STRING,
+ pedantic,
+ "using `to_string` on `&&T` where `T: ToString`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `new` not returning a type that contains `Self`.
+ ///
+ /// ### Why is this bad?
+ /// As a convention, `new` methods are used to make a new
+ /// instance of a type.
+ ///
+ /// ### Example
+ /// In an impl block:
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEW_RET_NO_SELF,
+ style,
+ "not returning type containing `Self` in a `new` method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string methods that receive a single-character
+ /// `str` as an argument, e.g., `_.split("x")`.
+ ///
+ /// ### Why is this bad?
+ /// Performing these methods using a `char` is faster than
+ /// using a `str`.
+ ///
+ /// ### Known problems
+ /// Does not catch multi-byte unicode characters.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// _.split("x");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// _.split('x');
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_CHAR_PATTERN,
+ perf,
+ "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// ### Why is this bad?
+ /// This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// ### Example
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITERATOR_STEP_BY_ZERO,
+ correctness,
+ "using `Iterator::step_by(0)`, which will panic at runtime"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for indirect collection of populated `Option`
+ ///
+ /// ### Why is this bad?
+ /// `Option` is like a collection of 0-1 things, so `flatten`
+ /// automatically does this without suspicious-looking `unwrap` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().flatten();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub OPTION_FILTER_MAP,
+ complexity,
+ "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `iter.nth(0)`.
+ ///
+ /// ### Why is this bad?
+ /// `iter.next()` is equivalent to
+ /// `iter.nth(0)`, as they both consume the next element,
+ /// but is more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().nth(0);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().next();
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub ITER_NTH_ZERO,
+ style,
+ "replace `iter.nth(0)` with `iter.next()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.iter().nth()` (and the related
+ /// `.iter_mut().nth()`) on standard library types with *O*(1) element access.
+ ///
+ /// ### Why is this bad?
+ /// `.get()` and `.get_mut()` are more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.get(3);
+ /// let bad_slice = &some_vec[..].get(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_NTH,
+ perf,
+ "using `.iter().nth()` on a standard library type with O(1) element access"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.skip(x).next()` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.nth(x)` is cleaner
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().skip(3).next();
+ /// let bad_slice = &some_vec[..].iter().skip(3).next();
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_SKIP_NEXT,
+ style,
+ "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 using `x.get(x.len() - 1)` instead of
+ /// `x.last()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `x.last()` is easier to read and has the same
+ /// result.
+ ///
+ /// Note that using `x[x.len() - 1]` is semantically different from
+ /// `x.last()`. Indexing into the array will panic on out-of-bounds
+ /// accesses, while `x.get()` and `x.last()` will return `None`.
+ ///
+ /// There is another lint (get_unwrap) that covers the case of using
+ /// `x.get(index).unwrap()` instead of `x[index]`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.get(x.len() - 1);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.last();
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub GET_LAST_WITH_LEN,
+ complexity,
+ "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.get().unwrap()` (or
+ /// `.get_mut().unwrap`) on a standard library type which implements `Index`
+ ///
+ /// ### Why is this bad?
+ /// Using the Index trait (`[]`) is more clear and more
+ /// concise.
+ ///
+ /// ### Known problems
+ /// Not a replacement for error handling: Using either
+ /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic`
+ /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a
+ /// temporary placeholder for dealing with the `Option` type, then this does
+ /// not mitigate the need for error handling. If there is a chance that `.get()`
+ /// will be `None` in your program, then it is advisable that the `None` case
+ /// is handled in a future refactor instead of using `.unwrap()` or the Index
+ /// trait.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec.get(3).unwrap();
+ /// *some_vec.get_mut(0).unwrap() = 1;
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec[3];
+ /// some_vec[0] = 1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for occurrences where one vector gets extended instead of append
+ ///
+ /// ### Why is this bad?
+ /// Using `append` instead of `extend` is more concise and faster
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// a.extend(b.drain(..));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// a.append(&mut b);
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub EXTEND_WITH_DRAIN,
+ perf,
+ "using vec.append(&mut vec) to move the full range of a vector to another"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.extend(s.chars())` where s is a
+ /// `&str` or `String`.
+ ///
+ /// ### Why is this bad?
+ /// `.push_str(s)` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.extend(abc.chars());
+ /// s.extend(def.chars());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.push_str(abc);
+ /// s.push_str(&def);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_EXTEND_CHARS,
+ style,
+ "using `x.extend(s.chars())` where s is a `&str` or `String`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.cloned().collect()` on slice to
+ /// create a `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// `.to_vec()` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s[..].iter().cloned().collect();
+ /// ```
+ /// The better use would be:
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s.to_vec();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_CLONED_COLLECT,
+ style,
+ "using `.cloned().collect()` on slice to create a `Vec`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.chars().last()` or
+ /// `_.chars().next_back()` on a `str` to check if it ends with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ends_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "_";
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-');
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let name = "_";
+ /// name.ends_with('_') || name.ends_with('-');
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_LAST_CMP,
+ style,
+ "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.as_ref()` or `.as_mut()` where the
+ /// types before and after the call are the same.
+ ///
+ /// ### Why is this bad?
+ /// The call is unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x.as_ref());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_ASREF,
+ complexity,
+ "using `as_ref` where the types before and after the call are the same"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `fold` when a more succinct alternative exists.
+ /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
+ /// `sum` or `product`.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[allow(unused)]
+ /// (0..3).fold(false, |acc, x| acc || x > 2);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// (0..3).any(|x| x > 2);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_FOLD,
+ style,
+ "using `fold` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// 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.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).filter_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).filter(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).filter_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);
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub UNNECESSARY_FILTER_MAP,
+ complexity,
+ "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`
+ /// or `iter_mut`.
+ ///
+ /// ### Why is this bad?
+ /// Readability. Calling `into_iter` on a reference will not move out its
+ /// content into the resulting iterator, which is confusing. It is better just call `iter` or
+ /// `iter_mut` directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![3, 4, 5];
+ /// (&vec).into_iter();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![3, 4, 5];
+ /// (&vec).iter();
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub INTO_ITER_ON_REF,
+ style,
+ "using `.into_iter()` on a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `map` followed by a `count`.
+ ///
+ /// ### Why is this bad?
+ /// It looks suspicious. Maybe `map` was confused with `filter`.
+ /// If the `map` call is intentional, this should be rewritten
+ /// using `inspect`. Or, if you intend to drive the iterator to
+ /// completion, you can just use `for_each` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).map(|x| x + 2).count();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub SUSPICIOUS_MAP,
+ suspicious,
+ "suspicious usage of map"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `MaybeUninit::uninit().assume_init()`.
+ ///
+ /// ### Why is this bad?
+ /// For most types, this is undefined behavior.
+ ///
+ /// ### Known problems
+ /// For now, we accept empty tuples and tuples / arrays
+ /// of `MaybeUninit`. There may be other types that allow uninitialized
+ /// data, but those are not yet rigorously defined.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Beware the UB
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
+ /// ```
+ ///
+ /// Note that the following is OK:
+ ///
+ /// ```rust
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: [MaybeUninit<bool>; 5] = unsafe {
+ /// MaybeUninit::uninit().assume_init()
+ /// };
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub UNINIT_ASSUMED_INIT,
+ correctness,
+ "`MaybeUninit::uninit().assume_init()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
+ ///
+ /// ### Why is this bad?
+ /// These can be written simply with `saturating_add/sub` methods.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.checked_add(y).unwrap_or(u32::MAX);
+ /// let sub = x.checked_sub(y).unwrap_or(u32::MIN);
+ /// ```
+ ///
+ /// can be written using dedicated methods for saturating addition/subtraction as:
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.saturating_add(y);
+ /// let sub = x.saturating_sub(y);
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub MANUAL_SATURATING_ARITHMETIC,
+ style,
+ "`.checked_add/sub(x).unwrap_or(MAX/MIN)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+ /// zero-sized types
+ ///
+ /// ### Why is this bad?
+ /// This is a no-op, and likely unintended
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub ZST_OFFSET,
+ correctness,
+ "Check for offset calculations on raw pointers to zero-sized types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `FileType::is_file()`.
+ ///
+ /// ### Why is this bad?
+ /// When people testing a file type with `FileType::is_file`
+ /// they are testing whether a path is something they can get bytes from. But
+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if filetype.is_file() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ ///
+ /// should be written as:
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if !filetype.is_dir() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- /// let data = cow.into_owned();
- /// assert!(matches!(data, String))
++ /// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str).
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.as_deref()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_ref().map(String::as_str)
+ /// # ;
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_deref()
+ /// # ;
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub OPTION_AS_REF_DEREF,
+ complexity,
+ "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `iter().next()` on a Slice or an Array
+ ///
+ /// ### Why is this bad?
+ /// These can be shortened into `.get()`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a[2..].iter().next();
+ /// b.iter().next();
+ /// ```
+ /// should be written as:
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a.get(2);
+ /// b.get(0);
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub ITER_NEXT_SLICE,
+ style,
+ "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns when using `push_str`/`insert_str` with a single-character string literal
+ /// where `push`/`insert` with a `char` would work fine.
+ ///
+ /// ### Why is this bad?
+ /// It's less clear that we are pushing a single character.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut string = String::new();
+ /// string.insert_str(0, "R");
+ /// string.push_str("R");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let mut string = String::new();
+ /// string.insert(0, 'R');
+ /// string.push('R');
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub SINGLE_CHAR_ADD_STR,
+ style,
+ "`push_str()` or `insert_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// ### Why is this bad?
+ /// Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// ### Known problems
+ /// It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).collect::<Result<(), _>()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `try_for_each` instead is more readable and idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// (0..3).try_for_each(|t| Err(t));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MAP_COLLECT_RESULT_UNIT,
+ style,
+ "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `from_iter()` function calls on types that implement the `FromIterator`
+ /// trait.
+ ///
+ /// ### Why is this bad?
+ /// It is recommended style to use collect. See
+ /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
+ ///
+ /// ### Example
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v = Vec::from_iter(five_fives);
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v: Vec<i32> = five_fives.collect();
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub FROM_ITER_INSTEAD_OF_COLLECT,
+ pedantic,
+ "use `.collect()` instead of `::from_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `inspect().for_each()`.
+ ///
+ /// ### Why is this bad?
+ /// It is the same as performing the computation
+ /// inside `inspect` at the beginning of the closure in `for_each`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .inspect(|&x| println!("inspect the number: {}", x))
+ /// .for_each(|&x| {
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .for_each(|&x| {
+ /// println!("inspect the number: {}", x);
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub INSPECT_FOR_EACH,
+ complexity,
+ "using `.inspect().for_each()`, which can be replaced with `.for_each()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `filter_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.filter_map(|x| x);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub FILTER_MAP_IDENTITY,
+ complexity,
+ "call to `filter_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map(f)` where `f` is the identity function.
+ ///
+ /// ### Why is this bad?
+ /// It can be written more concisely without the call to `map`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| 2*x).collect();
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub MAP_IDENTITY,
+ complexity,
+ "using iterator.map(|x| x)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.bytes().nth()`.
+ ///
+ /// ### Why is this bad?
+ /// `.as_bytes().get()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[allow(unused)]
+ /// "Hello".bytes().nth(3);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #[allow(unused)]
+ /// "Hello".as_bytes().get(3);
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub BYTES_NTH,
+ style,
+ "replace `.bytes().nth()` with `.as_bytes().get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
+ ///
+ /// ### Why is this bad?
+ /// These methods do the same thing as `_.clone()` but may be confusing as
+ /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.to_vec();
+ /// let c = a.to_owned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.clone();
+ /// let c = a.clone();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub IMPLICIT_CLONE,
+ pedantic,
+ "implicitly cloning a value by invoking a function on its dereferenced type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.iter().count()`.
+ ///
+ /// ### Why is this bad?
+ /// `.len()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let some_vec = vec![0, 1, 2, 3];
+ ///
+ /// some_vec.iter().count();
+ /// &some_vec[..].iter().count();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ ///
+ /// some_vec.len();
+ /// &some_vec[..].len();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub ITER_COUNT,
+ complexity,
+ "replace `.iter().count()` with `.len()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`.
+ ///
+ /// ### Why is this bad?
+ /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow`
+ /// itself, without taking ownership of the `Cow` contents (i.e.
+ /// it's equivalent to calling `Cow::clone`).
+ /// The similarly named `into_owned` method, on the other hand,
+ /// clones the `Cow` contents, effectively turning any `Cow::Borrowed`
+ /// into a `Cow::Owned`.
+ ///
+ /// Given the potential ambiguity, consider replacing `to_owned`
+ /// with `clone` for better readability or, if getting a `Cow::Owned`
+ /// was the original intent, using `into_owned` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::borrow::Cow;
+ /// let s = "Hello world!";
+ /// let cow = Cow::Borrowed(s);
+ ///
+ /// let data = cow.to_owned();
+ /// assert!(matches!(data, Cow::Borrowed(_)))
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::borrow::Cow;
+ /// let s = "Hello world!";
+ /// let cow = Cow::Borrowed(s);
+ ///
+ /// let data = cow.clone();
+ /// assert!(matches!(data, Cow::Borrowed(_)))
+ /// ```
+ /// or
+ /// ```rust
+ /// # use std::borrow::Cow;
+ /// let s = "Hello world!";
+ /// let cow = Cow::Borrowed(s);
+ ///
- #[clippy::version = "1.64.0"]
++ /// let _data: String = cow.into_owned();
+ /// ```
+ #[clippy::version = "1.65.0"]
+ pub SUSPICIOUS_TO_OWNED,
+ suspicious,
+ "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to [`splitn`]
+ /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
+ /// related functions with either zero or one splits.
+ ///
+ /// ### Why is this bad?
+ /// These calls don't actually split the value and are
+ /// likely to be intended as a different number.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let s = "";
+ /// for x in s.splitn(1, ":") {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let s = "";
+ /// for x in s.splitn(2, ":") {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub SUSPICIOUS_SPLITN,
+ correctness,
+ "checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of `str::repeat`
+ ///
+ /// ### Why is this bad?
+ /// These are both harder to read, as well as less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: String = std::iter::repeat('x').take(10).collect();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x: String = "x".repeat(10);
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub MANUAL_STR_REPEAT,
+ perf,
+ "manual implementation of `str::repeat`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn(2, _)`
+ ///
+ /// ### Why is this bad?
+ /// `split_once` is both clearer in intent and slightly more efficient.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let s = "key=value=add";
+ /// let (key, value) = s.splitn(2, '=').next_tuple()?;
+ /// let value = s.splitn(2, '=').nth(1)?;
+ ///
+ /// let mut parts = s.splitn(2, '=');
+ /// let key = parts.next()?;
+ /// let value = parts.next()?;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// let s = "key=value=add";
+ /// let (key, value) = s.split_once('=')?;
+ /// let value = s.split_once('=')?.1;
+ ///
+ /// let (key, value) = s.split_once('=')?;
+ /// ```
+ ///
+ /// ### Limitations
+ /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()`
+ /// in two separate `let` statements that immediately follow the `splitn()`
+ #[clippy::version = "1.57.0"]
+ pub MANUAL_SPLIT_ONCE,
+ complexity,
+ "replace `.splitn(2, pat)` with `.split_once(pat)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same.
+ /// ### Why is this bad?
+ /// The function `split` is simpler and there is no performance difference in these cases, considering
+ /// that both functions return a lazy iterator.
+ /// ### Example
+ /// ```rust
+ /// let str = "key=value=add";
+ /// let _ = str.splitn(3, '=').next().unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let str = "key=value=add";
+ /// let _ = str.split('=').next().unwrap();
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub NEEDLESS_SPLITN,
+ complexity,
+ "usages of `str::splitn` that can be replaced with `str::split`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned)
+ /// and other `to_owned`-like functions.
+ ///
+ /// ### Why is this bad?
+ /// The unnecessary calls result in useless allocations.
+ ///
+ /// ### Known problems
+ /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an
+ /// owned copy of a resource and the resource is later used mutably. See
+ /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy().to_string());
+ /// fn foo(s: &str) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy());
+ /// fn foo(s: &str) {}
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub UNNECESSARY_TO_OWNED,
+ perf,
+ "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 might be 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 sometimes more performant, there are cases where
+ /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+ /// will prevent loop unrolling and will result in a negative performance impact.
+ ///
+ /// Additionally, differences have been observed between aarch64 and x86_64 assembly output,
+ /// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_JOIN,
+ pedantic,
+ "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`,
+ /// for example, `Option<&T>::as_deref()` returns the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code and improving readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a;
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub NEEDLESS_OPTION_AS_DEREF,
+ complexity,
+ "no-op use of `deref` or `deref_mut` method to `Option`."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
+ /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
+ /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
+ ///
+ /// ### Why is this bad?
+ /// `is_digit(..)` is slower and requires specifying the radix.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let c: char = '6';
+ /// c.is_digit(10);
+ /// c.is_digit(16);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let c: char = '6';
+ /// c.is_ascii_digit();
+ /// c.is_ascii_hexdigit();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub IS_DIGIT_ASCII_RADIX,
+ style,
+ "use of `char::is_digit(..)` with literal radix of 10 or 16"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `take` function after `as_ref`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code. `take` writes `None` to its argument.
+ /// In this case the modification is useless as it's a temporary that cannot be read from afterwards.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref().take();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub NEEDLESS_OPTION_TAKE,
+ complexity,
+ "using `.as_ref().take()` on a temporary value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `replace` statements which have no effect.
+ ///
+ /// ### Why is this bad?
+ /// It's either a mistake or confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// "1234".replace("12", "12");
+ /// "1234".replacen("12", "12", 1);
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub NO_EFFECT_REPLACE,
+ suspicious,
+ "replace with no effect"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `.then_some(..).unwrap_or(..)`
+ ///
+ /// ### Why is this bad?
+ /// This can be written more clearly with `if .. else ..`
+ ///
+ /// ### Limitations
+ /// This lint currently only looks for usages of
+ /// `.then_some(..).unwrap_or(..)`, but will be expanded
+ /// to account for similar patterns.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = true;
+ /// x.then_some("a").unwrap_or("b");
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = true;
+ /// if x { "a" } else { "b" };
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub OBFUSCATED_IF_ELSE,
+ style,
+ "use of `.then_some(..).unwrap_or(..)` can be written \
+ more clearly with `if .. else ..`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item
+ ///
+ /// ### Why is this bad?
+ ///
+ /// It is simpler to use the once function from the standard library:
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let a = [123].iter();
+ /// let b = Some(123).into_iter();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::iter;
+ /// let a = iter::once(&123);
+ /// let b = iter::once(123);
+ /// ```
+ ///
+ /// ### Known problems
+ ///
+ /// The type of the resulting iterator might become incompatible with its usage
- #[clippy::version = "1.64.0"]
++ #[clippy::version = "1.65.0"]
+ pub ITER_ON_SINGLE_ITEMS,
+ nursery,
+ "Iterator for array of length 1"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections
+ ///
+ /// ### Why is this bad?
+ ///
+ /// It is simpler to use the empty function from the standard library:
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// use std::{slice, option};
+ /// let a: slice::Iter<i32> = [].iter();
+ /// let f: option::IntoIter<i32> = None.into_iter();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::iter;
+ /// let a: iter::Empty<i32> = iter::empty();
+ /// let b: iter::Empty<i32> = iter::empty();
+ /// ```
+ ///
+ /// ### Known problems
+ ///
+ /// The type of the resulting iterator might become incompatible with its usage
- ) -> Option<(&'tcx str, &'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>], Span)> {
- if let ExprKind::MethodCall(path, receiver, args, _) = recv.kind {
++ #[clippy::version = "1.65.0"]
+ pub ITER_ON_EMPTY_COLLECTIONS,
+ nursery,
+ "Iterator for empty array"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for naive byte counts
+ ///
+ /// ### Why is this bad?
+ /// The [`bytecount`](https://crates.io/crates/bytecount)
+ /// crate has methods to count your bytes faster, especially for large slices.
+ ///
+ /// ### Known problems
+ /// If you have predominantly small slices, the
+ /// `bytecount::count(..)` method may actually be slower. However, if you can
+ /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be
+ /// faster in those cases.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1_u8];
+ /// let count = vec.iter().filter(|x| **x == 0u8).count();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// # let vec = vec![1_u8];
+ /// let count = bytecount::count(&vec, 0u8);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NAIVE_BYTECOUNT,
+ pedantic,
+ "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for `str::bytes().count()` and suggests replacing it with
+ /// `str::len()`.
+ ///
+ /// ### Why is this bad?
+ /// `str::bytes().count()` is longer and may not be as performant as using
+ /// `str::len()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// "hello".bytes().count();
+ /// String::from("hello").bytes().count();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// "hello".len();
+ /// String::from("hello").len();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub BYTES_COUNT_TO_LEN,
+ complexity,
+ "Using `bytes().count()` when `len()` performs the same functionality"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `ends_with` with possible file extensions
+ /// and suggests to use a case-insensitive approach instead.
+ ///
+ /// ### Why is this bad?
+ /// `ends_with` is case-sensitive and may not detect files with a valid extension.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// filename.ends_with(".rs")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// let filename = std::path::Path::new(filename);
+ /// filename.extension()
+ /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ pedantic,
+ "Checks for calls to ends_with with case-sensitive file extensions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `x.get(0)` instead of
+ /// `x.first()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `x.first()` is easier to read and has the same
+ /// result.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let first_element = x.get(0);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let first_element = x.first();
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub GET_FIRST,
+ style,
+ "Using `x.get(0)` when `x.first()` is simpler"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds patterns that reimplement `Option::ok_or`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Concise code helps focusing on behavior instead of boilerplate.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.map_or(Err("error"), |v| Ok(v));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.ok_or("error");
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MANUAL_OK_OR,
+ pedantic,
+ "finds patterns that can be encoded more concisely with `Option::ok_or`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `map(|x| x.clone())` or
+ /// dereferencing closures for `Copy` types, on `Iterator` or `Option`,
+ /// and suggests `cloned()` or `copied()` instead
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.map(|i| *i);
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.cloned();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAP_CLONE,
+ style,
+ "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map_err(|_| Some::Enum)`
+ ///
+ /// ### Why is this bad?
+ /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error
+ ///
+ /// ### Example
+ /// Before:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible,
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {}
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(|_| Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// use std::{fmt, num::ParseIntError};
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible(ParseIntError),
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible(_) => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {
+ /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ /// match self {
+ /// Error::Indivisible(source) => Some(source),
+ /// _ => None,
+ /// }
+ /// }
+ /// }
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub MAP_ERR_IGNORE,
+ restriction,
+ "`map_err` should not ignore the original error"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `&mut Mutex::lock` calls
+ ///
+ /// ### Why is this bad?
+ /// `Mutex::lock` is less efficient than
+ /// calling `Mutex::get_mut`. In addition you also have a statically
+ /// guarantee that the mutex isn't locked, instead of just a runtime
+ /// guarantee.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let mut value = value_mutex.lock().unwrap();
+ /// *value += 1;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let value = value_mutex.get_mut().unwrap();
+ /// *value += 1;
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MUT_MUTEX_LOCK,
+ style,
+ "`&mut Mutex::lock` does unnecessary locking"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for duplicate open options as well as combinations
+ /// that make no sense.
+ ///
+ /// ### Why is this bad?
+ /// In the best case, the code will be harder to read than
+ /// necessary. I don't know the worst case.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::fs::OpenOptions;
+ ///
+ /// OpenOptions::new().read(true).truncate(true);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NONSENSICAL_OPEN_OPTIONS,
+ correctness,
+ "nonsensical combination of options for opening a file"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
+ /// calls on `PathBuf` that can cause overwrites.
+ ///
+ /// ### Why is this bad?
+ /// Calling `push` with a root path at the start can overwrite the
+ /// previous defined path.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("/bar");
+ /// assert_eq!(x, PathBuf::from("/bar"));
+ /// ```
+ /// Could be written:
+ ///
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("bar");
+ /// assert_eq!(x, PathBuf::from("/foo/bar"));
+ /// ```
+ #[clippy::version = "1.36.0"]
+ pub PATH_BUF_PUSH_OVERWRITE,
+ nursery,
+ "calling `push` with file system root on `PathBuf` can overwrite it"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for zipping a collection with the range of
+ /// `0.._.len()`.
+ ///
+ /// ### Why is this bad?
+ /// The code is better expressed with `.enumerate()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = vec![1];
+ /// let _ = x.iter().zip(0..x.len());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = vec![1];
+ /// let _ = x.iter().enumerate();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_ZIP_WITH_LEN,
+ complexity,
+ "zipping iterator with a range when `enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.repeat(1)` and suggest the following method for each types.
+ /// - `.to_string()` for `str`
+ /// - `.clone()` for `String`
+ /// - `.to_vec()` for `slice`
+ ///
+ /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if
+ /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306))
+ ///
+ /// ### Why is this bad?
+ /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning
+ /// the string is the intention behind this, `clone()` should be used.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").repeat(1);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").clone();
+ /// }
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub REPEAT_ONCE,
+ complexity,
+ "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` "
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// When sorting primitive values (integers, bools, chars, as well
+ /// as arrays, slices, and tuples of such items), it is typically better to
+ /// use an unstable sort than a stable sort.
+ ///
+ /// ### Why is this bad?
+ /// Typically, using a stable sort consumes more memory and cpu cycles.
+ /// Because values which compare equal are identical, preserving their
+ /// relative order (the guarantee that a stable sort provides) means
+ /// nothing, while the extra costs still apply.
+ ///
+ /// ### Known problems
+ ///
+ /// As pointed out in
+ /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241),
+ /// a stable sort can instead be significantly faster for certain scenarios
+ /// (eg. when a sorted vector is extended with new data and resorted).
+ ///
+ /// For more information and benchmarking results, please refer to the
+ /// issue linked above.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort_unstable();
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub STABLE_SORT_PRIMITIVE,
+ pedantic,
+ "use of sort() when sort_unstable() is equivalent"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `().hash(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => ().hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => 0_u8.hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNIT_HASH,
+ correctness,
+ "hashing a unit value, which does nothing"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects uses of `Vec::sort_by` passing in a closure
+ /// which compares the two arguments, either directly or indirectly.
+ ///
+ /// ### Why is this bad?
+ /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if
+ /// possible) than to use `Vec::sort_by` and a more complicated
+ /// closure.
+ ///
+ /// ### Known problems
+ /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already
+ /// imported by a use statement, then it will need to be added manually.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct A;
+ /// # impl A { fn foo(&self) {} }
+ /// # let mut vec: Vec<A> = Vec::new();
+ /// vec.sort_by(|a, b| a.foo().cmp(&b.foo()));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct A;
+ /// # impl A { fn foo(&self) {} }
+ /// # let mut vec: Vec<A> = Vec::new();
+ /// vec.sort_by_key(|a| a.foo());
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub UNNECESSARY_SORT_BY,
+ complexity,
+ "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds occurrences of `Vec::resize(0, an_int)`
+ ///
+ /// ### Why is this bad?
+ /// This is probably an argument inversion mistake.
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!(1, 2, 3, 4, 5).resize(0, 5)
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// vec!(1, 2, 3, 4, 5).clear()
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub VEC_RESIZE_TO_ZERO,
+ correctness,
+ "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of File::read_to_end and File::read_to_string.
+ ///
+ /// ### Why is this bad?
+ /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values.
+ /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html)
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # use std::io::Read;
+ /// # use std::fs::File;
+ /// let mut f = File::open("foo.txt").unwrap();
+ /// let mut bytes = Vec::new();
+ /// f.read_to_end(&mut bytes).unwrap();
+ /// ```
+ /// Can be written more concisely as
+ /// ```rust,no_run
+ /// # use std::fs;
+ /// let mut bytes = fs::read("foo.txt").unwrap();
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub VERBOSE_FILE_READS,
+ restriction,
+ "use of `File::read_to_end` or `File::read_to_string`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for iterating a map (`HashMap` or `BTreeMap`) and
+ /// ignoring either the keys or values.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Readability. There are `keys` and `values` methods that
+ /// can be used to express that we only need the keys or the values.
+ ///
+ /// ### Example
+ ///
+ /// ```
+ /// # use std::collections::HashMap;
+ /// let map: HashMap<u32, u32> = HashMap::new();
+ /// let values = map.iter().map(|(_, value)| value).collect::<Vec<_>>();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```
+ /// # use std::collections::HashMap;
+ /// let map: HashMap<u32, u32> = HashMap::new();
+ /// let values = map.values().collect::<Vec<_>>();
+ /// ```
+ #[clippy::version = "1.65.0"]
+ pub ITER_KV_MAP,
+ complexity,
+ "iterating on map using `iter` when `keys` or `values` would do"
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ ///
++ /// Checks an argument of `seek` method of `Seek` trait
++ /// and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
++ ///
++ /// ### Why is this bad?
++ ///
++ /// Readability. Use dedicated method.
++ ///
++ /// ### Example
++ ///
++ /// ```rust,no_run
++ /// use std::fs::File;
++ /// use std::io::{self, Write, Seek, SeekFrom};
++ ///
++ /// fn main() -> io::Result<()> {
++ /// let mut f = File::create("foo.txt")?;
++ /// f.write_all(b"Hello")?;
++ /// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?);
++ ///
++ /// Ok(())
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust,no_run
++ /// use std::fs::File;
++ /// use std::io::{self, Write, Seek, SeekFrom};
++ ///
++ /// fn main() -> io::Result<()> {
++ /// let mut f = File::create("foo.txt")?;
++ /// f.write_all(b"Hello")?;
++ /// eprintln!("Written {} bytes", f.stream_position()?);
++ ///
++ /// Ok(())
++ /// }
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub SEEK_FROM_CURRENT,
++ complexity,
++ "use dedicated method for seek from current position"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ ///
++ /// Checks for jumps to the start of a stream that implements `Seek`
++ /// and uses the `seek` method providing `Start` as parameter.
++ ///
++ /// ### Why is this bad?
++ ///
++ /// Readability. There is a specific method that was implemented for
++ /// this exact scenario.
++ ///
++ /// ### Example
++ /// ```rust
++ /// # use std::io;
++ /// fn foo<T: io::Seek>(t: &mut T) {
++ /// t.seek(io::SeekFrom::Start(0));
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// # use std::io;
++ /// fn foo<T: io::Seek>(t: &mut T) {
++ /// t.rewind();
++ /// }
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub SEEK_TO_START_INSTEAD_OF_REWIND,
++ complexity,
++ "jumping to the start of stream using `seek` method"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for functions collecting an iterator when collect
++ /// is not needed.
++ ///
++ /// ### Why is this bad?
++ /// `collect` causes the allocation of a new data structure,
++ /// when this allocation may not be needed.
++ ///
++ /// ### Example
++ /// ```rust
++ /// # let iterator = vec![1].into_iter();
++ /// let len = iterator.clone().collect::<Vec<_>>().len();
++ /// // should be
++ /// let len = iterator.count();
++ /// ```
++ #[clippy::version = "1.30.0"]
++ pub NEEDLESS_COLLECT,
++ nursery,
++ "collecting an iterator when collect is not needed"
++}
++
+pub struct Methods {
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
+ allow_expect_in_tests: bool,
+ allow_unwrap_in_tests: bool,
+}
+
+impl Methods {
+ #[must_use]
+ pub fn new(
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
+ allow_expect_in_tests: bool,
+ allow_unwrap_in_tests: bool,
+ ) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ msrv,
+ allow_expect_in_tests,
+ allow_unwrap_in_tests,
+ }
+ }
+}
+
+impl_lint_pass!(Methods => [
+ UNWRAP_USED,
+ EXPECT_USED,
+ SHOULD_IMPLEMENT_TRAIT,
+ WRONG_SELF_CONVENTION,
+ OK_EXPECT,
+ UNWRAP_OR_ELSE_DEFAULT,
+ MAP_UNWRAP_OR,
+ RESULT_MAP_OR_INTO_OPTION,
+ OPTION_MAP_OR_NONE,
+ BIND_INSTEAD_OF_MAP,
+ OR_FUN_CALL,
+ OR_THEN_UNWRAP,
+ EXPECT_FUN_CALL,
+ CHARS_NEXT_CMP,
+ CHARS_LAST_CMP,
+ CLONE_ON_COPY,
+ CLONE_ON_REF_PTR,
+ CLONE_DOUBLE_REF,
+ COLLAPSIBLE_STR_REPLACE,
+ ITER_OVEREAGER_CLONED,
+ CLONED_INSTEAD_OF_COPIED,
+ FLAT_MAP_OPTION,
+ INEFFICIENT_TO_STRING,
+ NEW_RET_NO_SELF,
+ SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_ADD_STR,
+ SEARCH_IS_SOME,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP_IDENTITY,
+ MAP_IDENTITY,
+ MANUAL_FILTER_MAP,
+ MANUAL_FIND_MAP,
+ OPTION_FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_COUNT,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ BYTES_NTH,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ GET_LAST_WITH_LEN,
+ 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,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ MAP_COLLECT_RESULT_UNIT,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ INSPECT_FOR_EACH,
+ IMPLICIT_CLONE,
+ SUSPICIOUS_TO_OWNED,
+ SUSPICIOUS_SPLITN,
+ MANUAL_STR_REPEAT,
+ EXTEND_WITH_DRAIN,
+ MANUAL_SPLIT_ONCE,
+ NEEDLESS_SPLITN,
+ UNNECESSARY_TO_OWNED,
+ UNNECESSARY_JOIN,
+ ERR_EXPECT,
+ NEEDLESS_OPTION_AS_DEREF,
+ IS_DIGIT_ASCII_RADIX,
+ NEEDLESS_OPTION_TAKE,
+ NO_EFFECT_REPLACE,
+ OBFUSCATED_IF_ELSE,
+ ITER_ON_SINGLE_ITEMS,
+ ITER_ON_EMPTY_COLLECTIONS,
+ NAIVE_BYTECOUNT,
+ BYTES_COUNT_TO_LEN,
+ CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ GET_FIRST,
+ MANUAL_OK_OR,
+ MAP_CLONE,
+ MAP_ERR_IGNORE,
+ MUT_MUTEX_LOCK,
+ NONSENSICAL_OPEN_OPTIONS,
+ PATH_BUF_PUSH_OVERWRITE,
+ RANGE_ZIP_WITH_LEN,
+ REPEAT_ONCE,
+ STABLE_SORT_PRIMITIVE,
+ UNIT_HASH,
+ UNNECESSARY_SORT_BY,
+ VEC_RESIZE_TO_ZERO,
+ VERBOSE_FILE_READS,
+ ITER_KV_MAP,
++ SEEK_FROM_CURRENT,
++ SEEK_TO_START_INSTEAD_OF_REWIND,
++ NEEDLESS_COLLECT,
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(
+ recv: &'tcx hir::Expr<'tcx>,
- return Some((name, receiver, args, path.ident.span));
++) -> Option<(&'tcx str, &'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>], Span, Span)> {
++ if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) && !receiver.span.from_expansion() {
+ let name = path.ident.name.as_str();
- // walk the return type and check for Self (this does not check associated types)
- if let Some(self_adt) = self_ty.ty_adt_def() {
- if contains_adt_constructor(ret_ty, self_adt) {
- return;
- }
- } else if ret_ty.contains(self_ty) {
++ return Some((name, receiver, args, path.ident.span, call_span));
+ }
+ }
+ None
+}
+
+impl<'tcx> LateLintPass<'tcx> for Methods {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ self.check_methods(cx, expr);
+
+ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ from_iter_instead_of_collect::check(cx, expr, args, func);
+ },
+ hir::ExprKind::MethodCall(method_call, receiver, args, _) => {
+ let method_span = method_call.ident.span;
+ or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
+ expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
+ clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args);
+ clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args);
+ inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);
+ single_char_add_str::check(cx, expr, receiver, args);
+ into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
+ single_char_pattern::check(cx, expr, method_call.ident.name, receiver, args);
+ unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv);
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ },
+ _ => (),
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id;
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.owner_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind {
+ let method_sig = cx.tcx.fn_sig(impl_item.owner_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
+ let first_arg_ty_opt = method_sig.inputs().iter().next().copied();
+ // if this impl block implements a trait, lint in trait definition instead
+ if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) {
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name
+ && sig.decl.inputs.len() == method_config.param_count
+ && method_config.output_type.matches(&sig.decl.output)
+ // in case there is no first arg, since we already have checked the number of arguments
+ // it's should be always true
+ && first_arg_ty_opt.map_or(true, |first_arg_ty| method_config
+ .self_kind.matches(cx, self_ty, first_arg_ty)
+ )
+ && fn_header_equals(method_config.fn_header, sig.header)
+ && method_config.lifetime_param_cond(impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name, method_config.trait_name, method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ ),
+ );
+ }
+ }
+ }
+
+ if sig.decl.implicit_self.has_implicit_self()
+ && !(self.avoid_breaking_exported_api
+ && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id))
+ && let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next()
+ && let Some(first_arg_ty) = first_arg_ty_opt
+ {
+ wrong_self_convention::check(
+ cx,
+ name,
+ self_ty,
+ first_arg_ty,
+ first_arg.pat.span,
+ implements_trait,
+ false
+ );
+ }
+ }
+
+ // if this impl block implements a trait, lint in trait definition instead
+ if implements_trait {
+ return;
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
- // if return type is impl trait, check the associated types
- if let ty::Opaque(def_id, _) = *ret_ty.kind() {
- // one of the associated types must be Self
- for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
- if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
- let assoc_ty = match projection_predicate.term.unpack() {
- ty::TermKind::Ty(ty) => ty,
- ty::TermKind::Const(_c) => continue,
- };
- // walk the associated type and check for Self
- if let Some(self_adt) = self_ty.ty_adt_def() {
- if contains_adt_constructor(assoc_ty, self_adt) {
- return;
- }
- } else if assoc_ty.contains(self_ty) {
- return;
- }
- }
- }
- }
-
++ if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) {
+ return;
+ }
+
- if let Some((name, recv, args, span)) = method_call(expr) {
+ if name == "new" && ret_ty != self_ty {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+
+ if_chain! {
+ if let TraitItemKind::Fn(ref sig, _) = item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
+
+ then {
+ let first_arg_span = first_arg_ty.span;
+ let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
+ let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
+ .self_ty()
+ .skip_binder();
+ wrong_self_convention::check(
+ cx,
+ item.ident.name.as_str(),
+ self_ty,
+ first_arg_ty,
+ first_arg_span,
+ false,
+ true,
+ );
+ }
+ }
+
+ if_chain! {
+ if item.ident.name == sym::new;
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id());
+ let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
+ .self_ty()
+ .skip_binder();
+ if !ret_ty.contains(self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+impl Methods {
+ #[allow(clippy::too_many_lines)]
+ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- ("collect", []) => match method_call(recv) {
- Some((name @ ("cloned" | "copied"), recv2, [], _)) => {
- iter_cloned_collect::check(cx, name, expr, recv2);
- },
- Some(("map", m_recv, [m_arg], _)) => {
- map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
- },
- Some(("take", take_self_arg, [take_arg], _)) => {
- if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
- manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
- }
- },
- _ => {},
++ if let Some((name, recv, args, span, call_span)) = method_call(expr) {
+ match (name, args) {
+ ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
+ zst_offset::check(cx, expr, recv);
+ },
+ ("and_then", [arg]) => {
+ let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
+ let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
+ if !biom_option_linted && !biom_result_linted {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
+ }
+ },
+ ("as_deref" | "as_deref_mut", []) => {
+ needless_option_as_deref::check(cx, expr, recv, name);
+ },
+ ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
+ ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
+ ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
+ ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv),
- Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
- Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _)) => {
++ ("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
++ needless_collect::check(cx, span, expr, recv, call_span);
++ match method_call(recv) {
++ Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
++ iter_cloned_collect::check(cx, name, expr, recv2);
++ },
++ Some(("map", m_recv, [m_arg], _, _)) => {
++ map_collect_result_unit::check(cx, expr, m_recv, m_arg);
++ },
++ Some(("take", take_self_arg, [take_arg], _, _)) => {
++ if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
++ manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
++ }
++ },
++ _ => {},
++ }
+ },
+ ("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
- Some(("map", _, [arg], _)) => suspicious_map::check(cx, expr, recv, arg),
- Some(("filter", recv2, [arg], _)) => bytecount::check(cx, expr, recv2, arg),
- Some(("bytes", recv2, [], _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
++ Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
++ Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _, _)) => {
+ iter_count::check(cx, expr, recv2, name2);
+ },
- Some(("ok", recv, [], _)) => ok_expect::check(cx, expr, recv),
- Some(("err", recv, [], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
++ Some(("map", _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg),
++ Some(("filter", recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg),
++ Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
+ _ => {},
+ },
+ ("drain", [arg]) => {
+ iter_with_drain::check(cx, expr, recv, span, arg);
+ },
+ ("ends_with", [arg]) => {
+ if let ExprKind::MethodCall(.., span) = expr.kind {
+ case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
+ }
+ },
+ ("expect", [_]) => match method_call(recv) {
- Some(("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, recv, recv2, false, true),
++ Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
++ Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
+ _ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
+ },
+ ("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests),
+ ("extend", [arg]) => {
+ string_extend_chars::check(cx, expr, recv, arg);
+ extend_with_drain::check(cx, expr, recv, arg);
+ },
+ ("filter_map", [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);
+ },
+ ("flatten", []) => match method_call(recv) {
- if let Some(("inspect", _, [_], span2)) = method_call(recv) {
++ 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, recv, recv2, false, true),
+ _ => {},
+ },
+ ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
+ ("for_each", [_]) => {
- if let Some(("collect", _, _, span)) = method_call(recv) {
++ if let Some(("inspect", _, [_], span2, _)) = method_call(recv) {
+ inspect_for_each::check(cx, expr, span2);
+ }
+ },
+ ("get", [arg]) => {
+ get_first::check(cx, expr, recv, arg);
+ get_last_with_len::check(cx, expr, recv, arg);
+ },
+ ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
+ ("hash", [arg]) => {
+ unit_hash::check(cx, expr, recv, arg);
+ },
+ ("is_file", []) => filetype_is_file::check(cx, expr, recv),
+ ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
+ ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
+ ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+ ("iter" | "iter_mut" | "into_iter", []) => {
+ iter_on_single_or_empty_collections::check(cx, expr, name, recv);
+ },
+ ("join", [join_arg]) => {
- if let Some((name2, recv2, args2, _span2)) = method_call(recv) {
++ if let Some(("collect", _, _, span, _)) = method_call(recv) {
+ unnecessary_join::check(cx, expr, recv, join_arg, span);
+ }
+ },
+ ("last", []) | ("skip", [_]) => {
- if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _)) = method_call(recv) {
++ if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
+ }
+ }
+ },
+ ("lock", []) => {
+ mut_mutex_lock::check(cx, expr, recv, span);
+ },
+ (name @ ("map" | "map_err"), [m_arg]) => {
+ if name == "map" {
+ map_clone::check(cx, expr, recv, m_arg, self.msrv);
- if let Some((name, recv2, args, span2)) = method_call(recv) {
++ if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) = method_call(recv) {
+ iter_kv_map::check(cx, map_name, expr, recv2, m_arg);
+ }
+ } else {
+ map_err_ignore::check(cx, expr, m_arg);
+ }
- if let Some((name2, recv2, args2, _)) = method_call(recv) {
++ if let Some((name, recv2, args, span2,_)) = method_call(recv) {
+ match (name, args) {
+ ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
+ ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
+ ("filter", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
+ },
+ ("find", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true);
+ },
+ _ => {},
+ }
+ }
+ map_identity::check(cx, expr, recv, m_arg, name, span);
+ },
+ ("map_or", [def, map]) => {
+ option_map_or_none::check(cx, expr, recv, def, map);
+ manual_ok_or::check(cx, expr, recv, def, map);
+ },
+ ("next", []) => {
- Some(("bytes", recv2, [], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
- Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
- Some(("iter", recv2, [], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
- Some(("iter_mut", recv2, [], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
++ if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
+ match (name2, args2) {
+ ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
+ ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
+ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
+ ("iter", []) => iter_next_slice::check(cx, expr, recv2),
+ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
+ ("skip_while", [_]) => skip_while_next::check(cx, expr),
+ _ => {},
+ }
+ }
+ },
+ ("nth", [n_arg]) => match method_call(recv) {
- if let Some((name2, recv2, args2, _span2)) = method_call(recv) {
++ Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
++ Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
++ Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
++ Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
+ _ => iter_nth_zero::check(cx, expr, recv, n_arg),
+ },
+ ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
+ ("open", [_]) => {
+ open_options::check(cx, expr, recv);
+ },
+ ("or_else", [arg]) => {
+ if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
+ }
+ },
+ ("push", [arg]) => {
+ path_buf_push_overwrite::check(cx, expr, arg);
+ },
+ ("read_to_end", [_]) => {
+ verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG);
+ },
+ ("read_to_string", [_]) => {
+ verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG);
+ },
+ ("repeat", [arg]) => {
+ repeat_once::check(cx, expr, recv, arg);
+ },
+ (name @ ("replace" | "replacen"), [arg1, arg2] | [arg1, arg2, _]) => {
+ no_effect_replace::check(cx, expr, arg1, arg2);
+
+ // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint
+ if name == "replace" && let Some(("replace", ..)) = method_call(recv) {
+ collapsible_str_replace::check(cx, expr, arg1, arg2);
+ }
+ },
+ ("resize", [count_arg, default_arg]) => {
+ vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
+ },
++ ("seek", [arg]) => {
++ if meets_msrv(self.msrv, msrvs::SEEK_FROM_CURRENT) {
++ seek_from_current::check(cx, expr, recv, arg);
++ }
++ if meets_msrv(self.msrv, msrvs::SEEK_REWIND) {
++ seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
++ }
++ },
+ ("sort", []) => {
+ stable_sort_primitive::check(cx, expr, recv);
+ },
+ ("sort_by", [arg]) => {
+ unnecessary_sort_by::check(cx, expr, recv, arg, false);
+ },
+ ("sort_unstable_by", [arg]) => {
+ unnecessary_sort_by::check(cx, expr, recv, arg, true);
+ },
+ ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
+ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+ suspicious_splitn::check(cx, name, expr, recv, count);
+ str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
+ }
+ },
+ ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
+ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+ suspicious_splitn::check(cx, name, expr, recv, count);
+ }
+ },
+ ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
+ ("take", [_arg]) => {
- Some(("get", recv, [get_arg], _)) => {
++ if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
+ }
+ }
+ },
+ ("take", []) => needless_option_take::check(cx, expr, recv),
+ ("then", [arg]) => {
+ if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
+ return;
+ }
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
+ },
+ ("to_owned", []) => {
+ if !suspicious_to_owned::check(cx, expr, recv) {
+ implicit_clone::check(cx, name, expr, recv);
+ }
+ },
+ ("to_os_string" | "to_path_buf" | "to_vec", []) => {
+ implicit_clone::check(cx, name, expr, recv);
+ },
+ ("unwrap", []) => {
+ match method_call(recv) {
- Some(("get_mut", recv, [get_arg], _)) => {
++ Some(("get", recv, [get_arg], _, _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, false);
+ },
- Some(("or", recv, [or_arg], or_span)) => {
++ Some(("get_mut", recv, [get_arg], _, _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, true);
+ },
- Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _)) => {
++ Some(("or", recv, [or_arg], or_span, _)) => {
+ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+ },
+ _ => {},
+ }
+ unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests);
+ },
+ ("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests),
+ ("unwrap_or", [u_arg]) => match method_call(recv) {
- Some(("map", m_recv, [m_arg], span)) => {
++ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
+ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
- Some(("then_some", t_recv, [t_arg], _)) => {
++ Some(("map", m_recv, [m_arg], span, _)) => {
+ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
- Some(("map", recv, [map_arg], _))
++ Some(("then_some", t_recv, [t_arg], _, _)) => {
+ obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
+ },
+ _ => {},
+ },
+ ("unwrap_or_else", [u_arg]) => match method_call(recv) {
- if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span)) = method_call(recv) {
++ Some(("map", recv, [map_arg], _, _))
+ if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
+ _ => {
+ unwrap_or_else_default::check(cx, expr, recv, u_arg);
+ unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
+ },
+ },
+ ("zip", [arg]) => {
+ if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind
+ && name.ident.name == sym::iter
+ {
+ range_zip_with_len::check(cx, expr, iter_recv, arg);
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+}
+
+fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
- fn is_bool(ty: &hir::Ty<'_>) -> bool {
- if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
- matches!(path.res, Res::PrimTy(PrimTy::Bool))
- } else {
- false
- }
- }
-
++ if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) = method_call(recv) {
+ search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
+ }
+}
+
+/// Used for `lint_binary_expr_with_method_call`.
+#[derive(Copy, Clone)]
+struct BinaryExprInfo<'a> {
+ expr: &'a hir::Expr<'a>,
+ chain: &'a hir::Expr<'a>,
+ other: &'a hir::Expr<'a>,
+ eq: bool,
+}
+
+/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) {
+ macro_rules! lint_with_both_lhs_and_rhs {
+ ($func:expr, $cx:expr, $info:ident) => {
+ if !$func($cx, $info) {
+ ::std::mem::swap(&mut $info.chain, &mut $info.other);
+ if $func($cx, $info) {
+ return;
+ }
+ }
+ };
+ }
+
+ lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
+}
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+];
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No, // When we want the first argument type to be different than `Self`
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = *ty.kind() {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_sym = match mutability {
+ hir::Mutability::Not => sym::AsRef,
+ hir::Mutability::Mut => sym::AsMut,
+ };
+
+ let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else {
+ return false
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ !matches_value(cx, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty)
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => matches_none(cx, parent_ty, ty),
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "`self` by value",
+ Self::Ref => "`self` by reference",
+ Self::RefMut => "`self` by mutable reference",
+ Self::No => "no `self`",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
--- /dev/null
--- /dev/null
++use super::NEEDLESS_COLLECT;
++use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
++use clippy_utils::higher;
++use clippy_utils::source::{snippet, snippet_with_applicability};
++use clippy_utils::sugg::Sugg;
++use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
++use clippy_utils::{
++ can_move_expr_to_closure, get_enclosing_block, get_parent_node, is_trait_method, path_to_local, path_to_local_id,
++ CaptureKind,
++};
++use rustc_data_structures::fx::FxHashMap;
++use rustc_errors::{Applicability, MultiSpan};
++use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
++use rustc_hir::{
++ BindingAnnotation, Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind,
++};
++use rustc_lint::LateContext;
++use rustc_middle::hir::nested_filter;
++use rustc_middle::ty::{self, AssocKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
++use rustc_span::symbol::Ident;
++use rustc_span::{sym, Span, Symbol};
++
++const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
++
++pub(super) fn check<'tcx>(
++ cx: &LateContext<'tcx>,
++ name_span: Span,
++ collect_expr: &'tcx Expr<'_>,
++ iter_expr: &'tcx Expr<'tcx>,
++ call_span: Span,
++) {
++ if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
++ match parent {
++ Node::Expr(parent) => {
++ if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
++ let mut app = Applicability::MachineApplicable;
++ let name = name.ident.as_str();
++ let collect_ty = cx.typeck_results().expr_ty(collect_expr);
++
++ let sugg: String = match name {
++ "len" => {
++ if let Some(adt) = collect_ty.ty_adt_def()
++ && matches!(
++ cx.tcx.get_diagnostic_name(adt.did()),
++ Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
++ )
++ {
++ "count()".into()
++ } else {
++ return;
++ }
++ },
++ "is_empty"
++ if is_is_empty_sig(cx, parent.hir_id)
++ && iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
++ {
++ "next().is_none()".into()
++ },
++ "contains" => {
++ if is_contains_sig(cx, parent.hir_id, iter_expr)
++ && let Some(arg) = args.first()
++ {
++ let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
++ (arg.span, "")
++ } else {
++ (arg.span, "*")
++ };
++ let snip = snippet_with_applicability(cx, span, "??", &mut app);
++ format!("any(|x| x == {prefix}{snip})")
++ } else {
++ return;
++ }
++ },
++ _ => return,
++ };
++
++ span_lint_and_sugg(
++ cx,
++ NEEDLESS_COLLECT,
++ call_span.with_hi(parent.span.hi()),
++ NEEDLESS_COLLECT_MSG,
++ "replace with",
++ sugg,
++ app,
++ );
++ }
++ },
++ Node::Local(l) => {
++ if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None)
++ = l.pat.kind
++ && let ty = cx.typeck_results().expr_ty(collect_expr)
++ && [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList].into_iter()
++ .any(|item| is_type_diagnostic_item(cx, ty, item))
++ && let iter_ty = cx.typeck_results().expr_ty(iter_expr)
++ && let Some(block) = get_enclosing_block(cx, l.hir_id)
++ && let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
++ && let [iter_call] = &*iter_calls
++ {
++ let mut used_count_visitor = UsedCountVisitor {
++ cx,
++ id,
++ count: 0,
++ };
++ walk_block(&mut used_count_visitor, block);
++ if used_count_visitor.count > 1 {
++ return;
++ }
++
++ // Suggest replacing iter_call with iter_replacement, and removing stmt
++ let mut span = MultiSpan::from_span(name_span);
++ span.push_span_label(iter_call.span, "the iterator could be used here instead");
++ span_lint_hir_and_then(
++ cx,
++ super::NEEDLESS_COLLECT,
++ collect_expr.hir_id,
++ span,
++ NEEDLESS_COLLECT_MSG,
++ |diag| {
++ let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
++ diag.multipart_suggestion(
++ iter_call.get_suggestion_text(),
++ vec![
++ (l.span, String::new()),
++ (iter_call.span, iter_replacement)
++ ],
++ Applicability::MaybeIncorrect,
++ );
++ },
++ );
++ }
++ },
++ _ => (),
++ }
++ }
++}
++
++/// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
++fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
++ cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| {
++ let sig = cx.tcx.fn_sig(id).skip_binder();
++ sig.inputs().len() == 1 && sig.output().is_bool()
++ })
++}
++
++/// Checks if `<iter_ty as Iterator>::Item` is the same as `<collect_ty as IntoIter>::Item`
++fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: Ty<'tcx>) -> bool {
++ let item = Symbol::intern("Item");
++ if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
++ && let Some(into_iter_trait) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
++ && let Some(iter_item_ty) = make_normalized_projection(cx.tcx, cx.param_env, iter_trait, item, [iter_ty])
++ && let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, item, [collect_ty])
++ && let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions(
++ cx.param_env,
++ cx.tcx.mk_projection(into_iter_item_proj.item_def_id, into_iter_item_proj.substs)
++ )
++ {
++ iter_item_ty == into_iter_item_ty
++ } else {
++ false
++ }
++}
++
++/// Checks if the given method call matches the expected signature of
++/// `([&[mut]] self, &<iter_ty as Iterator>::Item) -> bool`
++fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool {
++ let typeck = cx.typeck_results();
++ if let Some(id) = typeck.type_dependent_def_id(call_id)
++ && let sig = cx.tcx.fn_sig(id)
++ && sig.skip_binder().output().is_bool()
++ && let [_, search_ty] = *sig.skip_binder().inputs()
++ && let ty::Ref(_, search_ty, Mutability::Not) = *cx.tcx.erase_late_bound_regions(sig.rebind(search_ty)).kind()
++ && let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
++ && let Some(iter_item) = cx.tcx
++ .associated_items(iter_trait)
++ .find_by_name_and_kind(cx.tcx, Ident::with_dummy_span(Symbol::intern("Item")), AssocKind::Type, iter_trait)
++ && let substs = cx.tcx.mk_substs([GenericArg::from(typeck.expr_ty_adjusted(iter_expr))].into_iter())
++ && let proj_ty = cx.tcx.mk_projection(iter_item.def_id, substs)
++ && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, proj_ty)
++ {
++ item_ty == EarlyBinder(search_ty).subst(cx.tcx, cx.typeck_results().node_substs(call_id))
++ } else {
++ false
++ }
++}
++
++struct IterFunction {
++ func: IterFunctionKind,
++ span: Span,
++}
++impl IterFunction {
++ fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
++ match &self.func {
++ IterFunctionKind::IntoIter => String::new(),
++ IterFunctionKind::Len => String::from(".count()"),
++ IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
++ IterFunctionKind::Contains(span) => {
++ let s = snippet(cx, *span, "..");
++ if let Some(stripped) = s.strip_prefix('&') {
++ format!(".any(|x| x == {stripped})")
++ } else {
++ format!(".any(|x| x == *{s})")
++ }
++ },
++ }
++ }
++ fn get_suggestion_text(&self) -> &'static str {
++ match &self.func {
++ IterFunctionKind::IntoIter => {
++ "use the original Iterator instead of collecting it and then producing a new one"
++ },
++ IterFunctionKind::Len => {
++ "take the original Iterator's count instead of collecting it and finding the length"
++ },
++ IterFunctionKind::IsEmpty => {
++ "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
++ },
++ IterFunctionKind::Contains(_) => {
++ "check if the original Iterator contains an element instead of collecting then checking"
++ },
++ }
++ }
++}
++enum IterFunctionKind {
++ IntoIter,
++ Len,
++ IsEmpty,
++ Contains(Span),
++}
++
++struct IterFunctionVisitor<'a, 'tcx> {
++ illegal_mutable_capture_ids: HirIdSet,
++ current_mutably_captured_ids: HirIdSet,
++ cx: &'a LateContext<'tcx>,
++ uses: Vec<Option<IterFunction>>,
++ hir_id_uses_map: FxHashMap<HirId, usize>,
++ current_statement_hir_id: Option<HirId>,
++ seen_other: bool,
++ target: HirId,
++}
++impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
++ fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
++ for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
++ if check_loop_kind(expr).is_some() {
++ continue;
++ }
++ self.visit_block_expr(expr, hir_id);
++ }
++ if let Some(expr) = block.expr {
++ if let Some(loop_kind) = check_loop_kind(expr) {
++ if let LoopKind::Conditional(block_expr) = loop_kind {
++ self.visit_block_expr(block_expr, None);
++ }
++ } else {
++ self.visit_block_expr(expr, None);
++ }
++ }
++ }
++
++ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
++ // Check function calls on our collection
++ if let ExprKind::MethodCall(method_name, recv, [args @ ..], _) = &expr.kind {
++ if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
++ self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
++ self.visit_expr(recv);
++ return;
++ }
++
++ if path_to_local_id(recv, self.target) {
++ if self
++ .illegal_mutable_capture_ids
++ .intersection(&self.current_mutably_captured_ids)
++ .next()
++ .is_none()
++ {
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.insert(hir_id, self.uses.len());
++ }
++ match method_name.ident.name.as_str() {
++ "into_iter" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::IntoIter,
++ span: expr.span,
++ })),
++ "len" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::Len,
++ span: expr.span,
++ })),
++ "is_empty" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::IsEmpty,
++ span: expr.span,
++ })),
++ "contains" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::Contains(args[0].span),
++ span: expr.span,
++ })),
++ _ => {
++ self.seen_other = true;
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.remove(&hir_id);
++ }
++ },
++ }
++ }
++ return;
++ }
++
++ if let Some(hir_id) = path_to_local(recv) {
++ if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
++ if self
++ .illegal_mutable_capture_ids
++ .intersection(&self.current_mutably_captured_ids)
++ .next()
++ .is_none()
++ {
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.insert(hir_id, index);
++ }
++ } else {
++ self.uses[index] = None;
++ }
++ }
++ }
++ }
++ // Check if the collection is used for anything else
++ if path_to_local_id(expr, self.target) {
++ self.seen_other = true;
++ } else {
++ walk_expr(self, expr);
++ }
++ }
++}
++
++enum LoopKind<'tcx> {
++ Conditional(&'tcx Expr<'tcx>),
++ Loop,
++}
++
++fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option<LoopKind<'tcx>> {
++ if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) {
++ return Some(LoopKind::Conditional(let_expr));
++ }
++ if let Some(higher::While { condition, .. }) = higher::While::hir(expr) {
++ return Some(LoopKind::Conditional(condition));
++ }
++ if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) {
++ return Some(LoopKind::Conditional(arg));
++ }
++ if let ExprKind::Loop { .. } = expr.kind {
++ return Some(LoopKind::Loop);
++ }
++
++ None
++}
++
++impl<'tcx> IterFunctionVisitor<'_, 'tcx> {
++ fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) {
++ self.current_statement_hir_id = hir_id;
++ self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
++ self.visit_expr(expr);
++ }
++}
++
++fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
++ match stmt.kind {
++ StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
++ StmtKind::Item(..) => None,
++ StmtKind::Local(Local { init, pat, .. }) => {
++ if let PatKind::Binding(_, hir_id, ..) = pat.kind {
++ init.map(|init_expr| (init_expr, Some(hir_id)))
++ } else {
++ init.map(|init_expr| (init_expr, None))
++ }
++ },
++ }
++}
++
++struct UsedCountVisitor<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++ id: HirId,
++ count: usize,
++}
++
++impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
++ type NestedFilter = nested_filter::OnlyBodies;
++
++ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
++ if path_to_local_id(expr, self.id) {
++ self.count += 1;
++ } else {
++ walk_expr(self, expr);
++ }
++ }
++
++ fn nested_visit_map(&mut self) -> Self::Map {
++ self.cx.tcx.hir()
++ }
++}
++
++/// Detect the occurrences of calls to `iter` or `into_iter` for the
++/// given identifier
++fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
++ block: &'tcx Block<'tcx>,
++ id: HirId,
++ cx: &'a LateContext<'tcx>,
++ captured_ids: HirIdSet,
++) -> Option<Vec<IterFunction>> {
++ let mut visitor = IterFunctionVisitor {
++ uses: Vec::new(),
++ target: id,
++ seen_other: false,
++ cx,
++ current_mutably_captured_ids: HirIdSet::default(),
++ illegal_mutable_capture_ids: captured_ids,
++ hir_id_uses_map: FxHashMap::default(),
++ current_statement_hir_id: None,
++ };
++ visitor.visit_block(block);
++ if visitor.seen_other {
++ None
++ } else {
++ Some(visitor.uses.into_iter().flatten().collect())
++ }
++}
++
++fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
++ fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) {
++ match ty.kind() {
++ ty::Adt(_, generics) => {
++ for generic in *generics {
++ if let GenericArgKind::Type(ty) = generic.unpack() {
++ get_captured_ids_recursive(cx, ty, set);
++ }
++ }
++ },
++ ty::Closure(def_id, _) => {
++ let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap();
++ if let Node::Expr(closure_expr) = closure_hir_node {
++ can_move_expr_to_closure(cx, closure_expr)
++ .unwrap()
++ .into_iter()
++ .for_each(|(hir_id, capture_kind)| {
++ if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) {
++ set.insert(hir_id);
++ }
++ });
++ }
++ },
++ _ => (),
++ }
++ }
++
++ let mut set = HirIdSet::default();
++
++ get_captured_ids_recursive(cx, ty, &mut set);
++
++ set
++}
--- /dev/null
- pub(super) fn check<'tcx>(
- cx: &LateContext<'tcx>,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::OPTION_AS_REF_DEREF;
+
+/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
++pub(super) fn check(
++ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ as_ref_recv: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ is_mut: bool,
+ msrv: Option<RustcVersion>,
+) {
+ if !meets_msrv(msrv, msrvs::OPTION_AS_DEREF) {
+ return;
+ }
+
+ let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
+
+ let option_ty = cx.typeck_results().expr_ty(as_ref_recv);
+ if !is_type_diagnostic_item(cx, option_ty, sym::Option) {
+ return;
+ }
+
+ let deref_aliases: [&[&str]; 8] = [
+ &paths::DEREF_MUT_TRAIT_METHOD,
+ &paths::CSTRING_AS_C_STR,
+ &paths::OS_STRING_AS_OS_STR,
+ &paths::PATH_BUF_AS_PATH,
+ &paths::STRING_AS_STR,
+ &paths::STRING_AS_MUT_STR,
+ &paths::VEC_AS_SLICE,
+ &paths::VEC_AS_MUT_SLICE,
+ ];
+
+ let is_deref = match map_arg.kind {
+ hir::ExprKind::Path(ref expr_qpath) => {
+ cx.qpath_res(expr_qpath, map_arg.hir_id)
+ .opt_def_id()
+ .map_or(false, |fun_def_id| {
+ cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id)
+ || deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
+ })
+ },
+ hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(closure_body.value);
+
+ match &closure_expr.kind {
+ hir::ExprKind::MethodCall(_, receiver, [], _) => {
+ if_chain! {
+ if path_to_local_id(receiver, closure_body.params[0].pat.hir_id);
+ let adj = cx
+ .typeck_results()
+ .expr_adjustments(receiver)
+ .iter()
+ .map(|x| &x.kind)
+ .collect::<Box<[_]>>();
+ if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
+ then {
+ let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
+ cx.tcx.is_diagnostic_item(sym::deref_method, method_did)
+ || deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
+ } else {
+ false
+ }
+ }
+ },
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, inner) if same_mutability(m) => {
+ if_chain! {
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind;
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind;
+ then {
+ path_to_local_id(inner2, closure_body.params[0].pat.hir_id)
+ } else {
+ false
+ }
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ };
+
+ if is_deref {
+ let current_method = if is_mut {
+ format!(".as_mut().map({})", snippet(cx, map_arg.span, ".."))
+ } else {
+ format!(".as_ref().map({})", snippet(cx, map_arg.span, ".."))
+ };
+ let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
+ let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, ".."));
+ let suggestion = format!("try using {method_hint} instead");
+
+ let msg = format!(
+ "called `{current_method}` on an Option value. This can be done more directly \
+ by calling `{hint}` instead"
+ );
+ span_lint_and_sugg(
+ cx,
+ OPTION_AS_REF_DEREF,
+ expr.span,
+ &msg,
+ &suggestion,
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+}
--- /dev/null
- let macro_expanded_snipped;
- let sugg: Cow<'_, str> = {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{contains_return, is_trait_item, last_path_segment};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{kw, sym, Symbol};
+use std::borrow::Cow;
+
+use super::OR_FUN_CALL;
+
+/// Checks for the `OR_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ receiver: &'tcx hir::Expr<'_>,
+ args: &'tcx [hir::Expr<'_>],
+) {
+ /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
+ /// `or_insert(T::new())` or `or_insert(T::default())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_unwrap_or_default(
+ cx: &LateContext<'_>,
+ name: &str,
+ fun: &hir::Expr<'_>,
+ arg: &hir::Expr<'_>,
+ or_has_args: bool,
+ span: Span,
+ method_span: Span,
+ ) -> bool {
+ let is_default_default = || is_trait_item(cx, fun, sym::Default);
+
+ let implements_default = |arg, default_trait_id| {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ implements_trait(cx, arg_ty, default_trait_id, &[])
+ };
+
+ if_chain! {
+ if !or_has_args;
+ if let Some(sugg) = match name {
+ "unwrap_or" => Some("unwrap_or_default"),
+ "or_insert" => Some("or_default"),
+ _ => None,
+ };
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ let path = last_path_segment(qpath).ident.name;
+ // needs to target Default::default in particular or be *::new and have a Default impl
+ // available
+ if (matches!(path, kw::Default) && is_default_default())
+ || (matches!(path, sym::new) && implements_default(arg, default_trait_id));
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ method_span.with_hi(span.hi()),
+ &format!("use of `{name}` followed by a call to `{path}`"),
+ "try this",
+ format!("{sugg}()"),
+ Applicability::MachineApplicable,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ /// Checks for `*or(foo())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_general_case<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: &str,
+ method_span: Span,
+ self_expr: &hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
++ // `Some` if fn has second argument
++ second_arg: Option<&hir::Expr<'_>>,
+ span: Span,
+ // None if lambda is required
+ fun_span: Option<Span>,
+ ) {
+ // (path, fn_has_argument, methods, suffix)
+ const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
+ (sym::BTreeEntry, false, &["or_insert"], "with"),
+ (sym::HashMapEntry, false, &["or_insert"], "with"),
+ (sym::Option, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
+ (sym::Result, true, &["or", "unwrap_or"], "else"),
+ ];
+
+ if_chain! {
+ if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
+
+ if switch_to_lazy_eval(cx, arg);
+ if !contains_return(arg);
+
+ let self_ty = cx.typeck_results().expr_ty(self_expr);
+
+ if let Some(&(_, fn_has_arguments, poss, suffix)) =
+ KNOW_TYPES.iter().find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0));
+
+ if poss.contains(&name);
+
+ then {
- let snippet = {
- let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
- if not_macro_argument_snippet == "vec![]" {
- macro_expanded_snipped = snippet(cx, snippet_span, "..");
++ let sugg = {
+ let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
+ (false, Some(fun_span)) => (fun_span, false),
+ _ => (arg.span, true),
+ };
- Some(stripped) => Cow::from(stripped),
++
++ let format_span = |span: Span| {
++ let not_macro_argument_snippet = snippet_with_macro_callsite(cx, span, "..");
++ let snip = if not_macro_argument_snippet == "vec![]" {
++ let macro_expanded_snipped = snippet(cx, snippet_span, "..");
+ match macro_expanded_snipped.strip_prefix("$crate::vec::") {
- }
++ Some(stripped) => Cow::Owned(stripped.to_owned()),
+ None => macro_expanded_snipped,
+ }
+ } else {
+ not_macro_argument_snippet
- if use_lambda {
++ };
++
++ snip.to_string()
+ };
+
- format!("|{l_arg}| {snippet}").into()
++ let snip = format_span(snippet_span);
++ let snip = if use_lambda {
+ let l_arg = if fn_has_arguments { "_" } else { "" };
- snippet
++ format!("|{l_arg}| {snip}")
+ } else {
- if let [arg] = args {
- let inner_arg = if let hir::ExprKind::Block(
++ snip
++ };
++
++ if let Some(f) = second_arg {
++ let f = format_span(f.span);
++ format!("{snip}, {f}")
++ } else {
++ snip
+ }
+ };
+ let span_replace_word = method_span.with_hi(span.hi());
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{name}` followed by a function call"),
+ "try this",
+ format!("{name}_{suffix}({sugg})"),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+
- };
++ let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| {
++ if let hir::ExprKind::Block(
+ hir::Block {
+ stmts: [],
+ expr: Some(expr),
+ ..
+ },
+ _,
+ ) = arg.kind
+ {
+ expr
+ } else {
+ arg
- check_general_case(cx, name, method_span, receiver, arg, expr.span, fun_span);
++ }
++ };
++
++ if let [arg] = args {
++ let inner_arg = extract_inner_arg(arg);
+ match inner_arg.kind {
+ hir::ExprKind::Call(fun, or_args) => {
+ let or_has_args = !or_args.is_empty();
+ if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
+ let fun_span = if or_has_args { None } else { Some(fun.span) };
- check_general_case(cx, name, method_span, receiver, arg, expr.span, None);
++ check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
+ }
+ },
+ hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
++ check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
+ },
+ _ => (),
+ }
+ }
++
++ // `map_or` takes two arguments
++ if let [arg, lambda] = args {
++ let inner_arg = extract_inner_arg(arg);
++ if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind {
++ let fun_span = if or_args.is_empty() { Some(fun.span) } else { None };
++ check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span);
++ }
++ }
+}
--- /dev/null
--- /dev/null
++use rustc_ast::ast::{LitIntType, LitKind};
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind};
++use rustc_lint::LateContext;
++
++use clippy_utils::{
++ diagnostics::span_lint_and_sugg, get_trait_def_id, match_def_path, paths, source::snippet_with_applicability,
++ ty::implements_trait,
++};
++
++use super::SEEK_FROM_CURRENT;
++
++pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) {
++ let ty = cx.typeck_results().expr_ty(recv);
++
++ if let Some(def_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) {
++ if implements_trait(cx, ty, def_id, &[]) && arg_is_seek_from_current(cx, arg) {
++ let mut applicability = Applicability::MachineApplicable;
++ let snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability);
++
++ span_lint_and_sugg(
++ cx,
++ SEEK_FROM_CURRENT,
++ expr.span,
++ "using `SeekFrom::Current` to start from current position",
++ "replace with",
++ format!("{snip}.stream_position()"),
++ applicability,
++ );
++ }
++ }
++}
++
++fn arg_is_seek_from_current<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
++ if let ExprKind::Call(f, args) = expr.kind &&
++ let ExprKind::Path(ref path) = f.kind &&
++ let Some(def_id) = cx.qpath_res(path, f.hir_id).opt_def_id() &&
++ match_def_path(cx, def_id, &paths::STD_IO_SEEK_FROM_CURRENT) {
++ // check if argument of `SeekFrom::Current` is `0`
++ if args.len() == 1 &&
++ let ExprKind::Lit(ref lit) = args[0].kind &&
++ let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node {
++ return true
++ }
++ }
++
++ false
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::ty::implements_trait;
++use clippy_utils::{get_trait_def_id, match_def_path, paths};
++use rustc_ast::ast::{LitIntType, LitKind};
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind};
++use rustc_lint::LateContext;
++use rustc_span::Span;
++
++use super::SEEK_TO_START_INSTEAD_OF_REWIND;
++
++pub(super) fn check<'tcx>(
++ cx: &LateContext<'tcx>,
++ expr: &'tcx Expr<'_>,
++ recv: &'tcx Expr<'_>,
++ arg: &'tcx Expr<'_>,
++ name_span: Span,
++) {
++ // Get receiver type
++ let ty = cx.typeck_results().expr_ty(recv).peel_refs();
++
++ if let Some(seek_trait_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) &&
++ implements_trait(cx, ty, seek_trait_id, &[]) &&
++ let ExprKind::Call(func, args1) = arg.kind &&
++ let ExprKind::Path(ref path) = func.kind &&
++ let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() &&
++ match_def_path(cx, def_id, &paths::STD_IO_SEEKFROM_START) &&
++ args1.len() == 1 &&
++ let ExprKind::Lit(ref lit) = args1[0].kind &&
++ let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node
++ {
++ let method_call_span = expr.span.with_lo(name_span.lo());
++ span_lint_and_then(
++ cx,
++ SEEK_TO_START_INSTEAD_OF_REWIND,
++ method_call_span,
++ "used `seek` to go to the start of the stream",
++ |diag| {
++ let app = Applicability::MachineApplicable;
++
++ diag.span_suggestion(method_call_span, "replace with", "rewind()", app);
++ },
++ );
++ }
++}
--- /dev/null
- ""
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_lang_item;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+
+use super::STRING_EXTEND_CHARS;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+ if !is_type_lang_item(cx, obj_ty, hir::LangItem::String) {
+ return;
+ }
+ if let Some(arglists) = method_chain_args(arg, &["chars"]) {
+ let target = &arglists[0].0;
+ let self_ty = cx.typeck_results().expr_ty(target).peel_refs();
+ let ref_str = if *self_ty.kind() == ty::Str {
++ if matches!(target.kind, hir::ExprKind::Index(..)) {
++ "&"
++ } else {
++ ""
++ }
+ } else if is_type_lang_item(cx, self_ty, hir::LangItem::String) {
+ "&"
+ } else {
+ return;
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ STRING_EXTEND_CHARS,
+ expr.span,
+ "calling `.extend(_.chars())`",
+ "try this",
+ format!(
+ "{}.push_str({ref_str}{})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) {
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{expr_or_init, is_trait_method};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::SUSPICIOUS_MAP;
+
++pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) {
+ if_chain! {
+ if is_trait_method(cx, count_recv, sym::Iterator);
+ let closure = expr_or_init(cx, map_arg);
+ if let Some(def_id) = cx.tcx.hir().opt_local_def_id(closure.hir_id);
+ if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(def_id);
+ let closure_body = cx.tcx.hir().body(body_id);
+ if !cx.typeck_results().expr_ty(closure_body.value).is_unit();
+ then {
+ if let Some(map_mutated_vars) = mutated_variables(closure_body.value, cx) {
+ // A variable is used mutably inside of the closure. Suppress the lint.
+ if !map_mutated_vars.is_empty() {
+ return;
+ }
+ }
+ span_lint_and_help(
+ cx,
+ SUSPICIOUS_MAP,
+ expr.span,
+ "this call to `map()` won't have an effect on the call to `count()`",
+ None,
+ "make sure you did not confuse `map` with `filter`, `for_each` or `inspect`",
+ );
+ }
+ }
+}
--- /dev/null
- r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_lang_item};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{Ref, Slice};
+use rustc_span::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_lang_item(cx, *slice, LangItem::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,
+ );
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{is_in_test_function, is_lint_allowed};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_type_diagnostic_item;
- Some((UNWRAP_USED, "an Option", "None", ""))
++use clippy_utils::{is_in_cfg_test, is_lint_allowed};
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::{EXPECT_USED, UNWRAP_USED};
+
+/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`.
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ is_err: bool,
+ allow_unwrap_in_tests: bool,
+) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
- Some((UNWRAP_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an "))
++ Some((UNWRAP_USED, "an `Option`", "None", ""))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
- if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
++ Some((UNWRAP_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
+ } else {
+ None
+ };
+
+ let method_suffix = if is_err { "_err" } else { "" };
+
- &format!("used `unwrap{method_suffix}()` on `{kind}` value"),
++ if allow_unwrap_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) {
+ return;
+ }
+
+ if let Some((lint, kind, none_value, none_prefix)) = mess {
+ let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) {
+ format!(
+ "if you don't want to handle the `{none_value}` case gracefully, consider \
+ using `expect{method_suffix}()` to provide a better panic message"
+ )
+ } else {
+ format!("if this value is {none_prefix}`{none_value}`, it will panic")
+ };
+
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
++ &format!("used `unwrap{method_suffix}()` on {kind} value"),
+ None,
+ &help,
+ );
+ }
+}
--- /dev/null
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, None) {
+use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_opt};
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{def::Res, def_id::DefId, Item, ItemKind, UseKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Symbol;
+
+use crate::utils::conf::Rename;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports that do not rename the item as specified
+ /// in the `enforce-import-renames` config option.
+ ///
+ /// ### Why is this bad?
+ /// Consistency is important, if a project has defined import
+ /// renames they should be followed. More practically, some item names are too
+ /// vague outside of their defining scope this can enforce a more meaningful naming.
+ ///
+ /// ### Example
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// enforced-import-renames = [ { path = "serde_json::Value", rename = "JsonValue" }]
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// use serde_json::Value;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// use serde_json::Value as JsonValue;
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub MISSING_ENFORCED_IMPORT_RENAMES,
+ restriction,
+ "enforce import renames"
+}
+
+pub struct ImportRename {
+ conf_renames: Vec<Rename>,
+ renames: FxHashMap<DefId, Symbol>,
+}
+
+impl ImportRename {
+ pub fn new(conf_renames: Vec<Rename>) -> Self {
+ Self {
+ conf_renames,
+ renames: FxHashMap::default(),
+ }
+ }
+}
+
+impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
+
+impl LateLintPass<'_> for ImportRename {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for Rename { path, rename } in &self.conf_renames {
+ let segs = path.split("::").collect::<Vec<_>>();
++ for id in clippy_utils::def_path_def_ids(cx, &segs) {
+ self.renames.insert(id, Symbol::intern(rename));
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if_chain! {
+ if let ItemKind::Use(path, UseKind::Single) = &item.kind;
+ if let Res::Def(_, id) = path.res;
+ if let Some(name) = self.renames.get(&id);
+ // Remove semicolon since it is not present for nested imports
+ let span_without_semi = cx.sess().source_map().span_until_char(item.span, ';');
+ if let Some(snip) = snippet_opt(cx, span_without_semi);
+ if let Some(import) = match snip.split_once(" as ") {
+ None => Some(snip.as_str()),
+ Some((import, rename)) => {
+ if rename.trim() == name.as_str() {
+ None
+ } else {
+ Some(import.trim())
+ }
+ },
+ };
+ then {
+ span_lint_and_sugg(
+ cx,
+ MISSING_ENFORCED_IMPORT_RENAMES,
+ span_without_semi,
+ "this import should be renamed",
+ "try",
+ format!(
+ "{import} as {name}",
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly {
+use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
+use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a read and a write to the same variable where
+ /// whether the read occurs before or after the write depends on the evaluation
+ /// order of sub-expressions.
+ ///
+ /// ### Why is this bad?
+ /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands),
+ /// the operands of these expressions are evaluated before applying the effects of the expression.
+ ///
+ /// ### Known problems
+ /// Code which intentionally depends on the evaluation
+ /// order, or which is correct for any evaluation order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = 0;
+ ///
+ /// let a = {
+ /// x = 1;
+ /// 1
+ /// } + x;
+ /// // Unclear whether a is 1 or 2.
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let mut x = 0;
+ /// let tmp = {
+ /// x = 1;
+ /// 1
+ /// };
+ /// let a = tmp + x;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MIXED_READ_WRITE_IN_EXPRESSION,
+ restriction,
+ "whether a variable read occurs before a write depends on sub-expression evaluation order"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for diverging calls that are not match arms or
+ /// statements.
+ ///
+ /// ### Why is this bad?
+ /// It is often confusing to read. In addition, the
+ /// sub-expression evaluation order for Rust is not well documented.
+ ///
+ /// ### Known problems
+ /// Someone might want to use `some_bool || panic!()` as a
+ /// shorthand.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # fn b() -> bool { true }
+ /// # fn c() -> bool { true }
+ /// let a = b() || panic!() || c();
+ /// // `c()` is dead, `panic!()` is only called if `b()` returns `false`
+ /// let x = (a, b, c, panic!());
+ /// // can simply be replaced by `panic!()`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DIVERGING_SUB_EXPRESSION,
+ complexity,
+ "whether an expression contains a diverging sub expression"
+}
+
+declare_lint_pass!(EvalOrderDependence => [MIXED_READ_WRITE_IN_EXPRESSION, DIVERGING_SUB_EXPRESSION]);
+
+impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Find a write to a local variable.
+ let var = if_chain! {
+ if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind;
+ if let Some(var) = path_to_local(lhs);
+ if expr.span.desugaring_kind().is_none();
+ then { var } else { return; }
+ };
+ let mut visitor = ReadVisitor {
+ cx,
+ var,
+ write_expr: expr,
+ last_expr: expr,
+ };
+ check_for_unsequenced_reads(&mut visitor);
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if let Local { init: Some(e), .. } = local {
+ DivergenceVisitor { cx }.visit_expr(e);
+ }
+ },
+ StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
+ StmtKind::Item(..) => {},
+ }
+ }
+}
+
+struct DivergenceVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
+ fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Closure { .. } => {},
+ ExprKind::Match(e, arms, _) => {
+ self.visit_expr(e);
+ for arm in arms {
+ if let Some(Guard::If(if_expr)) = arm.guard {
+ self.visit_expr(if_expr);
+ }
+ // make sure top level arm expressions aren't linted
+ self.maybe_walk_expr(arm.body);
+ }
+ },
+ _ => walk_expr(self, e),
+ }
+ }
+ fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
+ span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
+ ExprKind::Call(func, _) => {
+ let typ = self.cx.typeck_results().expr_ty(func);
+ match typ.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let sig = typ.fn_sig(self.cx.tcx);
+ if self.cx.tcx.erase_late_bound_regions(sig).output().kind() == &ty::Never {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {},
+ }
+ },
+ ExprKind::MethodCall(..) => {
+ let borrowed_table = self.cx.typeck_results();
+ if borrowed_table.expr_ty(e).is_never() {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {
+ // do not lint expressions referencing objects of type `!`, as that required a
+ // diverging expression
+ // to begin with
+ },
+ }
+ self.maybe_walk_expr(e);
+ }
+ fn visit_block(&mut self, _: &'tcx Block<'_>) {
+ // don't continue over blocks, LateLintPass already does that
+ }
+}
+
+/// Walks up the AST from the given write expression (`vis.write_expr`) looking
+/// for reads to the same variable that are unsequenced relative to the write.
+///
+/// This means reads for which there is a common ancestor between the read and
+/// the write such that
+///
+/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x`
+/// and `|| x = 1` don't necessarily evaluate `x`), and
+///
+/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s,
+/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined
+/// evaluation order.
+///
+/// When such a read is found, the lint is triggered.
+fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) {
+ let map = &vis.cx.tcx.hir();
+ let mut cur_id = vis.write_expr.hir_id;
+ loop {
+ let parent_id = map.get_parent_node(cur_id);
+ if parent_id == cur_id {
+ break;
+ }
+ let Some(parent_node) = map.find(parent_id) else { break };
+
+ let stop_early = match parent_node {
+ Node::Expr(expr) => check_expr(vis, expr),
+ Node::Stmt(stmt) => check_stmt(vis, stmt),
+ Node::Item(_) => {
+ // We reached the top of the function, stop.
+ break;
+ },
+ _ => StopEarly::KeepGoing,
+ };
+ match stop_early {
+ StopEarly::Stop => break,
+ StopEarly::KeepGoing => {},
+ }
+
+ cur_id = parent_id;
+ }
+}
+
+/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If
+/// `check_expr` weren't an independent function, this would be unnecessary and
+/// we could just use `break`).
+enum StopEarly {
+ KeepGoing,
+ Stop,
+}
+
- fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
++fn check_expr<'tcx>(vis: &mut ReadVisitor<'_, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly {
+ if expr.hir_id == vis.last_expr.hir_id {
+ return StopEarly::KeepGoing;
+ }
+
+ match expr.kind {
+ ExprKind::Array(_)
+ | ExprKind::Tup(_)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Call(_, _)
+ | ExprKind::Assign(..)
+ | ExprKind::Index(_, _)
+ | ExprKind::Repeat(_, _)
+ | ExprKind::Struct(_, _, _) => {
+ walk_expr(vis, expr);
+ },
+ ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => {
+ if op.node == BinOpKind::And || op.node == BinOpKind::Or {
+ // x && y and x || y always evaluate x first, so these are
+ // strictly sequenced.
+ } else {
+ walk_expr(vis, expr);
+ }
+ },
+ ExprKind::Closure { .. } => {
+ // Either
+ //
+ // * `var` is defined in the closure body, in which case we've reached the top of the enclosing
+ // function and can stop, or
+ //
+ // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate
+ // its body, we don't necessarily have a write, so we need to stop to avoid generating false
+ // positives.
+ //
+ // This is also the only place we need to stop early (grrr).
+ return StopEarly::Stop;
+ },
+ // All other expressions either have only one child or strictly
+ // sequence the evaluation order of their sub-expressions.
+ _ => {},
+ }
+
+ vis.last_expr = expr;
+
+ StopEarly::KeepGoing
+}
+
++fn check_stmt<'tcx>(vis: &mut ReadVisitor<'_, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
+ // If the declaration is of a local variable, check its initializer
+ // expression if it has one. Otherwise, keep going.
+ StmtKind::Local(local) => local
+ .init
+ .as_ref()
+ .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
+ StmtKind::Item(..) => StopEarly::KeepGoing,
+ }
+}
+
+/// A visitor that looks for reads from a variable.
+struct ReadVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ /// The ID of the variable we're looking for.
+ var: HirId,
+ /// The expressions where the write to the variable occurred (for reporting
+ /// in the lint).
+ write_expr: &'tcx Expr<'tcx>,
+ /// The last (highest in the AST) expression we've checked, so we know not
+ /// to recheck it.
+ last_expr: &'tcx Expr<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if expr.hir_id == self.last_expr.hir_id {
+ return;
+ }
+
+ if path_to_local_id(expr, self.var) {
+ // Check that this is a read, not a write.
+ if !is_in_assignment_position(self.cx, expr) {
+ span_lint_and_note(
+ self.cx,
+ MIXED_READ_WRITE_IN_EXPRESSION,
+ expr.span,
+ &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)),
+ Some(self.write_expr.span),
+ "whether read occurs before this write depends on evaluation order",
+ );
+ }
+ }
+ match expr.kind {
+ // We're about to descend a closure. Since we don't know when (or
+ // if) the closure will be evaluated, any reads in it might not
+ // occur here (or ever). Like above, bail to avoid false positives.
+ ExprKind::Closure{..} |
+
+ // We want to avoid a false positive when a variable name occurs
+ // only to have its address taken, so we stop here. Technically,
+ // this misses some weird cases, eg.
+ //
+ // ```rust
+ // let mut x = 0;
+ // let a = foo(&{x = 1; x}, x);
+ // ```
+ //
+ // TODO: fix this
+ ExprKind::AddrOf(_, _, _) => {
+ return;
+ }
+ _ => {}
+ }
+
+ walk_expr(self, expr);
+ }
+}
+
+/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
+fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::Assign(lhs, ..) = parent.kind {
+ return lhs.hir_id == expr.hir_id;
+ }
+ }
+ false
+}
--- /dev/null
- use clippy_utils::trait_ref_of_method;
+use clippy_utils::diagnostics::span_lint;
- use rustc_session::{declare_lint_pass, declare_tool_lint};
++use clippy_utils::{def_path_def_ids, trait_ref_of_method};
++use rustc_data_structures::fx::FxHashSet;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::TypeVisitable;
+use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
- declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
++use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for sets/maps with mutable key types.
+ ///
+ /// ### Why is this bad?
+ /// All of `HashMap`, `HashSet`, `BTreeMap` and
+ /// `BtreeSet` rely on either the hash or the order of keys be unchanging,
+ /// so having types with interior mutability is a bad idea.
+ ///
+ /// ### Known problems
+ ///
+ /// #### False Positives
+ /// It's correct to use a struct that contains interior mutability as a key, when its
+ /// implementation of `Hash` or `Ord` doesn't access any of the interior mutable types.
+ /// However, this lint is unable to recognize this, so it will often cause false positives in
+ /// theses cases. The `bytes` crate is a great example of this.
+ ///
+ /// #### False Negatives
+ /// For custom `struct`s/`enum`s, this lint is unable to check for interior mutability behind
+ /// indirection. For example, `struct BadKey<'a>(&'a Cell<usize>)` will be seen as immutable
+ /// and cause a false negative if its implementation of `Hash`/`Ord` accesses the `Cell`.
+ ///
+ /// This lint does check a few cases for indirection. Firstly, using some standard library
+ /// types (`Option`, `Result`, `Box`, `Rc`, `Arc`, `Vec`, `VecDeque`, `BTreeMap` and
+ /// `BTreeSet`) directly as keys (e.g. in `HashMap<Box<Cell<usize>>, ()>`) **will** trigger the
+ /// lint, because the impls of `Hash`/`Ord` for these types directly call `Hash`/`Ord` on their
+ /// contained type.
+ ///
+ /// Secondly, the implementations of `Hash` and `Ord` for raw pointers (`*const T` or `*mut T`)
+ /// apply only to the **address** of the contained value. Therefore, interior mutability
+ /// behind raw pointers (e.g. in `HashSet<*mut Cell<usize>>`) can't impact the value of `Hash`
+ /// or `Ord`, and therefore will not trigger this link. For more info, see issue
+ /// [#6745](https://github.com/rust-lang/rust-clippy/issues/6745).
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::cmp::{PartialEq, Eq};
+ /// use std::collections::HashSet;
+ /// use std::hash::{Hash, Hasher};
+ /// use std::sync::atomic::AtomicUsize;
+ ///# #[allow(unused)]
+ ///
+ /// struct Bad(AtomicUsize);
+ /// impl PartialEq for Bad {
+ /// fn eq(&self, rhs: &Self) -> bool {
+ /// ..
+ /// ; unimplemented!();
+ /// }
+ /// }
+ ///
+ /// impl Eq for Bad {}
+ ///
+ /// impl Hash for Bad {
+ /// fn hash<H: Hasher>(&self, h: &mut H) {
+ /// ..
+ /// ; unimplemented!();
+ /// }
+ /// }
+ ///
+ /// fn main() {
+ /// let _: HashSet<Bad> = HashSet::new();
+ /// }
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub MUTABLE_KEY_TYPE,
+ suspicious,
+ "Check for mutable `Map`/`Set` key type"
+}
+
- check_sig(cx, item.hir_id(), sig.decl);
++#[derive(Clone)]
++pub struct MutableKeyType {
++ ignore_interior_mutability: Vec<String>,
++ ignore_mut_def_ids: FxHashSet<hir::def_id::DefId>,
++}
++
++impl_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
+
+impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
++ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
++ self.ignore_mut_def_ids.clear();
++ let mut path = Vec::new();
++ for ty in &self.ignore_interior_mutability {
++ path.extend(ty.split("::"));
++ for id in def_path_def_ids(cx, &path[..]) {
++ self.ignore_mut_def_ids.insert(id);
++ }
++ path.clear();
++ }
++ }
++
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+ if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
- check_sig(cx, item.hir_id(), sig.decl);
++ self.check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
+ if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
+ if trait_ref_of_method(cx, item.owner_id.def_id).is_none() {
- check_sig(cx, item.hir_id(), sig.decl);
++ self.check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
- check_ty(cx, local.span, cx.typeck_results().pat_ty(local.pat));
++ self.check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
+ if let hir::PatKind::Wild = local.pat.kind {
+ return;
+ }
- fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
- let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
- let fn_sig = cx.tcx.fn_sig(fn_def_id);
- for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
- check_ty(cx, hir_ty.span, *ty);
++ self.check_ty_(cx, local.span, cx.typeck_results().pat_ty(local.pat));
+ }
+}
+
- check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
- }
++impl MutableKeyType {
++ pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
++ Self {
++ ignore_interior_mutability,
++ ignore_mut_def_ids: FxHashSet::default(),
++ }
+ }
- // We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
- // generics (because the compiler cannot ensure immutability for unknown types).
- fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
- let ty = ty.peel_refs();
- 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()));
- if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
- span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+
- }
++ fn check_sig(&self, cx: &LateContext<'_>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
++ let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
++ let fn_sig = cx.tcx.fn_sig(fn_def_id);
++ for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
++ self.check_ty_(cx, hir_ty.span, *ty);
+ }
++ self.check_ty_(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
+ }
- /// Determines if a type contains interior mutability which would affect its implementation of
- /// [`Hash`] or [`Ord`].
- fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
- match *ty.kind() {
- Ref(_, inner_ty, mutbl) => {
- mutbl == hir::Mutability::Mut || is_interior_mutable_type(cx, inner_ty, span)
- }
- Slice(inner_ty) => is_interior_mutable_type(cx, inner_ty, span),
- Array(inner_ty, size) => {
- size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
- && is_interior_mutable_type(cx, inner_ty, span)
- }
- Tuple(fields) => fields.iter().any(|ty| is_interior_mutable_type(cx, ty, span)),
- Adt(def, substs) => {
- // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
- // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
- // because they have no impl for `Hash` or `Ord`.
- let is_std_collection = [
- sym::Option,
- sym::Result,
- sym::LinkedList,
- sym::Vec,
- sym::VecDeque,
- sym::BTreeMap,
- sym::BTreeSet,
- sym::Rc,
- 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();
- 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))
- } else {
- !ty.has_escaping_bound_vars()
- && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
- && !ty.is_freeze(cx.tcx, cx.param_env)
+
- _ => false,
++ // We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
++ // generics (because the compiler cannot ensure immutability for unknown types).
++ fn check_ty_<'tcx>(&self, cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
++ let ty = ty.peel_refs();
++ 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()));
++ if is_keyed_type && self.is_interior_mutable_type(cx, substs.type_at(0)) {
++ span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+ }
+ }
++ }
++
++ /// Determines if a type contains interior mutability which would affect its implementation of
++ /// [`Hash`] or [`Ord`].
++ fn is_interior_mutable_type<'tcx>(&self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
++ match *ty.kind() {
++ Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty),
++ Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty),
++ Array(inner_ty, size) => {
++ size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
++ && self.is_interior_mutable_type(cx, inner_ty)
++ },
++ Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)),
++ Adt(def, substs) => {
++ // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
++ // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
++ // because they have no impl for `Hash` or `Ord`.
++ let def_id = def.did();
++ let is_std_collection = [
++ sym::Option,
++ sym::Result,
++ sym::LinkedList,
++ sym::Vec,
++ sym::VecDeque,
++ sym::BTreeMap,
++ sym::BTreeSet,
++ sym::Rc,
++ sym::Arc,
++ ]
++ .iter()
++ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
++ let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
++ if is_std_collection || is_box || self.ignore_mut_def_ids.contains(&def_id) {
++ // The type is mutable if any of its type parameters are
++ substs.types().any(|ty| self.is_interior_mutable_type(cx, ty))
++ } else {
++ !ty.has_escaping_bound_vars()
++ && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
++ && !ty.is_freeze(cx.tcx, cx.param_env)
++ }
++ },
++ _ => false,
++ }
+ }
+}
--- /dev/null
- } else if let ty::Ref(_, _, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() {
- span_lint(
- self.cx,
- MUT_MUT,
- expr.span,
- "this expression mutably borrows a mutable reference. Consider reborrowing",
- );
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::higher;
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `mut mut` references.
+ ///
+ /// ### Why is this bad?
+ /// Multiple `mut`s don't add anything meaningful to the
+ /// source. This is either a copy'n'paste error, or it shows a fundamental
+ /// misunderstanding of references.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut y = 1;
+ /// let x = &mut &mut y;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUT_MUT,
+ pedantic,
+ "usage of double-mut refs, e.g., `&mut &mut ...`"
+}
+
+declare_lint_pass!(MutMut => [MUT_MUT]);
+
+impl<'tcx> LateLintPass<'tcx> for MutMut {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ intravisit::walk_block(&mut MutVisitor { cx }, block);
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_>) {
+ use rustc_hir::intravisit::Visitor;
+
+ MutVisitor { cx }.visit_ty(ty);
+ }
+}
+
+pub struct MutVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if in_external_macro(self.cx.sess(), expr.span) {
+ return;
+ }
+
+ if let Some(higher::ForLoop { arg, body, .. }) = higher::ForLoop::hir(expr) {
+ // A `for` loop lowers to:
+ // ```rust
+ // match ::std::iter::Iterator::next(&mut iter) {
+ // // ^^^^
+ // ```
+ // Let's ignore the generated code.
+ intravisit::walk_expr(self, arg);
+ intravisit::walk_expr(self, body);
+ } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
+ if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
+ span_lint(
+ self.cx,
+ MUT_MUT,
+ expr.span,
+ "generally you want to avoid `&mut &mut _` if possible",
+ );
++ } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() {
++ if ty.peel_refs().is_sized(self.cx.tcx, self.cx.param_env) {
++ span_lint(
++ self.cx,
++ MUT_MUT,
++ expr.span,
++ "this expression mutably borrows a mutable reference. Consider reborrowing",
++ );
++ }
+ }
+ }
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
+ if in_external_macro(self.cx.sess(), ty.span) {
+ return;
+ }
+
+ if let hir::TyKind::Rptr(
+ _,
+ hir::MutTy {
+ ty: pty,
+ mutbl: hir::Mutability::Mut,
+ },
+ ) = ty.kind
+ {
+ if let hir::TyKind::Rptr(
+ _,
+ hir::MutTy {
+ mutbl: hir::Mutability::Mut,
+ ..
+ },
+ ) = pty.kind
+ {
+ span_lint(
+ self.cx,
+ MUT_MUT,
+ ty.span,
+ "generally you want to avoid `&mut &mut _` if possible",
+ );
+ }
+ }
+
+ intravisit::walk_ty(self, ty);
+ }
+}
--- /dev/null
- fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
- if pat.span.from_expansion() {
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bindings that needlessly destructure a reference and borrow the inner
+ /// value with `&ref`.
+ ///
+ /// ### Why is this bad?
+ /// This pattern has no effect in almost all cases.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut v = Vec::<String>::new();
+ /// v.iter_mut().filter(|&ref a| a.is_empty());
+ ///
+ /// if let &[ref first, ref second] = v.as_slice() {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let mut v = Vec::<String>::new();
+ /// v.iter_mut().filter(|a| a.is_empty());
+ ///
+ /// if let [first, second] = v.as_slice() {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BORROWED_REFERENCE,
+ complexity,
+ "destructuring a reference and borrowing the inner value"
+}
+
+declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]);
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef {
- for (_, node) in cx.tcx.hir().parent_iter(pat.hir_id) {
++ fn check_pat(&mut self, cx: &LateContext<'tcx>, ref_pat: &'tcx Pat<'_>) {
++ if ref_pat.span.from_expansion() {
+ // OK, simple enough, lints doesn't check in macro.
+ return;
+ }
+
+ // Do not lint patterns that are part of an OR `|` pattern, the binding mode must match in all arms
- let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind else { return };
++ for (_, node) in cx.tcx.hir().parent_iter(ref_pat.hir_id) {
+ let Node::Pat(pat) = node else { break };
+
+ if matches!(pat.kind, PatKind::Or(_)) {
+ return;
+ }
+ }
+
+ // Only lint immutable refs, because `&mut ref T` may be useful.
- match sub_pat.kind {
++ let PatKind::Ref(pat, Mutability::Not) = ref_pat.kind else { return };
+
- pat.span,
++ match pat.kind {
+ // Check sub_pat got a `ref` keyword (excluding `ref mut`).
+ PatKind::Binding(BindingAnnotation::REF, _, ident, None) => {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_BORROWED_REFERENCE,
- let span = pat.span.until(ident.span);
++ ref_pat.span,
+ "this pattern takes a reference on something that is being dereferenced",
+ |diag| {
+ // `&ref ident`
+ // ^^^^^
- let mut suggestions = Vec::new();
-
- for element_pat in itertools::chain(before, after) {
- if let PatKind::Binding(BindingAnnotation::REF, _, ident, None) = element_pat.kind {
- // `&[..., ref ident, ...]`
- // ^^^^
- let span = element_pat.span.until(ident.span);
- suggestions.push((span, String::new()));
- } else {
- return;
- }
- }
++ let span = ref_pat.span.until(ident.span);
+ diag.span_suggestion_verbose(
+ span,
+ "try removing the `&ref` part",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ },
+ // Slices where each element is `ref`: `&[ref a, ref b, ..., ref z]`
+ PatKind::Slice(
+ before,
+ None
+ | Some(Pat {
+ kind: PatKind::Wild, ..
+ }),
+ after,
+ ) => {
- if !suggestions.is_empty() {
- span_lint_and_then(
- cx,
- NEEDLESS_BORROWED_REFERENCE,
- pat.span,
- "dereferencing a slice pattern where every element takes a reference",
- |diag| {
- // `&[...]`
- // ^
- let span = pat.span.until(sub_pat.span);
- suggestions.push((span, String::new()));
++ check_subpatterns(
++ cx,
++ "dereferencing a slice pattern where every element takes a reference",
++ ref_pat,
++ pat,
++ itertools::chain(before, after),
++ );
++ },
++ PatKind::Tuple(subpatterns, _) | PatKind::TupleStruct(_, subpatterns, _) => {
++ check_subpatterns(
++ cx,
++ "dereferencing a tuple pattern where every element takes a reference",
++ ref_pat,
++ pat,
++ subpatterns,
++ );
++ },
++ PatKind::Struct(_, fields, _) => {
++ check_subpatterns(
++ cx,
++ "dereferencing a struct pattern where every field's pattern takes a reference",
++ ref_pat,
++ pat,
++ fields.iter().map(|field| field.pat),
++ );
++ },
++ _ => {},
++ }
++ }
++}
+
- diag.multipart_suggestion(
- "try removing the `&` and `ref` parts",
- suggestions,
- Applicability::MachineApplicable,
- );
- },
- );
- }
++fn check_subpatterns<'tcx>(
++ cx: &LateContext<'tcx>,
++ message: &str,
++ ref_pat: &Pat<'_>,
++ pat: &Pat<'_>,
++ subpatterns: impl IntoIterator<Item = &'tcx Pat<'tcx>>,
++) {
++ let mut suggestions = Vec::new();
+
- _ => {},
++ for subpattern in subpatterns {
++ match subpattern.kind {
++ PatKind::Binding(BindingAnnotation::REF, _, ident, None) => {
++ // `ref ident`
++ // ^^^^
++ let span = subpattern.span.until(ident.span);
++ suggestions.push((span, String::new()));
+ },
++ PatKind::Wild => {},
++ _ => return,
+ }
+ }
++
++ if !suggestions.is_empty() {
++ span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, ref_pat.span, message, |diag| {
++ // `&pat`
++ // ^
++ let span = ref_pat.span.until(pat.span);
++ suggestions.push((span, String::new()));
++
++ diag.multipart_suggestion(
++ "try removing the `&` and `ref` parts",
++ suggestions,
++ Applicability::MachineApplicable,
++ );
++ });
++ }
+}
--- /dev/null
- fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
+//! Checks for continue statements in loops that are redundant.
+//!
+//! For example, the lint would catch
+//!
+//! ```rust
+//! let mut a = 1;
+//! let x = true;
+//!
+//! while a < 5 {
+//! a = 6;
+//! if x {
+//! // ...
+//! } else {
+//! continue;
+//! }
+//! println!("Hello, world");
+//! }
+//! ```
+//!
+//! And suggest something like this:
+//!
+//! ```rust
+//! let mut a = 1;
+//! let x = true;
+//!
+//! while a < 5 {
+//! a = 6;
+//! if x {
+//! // ...
+//! println!("Hello, world");
+//! }
+//! }
+//! ```
+//!
+//! This lint is **warn** by default.
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::{indent_of, snippet, snippet_block};
+use rustc_ast::ast;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// The lint checks for `if`-statements appearing in loops
+ /// that contain a `continue` statement in either their main blocks or their
+ /// `else`-blocks, when omitting the `else`-block possibly with some
+ /// rearrangement of code can make the code easier to understand.
+ ///
+ /// ### Why is this bad?
+ /// Having explicit `else` blocks for `if` statements
+ /// containing `continue` in their THEN branch adds unnecessary branching and
+ /// nesting to the code. Having an else block containing just `continue` can
+ /// also be better written by grouping the statements following the whole `if`
+ /// statement within the THEN block and omitting the else block completely.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn condition() -> bool { false }
+ /// # fn update_condition() {}
+ /// # let x = false;
+ /// while condition() {
+ /// update_condition();
+ /// if x {
+ /// // ...
+ /// } else {
+ /// continue;
+ /// }
+ /// println!("Hello, world");
+ /// }
+ /// ```
+ ///
+ /// Could be rewritten as
+ ///
+ /// ```rust
+ /// # fn condition() -> bool { false }
+ /// # fn update_condition() {}
+ /// # let x = false;
+ /// while condition() {
+ /// update_condition();
+ /// if x {
+ /// // ...
+ /// println!("Hello, world");
+ /// }
+ /// }
+ /// ```
+ ///
+ /// As another example, the following code
+ ///
+ /// ```rust
+ /// # fn waiting() -> bool { false }
+ /// loop {
+ /// if waiting() {
+ /// continue;
+ /// } else {
+ /// // Do something useful
+ /// }
+ /// # break;
+ /// }
+ /// ```
+ /// Could be rewritten as
+ ///
+ /// ```rust
+ /// # fn waiting() -> bool { false }
+ /// loop {
+ /// if waiting() {
+ /// continue;
+ /// }
+ /// // Do something useful
+ /// # break;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_CONTINUE,
+ pedantic,
+ "`continue` statements that can be replaced by a rearrangement of code"
+}
+
+declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]);
+
+impl EarlyLintPass for NeedlessContinue {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if !expr.span.from_expansion() {
+ check_and_warn(cx, expr);
+ }
+ }
+}
+
+/* This lint has to mainly deal with two cases of needless continue
+ * statements. */
+// Case 1 [Continue inside else block]:
+//
+// loop {
+// // region A
+// if cond {
+// // region B
+// } else {
+// continue;
+// }
+// // region C
+// }
+//
+// This code can better be written as follows:
+//
+// loop {
+// // region A
+// if cond {
+// // region B
+// // region C
+// }
+// }
+//
+// Case 2 [Continue inside then block]:
+//
+// loop {
+// // region A
+// if cond {
+// continue;
+// // potentially more code here.
+// } else {
+// // region B
+// }
+// // region C
+// }
+//
+//
+// This snippet can be refactored to:
+//
+// loop {
+// // region A
+// if !cond {
+// // region B
+// // region C
+// }
+// }
+//
+
+/// Given an expression, returns true if either of the following is true
+///
+/// - The expression is a `continue` node.
+/// - The expression node is a block with the first statement being a
+/// `continue`.
+fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
+ match else_expr.kind {
+ ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
+ ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
+ _ => false,
+ }
+}
+
+fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
+ block.stmts.get(0).map_or(false, |stmt| match stmt.kind {
+ ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
+ if let ast::ExprKind::Continue(ref l) = e.kind {
+ compare_labels(label, l.as_ref())
+ } else {
+ false
+ }
+ },
+ _ => false,
+ })
+}
+
+/// If the `continue` has a label, check it matches the label of the loop.
+fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool {
+ match (loop_label, continue_label) {
+ // `loop { continue; }` or `'a loop { continue; }`
+ (_, None) => true,
+ // `loop { continue 'a; }`
+ (None, _) => false,
+ // `'a loop { continue 'a; }` or `'a loop { continue 'b; }`
+ (Some(x), Some(y)) => x.ident == y.ident,
+ }
+}
+
+/// If `expr` is a loop expression (while/while let/for/loop), calls `func` with
+/// the AST object representing the loop block of `expr`.
+fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
+where
+ F: FnMut(&ast::Block, Option<&ast::Label>),
+{
+ if let ast::ExprKind::While(_, loop_block, label)
+ | ast::ExprKind::ForLoop(_, _, loop_block, label)
+ | ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind
+ {
+ func(loop_block, label.as_ref());
+ }
+}
+
+/// If `stmt` is an if expression node with an `else` branch, calls func with
+/// the
+/// following:
+///
+/// - The `if` expression itself,
+/// - The `if` condition expression,
+/// - The `then` block, and
+/// - The `else` expression.
+fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
+where
+ F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
+{
+ match stmt.kind {
+ ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
+ if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind {
+ func(e, cond, if_block, else_expr);
+ }
+ },
+ _ => {},
+ }
+}
+
+/// A type to distinguish between the two distinct cases this lint handles.
+#[derive(Copy, Clone, Debug)]
+enum LintType {
+ ContinueInsideElseBlock,
+ ContinueInsideThenBlock,
+}
+
+/// Data we pass around for construction of help messages.
+struct LintData<'a> {
+ /// The `if` expression encountered in the above loop.
+ if_expr: &'a ast::Expr,
+ /// The condition expression for the above `if`.
+ if_cond: &'a ast::Expr,
+ /// The `then` block of the `if` statement.
+ if_block: &'a ast::Block,
+ /// The `else` block of the `if` statement.
+ /// Note that we only work with `if` exprs that have an `else` branch.
+ else_expr: &'a ast::Expr,
+ /// The 0-based index of the `if` statement in the containing loop block.
+ stmt_idx: usize,
+ /// The statements of the loop block.
+ loop_block: &'a ast::Block,
+}
+
+const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
+
+const MSG_REDUNDANT_ELSE_BLOCK: &str = "this `else` block is redundant";
+
+const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "there is no need for an explicit `else` block for this `if` \
+ expression";
+
+const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "consider dropping the `else` clause and merging the code that \
+ follows (in the loop) with the `if` block";
+
+const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause";
+
+const DROP_CONTINUE_EXPRESSION_MSG: &str = "consider dropping the `continue` expression";
+
- fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
++fn emit_warning(cx: &EarlyContext<'_>, data: &LintData<'_>, header: &str, typ: LintType) {
+ // snip is the whole *help* message that appears after the warning.
+ // message is the warning message.
+ // expr is the expression which the lint warning message refers to.
+ let (snip, message, expr) = match typ {
+ LintType::ContinueInsideElseBlock => (
+ suggestion_snippet_for_continue_inside_else(cx, data),
+ MSG_REDUNDANT_ELSE_BLOCK,
+ data.else_expr,
+ ),
+ LintType::ContinueInsideThenBlock => (
+ suggestion_snippet_for_continue_inside_if(cx, data),
+ MSG_ELSE_BLOCK_NOT_NEEDED,
+ data.if_expr,
+ ),
+ };
+ span_lint_and_help(
+ cx,
+ NEEDLESS_CONTINUE,
+ expr.span,
+ message,
+ None,
+ &format!("{header}\n{snip}"),
+ );
+}
+
- fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
++fn suggestion_snippet_for_continue_inside_if(cx: &EarlyContext<'_>, data: &LintData<'_>) -> String {
+ let cond_code = snippet(cx, data.if_cond.span, "..");
+
+ let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span));
+
+ let else_code = snippet_block(cx, data.else_expr.span, "..", Some(data.if_expr.span));
+
+ let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+ format!(
+ "{indent}if {cond_code} {continue_code}\n{indent}{else_code}",
+ indent = " ".repeat(indent_if),
+ )
+}
+
- fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) {
++fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &LintData<'_>) -> String {
+ let cond_code = snippet(cx, data.if_cond.span, "..");
+
+ // Region B
+ let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)));
+
+ // Region C
+ // These is the code in the loop block that follows the if/else construction
+ // we are complaining about. We want to pull all of this code into the
+ // `then` block of the `if` statement.
+ let indent = span_of_first_expr_in_block(data.if_block)
+ .and_then(|span| indent_of(cx, span))
+ .unwrap_or(0);
+ let to_annex = data.loop_block.stmts[data.stmt_idx + 1..]
+ .iter()
+ .map(|stmt| {
+ let span = cx.sess().source_map().stmt_span(stmt.span, data.loop_block.span);
+ let snip = snippet_block(cx, span, "..", None).into_owned();
+ snip.lines()
+ .map(|line| format!("{}{line}", " ".repeat(indent)))
+ .collect::<Vec<_>>()
+ .join("\n")
+ })
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+ format!(
+ "{indent_if}if {cond_code} {block_code}\n{indent}// merged code follows:\n{to_annex}\n{indent_if}}}",
+ indent = " ".repeat(indent),
+ indent_if = " ".repeat(indent_if),
+ )
+}
+
++fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if_chain! {
+ if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind;
+ if let Some(last_stmt) = loop_block.stmts.last();
+ if let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind;
+ if let ast::ExprKind::Continue(_) = inner_expr.kind;
+ then {
+ span_lint_and_help(
+ cx,
+ NEEDLESS_CONTINUE,
+ last_stmt.span,
+ MSG_REDUNDANT_CONTINUE_EXPRESSION,
+ None,
+ DROP_CONTINUE_EXPRESSION_MSG,
+ );
+ }
+ }
+ with_loop_block(expr, |loop_block, label| {
+ for (i, stmt) in loop_block.stmts.iter().enumerate() {
+ with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
+ let data = &LintData {
+ stmt_idx: i,
+ if_expr,
+ if_cond: cond,
+ if_block: then_block,
+ else_expr,
+ loop_block,
+ };
+ if needless_continue_in_else(else_expr, label) {
+ emit_warning(
+ cx,
+ data,
+ DROP_ELSE_BLOCK_AND_MERGE_MSG,
+ LintType::ContinueInsideElseBlock,
+ );
+ } else if is_first_block_stmt_continue(then_block, label) {
+ emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
+ }
+ });
+ }
+ });
+}
+
+/// Eats at `s` from the end till a closing brace `}` is encountered, and then continues eating
+/// till a non-whitespace character is found. e.g., the string. If no closing `}` is present, the
+/// string will be preserved.
+///
+/// ```rust
+/// {
+/// let x = 5;
+/// }
+/// ```
+///
+/// is transformed to
+///
+/// ```text
+/// {
+/// let x = 5;
+/// ```
+#[must_use]
+fn erode_from_back(s: &str) -> String {
+ let mut ret = s.to_string();
+ while ret.pop().map_or(false, |c| c != '}') {}
+ while let Some(c) = ret.pop() {
+ if !c.is_whitespace() {
+ ret.push(c);
+ break;
+ }
+ }
+ if ret.is_empty() { s.to_string() } else { ret }
+}
+
+fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
+ block.stmts.get(0).map(|stmt| stmt.span)
+}
+
+#[cfg(test)]
+mod test {
+ use super::erode_from_back;
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_erode_from_back() {
+ let input = "\
+{
+ let x = 5;
+ let y = format!(\"{}\", 42);
+}";
+
+ let expected = "\
+{
+ let x = 5;
+ let y = format!(\"{}\", 42);";
+
+ let got = erode_from_back(input);
+ assert_eq!(expected, got);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_erode_from_back_no_brace() {
+ let input = "\
+let x = 5;
+let y = something();
+";
+ let expected = input;
+ let got = erode_from_back(input);
+ assert_eq!(expected, got);
+ }
+}
--- /dev/null
- fn fake_read(
- &mut self,
- _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
- _: FakeReadCause,
- _: HirId,
- ) {
- }
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::ptr::get_spans;
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item};
+use clippy_utils::{get_trait_def_id, is_self, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::Attribute;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Mutability, Node, PatKind, QPath, TyKind,
+};
+use rustc_hir::{HirIdMap, HirIdSet, LangItem};
+use rustc_hir_typeck::expr_use_visitor as euv;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::{self, TypeVisitable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::misc::can_type_implement_copy;
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, but not
+ /// consuming them in its
+ /// body.
+ ///
+ /// ### Why is this bad?
+ /// Taking arguments by reference is more flexible and can
+ /// sometimes avoid
+ /// unnecessary allocations.
+ ///
+ /// ### Known problems
+ /// * This lint suggests taking an argument by reference,
+ /// however sometimes it is better to let users decide the argument type
+ /// (by using `Borrow` trait, for example), depending on how the function is used.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(v: Vec<i32>) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
+ /// should be
+ /// ```rust
+ /// fn foo(v: &[i32]) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_PASS_BY_VALUE,
+ pedantic,
+ "functions taking arguments by value, but not consuming them in its body"
+}
+
+declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]);
+
+macro_rules! need {
+ ($e: expr) => {
+ if let Some(x) = $e {
+ x
+ } else {
+ return;
+ }
+ };
+}
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
+ #[expect(clippy::too_many_lines)]
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ match kind {
+ FnKind::ItemFn(.., header) => {
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ if header.abi != Abi::Rust || requires_exact_signature(attrs) {
+ return;
+ }
+ },
+ FnKind::Method(..) => (),
+ FnKind::Closure => return,
+ }
+
+ // Exclude non-inherent impls
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ // Allow `Borrow` or functions to be taken by value
+ let allowed_traits = [
+ need!(cx.tcx.lang_items().fn_trait()),
+ need!(cx.tcx.lang_items().fn_once_trait()),
+ need!(cx.tcx.lang_items().fn_mut_trait()),
+ need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)),
+ ];
+
+ let sized_trait = need!(cx.tcx.lang_items().sized_trait());
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
+ .filter(|p| !p.is_global())
+ .filter_map(|obligation| {
+ // Note that we do not want to deal with qualified predicates here.
+ match obligation.predicate.kind().no_bound_vars() {
+ Some(ty::PredicateKind::Trait(pred)) if pred.def_id() != sized_trait => Some(pred),
+ _ => None,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Collect moved variables and spans which will need dereferencings from the
+ // function body.
+ let MovedVariablesCtxt {
+ moved_vars,
+ spans_need_deref,
+ ..
+ } = {
+ let mut ctx = MovedVariablesCtxt::default();
+ let infcx = cx.tcx.infer_ctxt().build();
+ euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
+ ctx
+ };
+
+ let fn_sig = cx.tcx.fn_sig(fn_def_id);
+ let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
+
+ for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() {
+ // All spans generated from a proc-macro invocation are the same...
+ if span == input.span {
+ return;
+ }
+
+ // Ignore `self`s.
+ if idx == 0 {
+ if let PatKind::Binding(.., ident, _) = arg.pat.kind {
+ if ident.name == kw::SelfLower {
+ continue;
+ }
+ }
+ }
+
+ //
+ // * Exclude a type that is specifically bounded by `Borrow`.
+ // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
+ // `serde::Serialize`)
+ let (implements_borrow_trait, all_borrowable_trait) = {
+ let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::<Vec<_>>();
+
+ (
+ preds.iter().any(|t| cx.tcx.is_diagnostic_item(sym::Borrow, t.def_id())),
+ !preds.is_empty() && {
+ let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_erased, ty);
+ preds.iter().all(|t| {
+ let ty_params = t.trait_ref.substs.iter().skip(1).collect::<Vec<_>>();
+ implements_trait(cx, ty_empty_region, t.def_id(), &ty_params)
+ })
+ },
+ )
+ };
+
+ if_chain! {
+ if !is_self(arg);
+ if !ty.is_mutable_ptr();
+ if !is_copy(cx, ty);
+ if ty.is_sized(cx.tcx, cx.param_env);
+ if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[]));
+ if !implements_borrow_trait;
+ if !all_borrowable_trait;
+
+ if let PatKind::Binding(BindingAnnotation(_, Mutability::Not), canonical_id, ..) = arg.pat.kind;
+ if !moved_vars.contains(&canonical_id);
+ then {
+ // Dereference suggestion
+ 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,
+ traits::ObligationCause::dummy_with_span(span),
+ ).is_ok() {
+ diag.span_help(span, "consider marking this type as `Copy`");
+ }
+ }
+ }
+
+ let deref_span = spans_need_deref.get(&canonical_id);
+ if_chain! {
+ if is_type_diagnostic_item(cx, ty, sym::Vec);
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]);
+ if let TyKind::Path(QPath::Resolved(_, path)) = input.kind;
+ if let Some(elem_ty) = path.segments.iter()
+ .find(|seg| seg.ident.name == sym::Vec)
+ .and_then(|ps| ps.args.as_ref())
+ .map(|params| params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }).unwrap());
+ then {
+ let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ slice_ty,
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{x}` to")),
+ )
+ .as_ref(),
+ suggestion,
+ Applicability::Unspecified,
+ );
+ }
+
+ // cannot be destructured, no need for `*` suggestion
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ if is_type_lang_item(cx, ty, LangItem::String) {
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ "&str",
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{x}` to"))
+ )
+ .as_ref(),
+ suggestion,
+ Applicability::Unspecified,
+ );
+ }
+
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
+
+ // Suggests adding `*` to dereference the added reference.
+ if let Some(deref_span) = deref_span {
+ spans.extend(
+ deref_span
+ .iter()
+ .copied()
+ .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
+ );
+ spans.sort_by_key(|&(span, _)| span);
+ }
+ multispan_sugg(diag, "consider taking a reference instead", spans);
+ };
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_PASS_BY_VALUE,
+ input.span,
+ "this argument is passed by value, but not consumed in the function body",
+ sugg,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Functions marked with these attributes must have the exact signature.
+fn requires_exact_signature(attrs: &[Attribute]) -> bool {
+ attrs.iter().any(|attr| {
+ [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
+ .iter()
+ .any(|&allow| attr.has_name(allow))
+ })
+}
+
+#[derive(Default)]
+struct MovedVariablesCtxt {
+ moved_vars: HirIdSet,
+ /// Spans which need to be prefixed with `*` for dereferencing the
+ /// suggested additional reference.
+ spans_need_deref: HirIdMap<FxHashSet<Span>>,
+}
+
+impl MovedVariablesCtxt {
+ fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) {
+ if let euv::PlaceBase::Local(vid) = cmt.place.base {
+ self.moved_vars.insert(vid);
+ }
+ }
+}
+
+impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt {
+ fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId) {
+ self.move_common(cmt);
+ }
+
+ fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
+
+ fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
+
++ fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
--- /dev/null
- check_lit(cx, &token_lit, expr.span, true);
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_ast::token::{Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+use std::fmt::Write;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `\0` escapes in string and byte literals that look like octal
+ /// character escapes in C.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// C and other languages support octal character escapes in strings, where
+ /// a backslash is followed by up to three octal digits. For example, `\033`
+ /// stands for the ASCII character 27 (ESC). Rust does not support this
+ /// notation, but has the escape code `\0` which stands for a null
+ /// byte/character, and any following digits do not form part of the escape
+ /// sequence. Therefore, `\033` is not a compiler error but the result may
+ /// be surprising.
+ ///
+ /// ### Known problems
+ /// The actual meaning can be the intended one. `\x00` can be used in these
+ /// cases to be unambiguous.
+ ///
+ /// The lint does not trigger for format strings in `print!()`, `write!()`
+ /// and friends since the string is already preprocessed when Clippy lints
+ /// can see it.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
+ /// let two = "\033\0"; // \033 intended as null-3-3
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let one = "\x1b[1mWill this be bold?\x1b[0m";
+ /// let two = "\x0033\x00";
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub OCTAL_ESCAPES,
+ suspicious,
+ "string escape sequences looking like octal characters"
+}
+
+declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
+
+impl EarlyLintPass for OctalEscapes {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(token_lit) = &expr.kind {
+ if matches!(token_lit.kind, LitKind::Str) {
- check_lit(cx, &token_lit, expr.span, false);
++ check_lit(cx, token_lit, expr.span, true);
+ } else if matches!(token_lit.kind, LitKind::ByteStr) {
++ check_lit(cx, token_lit, expr.span, false);
+ }
+ }
+ }
+}
+
+fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
+ let contents = lit.symbol.as_str();
+ let mut iter = contents.char_indices().peekable();
+ let mut found = vec![];
+
+ // go through the string, looking for \0[0-7][0-7]?
+ while let Some((from, ch)) = iter.next() {
+ if ch == '\\' {
+ if let Some((_, '0')) = iter.next() {
+ // collect up to two further octal digits
+ if let Some((mut to, '0'..='7')) = iter.next() {
+ if let Some((_, '0'..='7')) = iter.peek() {
+ to += 1;
+ }
+ found.push((from, to + 1));
+ }
+ }
+ }
+ }
+
+ if found.is_empty() {
+ return;
+ }
+
+ // construct two suggestion strings, one with \x escapes with octal meaning
+ // as in C, and one with \x00 for null bytes.
+ let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
+ let mut suggest_2 = suggest_1.clone();
+ let mut index = 0;
+ for (from, to) in found {
+ suggest_1.push_str(&contents[index..from]);
+ suggest_2.push_str(&contents[index..from]);
+
+ // construct a replacement escape
+ // the maximum value is \077, or \x3f, so u8 is sufficient here
+ if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
+ write!(suggest_1, "\\x{n:02x}").unwrap();
+ }
+
+ // append the null byte as \x00 and the following digits literally
+ suggest_2.push_str("\\x00");
+ suggest_2.push_str(&contents[from + 2..to]);
+
+ index = to;
+ }
+ suggest_1.push_str(&contents[index..]);
+ suggest_1.push('"');
+ suggest_2.push_str(&contents[index..]);
+ suggest_2.push('"');
+
+ span_lint_and_then(
+ cx,
+ OCTAL_ESCAPES,
+ span,
+ &format!(
+ "octal-looking escape in {} literal",
+ if is_string { "string" } else { "byte string" }
+ ),
+ |diag| {
+ diag.help(&format!(
+ "octal escapes are not supported, `\\0` is always a null {}",
+ if is_string { "character" } else { "byte" }
+ ));
+ // suggestion 1: equivalent hex escape
+ diag.span_suggestion(
+ span,
+ "if an octal escape was intended, use the hexadecimal representation instead",
+ suggest_1,
+ Applicability::MaybeIncorrect,
+ );
+ // suggestion 2: unambiguous null byte
+ diag.span_suggestion(
+ span,
+ &format!(
+ "if the null {} is intended, disambiguate using",
+ if is_string { "character" } else { "byte" }
+ ),
+ suggest_2,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+}
--- /dev/null
- use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
+use super::ARITHMETIC_SIDE_EFFECTS;
- /// Assuming that `expr` is a literal integer, checks operators (+=, -=, *, /) in a
- /// non-constant environment that won't overflow.
- fn has_valid_op(op: &Spanned<hir::BinOpKind>, expr: &hir::Expr<'_>) -> bool {
- if let hir::ExprKind::Lit(ref lit) = expr.kind &&
- let ast::LitKind::Int(value, _) = lit.node
- {
- match (&op.node, value) {
- (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
- (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
- | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
- | (hir::BinOpKind::Mul, 0 | 1) => true,
- _ => false,
- }
- } else {
- false
- }
- }
-
++use clippy_utils::{
++ consts::{constant, constant_simple},
++ diagnostics::span_lint,
++ peel_hir_expr_refs,
++};
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::impl_lint_pass;
+use rustc_span::source_map::{Span, Spanned};
+
+const HARD_CODED_ALLOWED: &[&str] = &[
+ "&str",
+ "f32",
+ "f64",
+ "std::num::Saturating",
+ "std::num::Wrapping",
+ "std::string::String",
+];
+
+#[derive(Debug)]
+pub struct ArithmeticSideEffects {
+ allowed: FxHashSet<String>,
+ // Used to check whether expressions are constants, such as in enum discriminants and consts
+ const_span: Option<Span>,
+ expr_span: Option<Span>,
+}
+
+impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
+
+impl ArithmeticSideEffects {
+ #[must_use]
+ pub fn new(mut allowed: FxHashSet<String>) -> Self {
+ allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
+ Self {
+ allowed,
+ const_span: None,
+ expr_span: None,
+ }
+ }
+
- /// If `expr` does not match any variant of `LiteralIntegerTy`, returns `None`.
- fn literal_integer<'expr, 'tcx>(expr: &'expr hir::Expr<'tcx>) -> Option<LiteralIntegerTy<'expr, 'tcx>> {
- if matches!(expr.kind, hir::ExprKind::Lit(_)) {
- return Some(LiteralIntegerTy::Value(expr));
+ /// Checks if the given `expr` has any of the inner `allowed` elements.
+ fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
+ self.allowed
+ .contains(ty.to_string().split('<').next().unwrap_or_default())
+ }
+
+ // For example, 8i32 or &i64::MAX.
+ fn is_integral(ty: Ty<'_>) -> bool {
+ ty.peel_refs().is_integral()
+ }
+
+ // Common entry-point to avoid code duplication.
+ fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ let msg = "arithmetic operation that can potentially result in unexpected side-effects";
+ span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
+ self.expr_span = Some(expr.span);
+ }
+
- if let hir::ExprKind::AddrOf(.., inn) = expr.kind && let hir::ExprKind::Lit(_) = inn.kind {
- return Some(LiteralIntegerTy::Ref(inn));
++ /// If `expr` is not a literal integer like `1`, returns `None`.
++ fn literal_integer(expr: &hir::Expr<'_>) -> Option<u128> {
++ if let hir::ExprKind::Lit(ref lit) = expr.kind && let ast::LitKind::Int(n, _) = lit.node {
++ Some(n)
+ }
- None
++ else {
++ None
+ }
- match (Self::literal_integer(lhs), Self::literal_integer(rhs)) {
- (None, Some(lit_int_ty)) | (Some(lit_int_ty), None) => Self::has_valid_op(op, lit_int_ty.into()),
- (Some(LiteralIntegerTy::Value(_)), Some(LiteralIntegerTy::Value(_))) => true,
- (None, None) | (Some(_), Some(_)) => false,
+ }
+
+ /// Manages when the lint should be triggered. Operations in constant environments, hard coded
+ /// types, custom allowed types and non-constant operations that won't overflow are ignored.
+ fn manage_bin_ops<'tcx>(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'tcx>,
+ op: &Spanned<hir::BinOpKind>,
+ lhs: &hir::Expr<'tcx>,
+ rhs: &hir::Expr<'tcx>,
+ ) {
+ if constant_simple(cx, cx.typeck_results(), expr).is_some() {
+ return;
+ }
+ if !matches!(
+ op.node,
+ hir::BinOpKind::Add
+ | hir::BinOpKind::Sub
+ | hir::BinOpKind::Mul
+ | hir::BinOpKind::Div
+ | hir::BinOpKind::Rem
+ | hir::BinOpKind::Shl
+ | hir::BinOpKind::Shr
+ ) {
+ return;
+ };
+ let lhs_ty = cx.typeck_results().expr_ty(lhs);
+ let rhs_ty = cx.typeck_results().expr_ty(rhs);
+ let lhs_and_rhs_have_the_same_ty = lhs_ty == rhs_ty;
+ if lhs_and_rhs_have_the_same_ty && self.is_allowed_ty(lhs_ty) && self.is_allowed_ty(rhs_ty) {
+ return;
+ }
+ let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
- if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
++ let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
++ let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
++ match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) {
++ (None, None) => false,
++ (None, Some(n)) | (Some(n), None) => match (&op.node, n) {
++ (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
++ (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
++ | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
++ | (hir::BinOpKind::Mul, 0 | 1) => true,
++ _ => false,
++ },
++ (Some(_), Some(_)) => {
++ matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
++ },
+ }
+ } else {
+ false
+ };
+ if !has_valid_op {
+ self.issue_lint(cx, expr);
+ }
+ }
++
++ fn manage_unary_ops<'tcx>(
++ &mut self,
++ cx: &LateContext<'tcx>,
++ expr: &hir::Expr<'tcx>,
++ un_expr: &hir::Expr<'tcx>,
++ un_op: hir::UnOp,
++ ) {
++ let hir::UnOp::Neg = un_op else { return; };
++ if constant(cx, cx.typeck_results(), un_expr).is_some() {
++ return;
++ }
++ let ty = cx.typeck_results().expr_ty(expr).peel_refs();
++ if self.is_allowed_ty(ty) {
++ return;
++ }
++ let actual_un_expr = peel_hir_expr_refs(un_expr).0;
++ if Self::literal_integer(actual_un_expr).is_some() {
++ return;
++ }
++ self.issue_lint(cx, expr);
++ }
++
++ fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool {
++ self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span))
++ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
- hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
++ if self.should_skip_expr(expr) {
+ return;
+ }
+ match &expr.kind {
- hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
- if constant_simple(cx, cx.typeck_results(), expr).is_none() {
- self.issue_lint(cx, expr);
- }
++ hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
+ self.manage_bin_ops(cx, expr, op, lhs, rhs);
+ },
-
- /// Tells if an expression is a integer declared by value or by reference.
- ///
- /// If `LiteralIntegerTy::Ref`, then the contained value will be `hir::ExprKind::Lit` rather
- /// than `hirExprKind::Addr`.
- enum LiteralIntegerTy<'expr, 'tcx> {
- /// For example, `&199`
- Ref(&'expr hir::Expr<'tcx>),
- /// For example, `1` or `i32::MAX`
- Value(&'expr hir::Expr<'tcx>),
- }
-
- impl<'expr, 'tcx> From<LiteralIntegerTy<'expr, 'tcx>> for &'expr hir::Expr<'tcx> {
- fn from(from: LiteralIntegerTy<'expr, 'tcx>) -> Self {
- match from {
- LiteralIntegerTy::Ref(elem) | LiteralIntegerTy::Value(elem) => elem,
- }
- }
- }
++ hir::ExprKind::Unary(un_op, un_expr) => {
++ self.manage_unary_ops(cx, expr, un_expr, *un_op);
+ },
+ _ => {},
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
+ let body_owner = cx.tcx.hir().body_owner(body.id());
+ let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
+ let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
+ if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
+ let body_span = cx.tcx.hir().span_with_body(body_owner);
+ if let Some(span) = self.const_span && span.contains(body_span) {
+ return;
+ }
+ self.const_span = Some(body_span);
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
+ let body_owner = cx.tcx.hir().body_owner(body.id());
+ let body_span = cx.tcx.hir().span(body_owner);
+ if let Some(span) = self.const_span && span.contains(body_span) {
+ return;
+ }
+ self.const_span = None;
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if Some(expr.span) == self.expr_span {
+ self.expr_span = None;
+ }
+ }
+}
--- /dev/null
- fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::get_enclosing_block;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::OP_REF;
+
+#[expect(clippy::similar_names, clippy::too_many_lines)]
+pub(crate) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+) {
+ let (trait_id, requires_ref) = match op {
+ BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false),
+ BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false),
+ BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false),
+ BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false),
+ BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false),
+ // don't lint short circuiting ops
+ BinOpKind::And | BinOpKind::Or => return,
+ BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false),
+ BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false),
+ BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false),
+ BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false),
+ BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false),
+ BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true),
+ BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => {
+ (cx.tcx.lang_items().partial_ord_trait(), true)
+ },
+ };
+ if let Some(trait_id) = trait_id {
+ match (&left.kind, &right.kind) {
+ // do not suggest to dereference literals
+ (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
+ // &foo == &bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ let rty = cx.typeck_results().expr_ty(r);
+ let lcpy = is_copy(cx, lty);
+ let rcpy = is_copy(cx, rty);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ // either operator autorefs or both args are copyable
+ if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of both operands",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ multispan_sugg(
+ diag,
+ "use the values directly",
+ vec![(left.span, lsnip), (right.span, rsnip)],
+ );
+ },
+ );
+ } else if lcpy
+ && !rcpy
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ } else if !lcpy
+ && rcpy
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of right operand",
+ |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // &foo == bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ let rty = cx.typeck_results().expr_ty(right);
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ let lcpy = is_copy(cx, lty);
+ if (requires_ref || lcpy)
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // foo == &bar
+ (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let rty = cx.typeck_results().expr_ty(r);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ let lty = cx.typeck_results().expr_ty(left);
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ let rcpy = is_copy(cx, rty);
+ if (requires_ref || rcpy)
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ });
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn in_impl<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ bin_op: DefId,
+) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
+ if_chain! {
+ if let Some(block) = get_enclosing_block(cx, e.hir_id);
+ if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id());
+ let item = cx.tcx.hir().expect_item(impl_def_id.expect_local());
+ if let ItemKind::Impl(item) = &item.kind;
+ if let Some(of_trait) = &item.of_trait;
+ if let Some(seg) = of_trait.path.segments.last();
+ if let Res::Def(_, trait_id) = seg.res;
+ if trait_id == bin_op;
+ if let Some(generic_args) = seg.args;
+ if let Some(GenericArg::Type(other_ty)) = generic_args.args.last();
+
+ then {
+ Some((item.self_ty, other_ty))
+ }
+ else {
+ None
+ }
+ }
+}
+
++fn are_equal(cx: &LateContext<'_>, 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();
+ let item = cx.tcx.hir().expect_item(local_did);
+ let middle_ty_id = item.owner_id.to_def_id();
+ if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
+ if let Res::Def(_, hir_ty_id) = path.res;
+
+ then {
+ hir_ty_id == middle_ty_id
+ }
+ else {
+ false
+ }
+ }
+}
--- /dev/null
- if arms.len() == 2 {
- return if is_none_or_err_arm(cx, &arms[1]) {
- Some((arms[0].pat, arms[0].body, arms[1].body))
- } else if is_none_or_err_arm(cx, &arms[0]) {
- Some((arms[1].pat, arms[1].body, arms[0].body))
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{
+ can_move_expr_to_closure, eager_or_lazy, higher, in_constant, is_else_clause, is_res_lang_ctor, peel_blocks,
+ peel_hir_expr_while, CaptureKind,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
+use rustc_hir::{
+ def::Res, Arm, BindingAnnotation, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints usage of `if let Some(v) = ... { y } else { x }` and
+ /// `match .. { Some(v) => y, None/_ => x }` which are more
+ /// idiomatically done with `Option::map_or` (if the else bit is a pure
+ /// expression) or `Option::map_or_else` (if the else bit is an impure
+ /// expression).
+ ///
+ /// ### Why is this bad?
+ /// Using the dedicated functions of the `Option` type is clearer and
+ /// more concise than an `if let` expression.
+ ///
+ /// ### Known problems
+ /// This lint uses a deliberately conservative metric for checking
+ /// if the inside of either body contains breaks or continues which will
+ /// cause it to not suggest a fix if either block contains a loop with
+ /// continues or breaks contained within the loop.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let optional: Option<u32> = Some(0);
+ /// # fn do_complicated_function() -> u32 { 5 };
+ /// let _ = if let Some(foo) = optional {
+ /// foo
+ /// } else {
+ /// 5
+ /// };
+ /// let _ = match optional {
+ /// Some(val) => val + 1,
+ /// None => 5
+ /// };
+ /// let _ = if let Some(foo) = optional {
+ /// foo
+ /// } else {
+ /// let y = do_complicated_function();
+ /// y*y
+ /// };
+ /// ```
+ ///
+ /// should be
+ ///
+ /// ```rust
+ /// # let optional: Option<u32> = Some(0);
+ /// # fn do_complicated_function() -> u32 { 5 };
+ /// let _ = optional.map_or(5, |foo| foo);
+ /// let _ = optional.map_or(5, |val| val + 1);
+ /// let _ = optional.map_or_else(||{
+ /// let y = do_complicated_function();
+ /// y*y
+ /// }, |foo| foo);
+ /// ```
+ // FIXME: Before moving this lint out of nursery, the lint name needs to be updated. It now also
+ // covers matches and `Result`.
+ #[clippy::version = "1.47.0"]
+ pub OPTION_IF_LET_ELSE,
+ nursery,
+ "reimplementation of Option::map_or"
+}
+
+declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]);
+
+/// A struct containing information about occurrences of construct that this lint detects
+///
+/// Such as:
+///
+/// ```ignore
+/// if let Some(..) = {..} else {..}
+/// ```
+/// or
+/// ```ignore
+/// match x {
+/// Some(..) => {..},
+/// None/_ => {..}
+/// }
+/// ```
+struct OptionOccurrence {
+ option: String,
+ method_sugg: String,
+ some_expr: String,
+ none_expr: String,
+}
+
+fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String {
+ format!(
+ "{}{}",
+ Sugg::hir_with_macro_callsite(cx, cond_expr, "..").maybe_par(),
+ if as_mut {
+ ".as_mut()"
+ } else if as_ref {
+ ".as_ref()"
+ } else {
+ ""
+ }
+ )
+}
+
+fn try_get_option_occurrence<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &Pat<'tcx>,
+ expr: &Expr<'_>,
+ if_then: &'tcx Expr<'_>,
+ if_else: &'tcx Expr<'_>,
+) -> Option<OptionOccurrence> {
+ let cond_expr = match expr.kind {
+ ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr,
+ _ => expr,
+ };
+ let inner_pat = try_get_inner_pat(cx, pat)?;
+ if_chain! {
+ if let PatKind::Binding(bind_annotation, _, id, None) = inner_pat.kind;
+ if let Some(some_captures) = can_move_expr_to_closure(cx, if_then);
+ if let Some(none_captures) = can_move_expr_to_closure(cx, if_else);
+ if some_captures
+ .iter()
+ .filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2)))
+ .all(|(x, y)| x.is_imm_ref() && y.is_imm_ref());
+ then {
+ let capture_mut = if bind_annotation == BindingAnnotation::MUT { "mut " } else { "" };
+ let some_body = peel_blocks(if_then);
+ let none_body = peel_blocks(if_else);
+ let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" };
+ let capture_name = id.name.to_ident_string();
+ let (as_ref, as_mut) = match &expr.kind {
+ ExprKind::AddrOf(_, Mutability::Not, _) => (true, false),
+ ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true),
+ _ => (bind_annotation == BindingAnnotation::REF, bind_annotation == BindingAnnotation::REF_MUT),
+ };
+
+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
+ // TODO: check all the references made in the scrutinee expression. This will require interacting
+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
+ if as_ref || as_mut {
+ let e = peel_hir_expr_while(cond_expr, |e| match e.kind {
+ ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
+ _ => None,
+ });
+ if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
+ match some_captures.get(local_id)
+ .or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id)))
+ {
+ Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
+ Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,
+ Some(CaptureKind::Ref(Mutability::Not)) | None => (),
+ }
+ }
+ }
+
+ return Some(OptionOccurrence {
+ option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
+ method_sugg: method_sugg.to_string(),
+ some_expr: format!("|{capture_mut}{capture_name}| {}", Sugg::hir_with_macro_callsite(cx, some_body, "..")),
+ none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")),
+ });
+ }
+ }
+
+ None
+}
+
+fn try_get_inner_pat<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<&'tcx Pat<'tcx>> {
+ if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind {
+ let res = cx.qpath_res(qpath, pat.hir_id);
+ if is_res_lang_ctor(cx, res, OptionSome) || is_res_lang_ctor(cx, res, ResultOk) {
+ return Some(inner_pat);
+ }
+ }
+ None
+}
+
+/// If this expression is the option if let/else construct we're detecting, then
+/// this function returns an `OptionOccurrence` struct with details if
+/// this construct is found, or None if this construct is not found.
+fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurrence> {
+ if let Some(higher::IfLet {
+ let_pat,
+ let_expr,
+ if_then,
+ if_else: Some(if_else),
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ if !is_else_clause(cx.tcx, expr) {
+ return try_get_option_occurrence(cx, let_pat, let_expr, if_then, if_else);
+ }
+ }
+ None
+}
+
+fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurrence> {
+ if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
+ if let Some((let_pat, if_then, if_else)) = try_convert_match(cx, arms) {
+ return try_get_option_occurrence(cx, let_pat, ex, if_then, if_else);
+ }
+ }
+ None
+}
+
+fn try_convert_match<'tcx>(
+ cx: &LateContext<'tcx>,
+ arms: &[Arm<'tcx>],
+) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
++ if let [first_arm, second_arm] = arms
++ && first_arm.guard.is_none()
++ && second_arm.guard.is_none()
++ {
++ return if is_none_or_err_arm(cx, second_arm) {
++ Some((first_arm.pat, first_arm.body, second_arm.body))
++ } else if is_none_or_err_arm(cx, first_arm) {
++ Some((second_arm.pat, second_arm.body, first_arm.body))
+ } else {
+ None
+ };
+ }
+ None
+}
+
+fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ match arm.pat.kind {
+ PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone),
+ PatKind::TupleStruct(ref qpath, [first_pat], _) => {
+ is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr)
+ && matches!(first_pat.kind, PatKind::Wild)
+ },
+ PatKind::Wild => true,
+ _ => false,
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ // Don't lint macros and constants
+ if expr.span.from_expansion() || in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ let detection = detect_option_if_let_else(cx, expr).or_else(|| detect_option_match(cx, expr));
+ if let Some(det) = detection {
+ span_lint_and_sugg(
+ cx,
+ OPTION_IF_LET_ELSE,
+ expr.span,
+ format!("use Option::{} instead of an if let/else", det.method_sugg).as_str(),
+ "try",
+ format!(
+ "{}.{}({}, {})",
+ det.option, det.method_sugg, det.none_expr, det.some_expr
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+}
--- /dev/null
- #[clippy::version = "1.64.0"]
+use clippy_utils::{
+ diagnostics::span_lint_and_sugg, is_res_lang_ctor, path_res, peel_hir_expr_refs, peel_ref_operators, sugg,
+ ty::is_type_diagnostic_item,
+};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for binary comparisons to a literal `Option::None`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// A programmer checking if some `foo` is `None` via a comparison `foo == None`
+ /// is usually inspired from other programming languages (e.g. `foo is None`
+ /// in Python).
+ /// Checking if a value of type `Option<T>` is (not) equal to `None` in that
+ /// way relies on `T: PartialEq` to do the comparison, which is unneeded.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(f: Option<u32>) -> &'static str {
+ /// if f != None { "yay" } else { "nay" }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn foo(f: Option<u32>) -> &'static str {
+ /// if f.is_some() { "yay" } else { "nay" }
+ /// }
+ /// ```
++ #[clippy::version = "1.65.0"]
+ pub PARTIALEQ_TO_NONE,
+ style,
+ "Binary comparison to `Option<T>::None` relies on `T: PartialEq`, which is unneeded"
+}
+declare_lint_pass!(PartialeqToNone => [PARTIALEQ_TO_NONE]);
+
+impl<'tcx> LateLintPass<'tcx> for PartialeqToNone {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ // Skip expanded code, as we have no control over it anyway...
+ if e.span.from_expansion() {
+ return;
+ }
+
+ // If the expression is of type `Option`
+ let is_ty_option =
+ |expr: &Expr<'_>| is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr).peel_refs(), sym::Option);
+
+ // If the expression is a literal `Option::None`
+ let is_none_ctor = |expr: &Expr<'_>| {
+ !expr.span.from_expansion()
+ && is_res_lang_ctor(cx, path_res(cx, peel_hir_expr_refs(expr).0), LangItem::OptionNone)
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ if let ExprKind::Binary(op, left_side, right_side) = e.kind {
+ // All other comparisons (e.g. `>= None`) have special meaning wrt T
+ let is_eq = match op.node {
+ BinOpKind::Eq => true,
+ BinOpKind::Ne => false,
+ _ => return,
+ };
+
+ // We are only interested in comparisons between `Option` and a literal `Option::None`
+ let scrutinee = match (
+ is_none_ctor(left_side) && is_ty_option(right_side),
+ is_none_ctor(right_side) && is_ty_option(left_side),
+ ) {
+ (true, false) => right_side,
+ (false, true) => left_side,
+ _ => return,
+ };
+
+ // Peel away refs/derefs (as long as we don't cross manual deref impls), as
+ // autoref/autoderef will take care of those
+ let sugg = format!(
+ "{}.{}",
+ sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability)
+ .maybe_par(),
+ if is_eq { "is_none()" } else { "is_some()" }
+ );
+
+ span_lint_and_sugg(
+ cx,
+ PARTIALEQ_TO_NONE,
+ e.span,
+ "binary comparison to literal `Option::None`",
+ if is_eq {
+ "use `Option::is_none()` instead"
+ } else {
+ "use `Option::is_some()` instead"
+ },
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{
+ intravisit, Body, Expr, ExprKind, FnDecl, HirId, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for patterns that aren't exact representations of the types
+ /// they are applied to.
+ ///
+ /// To satisfy this lint, you will have to adjust either the expression that is matched
+ /// against or the pattern itself, as well as the bindings that are introduced by the
+ /// adjusted patterns. For matching you will have to either dereference the expression
+ /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
+ /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
+ /// to use the inverse. You can leave them as plain bindings if you wish for the value
+ /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
+ /// a reference into the matched structure.
+ ///
+ /// If you are looking for a way to learn about ownership semantics in more detail, it
+ /// is recommended to look at IDE options available to you to highlight types, lifetimes
+ /// and reference semantics in your code. The available tooling would expose these things
+ /// in a general way even outside of the various pattern matching mechanics. Of course
+ /// this lint can still be used to highlight areas of interest and ensure a good understanding
+ /// of ownership semantics.
+ ///
+ /// ### Why is this bad?
+ /// It isn't bad in general. But in some contexts it can be desirable
+ /// because it increases ownership hints in the code, and will guard against some changes
+ /// in ownership.
+ ///
+ /// ### Example
+ /// This example shows the basic adjustments necessary to satisfy the lint. Note how
+ /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
+ /// is bound to a shared borrow via `ref inner`.
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// let value = &Some(Box::new(23));
+ /// match value {
+ /// Some(inner) => println!("{}", inner),
+ /// None => println!("none"),
+ /// }
+ ///
+ /// // Good
+ /// let value = &Some(Box::new(23));
+ /// match *value {
+ /// Some(ref inner) => println!("{}", inner),
+ /// None => println!("none"),
+ /// }
+ /// ```
+ ///
+ /// The following example demonstrates one of the advantages of the more verbose style.
+ /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
+ /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
+ /// accidentally modify the wrong part of the structure.
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// let mut values = vec![(2, 3), (3, 4)];
+ /// for (a, b) in &mut values {
+ /// *a += *b;
+ /// }
+ ///
+ /// // Good
+ /// let mut values = vec![(2, 3), (3, 4)];
+ /// for &mut (ref mut a, b) in &mut values {
+ /// *a += b;
+ /// }
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub PATTERN_TYPE_MISMATCH,
+ restriction,
+ "type of pattern does not match the expression type"
+}
+
+declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
+
+impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if let StmtKind::Local(local) = stmt.kind {
+ if in_external_macro(cx.sess(), local.pat.span) {
+ return;
+ }
+ let deref_possible = match local.source {
+ LocalSource::Normal => DerefPossible::Possible,
+ _ => DerefPossible::Impossible,
+ };
+ apply_lint(cx, local.pat, deref_possible);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Match(_, arms, _) = expr.kind {
+ for arm in arms {
+ let pat = &arm.pat;
+ if apply_lint(cx, pat, DerefPossible::Possible) {
+ break;
+ }
+ }
+ }
+ if let ExprKind::Let(Let { pat, .. }) = expr.kind {
+ apply_lint(cx, pat, DerefPossible::Possible);
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ for param in body.params {
+ apply_lint(cx, param.pat, DerefPossible::Impossible);
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum DerefPossible {
+ Possible,
+ Impossible,
+}
+
- fn find_first_mismatch<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
++fn apply_lint(cx: &LateContext<'_>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
+ let maybe_mismatch = find_first_mismatch(cx, pat);
+ if let Some((span, mutability, level)) = maybe_mismatch {
+ span_lint_and_help(
+ cx,
+ PATTERN_TYPE_MISMATCH,
+ span,
+ "type of pattern does not match the expression type",
+ None,
+ &format!(
+ "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
+ match (deref_possible, level) {
+ (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
+ _ => "",
+ },
+ match mutability {
+ Mutability::Mut => "&mut _",
+ Mutability::Not => "&_",
+ },
+ ),
+ );
+ true
+ } else {
+ false
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+enum Level {
+ Top,
+ Lower,
+}
+
++fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
+ let mut result = None;
+ pat.walk(|p| {
+ if result.is_some() {
+ return false;
+ }
+ if in_external_macro(cx.sess(), p.span) {
+ return true;
+ }
+ let adjust_pat = match p.kind {
+ PatKind::Or([p, ..]) => p,
+ _ => p,
+ };
+ if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
+ if let [first, ..] = **adjustments {
+ if let ty::Ref(.., mutability) = *first.kind() {
+ let level = if p.hir_id == pat.hir_id {
+ Level::Top
+ } else {
+ Level::Lower
+ };
+ result = Some((p.span, mutability, level));
+ }
+ }
+ }
+ result.is_none()
+ });
+ result
+}
--- /dev/null
- fn is_expr_ty_usize<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet_opt;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+use std::fmt;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of the `offset` pointer method with a `usize` casted to an
+ /// `isize`.
+ ///
+ /// ### Why is this bad?
+ /// If we’re always increasing the pointer address, we can avoid the numeric
+ /// cast by using the `add` method instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![b'a', b'b', b'c'];
+ /// let ptr = vec.as_ptr();
+ /// let offset = 1_usize;
+ ///
+ /// unsafe {
+ /// ptr.offset(offset as isize);
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// let vec = vec![b'a', b'b', b'c'];
+ /// let ptr = vec.as_ptr();
+ /// let offset = 1_usize;
+ ///
+ /// unsafe {
+ /// ptr.add(offset);
+ /// }
+ /// ```
+ #[clippy::version = "1.30.0"]
+ pub PTR_OFFSET_WITH_CAST,
+ complexity,
+ "unneeded pointer offset cast"
+}
+
+declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]);
+
+impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call
+ let Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else {
+ return
+ };
+
+ // Check if the argument to the method call is a cast from usize
+ let Some(cast_lhs_expr) = expr_as_cast_from_usize(cx, arg_expr) else {
+ return
+ };
+
+ let msg = format!("use of `{method}` with a `usize` casted to an `isize`");
+ if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) {
+ span_lint_and_sugg(
+ cx,
+ PTR_OFFSET_WITH_CAST,
+ expr.span,
+ &msg,
+ "try",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg);
+ }
+ }
+}
+
+// If the given expression is a cast from a usize, return the lhs of the cast
+fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind {
+ if is_expr_ty_usize(cx, cast_lhs_expr) {
+ return Some(cast_lhs_expr);
+ }
+ }
+ None
+}
+
+// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the
+// receiver, the arg of the method call, and the method.
+fn expr_as_ptr_offset_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
+ if let ExprKind::MethodCall(path_segment, arg_0, [arg_1, ..], _) = &expr.kind {
+ if is_expr_ty_raw_ptr(cx, arg_0) {
+ if path_segment.ident.name == sym::offset {
+ return Some((arg_0, arg_1, Method::Offset));
+ }
+ if path_segment.ident.name == sym!(wrapping_offset) {
+ return Some((arg_0, arg_1, Method::WrappingOffset));
+ }
+ }
+ }
+ None
+}
+
+// Is the type of the expression a usize?
- fn is_expr_ty_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
++fn is_expr_ty_usize(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize
+}
+
+// Is the type of the expression a raw pointer?
- fn build_suggestion<'tcx>(
- cx: &LateContext<'tcx>,
++fn is_expr_ty_raw_ptr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr).is_unsafe_ptr()
+}
+
++fn build_suggestion(
++ cx: &LateContext<'_>,
+ method: Method,
+ receiver_expr: &Expr<'_>,
+ cast_lhs_expr: &Expr<'_>,
+) -> Option<String> {
+ let receiver = snippet_opt(cx, receiver_expr.span)?;
+ let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?;
+ Some(format!("{receiver}.{}({cast_lhs})", method.suggestion()))
+}
+
+#[derive(Copy, Clone)]
+enum Method {
+ Offset,
+ WrappingOffset,
+}
+
+impl Method {
+ #[must_use]
+ fn suggestion(self) -> &'static str {
+ match self {
+ Self::Offset => "add",
+ Self::WrappingOffset => "wrapping_add",
+ }
+ }
+}
+
+impl fmt::Display for Method {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Offset => write!(f, "offset"),
+ Self::WrappingOffset => write!(f, "wrapping_offset"),
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{
+ eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor, path_to_local, path_to_local_id,
+ peel_blocks, peel_blocks_with_stmt,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
+use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, Node, PatKind, PathSegment, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, symbol::Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions that could be replaced by the question mark operator.
+ ///
+ /// ### Why is this bad?
+ /// Question mark usage is more idiomatic.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if option.is_none() {
+ /// return None;
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```ignore
+ /// option?;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub QUESTION_MARK,
+ style,
+ "checks for expressions that could be replaced by the question mark operator"
+}
+
+declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
+
+enum IfBlockType<'hir> {
+ /// An `if x.is_xxx() { a } else { b } ` expression.
+ ///
+ /// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)
+ IfIs(
+ &'hir Expr<'hir>,
+ Ty<'hir>,
+ Symbol,
+ &'hir Expr<'hir>,
+ Option<&'hir Expr<'hir>>,
+ ),
+ /// An `if let Xxx(a) = b { c } else { d }` expression.
+ ///
+ /// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
+ /// if_else (d)
+ IfLet(
+ Res,
+ Ty<'hir>,
+ Symbol,
+ &'hir Expr<'hir>,
+ &'hir Expr<'hir>,
+ Option<&'hir Expr<'hir>>,
+ ),
+}
+
+/// Checks if the given expression on the given context matches the following structure:
+///
+/// ```ignore
+/// if option.is_none() {
+/// return None;
+/// }
+/// ```
+///
+/// ```ignore
+/// if result.is_err() {
+/// return result;
+/// }
+/// ```
+///
+/// If it matches, it will suggest to use the question mark operator instead
+fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
+ if !is_else_clause(cx.tcx, expr);
+ if let ExprKind::MethodCall(segment, caller, ..) = &cond.kind;
+ let caller_ty = cx.typeck_results().expr_ty(caller);
+ let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else);
+ if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
+ let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.param_env) &&
+ !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
+ let sugg = if let Some(else_inner) = r#else {
+ if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
+ format!("Some({receiver_str}?)")
+ } else {
+ return;
+ }
+ } else {
+ format!("{receiver_str}{}?;", if by_ref { ".as_ref()" } else { "" })
+ };
+
+ span_lint_and_sugg(
+ cx,
+ QUESTION_MARK,
+ expr.span,
+ "this block may be rewritten with the `?` operator",
+ "replace it with",
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ if_chain! {
+ if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr);
+ if !is_else_clause(cx.tcx, expr);
+ if let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind;
+ if ddpos.as_opt_usize().is_none();
+ if let PatKind::Binding(BindingAnnotation(by_ref, _), bind_id, ident, None) = field.kind;
+ let caller_ty = cx.typeck_results().expr_ty(let_expr);
+ let if_block = IfBlockType::IfLet(
+ cx.qpath_res(path1, let_pat.hir_id),
+ caller_ty,
+ ident.name,
+ let_expr,
+ if_then,
+ if_else
+ );
+ if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
+ || is_early_return(sym::Result, cx, &if_block);
+ if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
+ let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
+ let sugg = format!(
+ "{receiver_str}{}?{}",
+ if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
+ if requires_semi { ";" } else { "" }
+ );
+ span_lint_and_sugg(
+ cx,
+ QUESTION_MARK,
+ expr.span,
+ "this block may be rewritten with the `?` operator",
+ "replace it with",
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
+
+fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool {
+ match *if_block {
+ IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => {
+ // If the block could be identified as `if x.is_none()/is_err()`,
+ // we then only need to check the if_then return to see if it is none/err.
+ is_type_diagnostic_item(cx, caller_ty, smbl)
+ && expr_return_none_or_err(smbl, cx, if_then, caller, None)
+ && match smbl {
+ sym::Option => call_sym == sym!(is_none),
+ sym::Result => call_sym == sym!(is_err),
+ _ => false,
+ }
+ },
+ IfBlockType::IfLet(res, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => {
+ is_type_diagnostic_item(cx, let_expr_ty, smbl)
+ && match smbl {
+ sym::Option => {
+ // We only need to check `if let Some(x) = option` not `if let None = option`,
+ // because the later one will be suggested as `if option.is_none()` thus causing conflict.
+ is_res_lang_ctor(cx, res, OptionSome)
+ && if_else.is_some()
+ && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None)
+ },
+ sym::Result => {
+ (is_res_lang_ctor(cx, res, ResultOk)
+ && if_else.is_some()
+ && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym)))
+ || is_res_lang_ctor(cx, res, ResultErr)
+ && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym))
++ && if_else.is_none()
+ },
+ _ => false,
+ }
+ },
+ }
+}
+
+fn expr_return_none_or_err(
+ smbl: Symbol,
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cond_expr: &Expr<'_>,
+ err_sym: Option<Symbol>,
+) -> bool {
+ match peel_blocks_with_stmt(expr).kind {
+ ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym),
+ ExprKind::Path(ref qpath) => match smbl {
+ sym::Option => is_res_lang_ctor(cx, cx.qpath_res(qpath, expr.hir_id), OptionNone),
+ sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
+ _ => false,
+ },
+ ExprKind::Call(call_expr, args_expr) => {
+ if_chain! {
+ if smbl == sym::Result;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind;
+ if let Some(segment) = path.segments.first();
+ if let Some(err_sym) = err_sym;
+ if let Some(arg) = args_expr.first();
+ if let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind;
+ if let Some(PathSegment { ident, .. }) = arg_path.segments.first();
+ then {
+ return segment.ident.name == sym::Err && err_sym == ident.name;
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for QuestionMark {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !in_constant(cx, expr.hir_id) {
+ check_is_none_or_err_and_early_return(cx, expr);
+ check_if_let_some_or_err_and_early_return(cx, expr);
+ }
+ }
+}
--- /dev/null
- fn count_closure_usage<'a, 'tcx>(
- cx: &'a LateContext<'tcx>,
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit as ast_visit;
+use rustc_ast::visit::Visitor as AstVisitor;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::intravisit::Visitor as HirVisitor;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects closures called in the same expression where they
+ /// are defined.
+ ///
+ /// ### Why is this bad?
+ /// It is unnecessarily adding to the expression's
+ /// complexity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = (|| 42)();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = 42;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_CLOSURE_CALL,
+ complexity,
+ "throwaway closures called in the expression they are defined"
+}
+
+declare_lint_pass!(RedundantClosureCall => [REDUNDANT_CLOSURE_CALL]);
+
+// Used to find `return` statements or equivalents e.g., `?`
+struct ReturnVisitor {
+ found_return: bool,
+}
+
+impl ReturnVisitor {
+ #[must_use]
+ fn new() -> Self {
+ Self { found_return: false }
+ }
+}
+
+impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor {
+ fn visit_expr(&mut self, ex: &'ast ast::Expr) {
+ if let ast::ExprKind::Ret(_) | ast::ExprKind::Try(_) = ex.kind {
+ self.found_return = true;
+ }
+
+ ast_visit::walk_expr(self, ex);
+ }
+}
+
+impl EarlyLintPass for RedundantClosureCall {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ if_chain! {
+ if let ast::ExprKind::Call(ref paren, _) = expr.kind;
+ if let ast::ExprKind::Paren(ref closure) = paren.kind;
+ if let ast::ExprKind::Closure(box ast::Closure { ref asyncness, ref fn_decl, ref body, .. }) = closure.kind;
+ then {
+ let mut visitor = ReturnVisitor::new();
+ visitor.visit_expr(body);
+ if !visitor.found_return {
+ span_lint_and_then(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ expr.span,
+ "try not to call a closure in the expression where it is declared",
+ |diag| {
+ if fn_decl.inputs.is_empty() {
+ let app = Applicability::MachineApplicable;
+ let mut hint = Sugg::ast(cx, body, "..");
+
+ if asyncness.is_async() {
+ // `async x` is a syntax error, so it becomes `async { x }`
+ if !matches!(body.kind, ast::ExprKind::Block(_, _)) {
+ hint = hint.blockify();
+ }
+
+ hint = hint.asyncify();
+ }
+
+ diag.span_suggestion(expr.span, "try doing something like", hint.to_string(), app);
+ }
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
++ fn count_closure_usage<'tcx>(
++ cx: &LateContext<'tcx>,
+ block: &'tcx hir::Block<'_>,
+ path: &'tcx hir::Path<'tcx>,
+ ) -> usize {
+ struct ClosureUsageCount<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ path: &'tcx hir::Path<'tcx>,
+ count: usize,
+ }
+ impl<'a, 'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if_chain! {
+ if let hir::ExprKind::Call(closure, _) = expr.kind;
+ if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = closure.kind;
+ if self.path.segments[0].ident == path.segments[0].ident;
+ if self.path.res == path.res;
+ then {
+ self.count += 1;
+ }
+ }
+ hir_visit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+ }
+ let mut closure_usage_count = ClosureUsageCount { cx, path, count: 0 };
+ closure_usage_count.visit_block(block);
+ closure_usage_count.count
+ }
+
+ for w in block.stmts.windows(2) {
+ if_chain! {
+ if let hir::StmtKind::Local(local) = w[0].kind;
+ if let Option::Some(t) = local.init;
+ if let hir::ExprKind::Closure { .. } = t.kind;
+ if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind;
+ if let hir::StmtKind::Semi(second) = w[1].kind;
+ if let hir::ExprKind::Assign(_, call, _) = second.kind;
+ if let hir::ExprKind::Call(closure, _) = call.kind;
+ if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = closure.kind;
+ if ident == path.segments[0].ident;
+ if count_closure_usage(cx, block, path) == 1;
+ then {
+ span_lint(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ second.span,
+ "closure called just once immediately after it was declared",
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- self.is_exported.push(cx.effective_visibilities.is_exported(item.owner_id.def_id));
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::CRATE_DEF_ID;
+use rustc_span::hygiene::MacroKind;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items declared `pub(crate)` that are not crate visible because they
+ /// are inside a private module.
+ ///
+ /// ### Why is this bad?
+ /// Writing `pub(crate)` is misleading when it's redundant due to the parent
+ /// module's visibility.
+ ///
+ /// ### Example
+ /// ```rust
+ /// mod internal {
+ /// pub(crate) fn internal_fn() { }
+ /// }
+ /// ```
+ /// This function is not visible outside the module and it can be declared with `pub` or
+ /// private visibility
+ /// ```rust
+ /// mod internal {
+ /// pub fn internal_fn() { }
+ /// }
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub REDUNDANT_PUB_CRATE,
+ nursery,
+ "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them."
+}
+
+#[derive(Default)]
+pub struct RedundantPubCrate {
+ is_exported: Vec<bool>,
+}
+
+impl_lint_pass!(RedundantPubCrate => [REDUNDANT_PUB_CRATE]);
+
+impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if_chain! {
+ if cx.tcx.visibility(item.owner_id.def_id) == ty::Visibility::Restricted(CRATE_DEF_ID.to_def_id());
+ if !cx.effective_visibilities.is_exported(item.owner_id.def_id) && self.is_exported.last() == Some(&false);
+ if is_not_macro_export(item);
+ then {
+ let span = item.span.with_hi(item.ident.span.hi());
+ let descr = cx.tcx.def_kind(item.owner_id).descr(item.owner_id.to_def_id());
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PUB_CRATE,
+ span,
+ &format!("pub(crate) {descr} inside private module"),
+ |diag| {
+ diag.span_suggestion(
+ item.vis_span,
+ "consider using",
+ "pub".to_string(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+
+ if let ItemKind::Mod { .. } = item.kind {
++ self.is_exported
++ .push(cx.effective_visibilities.is_exported(item.owner_id.def_id));
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if let ItemKind::Mod { .. } = item.kind {
+ self.is_exported.pop().expect("unbalanced check_item/check_item_post");
+ }
+ }
+}
+
+fn is_not_macro_export<'tcx>(item: &'tcx Item<'tcx>) -> bool {
+ if let ItemKind::Use(path, _) = item.kind {
+ if let Res::Def(DefKind::Macro(MacroKind::Bang), _) = path.res {
+ return false;
+ }
+ } else if let ItemKind::Macro(..) = item.kind {
+ return false;
+ }
+
+ true
+}
--- /dev/null
- ("clippy::for_loop_over_option", "for_loops_over_fallibles"),
- ("clippy::for_loop_over_result", "for_loops_over_fallibles"),
+// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.
+
+#[rustfmt::skip]
+pub static RENAMED_LINTS: &[(&str, &str)] = &[
+ ("clippy::blacklisted_name", "clippy::disallowed_names"),
+ ("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"),
+ ("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"),
+ ("clippy::box_vec", "clippy::box_collection"),
+ ("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"),
+ ("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"),
+ ("clippy::disallowed_method", "clippy::disallowed_methods"),
+ ("clippy::disallowed_type", "clippy::disallowed_types"),
+ ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
+ ("clippy::identity_conversion", "clippy::useless_conversion"),
+ ("clippy::if_let_some_result", "clippy::match_result_ok"),
+ ("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
+ ("clippy::new_without_default_derive", "clippy::new_without_default"),
+ ("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
+ ("clippy::option_expect_used", "clippy::expect_used"),
+ ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"),
+ ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"),
+ ("clippy::option_unwrap_used", "clippy::unwrap_used"),
+ ("clippy::ref_in_deref", "clippy::needless_borrow"),
+ ("clippy::result_expect_used", "clippy::expect_used"),
+ ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
+ ("clippy::result_unwrap_used", "clippy::unwrap_used"),
+ ("clippy::single_char_push_str", "clippy::single_char_add_str"),
+ ("clippy::stutter", "clippy::module_name_repetitions"),
+ ("clippy::to_string_in_display", "clippy::recursive_format_impl"),
+ ("clippy::zero_width_space", "clippy::invisible_characters"),
+ ("clippy::drop_bounds", "drop_bounds"),
++ ("clippy::for_loop_over_option", "for_loops_over_fallibles"),
++ ("clippy::for_loop_over_result", "for_loops_over_fallibles"),
+ ("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
+ ("clippy::into_iter_on_array", "array_into_iter"),
+ ("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
+ ("clippy::invalid_ref", "invalid_value"),
++ ("clippy::let_underscore_drop", "let_underscore_drop"),
+ ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
+ ("clippy::panic_params", "non_fmt_panics"),
+ ("clippy::positional_named_format_parameters", "named_arguments_used_positionally"),
+ ("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"),
+ ("clippy::unknown_clippy_lints", "unknown_lints"),
+ ("clippy::unused_label", "unused_labels"),
+];
--- /dev/null
- use rustc_session::{declare_lint_pass, declare_tool_lint};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
++use rustc_ast::node_id::{NodeId, NodeMap};
+use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
- declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]);
++use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{edition::Edition, symbol::kw, Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checking for imports with single component use path.
+ ///
+ /// ### Why is this bad?
+ /// Import with single component use path such as `use cratename;`
+ /// is not necessary, and thus should be removed.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use regex;
+ ///
+ /// fn main() {
+ /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
+ /// }
+ /// ```
+ /// Better as
+ /// ```rust,ignore
+ /// fn main() {
+ /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
+ /// }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub SINGLE_COMPONENT_PATH_IMPORTS,
+ style,
+ "imports with single component path are redundant"
+}
+
- check_mod(cx, &krate.items);
- }
- }
++#[derive(Default)]
++pub struct SingleComponentPathImports {
++ /// Buffer found usages to emit when visiting that item so that `#[allow]` works as expected
++ found: NodeMap<Vec<SingleUse>>,
++}
++
++struct SingleUse {
++ name: Symbol,
++ span: Span,
++ item_id: NodeId,
++ can_suggest: bool,
++}
++
++impl_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]);
+
+impl EarlyLintPass for SingleComponentPathImports {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+ if cx.sess().opts.edition < Edition::Edition2018 {
+ return;
+ }
- fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) {
- // keep track of imports reused with `self` keyword,
- // such as `self::crypto_hash` in the example below
- // ```rust,ignore
- // use self::crypto_hash::{Algorithm, Hasher};
- // ```
- let mut imports_reused_with_self = Vec::new();
-
- // keep track of single use statements
- // such as `crypto_hash` in the example below
- // ```rust,ignore
- // use crypto_hash;
- // ```
- let mut single_use_usages = Vec::new();
-
- // keep track of macros defined in the module as we don't want it to trigger on this (#7106)
- // ```rust,ignore
- // macro_rules! foo { () => {} };
- // pub(crate) use foo;
- // ```
- let mut macros = Vec::new();
-
- for item in items {
- track_uses(
- cx,
- item,
- &mut imports_reused_with_self,
- &mut single_use_usages,
- &mut macros,
- );
+
- for (name, span, can_suggest) in single_use_usages {
- if !imports_reused_with_self.contains(&name) {
++ self.check_mod(cx, &krate.items);
+ }
+
- fn track_uses(
- cx: &EarlyContext<'_>,
- item: &Item,
- imports_reused_with_self: &mut Vec<Symbol>,
- single_use_usages: &mut Vec<(Symbol, Span, bool)>,
- macros: &mut Vec<Symbol>,
- ) {
- if item.span.from_expansion() || item.vis.kind.is_pub() {
- return;
- }
++ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
++ for SingleUse { span, can_suggest, .. } in self.found.remove(&item.id).into_iter().flatten() {
+ if can_suggest {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_COMPONENT_PATH_IMPORTS,
+ span,
+ "this import is redundant",
+ "remove it entirely",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_help(
+ cx,
+ SINGLE_COMPONENT_PATH_IMPORTS,
+ span,
+ "this import is redundant",
+ None,
+ "remove this import",
+ );
+ }
+ }
+ }
+}
+
- match &item.kind {
- ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
- check_mod(cx, items);
- },
- ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
- macros.push(item.ident.name);
- },
- ItemKind::Use(use_tree) => {
- let segments = &use_tree.prefix.segments;
-
- // 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 !macros.contains(&name) {
- single_use_usages.push((name, item.span, true));
- }
- }
- return;
++impl SingleComponentPathImports {
++ fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
++ // keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example
++ // below. Removing the `use crypto_hash;` would make this a compile error
++ // ```
++ // use crypto_hash;
++ //
++ // use self::crypto_hash::{Algorithm, Hasher};
++ // ```
++ let mut imports_reused_with_self = Vec::new();
+
- if segments.is_empty() {
- // keep track of `use {some_module, some_other_module};` usages
- if let UseTreeKind::Nested(trees) = &use_tree.kind {
- for tree in trees {
- let segments = &tree.0.prefix.segments;
- if segments.len() == 1 {
- if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
- let name = segments[0].ident.name;
- if !macros.contains(&name) {
- single_use_usages.push((name, tree.0.span, false));
- }
- }
++ // keep track of single use statements such as `crypto_hash` in the example below
++ // ```
++ // use crypto_hash;
++ // ```
++ let mut single_use_usages = Vec::new();
++
++ // keep track of macros defined in the module as we don't want it to trigger on this (#7106)
++ // ```
++ // macro_rules! foo { () => {} };
++ // pub(crate) use foo;
++ // ```
++ let mut macros = Vec::new();
++
++ for item in items {
++ self.track_uses(
++ cx,
++ item,
++ &mut imports_reused_with_self,
++ &mut single_use_usages,
++ &mut macros,
++ );
++ }
++
++ for usage in single_use_usages {
++ if !imports_reused_with_self.contains(&usage.name) {
++ self.found.entry(usage.item_id).or_default().push(usage);
+ }
++ }
++ }
+
- } else {
- // keep track of `use self::some_module` usages
- if segments[0].ident.name == kw::SelfLower {
- // simple case such as `use self::module::SomeStruct`
- if segments.len() > 1 {
- imports_reused_with_self.push(segments[1].ident.name);
- return;
- }
++ fn track_uses(
++ &mut self,
++ cx: &EarlyContext<'_>,
++ item: &Item,
++ imports_reused_with_self: &mut Vec<Symbol>,
++ single_use_usages: &mut Vec<SingleUse>,
++ macros: &mut Vec<Symbol>,
++ ) {
++ if item.span.from_expansion() || item.vis.kind.is_pub() {
++ return;
++ }
++
++ match &item.kind {
++ ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
++ self.check_mod(cx, items);
++ },
++ ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
++ macros.push(item.ident.name);
++ },
++ ItemKind::Use(use_tree) => {
++ let segments = &use_tree.prefix.segments;
++
++ // 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 !macros.contains(&name) {
++ single_use_usages.push(SingleUse {
++ name,
++ span: item.span,
++ item_id: item.id,
++ can_suggest: true,
++ });
+ }
+ }
++ return;
+ }
- // nested case such as `use self::{module1::Struct1, module2::Struct2}`
+
- if !segments.is_empty() {
- imports_reused_with_self.push(segments[0].ident.name);
++ if segments.is_empty() {
++ // keep track of `use {some_module, some_other_module};` usages
+ if let UseTreeKind::Nested(trees) = &use_tree.kind {
+ for tree in trees {
+ let segments = &tree.0.prefix.segments;
- }
- },
- _ => {},
++ if segments.len() == 1 {
++ if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
++ let name = segments[0].ident.name;
++ if !macros.contains(&name) {
++ single_use_usages.push(SingleUse {
++ name,
++ span: tree.0.span,
++ item_id: item.id,
++ can_suggest: false,
++ });
++ }
++ }
++ }
++ }
++ }
++ } else {
++ // keep track of `use self::some_module` usages
++ if segments[0].ident.name == kw::SelfLower {
++ // simple case such as `use self::module::SomeStruct`
++ if segments.len() > 1 {
++ imports_reused_with_self.push(segments[1].ident.name);
++ return;
++ }
++
++ // nested case such as `use self::{module1::Struct1, module2::Struct2}`
++ if let UseTreeKind::Nested(trees) = &use_tree.kind {
++ for tree in trees {
++ let segments = &tree.0.prefix.segments;
++ if !segments.is_empty() {
++ imports_reused_with_self.push(segments[0].ident.name);
++ }
+ }
+ }
+ }
+ }
++ },
++ _ => {},
++ }
+ }
+}
--- /dev/null
- fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{
+ get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor};
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks slow zero-filled vector initialization
+ ///
+ /// ### Why is this bad?
+ /// These structures are non-idiomatic and less efficient than simply using
+ /// `vec![0; len]`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use core::iter::repeat;
+ /// # let len = 4;
+ /// let mut vec1 = Vec::with_capacity(len);
+ /// vec1.resize(len, 0);
+ ///
+ /// let mut vec1 = Vec::with_capacity(len);
+ /// vec1.resize(vec1.capacity(), 0);
+ ///
+ /// let mut vec2 = Vec::with_capacity(len);
+ /// vec2.extend(repeat(0).take(len));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let len = 4;
+ /// let mut vec1 = vec![0; len];
+ /// let mut vec2 = vec![0; len];
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub SLOW_VECTOR_INITIALIZATION,
+ perf,
+ "slow vector initialization"
+}
+
+declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
+
+/// `VecAllocation` contains data regarding a vector allocated with `with_capacity` and then
+/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
+/// `vec = Vec::with_capacity(0)`
+struct VecAllocation<'tcx> {
+ /// HirId of the variable
+ local_id: HirId,
+
+ /// Reference to the expression which allocates the vector
+ allocation_expr: &'tcx Expr<'tcx>,
+
+ /// Reference to the expression used as argument on `with_capacity` call. This is used
+ /// to only match slow zero-filling idioms of the same length than vector initialization.
+ len_expr: &'tcx Expr<'tcx>,
+}
+
+/// Type of slow initialization
+enum InitializationType<'tcx> {
+ /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))`
+ Extend(&'tcx Expr<'tcx>),
+
+ /// Resize is a slow initialization with the form `vec.resize(.., 0)`
+ Resize(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)`
+ if_chain! {
+ if let ExprKind::Assign(left, right, _) = expr.kind;
+
+ // Extract variable
+ if let Some(local_id) = path_to_local(left);
+
+ // Extract len argument
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, right);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: right,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, expr.hir_id);
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
+ if_chain! {
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(BindingAnnotation::MUT, local_id, _, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, init);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: init,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, stmt.hir_id);
+ }
+ }
+ }
+}
+
+impl SlowVectorInit {
+ /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
+ /// of the first argument of `with_capacity` call if it matches or `None` if it does not.
+ fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = expr.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind;
+ if name.ident.as_str() == "with_capacity";
+ if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Search initialization for the given vector
+ fn search_initialization<'tcx>(cx: &LateContext<'tcx>, vec_alloc: VecAllocation<'tcx>, parent_node: HirId) {
+ let enclosing_body = get_enclosing_block(cx, parent_node);
+
+ if enclosing_body.is_none() {
+ return;
+ }
+
+ let mut v = VectorInitializationVisitor {
+ cx,
+ vec_alloc,
+ slow_expression: None,
+ initialization_found: false,
+ };
+
+ v.visit_block(enclosing_body.unwrap());
+
+ if let Some(ref allocation_expr) = v.slow_expression {
+ Self::lint_initialization(cx, allocation_expr, &v.vec_alloc);
+ }
+ }
+
+ fn lint_initialization<'tcx>(
+ cx: &LateContext<'tcx>,
+ initialization: &InitializationType<'tcx>,
+ vec_alloc: &VecAllocation<'_>,
+ ) {
+ match initialization {
+ InitializationType::Extend(e) | InitializationType::Resize(e) => {
+ Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization");
+ },
+ };
+ }
+
++ fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
+ let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
+
+ span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
+ diag.span_suggestion(
+ vec_alloc.allocation_expr.span,
+ "consider replace allocation with",
+ format!("vec![0; {len_expr}]"),
+ Applicability::Unspecified,
+ );
+ });
+ }
+}
+
+/// `VectorInitializationVisitor` searches for unsafe or slow vector initializations for the given
+/// vector.
+struct VectorInitializationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+
+ /// Contains the information.
+ vec_alloc: VecAllocation<'tcx>,
+
+ /// Contains the slow initialization expression, if one was found.
+ slow_expression: Option<InitializationType<'tcx>>,
+
+ /// `true` if the initialization of the vector has been found on the visited block.
+ initialization_found: bool,
+}
+
+impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
+ /// Checks if the given expression is extending a vector with `repeat(0).take(..)`
+ fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.initialization_found;
+ if let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind;
+ if path_to_local_id(self_arg, self.vec_alloc.local_id);
+ if path.ident.name == sym!(extend);
+ if self.is_repeat_take(extend_arg);
+
+ then {
+ self.slow_expression = Some(InitializationType::Extend(expr));
+ }
+ }
+ }
+
+ /// Checks if the given expression is resizing a vector with 0
+ fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if self.initialization_found
+ && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind
+ && path_to_local_id(self_arg, self.vec_alloc.local_id)
+ && path.ident.name == sym!(resize)
+ // Check that is filled with 0
+ && is_integer_literal(fill_arg, 0) {
+ // Check that len expression is equals to `with_capacity` expression
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
+ self.slow_expression = Some(InitializationType::Resize(expr));
+ } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" {
+ self.slow_expression = Some(InitializationType::Resize(expr));
+ }
+ }
+ }
+
+ /// Returns `true` if give expression is `repeat(0).take(...)`
+ fn is_repeat_take(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(take_path, recv, [len_arg, ..], _) = expr.kind;
+ if take_path.ident.name == sym!(take);
+ // Check that take is applied to `repeat(0)`
+ if self.is_repeat_zero(recv);
+ then {
+ // Check that len expression is equals to `with_capacity` expression
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
+ return true;
+ } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" {
+ return true;
+ }
+ }
+ }
+
+ false
+ }
+
+ /// Returns `true` if given expression is `repeat(0)`
+ fn is_repeat_zero(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, [repeat_arg]) = expr.kind;
+ if is_path_diagnostic_item(self.cx, fn_expr, sym::iter_repeat);
+ if is_integer_literal(repeat_arg, 0);
+ then {
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> {
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if self.initialization_found {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.search_slow_extend_filling(expr);
+ self.search_slow_resize_filling(expr);
+ },
+ _ => (),
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ if self.initialization_found {
+ if let Some(s) = block.stmts.get(0) {
+ self.visit_stmt(s);
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_block(self, block);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // Skip all the expressions previous to the vector initialization
+ if self.vec_alloc.allocation_expr.hir_id == expr.hir_id {
+ self.initialization_found = true;
+ }
+
+ walk_expr(self, expr);
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::{numeric_literal::NumericLiteral, source::snippet_with_context};
++use rustc_errors::Applicability;
++use rustc_hir::{BinOpKind, Expr, ExprKind};
++use rustc_lint::{LateContext, LateLintPass, LintContext};
++use rustc_middle::lint::in_external_macro;
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal.
++ /// ### Why is this bad?
++ /// It's most probably a typo and may lead to unexpected behaviours.
++ /// ### Example
++ /// ```rust
++ /// let x = 3_i32 ^ 4_i32;
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// let x = 3_i32.pow(4);
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub SUSPICIOUS_XOR_USED_AS_POW,
++ restriction,
++ "XOR (`^`) operator possibly used as exponentiation operator"
++}
++declare_lint_pass!(ConfusingXorAndPow => [SUSPICIOUS_XOR_USED_AS_POW]);
++
++impl LateLintPass<'_> for ConfusingXorAndPow {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
++ if !in_external_macro(cx.sess(), expr.span) &&
++ let ExprKind::Binary(op, left, right) = &expr.kind &&
++ op.node == BinOpKind::BitXor &&
++ left.span.ctxt() == right.span.ctxt() &&
++ let ExprKind::Lit(lit_left) = &left.kind &&
++ let ExprKind::Lit(lit_right) = &right.kind &&
++ let snip_left = snippet_with_context(cx, lit_left.span, lit_left.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) &&
++ let snip_right = snippet_with_context(cx, lit_right.span, lit_right.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) &&
++ let Some(left_val) = NumericLiteral::from_lit_kind(&snip_left.0, &lit_left.node) &&
++ let Some(right_val) = NumericLiteral::from_lit_kind(&snip_right.0, &lit_right.node) &&
++ left_val.is_decimal() &&
++ right_val.is_decimal() {
++ clippy_utils::diagnostics::span_lint_and_sugg(
++ cx,
++ SUSPICIOUS_XOR_USED_AS_POW,
++ expr.span,
++ "`^` is not the exponentiation operator",
++ "did you mean to write",
++ format!("{}.pow({})", left_val.format(), right_val.format()),
++ Applicability::MaybeIncorrect,
++ );
++ }
++ }
++}
--- /dev/null
- use clippy_utils::{can_mut_borrow_both, eq_expr_value, std_or_core};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
++use clippy_utils::{can_mut_borrow_both, eq_expr_value, in_constant, std_or_core};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual swapping.
+ ///
++ /// Note that the lint will not be emitted in const blocks, as the suggestion would not be applicable.
++ ///
+ /// ### Why is this bad?
+ /// The `std::mem::swap` function exposes the intent better
+ /// without deinitializing or copying either variable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 42;
+ /// let mut b = 1337;
+ ///
+ /// let t = b;
+ /// b = a;
+ /// a = t;
+ /// ```
+ /// Use std::mem::swap():
+ /// ```rust
+ /// let mut a = 1;
+ /// let mut b = 2;
+ /// std::mem::swap(&mut a, &mut b);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MANUAL_SWAP,
+ complexity,
+ "manual swap of two variables"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `foo = bar; bar = foo` sequences.
+ ///
+ /// ### Why is this bad?
+ /// This looks like a failed attempt to swap.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut a = 1;
+ /// # let mut b = 2;
+ /// a = b;
+ /// b = a;
+ /// ```
+ /// If swapping is intended, use `swap()` instead:
+ /// ```rust
+ /// # let mut a = 1;
+ /// # let mut b = 2;
+ /// std::mem::swap(&mut a, &mut b);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ALMOST_SWAPPED,
+ correctness,
+ "`foo = bar; bar = foo` sequence"
+}
+
+declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]);
+
+impl<'tcx> LateLintPass<'tcx> for Swap {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ check_manual_swap(cx, block);
+ check_suspicious_swap(cx, block);
+ check_xor_swap(cx, block);
+ }
+}
+
+fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ if !can_mut_borrow_both(cx, e1, e2) {
+ if let ExprKind::Index(lhs1, idx1) = e1.kind {
+ if let ExprKind::Index(lhs2, idx2) = e2.kind {
+ if eq_expr_value(cx, lhs1, lhs2) {
+ let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
+
+ if matches!(ty.kind(), ty::Slice(_))
+ || matches!(ty.kind(), ty::Array(_, _))
+ || is_type_diagnostic_item(cx, ty, sym::Vec)
+ || is_type_diagnostic_item(cx, ty, sym::VecDeque)
+ {
+ let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ MANUAL_SWAP,
+ span,
+ &format!("this looks like you are swapping elements of `{slice}` manually"),
+ "try",
+ format!(
+ "{}.swap({}, {})",
+ slice.maybe_par(),
+ snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
+ snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability);
+ let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability);
+ let Some(sugg) = std_or_core(cx) else { return };
+
+ span_lint_and_then(
+ cx,
+ MANUAL_SWAP,
+ span,
+ &format!("this looks like you are swapping `{first}` and `{second}` manually"),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try",
+ format!("{sugg}::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
+ applicability,
+ );
+ if !is_xor_based {
+ diag.note(&format!("or maybe you should use `{sugg}::mem::replace`?"));
+ }
+ },
+ );
+}
+
+/// Implementation of the `MANUAL_SWAP` lint.
+fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
++ if in_constant(cx, block.hir_id) {
++ return;
++ }
++
+ for w in block.stmts.windows(3) {
+ if_chain! {
+ // let t = foo();
+ if let StmtKind::Local(tmp) = w[0].kind;
+ if let Some(tmp_init) = tmp.init;
+ if let PatKind::Binding(.., ident, None) = tmp.pat.kind;
+
+ // foo() = bar();
+ if let StmtKind::Semi(first) = w[1].kind;
+ if let ExprKind::Assign(lhs1, rhs1, _) = first.kind;
+
+ // bar() = t;
+ if let StmtKind::Semi(second) = w[2].kind;
+ if let ExprKind::Assign(lhs2, rhs2, _) = second.kind;
+ if let ExprKind::Path(QPath::Resolved(None, rhs2)) = rhs2.kind;
+ if rhs2.segments.len() == 1;
+
+ if ident.name == rhs2.segments[0].ident.name;
+ if eq_expr_value(cx, tmp_init, lhs1);
+ if eq_expr_value(cx, rhs1, lhs2);
+ then {
+ let span = w[0].span.to(second.span);
+ generate_swap_warning(cx, lhs1, lhs2, span, false);
+ }
+ }
+ }
+}
+
+/// Implementation of the `ALMOST_SWAPPED` lint.
+fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
+ for w in block.stmts.windows(2) {
+ if_chain! {
+ if let StmtKind::Semi(first) = w[0].kind;
+ if let StmtKind::Semi(second) = w[1].kind;
+ if first.span.ctxt() == second.span.ctxt();
+ if let ExprKind::Assign(lhs0, rhs0, _) = first.kind;
+ if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
+ if eq_expr_value(cx, lhs0, rhs1);
+ if eq_expr_value(cx, lhs1, rhs0);
+ then {
+ let lhs0 = Sugg::hir_opt(cx, lhs0);
+ let rhs0 = Sugg::hir_opt(cx, rhs0);
+ let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) {
+ (
+ format!(" `{first}` and `{second}`"),
+ first.mut_addr().to_string(),
+ second.mut_addr().to_string(),
+ )
+ } else {
+ (String::new(), String::new(), String::new())
+ };
+
+ let span = first.span.to(second.span);
+ let Some(sugg) = std_or_core(cx) else { return };
+
+ span_lint_and_then(cx,
+ ALMOST_SWAPPED,
+ span,
+ &format!("this looks like you are trying to swap{what}"),
+ |diag| {
+ if !what.is_empty() {
+ diag.span_suggestion(
+ span,
+ "try",
+ format!(
+ "{sugg}::mem::swap({lhs}, {rhs})",
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ diag.note(
+ &format!("or maybe you should use `{sugg}::mem::replace`?")
+ );
+ }
+ });
+ }
+ }
+ }
+}
+
+/// Implementation of the xor case for `MANUAL_SWAP` lint.
+fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
+ for window in block.stmts.windows(3) {
+ if_chain! {
+ if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]);
+ if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]);
+ if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]);
+ if eq_expr_value(cx, lhs0, rhs1);
+ if eq_expr_value(cx, lhs2, rhs1);
+ if eq_expr_value(cx, lhs1, rhs0);
+ if eq_expr_value(cx, lhs1, rhs2);
+ then {
+ let span = window[0].span.to(window[2].span);
+ generate_swap_warning(cx, lhs0, rhs0, span, true);
+ }
+ };
+ }
+}
+
+/// Returns the lhs and rhs of an xor assignment statement.
+fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
+ if let StmtKind::Semi(expr) = stmt.kind {
+ if let ExprKind::AssignOp(
+ Spanned {
+ node: BinOpKind::BitXor,
+ ..
+ },
+ lhs,
+ rhs,
+ ) = expr.kind
+ {
+ return Some((lhs, rhs));
+ }
+ }
+ None
+}
--- /dev/null
- use rustc_hir::{HirId, Item, ItemKind};
+use clippy_utils::diagnostics::span_lint_and_help;
- use rustc_span::sym;
++use clippy_utils::has_repr_attr;
++use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Const;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
-
- fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
- cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::repr))
- }
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// 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 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
+ /// struct RarelyUseful {
+ /// some_field: u32,
+ /// last: [u32; 0],
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// struct MoreOftenUseful {
+ /// some_field: usize,
+ /// last: [u32; 0],
+ /// }
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub TRAILING_EMPTY_ARRAY,
+ nursery,
+ "struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute"
+}
+declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
+
+impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
+ span_lint_and_help(
+ cx,
+ TRAILING_EMPTY_ARRAY,
+ item.span,
+ "trailing zero-sized array in a struct which is not marked with a `repr` attribute",
+ None,
+ &format!(
+ "consider annotating `{}` with `#[repr(C)]` or another `repr` attribute",
+ cx.tcx.def_path_str(item.owner_id.to_def_id())
+ ),
+ );
+ }
+ }
+}
+
+fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if_chain! {
+ // First check if last field is an array
+ if let ItemKind::Struct(data, _) = &item.kind;
+ if let Some(last_field) = data.fields().last();
+ if let rustc_hir::TyKind::Array(_, rustc_hir::ArrayLen::Body(length)) = last_field.ty.kind;
+
+ // Then check if that that array zero-sized
+ let length_ldid = cx.tcx.hir().local_def_id(length.hir_id);
+ let length = Const::from_anon_const(cx.tcx, length_ldid);
+ let length = length.try_eval_usize(cx.tcx, cx.param_env);
+ if let Some(length) = length;
+ then {
+ length == 0
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- (
- ReducedTy::OrderedFields(_, Some(from_sub_ty)),
- ReducedTy::UnorderedFields(to_sub_ty),
- )
- | (
- ReducedTy::UnorderedFields(from_sub_ty),
- ReducedTy::OrderedFields(_, Some(to_sub_ty)),
- ) => {
+use super::TRANSMUTE_UNDEFINED_REPR;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::ty::is_c_void;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::SubstsRef;
+use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
+
+#[expect(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty_orig: Ty<'tcx>,
+ to_ty_orig: Ty<'tcx>,
+) -> bool {
+ let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
+ let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
+
+ while from_ty != to_ty {
+ let reduced_tys = reduce_refs(cx, from_ty, to_ty);
+ match (reduce_ty(cx, reduced_tys.from_ty), reduce_ty(cx, reduced_tys.to_ty)) {
+ // Various forms of type erasure.
+ (ReducedTy::TypeErasure { raw_ptr_only: false }, _)
+ | (_, ReducedTy::TypeErasure { raw_ptr_only: false }) => return false,
+ (ReducedTy::TypeErasure { .. }, _) if reduced_tys.from_raw_ptr => return false,
+ (_, ReducedTy::TypeErasure { .. }) if reduced_tys.to_raw_ptr => return false,
+
+ // `Repr(C)` <-> unordered type.
+ // If the first field of the `Repr(C)` type matches then the transmute is ok
- }
- (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty))
- if reduced_tys.to_fat_ptr =>
- {
++ (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty))
++ | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
- }
++ },
++ (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
- }
++ },
+ (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty)))
+ if reduced_tys.from_fat_ptr =>
+ {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
- }
++ },
+
+ // ptr <-> ptr
+ (ReducedTy::Other(from_sub_ty), ReducedTy::Other(to_sub_ty))
+ if matches!(from_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_))
+ && matches!(to_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) =>
+ {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
- }
++ },
+
+ // fat ptr <-> (*size, *size)
+ (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty))
+ if reduced_tys.from_fat_ptr && is_size_pair(to_ty) =>
+ {
+ return false;
- }
++ },
+ (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_))
+ if reduced_tys.to_fat_ptr && is_size_pair(from_ty) =>
+ {
+ return false;
- diag.note(&format!(
- "the contained type `{from_ty}` has an undefined layout"
- ));
++ },
+
+ // fat ptr -> some struct | some struct -> fat ptr
+ (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if from_ty_orig.peel_refs() != from_ty.peel_refs() {
- }
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
- diag.note(&format!(
- "the contained type `{to_ty}` has an undefined layout"
- ));
++ },
+ (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute to `{to_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if to_ty_orig.peel_refs() != to_ty.peel_refs() {
- }
++ diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
- (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty))
- if from_ty != to_ty =>
- {
++ },
+
- diag.note(&format!(
- "the contained type `{from_ty}` has an undefined layout"
- ));
++ (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,
+ e.span,
+ &format!(
+ "transmute from `{from_ty_orig}` to `{to_ty_orig}`, both of which have an undefined layout"
+ ),
+ |diag| {
+ if let Some(same_adt_did) = same_adt_did {
+ diag.note(&format!(
+ "two instances of the same generic type (`{}`) may have different layouts",
+ cx.tcx.item_name(same_adt_did)
+ ));
+ } else {
+ if from_ty_orig.peel_refs() != from_ty {
- diag.note(&format!(
- "the contained type `{to_ty}` has an undefined layout"
- ));
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ if to_ty_orig.peel_refs() != to_ty {
- }
++ diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
+ }
+ }
+ },
+ );
+ return true;
- ReducedTy::Other(_)
- | ReducedTy::OrderedFields(..)
- | ReducedTy::TypeErasure { raw_ptr_only: true },
++ },
+ (
+ ReducedTy::UnorderedFields(from_ty),
- diag.note(&format!(
- "the contained type `{from_ty}` has an undefined layout"
- ));
++ ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if from_ty_orig.peel_refs() != from_ty {
- }
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
- ReducedTy::Other(_)
- | ReducedTy::OrderedFields(..)
- | ReducedTy::TypeErasure { raw_ptr_only: true },
++ },
+ (
- diag.note(&format!(
- "the contained type `{to_ty}` has an undefined layout"
- ));
++ ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
+ ReducedTy::UnorderedFields(to_ty),
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute into `{to_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if to_ty_orig.peel_refs() != to_ty {
- }
++ diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
- ReducedTy::OrderedFields(..)
- | ReducedTy::Other(_)
- | ReducedTy::TypeErasure { raw_ptr_only: true },
- ReducedTy::OrderedFields(..)
- | ReducedTy::Other(_)
- | ReducedTy::TypeErasure { raw_ptr_only: true },
++ },
+ (
- }
++ ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
++ ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
+ )
+ | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
+ break;
- fn reduce_refs<'tcx>(
- cx: &LateContext<'tcx>,
- mut from_ty: Ty<'tcx>,
- mut to_ty: Ty<'tcx>,
- ) -> ReducedTys<'tcx> {
++ },
+ }
+ }
+
+ false
+}
+
+#[expect(clippy::struct_excessive_bools)]
+struct ReducedTys<'tcx> {
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ from_raw_ptr: bool,
+ to_raw_ptr: bool,
+ from_fat_ptr: bool,
+ to_fat_ptr: bool,
+}
+
+/// Remove references so long as both types are references.
- let (from_fat_ptr, to_fat_ptr) =
- loop {
- break match (from_ty.kind(), to_ty.kind()) {
- (
- &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
- &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
- ) => {
- from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
- from_ty = from_sub_ty;
- to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
- to_ty = to_sub_ty;
- continue;
- }
- (
- &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })),
- _,
- ) if !unsized_ty.is_sized(cx.tcx, cx.param_env) => (true, false),
- (
- _,
- &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })),
- ) if !unsized_ty.is_sized(cx.tcx, cx.param_env) => (false, true),
- _ => (false, false),
- };
++fn reduce_refs<'tcx>(cx: &LateContext<'tcx>, mut from_ty: Ty<'tcx>, mut to_ty: Ty<'tcx>) -> ReducedTys<'tcx> {
+ let mut from_raw_ptr = false;
+ let mut to_raw_ptr = false;
- ReducedTys { from_ty, to_ty, from_raw_ptr, to_raw_ptr, from_fat_ptr, to_fat_ptr }
++ let (from_fat_ptr, to_fat_ptr) = loop {
++ break match (from_ty.kind(), to_ty.kind()) {
++ (
++ &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
++ &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
++ ) => {
++ from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
++ from_ty = from_sub_ty;
++ to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
++ to_ty = to_sub_ty;
++ continue;
++ },
++ (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
++ if !unsized_ty.is_sized(cx.tcx, cx.param_env) =>
++ {
++ (true, false)
++ },
++ (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
++ if !unsized_ty.is_sized(cx.tcx, cx.param_env) =>
++ {
++ (false, true)
++ },
++ _ => (false, false),
+ };
- }
++ };
++ ReducedTys {
++ from_ty,
++ to_ty,
++ from_raw_ptr,
++ to_raw_ptr,
++ from_fat_ptr,
++ to_fat_ptr,
++ }
+}
+
+enum ReducedTy<'tcx> {
+ /// The type can be used for type erasure.
+ TypeErasure { raw_ptr_only: bool },
+ /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
+ /// sized fields with a defined order.
+ /// The second value is the first non-zero sized type.
+ OrderedFields(Ty<'tcx>, Option<Ty<'tcx>>),
+ /// The type is a struct containing multiple non-zero sized fields with no defined order.
+ UnorderedFields(Ty<'tcx>),
+ /// Any other type.
+ Other(Ty<'tcx>),
+}
+
+/// Reduce structs containing a single non-zero sized field to it's contained type.
+fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
+ loop {
+ ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
+ return match *ty.kind() {
+ ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => {
+ ReducedTy::TypeErasure { raw_ptr_only: false }
- }
++ },
+ ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
+ ty = sub_ty;
+ continue;
- }
++ },
+ ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false },
+ ty::Tuple(args) => {
+ let mut iter = args.iter();
+ let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
+ return ReducedTy::OrderedFields(ty, None);
+ };
+ if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
+ ty = sized_ty;
+ continue;
+ }
+ ReducedTy::UnorderedFields(ty)
- }
- ty::Adt(def, _)
- if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) =>
- {
++ },
+ ty::Adt(def, substs) if def.is_struct() => {
+ let mut iter = def
+ .non_enum_variant()
+ .fields
+ .iter()
+ .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs));
+ let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
+ return ReducedTy::TypeErasure { raw_ptr_only: false };
+ };
+ if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
+ ty = sized_ty;
+ continue;
+ }
+ if def.repr().inhibit_struct_field_reordering_opt() {
+ ReducedTy::OrderedFields(ty, Some(sized_ty))
+ } else {
+ ReducedTy::UnorderedFields(ty)
+ }
- }
++ },
++ ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
+ ReducedTy::TypeErasure { raw_ptr_only: false }
- (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2))
- if adt1 == adt2 && same_except_params(subs1, subs2) =>
- {
- ()
- }
++ },
+ // 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 { raw_ptr_only: false },
+ ty::Foreign(_) | ty::Param(_) => ReducedTy::TypeErasure { raw_ptr_only: false },
+ ty::Int(_) | ty::Uint(_) => ReducedTy::TypeErasure { raw_ptr_only: true },
+ _ => ReducedTy::Other(ty),
+ };
+ }
+}
+
+fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if_chain! {
+ 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
+ } 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<'tcx>(subs1: SubstsRef<'tcx>, subs2: SubstsRef<'tcx>) -> 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
+}
--- /dev/null
- use rustc_hir as hir;
++use rustc_hir as hir;
+use rustc_hir::Expr;
+use rustc_hir_typeck::{cast, FnCtxt, Inherited};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{cast::CastKind, Ty};
+use rustc_span::DUMMY_SP;
- &fn_ctxt, e, from_ty, to_ty,
+
+// check if the component types of the transmuted collection and the result have different ABI,
+// size or alignment
+pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {
+ if let Ok(from) = cx.tcx.try_normalize_erasing_regions(cx.param_env, from)
+ && let Ok(to) = cx.tcx.try_normalize_erasing_regions(cx.param_env, to)
+ && let Ok(from_layout) = cx.tcx.layout_of(cx.param_env.and(from))
+ && let Ok(to_layout) = cx.tcx.layout_of(cx.param_env.and(to))
+ {
+ from_layout.size != to_layout.size || from_layout.align.abi != to_layout.align.abi
+ } else {
+ // no idea about layout, so don't lint
+ false
+ }
+}
+
+/// Check if the type conversion can be expressed as a pointer cast, instead of
+/// a transmute. In certain cases, including some invalid casts from array
+/// references to pointers, this may cause additional errors to be emitted and/or
+/// ICE error messages. This function will panic if that occurs.
+pub(super) fn can_be_expressed_as_pointer_cast<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+) -> bool {
+ use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
+ matches!(
+ check_cast(cx, e, from_ty, to_ty),
+ Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast)
+ )
+}
+
+/// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of
+/// the cast. In certain cases, including some invalid casts from array references
+/// to pointers, this may cause additional errors to be emitted and/or ICE error
+/// messages. This function will panic if that occurs.
+fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> {
+ let hir_id = e.hir_id;
+ let local_def_id = hir_id.owner.def_id;
+
+ Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
+ let fn_ctxt = FnCtxt::new(inherited, cx.param_env, hir_id);
+
+ // If we already have errors, we can't be sure we can pointer cast.
+ assert!(
+ !fn_ctxt.errors_reported_since_creation(),
+ "Newly created FnCtxt contained errors"
+ );
+
+ if let Ok(check) = cast::CastCheck::new(
- DUMMY_SP, DUMMY_SP, hir::Constness::NotConst,
++ &fn_ctxt,
++ e,
++ from_ty,
++ to_ty,
+ // We won't show any error to the user, so we don't care what the span is here.
++ DUMMY_SP,
++ DUMMY_SP,
++ hir::Constness::NotConst,
+ ) {
+ let res = check.do_check(&fn_ctxt);
+
+ // do_check's documentation says that it might return Ok and create
+ // errors in the fcx instead of returning Err in some cases. Those cases
+ // should be filtered out before getting here.
+ assert!(
+ !fn_ctxt.errors_reported_since_creation(),
+ "`fn_ctxt` contained errors after cast check!"
+ );
+
+ res.ok()
+ } else {
+ None
+ }
+ })
+}
--- /dev/null
- .filter(|&name| matches!(name, sym::HashMap | sym::Vec | sym::HashSet
- | sym::VecDeque
- | sym::LinkedList
- | sym::BTreeMap
- | sym::BTreeSet
- | sym::BinaryHeap))
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{path_def_id, qpath_generic_tys};
+use rustc_hir::{self as hir, def_id::DefId, QPath};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Symbol};
+
+use super::BOX_COLLECTION;
+
+pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
+ if_chain! {
+ if Some(def_id) == cx.tcx.lang_items().owned_box();
+ if let Some(item_type) = get_std_collection(cx, qpath);
+ then {
+ let generic = match item_type {
+ sym::String => "",
+ _ => "<..>",
+ };
+
+ let box_content = format!("{item_type}{generic}");
+ span_lint_and_help(
+ cx,
+ BOX_COLLECTION,
+ hir_ty.span,
+ &format!(
+ "you seem to be trying to use `Box<{box_content}>`. Consider using just `{box_content}`"),
+ None,
+ &format!(
+ "`{box_content}` is already on the heap, `Box<{box_content}>` makes an extra allocation")
+ );
+ true
+ } else {
+ false
+ }
+ }
+}
+
+fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Symbol> {
+ let param = qpath_generic_tys(qpath).next()?;
+ let id = path_def_id(cx, param)?;
+ cx.tcx
+ .get_diagnostic_name(id)
++ .filter(|&name| {
++ matches!(
++ name,
++ sym::HashMap
++ | sym::Vec
++ | sym::HashSet
++ | sym::VecDeque
++ | sym::LinkedList
++ | sym::BTreeMap
++ | sym::BTreeSet
++ | sym::BinaryHeap
++ )
++ })
+ .or_else(|| {
+ cx.tcx
+ .lang_items()
+ .string()
+ .filter(|did| id == *did)
+ .map(|_| sym::String)
+ })
+}
--- /dev/null
- let is_exported = cx.effective_visibilities.is_exported(cx.tcx.hir().local_def_id(field.hir_id));
+mod borrowed_box;
+mod box_collection;
+mod linked_list;
+mod option_option;
+mod rc_buffer;
+mod rc_mutex;
+mod redundant_allocation;
+mod type_complexity;
+mod utils;
+mod vec_box;
+
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem,
+ TraitItemKind, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Box<T>` where T is a collection such as Vec anywhere in the code.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// Collections already keeps their contents in a separate area on
+ /// the heap. So if you `Box` them, you just add another level of indirection
+ /// without any benefit whatsoever.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Box<Vec<Foo>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Vec<Foo>,
+ /// }
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub BOX_COLLECTION,
+ perf,
+ "usage of `Box<Vec<T>>`, vector elements are already on the heap"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Vec<Box<T>>` where T: Sized anywhere in the code.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// `Vec` already keeps its contents in a separate area on
+ /// the heap. So if you `Box` its contents, you just add another level of indirection.
+ ///
+ /// ### Known problems
+ /// Vec<Box<T: Sized>> makes sense if T is a large type (see [#3530](https://github.com/rust-lang/rust-clippy/issues/3530),
+ /// 1st comment).
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X {
+ /// values: Vec<Box<i32>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// struct X {
+ /// values: Vec<i32>,
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub VEC_BOX,
+ complexity,
+ "usage of `Vec<Box<T>>` where T: Sized, vector elements are already on the heap"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Option<Option<_>>` in function signatures and type
+ /// definitions
+ ///
+ /// ### Why is this bad?
+ /// `Option<_>` represents an optional value. `Option<Option<_>>`
+ /// represents an optional optional value which is logically the same thing as an optional
+ /// value but has an unneeded extra level of wrapping.
+ ///
+ /// If you have a case where `Some(Some(_))`, `Some(None)` and `None` are distinct cases,
+ /// consider a custom `enum` instead, with clear names for each case.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn get_data() -> Option<Option<u32>> {
+ /// None
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// pub enum Contents {
+ /// Data(Vec<u8>), // Was Some(Some(Vec<u8>))
+ /// NotYetFetched, // Was Some(None)
+ /// None, // Was None
+ /// }
+ ///
+ /// fn get_data() -> Contents {
+ /// Contents::None
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_OPTION,
+ pedantic,
+ "usage of `Option<Option<T>>`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of any `LinkedList`, suggesting to use a
+ /// `Vec` or a `VecDeque` (formerly called `RingBuf`).
+ ///
+ /// ### Why is this bad?
+ /// Gankro says:
+ ///
+ /// > The TL;DR of `LinkedList` is that it's built on a massive amount of
+ /// pointers and indirection.
+ /// > It wastes memory, it has terrible cache locality, and is all-around slow.
+ /// `RingBuf`, while
+ /// > "only" amortized for push/pop, should be faster in the general case for
+ /// almost every possible
+ /// > workload, and isn't even amortized at all if you can predict the capacity
+ /// you need.
+ /// >
+ /// > `LinkedList`s are only really good if you're doing a lot of merging or
+ /// splitting of lists.
+ /// > This is because they can just mangle some pointers instead of actually
+ /// copying the data. Even
+ /// > if you're doing a lot of insertion in the middle of the list, `RingBuf`
+ /// can still be better
+ /// > because of how expensive it is to seek to the middle of a `LinkedList`.
+ ///
+ /// ### Known problems
+ /// False positives – the instances where using a
+ /// `LinkedList` makes sense are few and far between, but they can still happen.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::LinkedList;
+ /// let x: LinkedList<usize> = LinkedList::new();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LINKEDLIST,
+ pedantic,
+ "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `&Box<T>` anywhere in the code.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// A `&Box<T>` parameter requires the function caller to box `T` first before passing it to a function.
+ /// Using `&T` defines a concrete type for the parameter and generalizes the function, this would also
+ /// auto-deref to `&T` at the function call site if passed a `&Box<T>`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn foo(bar: &Box<T>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// fn foo(bar: &T) { ... }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BORROWED_BOX,
+ complexity,
+ "a borrow of a boxed type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of redundant allocations anywhere in the code.
+ ///
+ /// ### Why is this bad?
+ /// Expressions such as `Rc<&T>`, `Rc<Rc<T>>`, `Rc<Arc<T>>`, `Rc<Box<T>>`, `Arc<&T>`, `Arc<Rc<T>>`,
+ /// `Arc<Arc<T>>`, `Arc<Box<T>>`, `Box<&T>`, `Box<Rc<T>>`, `Box<Arc<T>>`, `Box<Box<T>>`, add an unnecessary level of indirection.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// fn foo(bar: Rc<&usize>) {}
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// fn foo(bar: &usize) {}
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub REDUNDANT_ALLOCATION,
+ perf,
+ "redundant allocation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Rc<T>` and `Arc<T>` when `T` is a mutable buffer type such as `String` or `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// Expressions such as `Rc<String>` usually have no advantage over `Rc<str>`, since
+ /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow<str>`.
+ ///
+ /// While mutating a buffer type would still be possible with `Rc::get_mut()`, it only
+ /// works if there are no additional references yet, which usually defeats the purpose of
+ /// enclosing it in a shared ownership type. Instead, additionally wrapping the inner
+ /// type with an interior mutable container (such as `RefCell` or `Mutex`) would normally
+ /// be used.
+ ///
+ /// ### Known problems
+ /// This pattern can be desirable to avoid the overhead of a `RefCell` or `Mutex` for
+ /// cases where mutation only happens before there are any additional references.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// # use std::rc::Rc;
+ /// fn foo(interned: Rc<String>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// fn foo(interned: Rc<str>) { ... }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub RC_BUFFER,
+ restriction,
+ "shared ownership of a buffer type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for types used in structs, parameters and `let`
+ /// declarations above a certain complexity threshold.
+ ///
+ /// ### Why is this bad?
+ /// Too complex types make the code less readable. Consider
+ /// using a `type` definition to simplify them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// struct Foo {
+ /// inner: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>>,
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TYPE_COMPLEXITY,
+ complexity,
+ "usage of very complex types that might be better factored into `type` definitions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Rc<Mutex<T>>`.
+ ///
+ /// ### Why is this bad?
+ /// `Rc` is used in single thread and `Mutex` is used in multi thread.
+ /// Consider using `Rc<RefCell<T>>` in single thread or `Arc<Mutex<T>>` in multi thread.
+ ///
+ /// ### Known problems
+ /// Sometimes combining generic types can lead to the requirement that a
+ /// type use Rc in conjunction with Mutex. We must consider those cases false positives, but
+ /// alas they are quite hard to rule out. Luckily they are also rare.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::rc::Rc;
+ /// use std::sync::Mutex;
+ /// fn foo(interned: Rc<Mutex<i32>>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// use std::rc::Rc;
+ /// use std::cell::RefCell
+ /// fn foo(interned: Rc<RefCell<i32>>) { ... }
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub RC_MUTEX,
+ restriction,
+ "usage of `Rc<Mutex<T>>`"
+}
+
+pub struct Types {
+ vec_box_size_threshold: u64,
+ type_complexity_threshold: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl_lint_pass!(Types => [BOX_COLLECTION, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]);
+
+impl<'tcx> LateLintPass<'tcx> for Types {
+ fn check_fn(&mut self, cx: &LateContext<'_>, _: FnKind<'_>, decl: &FnDecl<'_>, _: &Body<'_>, _: Span, id: HirId) {
+ let is_in_trait_impl =
+ if let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id(cx.tcx.hir().get_parent_item(id).def_id) {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ };
+
+ let is_exported = cx.effective_visibilities.is_exported(cx.tcx.hir().local_def_id(id));
+
+ self.check_fn_decl(
+ cx,
+ decl,
+ CheckTyContext {
+ is_in_trait_impl,
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let is_exported = cx.effective_visibilities.is_exported(item.owner_id.def_id);
+
+ match item.kind {
+ ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ ),
+ // functions, enums, structs, impls and traits are covered
+ _ => (),
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ match item.kind {
+ ImplItemKind::Const(ty, _) => {
+ let is_in_trait_impl = if let Some(hir::Node::Item(item)) = cx
+ .tcx
+ .hir()
+ .find_by_def_id(cx.tcx.hir().get_parent_item(item.hir_id()).def_id)
+ {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ };
+
+ self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_in_trait_impl,
+ ..CheckTyContext::default()
+ },
+ );
+ },
+ // Methods are covered by check_fn.
+ // Type aliases are ignored because oftentimes it's impossible to
+ // make type alias declaration in trait simpler, see #1013
+ ImplItemKind::Fn(..) | ImplItemKind::Type(..) => (),
+ }
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
++ let is_exported = cx
++ .effective_visibilities
++ .is_exported(cx.tcx.hir().local_def_id(field.hir_id));
+
+ self.check_ty(
+ cx,
+ field.ty,
+ CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) {
+ let is_exported = cx.effective_visibilities.is_exported(item.owner_id.def_id);
+
+ let context = CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ };
+
+ match item.kind {
+ TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => {
+ self.check_ty(cx, ty, context);
+ },
+ TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, context),
+ TraitItemKind::Type(..) => (),
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
+ if let Some(ty) = local.ty {
+ self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_local: true,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+ }
+}
+
+impl Types {
+ pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64, avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ vec_box_size_threshold,
+ type_complexity_threshold,
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, context: CheckTyContext) {
+ // Ignore functions in trait implementations as they are usually forced by the trait definition.
+ //
+ // FIXME: ideally we would like to warn *if the complicated type can be simplified*, but it's hard
+ // to check.
+ if context.is_in_trait_impl {
+ return;
+ }
+
+ for input in decl.inputs {
+ self.check_ty(cx, input, context);
+ }
+
+ if let FnRetTy::Return(ty) = decl.output {
+ self.check_ty(cx, ty, context);
+ }
+ }
+
+ /// Recursively check for `TypePass` lints in the given type. Stop at the first
+ /// lint found.
+ ///
+ /// The parameter `is_local` distinguishes the context of the type.
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, mut context: CheckTyContext) {
+ if hir_ty.span.from_expansion() {
+ return;
+ }
+
+ // Skip trait implementations; see issue #605.
+ if context.is_in_trait_impl {
+ return;
+ }
+
+ if !context.is_nested_call && type_complexity::check(cx, hir_ty, self.type_complexity_threshold) {
+ return;
+ }
+
+ match hir_ty.kind {
+ TyKind::Path(ref qpath) if !context.is_local => {
+ let hir_id = hir_ty.hir_id;
+ let res = cx.qpath_res(qpath, hir_id);
+ if let Some(def_id) = res.opt_def_id() {
+ if self.is_type_change_allowed(context) {
+ // All lints that are being checked in this block are guarded by
+ // the `avoid_breaking_exported_api` configuration. When adding a
+ // new lint, please also add the name to the configuration documentation
+ // in `clippy_lints::utils::conf.rs`
+
+ let mut triggered = false;
+ triggered |= box_collection::check(cx, hir_ty, qpath, def_id);
+ triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
+ triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
+ triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
+ triggered |= option_option::check(cx, hir_ty, qpath, def_id);
+ triggered |= linked_list::check(cx, hir_ty, def_id);
+ triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
+
+ if triggered {
+ return;
+ }
+ }
+ }
+ match *qpath {
+ QPath::Resolved(Some(ty), p) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ for ty in p.segments.iter().flat_map(|seg| {
+ seg.args
+ .as_ref()
+ .map_or_else(|| [].iter(), |params| params.args.iter())
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ }) {
+ self.check_ty(cx, ty, context);
+ }
+ },
+ QPath::Resolved(None, p) => {
+ context.is_nested_call = true;
+ for ty in p.segments.iter().flat_map(|seg| {
+ seg.args
+ .as_ref()
+ .map_or_else(|| [].iter(), |params| params.args.iter())
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ }) {
+ self.check_ty(cx, ty, context);
+ }
+ },
+ QPath::TypeRelative(ty, seg) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ if let Some(params) = seg.args {
+ for ty in params.args.iter().filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }) {
+ self.check_ty(cx, ty, context);
+ }
+ }
+ },
+ QPath::LangItem(..) => {},
+ }
+ },
+ TyKind::Rptr(lt, ref mut_ty) => {
+ context.is_nested_call = true;
+ if !borrowed_box::check(cx, hir_ty, lt, mut_ty) {
+ self.check_ty(cx, mut_ty.ty, context);
+ }
+ },
+ TyKind::Slice(ty) | TyKind::Array(ty, _) | TyKind::Ptr(MutTy { ty, .. }) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ },
+ TyKind::Tup(tys) => {
+ context.is_nested_call = true;
+ for ty in tys {
+ self.check_ty(cx, ty, context);
+ }
+ },
+ _ => {},
+ }
+ }
+
+ /// This function checks if the type is allowed to change in the current context
+ /// based on the `avoid_breaking_exported_api` configuration
+ fn is_type_change_allowed(&self, context: CheckTyContext) -> bool {
+ !(context.is_exported && self.avoid_breaking_exported_api)
+ }
+}
+
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Clone, Copy, Default)]
+struct CheckTyContext {
+ is_in_trait_impl: bool,
+ /// `true` for types on local variables.
+ is_local: bool,
+ /// `true` for types that are part of the public API.
+ is_exported: bool,
+ is_nested_call: bool,
+}
--- /dev/null
- pub(super) fn check(
- cx: &LateContext<'_>,
- hir_ty: &hir::Ty<'_>,
- qpath: &QPath<'_>,
- def_id: DefId,
- ) -> bool {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::{path_def_id, qpath_generic_tys};
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::LateContext;
++use rustc_middle::ty::TypeVisitable;
+use rustc_span::symbol::sym;
+
+use super::{utils, REDUNDANT_ALLOCATION};
+
- diag.span_suggestion(
- hir_ty.span,
- "try",
- format!("{generic_snippet}"),
- applicability,
- );
++pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() {
+ "Box"
+ } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
+ "Rc"
+ } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) {
+ "Arc"
+ } else {
+ return false;
+ };
+
+ if let Some(span) = utils::match_borrows_parameter(cx, qpath) {
+ let generic_snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{outer_sym}<{generic_snippet}>`"),
+ |diag| {
- Some(ty) => {
++ diag.span_suggestion(hir_ty.span, "try", format!("{generic_snippet}"), applicability);
+ diag.note(&format!(
+ "`{generic_snippet}` is already a pointer, `{outer_sym}<{generic_snippet}>` allocates a pointer on the heap"
+ ));
+ },
+ );
+ return true;
+ }
+
+ let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
+ let Some(id) = path_def_id(cx, ty) else { return false };
+ let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) {
+ Some(sym::Arc) => ("Arc", ty),
+ Some(sym::Rc) => ("Rc", ty),
+ _ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty),
+ _ => return false,
+ };
+
+ let TyKind::Path(inner_qpath) = &ty.kind else {
+ return false
+ };
+ let inner_span = match qpath_generic_tys(inner_qpath).next() {
- if !hir_ty_to_ty(cx.tcx, ty).is_sized(cx.tcx, cx.param_env) {
++ Some(hir_ty) => {
+ // Reallocation of a fat pointer causes it to become thin. `hir_ty_to_ty` is safe to use
+ // here because `mod.rs` guarantees this lint is only run on types outside of bodies and
+ // is not run on locals.
- ty.span
- }
++ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
++ if ty.has_escaping_bound_vars() || !ty.is_sized(cx.tcx, cx.param_env) {
+ return false;
+ }
++ hir_ty.span
++ },
+ None => return false,
+ };
+ if inner_sym == outer_sym {
+ let generic_snippet = snippet_with_applicability(cx, inner_span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"),
+ |diag| {
+ diag.span_suggestion(
+ hir_ty.span,
+ "try",
+ format!("{outer_sym}<{generic_snippet}>"),
+ applicability,
+ );
+ diag.note(&format!(
+ "`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation"
+ ));
+ },
+ );
+ } else {
+ let generic_snippet = snippet(cx, inner_span, "..");
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"),
+ |diag| {
+ diag.note(&format!(
+ "`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation"
+ ));
+ diag.help(&format!(
+ "consider using just `{outer_sym}<{generic_snippet}>` or `{inner_sym}<{generic_snippet}>`"
+ ));
+ },
+ );
+ }
+ true
+}
--- /dev/null
- if ty_ty_size <= box_size_threshold;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::last_path_segment;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, def_id::DefId, GenericArg, QPath, TyKind};
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::LateContext;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::TypeVisitable;
+use rustc_span::symbol::sym;
+
+use super::VEC_BOX;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ hir_ty: &hir::Ty<'_>,
+ qpath: &QPath<'_>,
+ def_id: DefId,
+ box_size_threshold: u64,
+) -> bool {
+ if cx.tcx.is_diagnostic_item(sym::Vec, def_id) {
+ if_chain! {
+ // Get the _ part of Vec<_>
+ if let Some(last) = last_path_segment(qpath).args;
+ if let Some(ty) = last.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ // ty is now _ at this point
+ if let TyKind::Path(ref ty_qpath) = ty.kind;
+ let res = cx.qpath_res(ty_qpath, ty.hir_id);
+ if let Some(def_id) = res.opt_def_id();
+ if Some(def_id) == cx.tcx.lang_items().owned_box();
+ // At this point, we know ty is Box<T>, now get T
+ if let Some(last) = last_path_segment(ty_qpath).args;
+ if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty);
+ if !ty_ty.has_escaping_bound_vars();
+ if ty_ty.is_sized(cx.tcx, cx.param_env);
+ if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes());
++ if ty_ty_size < box_size_threshold;
+ then {
+ span_lint_and_sugg(
+ cx,
+ VEC_BOX,
+ hir_ty.span,
+ "`Vec<T>` is already on the heap, the boxing is unnecessary",
+ "try",
+ format!("Vec<{}>", snippet(cx, boxed_ty.span, "..")),
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
+ }
+ }
+ } else {
+ false
+ }
+}
--- /dev/null
- && !block_has_safety_comment(cx, block)
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::walk_span_to_context;
+use clippy_utils::{get_parent_node, is_lint_allowed};
+use rustc_data_structures::sync::Lrc;
+use rustc_hir as hir;
+use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{BytePos, Pos, Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
+ /// explaining why the unsafe operations performed inside
+ /// the block are safe.
+ ///
+ /// Note the comment must appear on the line(s) preceding the unsafe block
+ /// with nothing appearing in between. The following is ok:
+ /// ```ignore
+ /// foo(
+ /// // SAFETY:
+ /// // This is a valid safety comment
+ /// unsafe { *x }
+ /// )
+ /// ```
+ /// But neither of these are:
+ /// ```ignore
+ /// // SAFETY:
+ /// // This is not a valid safety comment
+ /// foo(
+ /// /* SAFETY: Neither is this */ unsafe { *x },
+ /// );
+ /// ```
+ ///
+ /// ### Why is this bad?
+ /// Undocumented unsafe blocks and impls can make it difficult to
+ /// read and maintain code, as well as uncover unsoundness
+ /// and bugs.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// // SAFETY: references are guaranteed to be non-null.
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNDOCUMENTED_UNSAFE_BLOCKS,
+ restriction,
+ "creating an unsafe block without explaining why it is safe"
+}
+
+declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
+
+impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
+ fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
+ if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
+ && !in_external_macro(cx.tcx.sess, block.span)
+ && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
+ && !is_unsafe_from_proc_macro(cx, block.span)
- fn block_has_safety_comment(cx: &LateContext<'_>, block: &hir::Block<'_>) -> bool {
++ && !block_has_safety_comment(cx, block.span)
++ && !block_parents_have_safety_comment(cx, block.hir_id)
+ {
+ let source_map = cx.tcx.sess.source_map();
+ let span = if source_map.is_multiline(block.span) {
+ source_map.span_until_char(block.span, '\n')
+ } else {
+ block.span
+ };
+
+ span_lint_and_help(
+ cx,
+ UNDOCUMENTED_UNSAFE_BLOCKS,
+ span,
+ "unsafe block missing a safety comment",
+ None,
+ "consider adding a safety comment on the preceding line",
+ );
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if let hir::ItemKind::Impl(imple) = item.kind
+ && imple.unsafety == hir::Unsafety::Unsafe
+ && !in_external_macro(cx.tcx.sess, item.span)
+ && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
+ && !is_unsafe_from_proc_macro(cx, item.span)
+ && !item_has_safety_comment(cx, item)
+ {
+ let source_map = cx.tcx.sess.source_map();
+ let span = if source_map.is_multiline(item.span) {
+ source_map.span_until_char(item.span, '\n')
+ } else {
+ item.span
+ };
+
+ span_lint_and_help(
+ cx,
+ UNDOCUMENTED_UNSAFE_BLOCKS,
+ span,
+ "unsafe impl missing a safety comment",
+ None,
+ "consider adding a safety comment on the preceding line",
+ );
+ }
+ }
+}
+
+fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
+ let source_map = cx.sess().source_map();
+ let file_pos = source_map.lookup_byte_offset(span.lo());
+ file_pos
+ .sf
+ .src
+ .as_deref()
+ .and_then(|src| src.get(file_pos.pos.to_usize()..))
+ .map_or(true, |src| !src.starts_with("unsafe"))
+}
+
++// Checks if any parent {expression, statement, block, local, const, static}
++// has a safety comment
++fn block_parents_have_safety_comment(cx: &LateContext<'_>, id: hir::HirId) -> bool {
++ if let Some(node) = get_parent_node(cx.tcx, id) {
++ return match node {
++ Node::Expr(expr) => !is_branchy(expr) && span_in_body_has_safety_comment(cx, expr.span),
++ Node::Stmt(hir::Stmt {
++ kind:
++ hir::StmtKind::Local(hir::Local { span, .. })
++ | hir::StmtKind::Expr(hir::Expr { span, .. })
++ | hir::StmtKind::Semi(hir::Expr { span, .. }),
++ ..
++ })
++ | Node::Local(hir::Local { span, .. })
++ | Node::Item(hir::Item {
++ kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
++ span,
++ ..
++ }) => span_in_body_has_safety_comment(cx, *span),
++ _ => false,
++ };
++ }
++ false
++}
++
++/// Checks if an expression is "branchy", e.g. loop, match/if/etc.
++fn is_branchy(expr: &hir::Expr<'_>) -> bool {
++ matches!(
++ expr.kind,
++ hir::ExprKind::If(..) | hir::ExprKind::Loop(..) | hir::ExprKind::Match(..)
++ )
++}
++
+/// Checks if the lines immediately preceding the block contain a safety comment.
- span_from_macro_expansion_has_safety_comment(cx, block.span) || span_in_body_has_safety_comment(cx, block.span)
++fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
+ // This intentionally ignores text before the start of a function so something like:
+ // ```
+ // // SAFETY: reason
+ // fn foo() { unsafe { .. } }
+ // ```
+ // won't work. This is to avoid dealing with where such a comment should be place relative to
+ // attributes and doc comments.
+
++ span_from_macro_expansion_has_safety_comment(cx, span) || span_in_body_has_safety_comment(cx, span)
+}
+
+/// Checks if the lines immediately preceding the item contain a safety comment.
+#[allow(clippy::collapsible_match)]
+fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> bool {
+ if span_from_macro_expansion_has_safety_comment(cx, item.span) {
+ return true;
+ }
+
+ if item.span.ctxt() == SyntaxContext::root() {
+ if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
+ let comment_start = match parent_node {
+ Node::Crate(parent_mod) => {
+ comment_start_before_impl_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
+ },
+ Node::Item(parent_item) => {
+ if let ItemKind::Mod(parent_mod) = &parent_item.kind {
+ comment_start_before_impl_in_mod(cx, parent_mod, parent_item.span, item)
+ } else {
+ // Doesn't support impls in this position. Pretend a comment was found.
+ return true;
+ }
+ },
+ Node::Stmt(stmt) => {
+ if let Some(stmt_parent) = get_parent_node(cx.tcx, stmt.hir_id) {
+ match stmt_parent {
+ Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
+ _ => {
+ // Doesn't support impls in this position. Pretend a comment was found.
+ return true;
+ },
+ }
+ } else {
+ // Problem getting the parent node. Pretend a comment was found.
+ return true;
+ }
+ },
+ _ => {
+ // Doesn't support impls in this position. Pretend a comment was found.
+ return true;
+ },
+ };
+
+ let source_map = cx.sess().source_map();
+ if let Some(comment_start) = comment_start
+ && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
+ && let Ok(comment_start_line) = source_map.lookup_line(comment_start)
+ && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
+ && let Some(src) = unsafe_line.sf.src.as_deref()
+ {
+ unsafe_line.sf.lines(|lines| {
+ comment_start_line.line < unsafe_line.line && text_has_safety_comment(
+ src,
+ &lines[comment_start_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos.to_usize(),
+ )
+ })
+ } else {
+ // Problem getting source text. Pretend a comment was found.
+ true
+ }
+ } else {
+ // No parent node. Pretend a comment was found.
+ true
+ }
+ } else {
+ false
+ }
+}
+
+fn comment_start_before_impl_in_mod(
+ cx: &LateContext<'_>,
+ parent_mod: &hir::Mod<'_>,
+ parent_mod_span: Span,
+ imple: &hir::Item<'_>,
+) -> Option<BytePos> {
+ parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
+ if *item_id == imple.item_id() {
+ if idx == 0 {
+ // mod A { /* comment */ unsafe impl T {} ... }
+ // ^------------------------------------------^ returns the start of this span
+ // ^---------------------^ finally checks comments in this range
+ if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
+ return Some(sp.lo());
+ }
+ } else {
+ // some_item /* comment */ unsafe impl T {}
+ // ^-------^ returns the end of this span
+ // ^---------------^ finally checks comments in this range
+ let prev_item = cx.tcx.hir().item(parent_mod.item_ids[idx - 1]);
+ if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
+ return Some(sp.hi());
+ }
+ }
+ }
+ None
+ })
+}
+
+fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
+ let source_map = cx.sess().source_map();
+ let ctxt = span.ctxt();
+ if ctxt == SyntaxContext::root() {
+ false
+ } else {
+ // From a macro expansion. Get the text from the start of the macro declaration to start of the
+ // unsafe block.
+ // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
+ // ^--------------------------------------------^
+ if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
+ && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo())
+ && Lrc::ptr_eq(&unsafe_line.sf, ¯o_line.sf)
+ && let Some(src) = unsafe_line.sf.src.as_deref()
+ {
+ unsafe_line.sf.lines(|lines| {
+ macro_line.line < unsafe_line.line && text_has_safety_comment(
+ src,
+ &lines[macro_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos.to_usize(),
+ )
+ })
+ } else {
+ // Problem getting source text. Pretend a comment was found.
+ true
+ }
+ }
+}
+
+fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
+ let body = cx.enclosing_body?;
+ let map = cx.tcx.hir();
+ let mut span = map.body(body).value.span;
+ for (_, node) in map.parent_iter(body.hir_id) {
+ match node {
+ Node::Expr(e) => span = e.span,
+ Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (),
+ _ => break,
+ }
+ }
+ Some(span)
+}
+
+fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
+ let source_map = cx.sess().source_map();
+ let ctxt = span.ctxt();
+ if ctxt == SyntaxContext::root()
+ && let Some(search_span) = get_body_search_span(cx)
+ {
+ if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
+ && let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
+ && let Ok(body_line) = source_map.lookup_line(body_span.lo())
+ && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
+ && let Some(src) = unsafe_line.sf.src.as_deref()
+ {
+ // Get the text from the start of function body to the unsafe block.
+ // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
+ // ^-------------^
+ unsafe_line.sf.lines(|lines| {
+ body_line.line < unsafe_line.line && text_has_safety_comment(
+ src,
+ &lines[body_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos.to_usize(),
+ )
+ })
+ } else {
+ // Problem getting source text. Pretend a comment was found.
+ true
+ }
+ } else {
+ false
+ }
+}
+
+/// Checks if the given text has a safety comment for the immediately proceeding line.
+fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> bool {
+ let mut lines = line_starts
+ .array_windows::<2>()
+ .rev()
+ .map_while(|[start, end]| {
+ let start = start.to_usize() - offset;
+ let end = end.to_usize() - offset;
+ src.get(start..end).map(|text| (start, text.trim_start()))
+ })
+ .filter(|(_, text)| !text.is_empty());
+
+ let Some((line_start, line)) = lines.next() else {
+ return false;
+ };
+ // Check for a sequence of line comments.
+ if line.starts_with("//") {
+ let mut line = line;
+ loop {
+ if line.to_ascii_uppercase().contains("SAFETY:") {
+ return true;
+ }
+ match lines.next() {
+ Some((_, x)) if x.starts_with("//") => line = x,
+ _ => return false,
+ }
+ }
+ }
+ // No line comments; look for the start of a block comment.
+ // This will only find them if they are at the start of a line.
+ let (mut line_start, mut line) = (line_start, line);
+ loop {
+ if line.starts_with("/*") {
+ let src = src[line_start..line_starts.last().unwrap().to_usize() - offset].trim_start();
+ let mut tokens = tokenize(src);
+ return src[..tokens.next().unwrap().len as usize]
+ .to_ascii_uppercase()
+ .contains("SAFETY:")
+ && tokens.all(|t| t.kind == TokenKind::Whitespace);
+ }
+ match lines.next() {
+ Some(x) => (line_start, line) = x,
+ None => return false,
+ }
+ }
+}
--- /dev/null
- for &(ref use_tree, _) in nested_use_tree {
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Item, ItemKind, UseTree, UseTreeKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports that remove "unsafe" from an item's
+ /// name.
+ ///
+ /// ### Why is this bad?
+ /// Renaming makes it less clear which traits and
+ /// structures are unsafe.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::cell::{UnsafeCell as TotallySafeCell};
+ ///
+ /// extern crate crossbeam;
+ /// use crossbeam::{spawn_unsafe as spawn};
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNSAFE_REMOVED_FROM_NAME,
+ style,
+ "`unsafe` removed from API names on import"
+}
+
+declare_lint_pass!(UnsafeNameRemoval => [UNSAFE_REMOVED_FROM_NAME]);
+
+impl EarlyLintPass for UnsafeNameRemoval {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if let ItemKind::Use(ref use_tree) = item.kind {
+ check_use_tree(use_tree, cx, item.span);
+ }
+ }
+}
+
+fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) {
+ match use_tree.kind {
+ UseTreeKind::Simple(Some(new_name), ..) => {
+ let old_name = use_tree
+ .prefix
+ .segments
+ .last()
+ .expect("use paths cannot be empty")
+ .ident;
+ unsafe_to_safe_check(old_name, new_name, cx, span);
+ },
+ UseTreeKind::Simple(None, ..) | UseTreeKind::Glob => {},
+ UseTreeKind::Nested(ref nested_use_tree) => {
++ for (use_tree, _) in nested_use_tree {
+ check_use_tree(use_tree, cx, span);
+ }
+ },
+ }
+}
+
+fn unsafe_to_safe_check(old_name: Ident, new_name: Ident, cx: &EarlyContext<'_>, span: Span) {
+ let old_str = old_name.name.as_str();
+ let new_str = new_name.name.as_str();
+ if contains_unsafe(old_str) && !contains_unsafe(new_str) {
+ span_lint(
+ cx,
+ UNSAFE_REMOVED_FROM_NAME,
+ span,
+ &format!("removed `unsafe` from the name of `{old_str}` in use as `{new_str}`"),
+ );
+ }
+}
+
+#[must_use]
+fn contains_unsafe(name: &str) -> bool {
+ name.contains("Unsafe") || name.contains("unsafe")
+}
--- /dev/null
- #[clippy::version = "1.64.0"]
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{match_type, peel_mid_ty_refs_is_mutable};
+use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, paths, peel_ref_operators};
+use rustc_ast::Mutability;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{Block, Expr, ExprKind, HirId, Local, Node, PatKind, PathSegment, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter::OnlyBodies;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the creation of a `peekable` iterator that is never `.peek()`ed
+ ///
+ /// ### Why is this bad?
+ /// Creating a peekable iterator without using any of its methods is likely a mistake,
+ /// or just a leftover after a refactor.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let collection = vec![1, 2, 3];
+ /// let iter = collection.iter().peekable();
+ ///
+ /// for item in iter {
+ /// // ...
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let collection = vec![1, 2, 3];
+ /// let iter = collection.iter();
+ ///
+ /// for item in iter {
+ /// // ...
+ /// }
+ /// ```
++ #[clippy::version = "1.65.0"]
+ pub UNUSED_PEEKABLE,
+ nursery,
+ "creating a peekable iterator without using any of its methods"
+}
+
+declare_lint_pass!(UnusedPeekable => [UNUSED_PEEKABLE]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedPeekable {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ // Don't lint `Peekable`s returned from a block
+ if let Some(expr) = block.expr
+ && let Some(ty) = cx.typeck_results().expr_ty_opt(peel_ref_operators(cx, expr))
+ && match_type(cx, ty, &paths::PEEKABLE)
+ {
+ return;
+ }
+
+ for (idx, stmt) in block.stmts.iter().enumerate() {
+ if !stmt.span.from_expansion()
+ && let StmtKind::Local(local) = stmt.kind
+ && let PatKind::Binding(_, binding, ident, _) = local.pat.kind
+ && let Some(init) = local.init
+ && !init.span.from_expansion()
+ && let Some(ty) = cx.typeck_results().expr_ty_opt(init)
+ && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty)
+ && match_type(cx, ty, &paths::PEEKABLE)
+ {
+ let mut vis = PeekableVisitor::new(cx, binding);
+
+ if idx + 1 == block.stmts.len() && block.expr.is_none() {
+ return;
+ }
+
+ for stmt in &block.stmts[idx..] {
+ vis.visit_stmt(stmt);
+ }
+
+ if let Some(expr) = block.expr {
+ vis.visit_expr(expr);
+ }
+
+ if !vis.found_peek_call {
+ span_lint_and_help(
+ cx,
+ UNUSED_PEEKABLE,
+ ident.span,
+ "`peek` never called on `Peekable` iterator",
+ None,
+ "consider removing the call to `peekable`"
+ );
+ }
+ }
+ }
+ }
+}
+
+struct PeekableVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ expected_hir_id: HirId,
+ found_peek_call: bool,
+}
+
+impl<'a, 'tcx> PeekableVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, expected_hir_id: HirId) -> Self {
+ Self {
+ cx,
+ expected_hir_id,
+ found_peek_call: false,
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> {
+ type NestedFilter = OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+ if self.found_peek_call {
+ return;
+ }
+
+ if path_to_local_id(ex, self.expected_hir_id) {
+ for (_, node) in self.cx.tcx.hir().parent_iter(ex.hir_id) {
+ match node {
+ Node::Expr(expr) => {
+ match expr.kind {
+ // some_function(peekable)
+ //
+ // If the Peekable is passed to a function, stop
+ ExprKind::Call(_, args) => {
+ if let Some(func_did) = fn_def_id(self.cx, expr)
+ && let Some(into_iter_did) = self
+ .cx
+ .tcx
+ .lang_items()
+ .into_iter_fn()
+ && func_did == into_iter_did
+ {
+ // Probably a for loop desugar, stop searching
+ return;
+ }
+
+ if args.iter().any(|arg| arg_is_mut_peekable(self.cx, arg)) {
+ self.found_peek_call = true;
+ }
+
+ return;
+ },
+ // Catch anything taking a Peekable mutably
+ ExprKind::MethodCall(
+ PathSegment {
+ ident: method_name_ident,
+ ..
+ },
+ self_arg,
+ remaining_args,
+ _,
+ ) => {
+ let method_name = method_name_ident.name.as_str();
+
+ // `Peekable` methods
+ if matches!(method_name, "peek" | "peek_mut" | "next_if" | "next_if_eq")
+ && arg_is_mut_peekable(self.cx, self_arg)
+ {
+ self.found_peek_call = true;
+ return;
+ }
+
+ // foo.some_method() excluding Iterator methods
+ if remaining_args.iter().any(|arg| arg_is_mut_peekable(self.cx, arg))
+ && !is_trait_method(self.cx, expr, sym::Iterator)
+ {
+ self.found_peek_call = true;
+ return;
+ }
+
+ // foo.by_ref(), keep checking for `peek`
+ if method_name == "by_ref" {
+ continue;
+ }
+
+ return;
+ },
+ ExprKind::AddrOf(_, Mutability::Mut, _) | ExprKind::Unary(..) | ExprKind::DropTemps(_) => {
+ },
+ ExprKind::AddrOf(_, Mutability::Not, _) => return,
+ _ => {
+ self.found_peek_call = true;
+ return;
+ },
+ }
+ },
+ Node::Local(Local { init: Some(init), .. }) => {
+ if arg_is_mut_peekable(self.cx, init) {
+ self.found_peek_call = true;
+ }
+
+ return;
+ },
+ Node::Stmt(stmt) => {
+ match stmt.kind {
+ StmtKind::Local(_) | StmtKind::Item(_) => self.found_peek_call = true,
+ StmtKind::Expr(_) | StmtKind::Semi(_) => {},
+ }
+
+ return;
+ },
+ Node::Block(_) | Node::ExprField(_) => {},
+ _ => {
+ return;
+ },
+ }
+ }
+ }
+
+ walk_expr(self, ex);
+ }
+}
+
+fn arg_is_mut_peekable(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool {
+ if let Some(ty) = cx.typeck_results().expr_ty_opt(arg)
+ && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty)
+ && match_type(cx, ty, &paths::PEEKABLE)
+ {
+ true
+ } else {
+ false
+ }
+}
--- /dev/null
- if let ExprKind::MethodCall(box MethodCall { seg, receiver, .. }) = &expr.kind
- && let method_name = seg.ident.name.as_str()
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast::{Expr, ExprKind, MethodCall};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Detects cases where a whole-number literal float is being rounded, using
+ /// the `floor`, `ceil`, or `round` methods.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// This is unnecessary and confusing to the reader. Doing this is probably a mistake.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1f32.ceil();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = 1f32;
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub UNUSED_ROUNDING,
+ nursery,
+ "Uselessly rounding a whole number floating-point literal"
+}
+declare_lint_pass!(UnusedRounding => [UNUSED_ROUNDING]);
+
+fn is_useless_rounding(expr: &Expr) -> Option<(&str, String)> {
- let f = token_lit.symbol.as_str().parse::<f64>().unwrap();
++ if let ExprKind::MethodCall(box MethodCall { seg:name_ident, receiver, .. }) = &expr.kind
++ && let method_name = name_ident.ident.name.as_str()
+ && (method_name == "ceil" || method_name == "round" || method_name == "floor")
+ && let ExprKind::Lit(token_lit) = &receiver.kind
+ && token_lit.is_semantic_float() {
- match token_lit.suffix {
- Some(suffix) => f_str.push_str(suffix.as_str()),
- None => {}
+ let mut f_str = token_lit.symbol.to_string();
++ let f = f_str.trim_end_matches('_').parse::<f64>().unwrap();
++ if let Some(suffix) = token_lit.suffix {
++ f_str.push_str(suffix.as_str());
+ }
+ if f.fract() == 0.0 {
+ Some((method_name, f_str))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+impl EarlyLintPass for UnusedRounding {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if let Some((method_name, float)) = is_useless_rounding(expr) {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_ROUNDING,
+ expr.span,
+ &format!("used the `{method_name}` method with a whole number float"),
+ &format!("remove the `{method_name}` method call"),
+ float,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- use rustc_ast::ast;
- use rustc_ast::visit::FnKind;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{position_before_rarrow, snippet_opt};
+use if_chain::if_chain;
++use rustc_ast::{ast, visit::FnKind, ClosureBinder};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unit (`()`) expressions that can be removed.
+ ///
+ /// ### Why is this bad?
+ /// Such expressions add no value, but can make the code
+ /// less readable. Depending on formatting they can make a `break` or `return`
+ /// statement look like a function call.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ /// is equivalent to
+ /// ```rust
+ /// fn return_unit() {}
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
+
+impl EarlyLintPass for UnusedUnit {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
++ // implicit types in closure signatures are forbidden when `for<...>` is present
++ if let FnKind::Closure(&ClosureBinder::For { .. }, ..) = kind {
++ return;
++ }
++
+ lint_unneeded_unit_return(cx, ty, span);
+ }
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if_chain! {
+ if let Some(stmt) = block.stmts.last();
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind;
+ if is_unit_expr(expr);
+ let ctxt = block.span.ctxt();
+ if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt;
+ then {
+ let sp = expr.span;
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ sp,
+ "unneeded unit expression",
+ "remove the final `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
+ if is_unit_expr(expr) && !expr.span.from_expansion() {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ expr.span,
+ "unneeded `()`",
+ "remove the `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef) {
+ let segments = &poly.trait_ref.path.segments;
+
+ if_chain! {
+ if segments.len() == 1;
+ if ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str());
+ if let Some(args) = &segments[0].args;
+ if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
+ if let ast::FnRetTy::Ty(ty) = &generic_args.output;
+ if ty.kind.is_unit();
+ then {
+ lint_unneeded_unit_return(cx, ty, generic_args.span);
+ }
+ }
+ }
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<Span> {
+ if span.from_expansion() {
+ Some(span.ctxt().outer_expn_data().def_site)
+ } else {
+ None
+ }
+}
+
+// is this expr a `()` unit?
+fn is_unit_expr(expr: &ast::Expr) -> bool {
+ if let ast::ExprKind::Tup(ref vals) = expr.kind {
+ vals.is_empty()
+ } else {
+ false
+ }
+}
+
+fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
+ let (ret_span, appl) =
+ snippet_opt(cx, span.with_hi(ty.span.hi())).map_or((ty.span, Applicability::MaybeIncorrect), |fn_source| {
+ position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+ (
+ #[expect(clippy::cast_possible_truncation)]
+ ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+ Applicability::MachineApplicable,
+ )
+ })
+ });
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ ret_span,
+ "unneeded unit return type",
+ "remove the `-> ()`",
+ String::new(),
+ appl,
+ );
+}
--- /dev/null
- /// - False positive with associated types in traits (#4140)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::same_type_and_consts;
+use clippy_utils::{is_from_proc_macro, meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ self as hir,
+ def::{CtorOf, DefKind, Res},
+ def_id::LocalDefId,
+ intravisit::{walk_inf, walk_ty, Visitor},
+ Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
+ TyKind,
+};
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary repetition of structure name when a
+ /// replacement with `Self` is applicable.
+ ///
+ /// ### Why is this bad?
+ /// Unnecessary repetition. Mixed use of `Self` and struct
+ /// name
+ /// feels inconsistent.
+ ///
+ /// ### Known problems
+ /// - Unaddressed false negative in fn bodies of trait implementations
- ExprKind::Struct(QPath::Resolved(_, path), ..) => match path.res {
- Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } => (),
- Res::Def(DefKind::Variant, _) => lint_path_to_variant(cx, path),
- _ => span_lint(cx, path.span),
- },
- // tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`)
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo;
+ /// impl Foo {
+ /// fn new() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
+ /// could be
+ /// ```rust
+ /// struct Foo;
+ /// impl Foo {
+ /// fn new() -> Self {
+ /// Self {}
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USE_SELF,
+ nursery,
+ "unnecessary structure name repetition whereas `Self` is applicable"
+}
+
+#[derive(Default)]
+pub struct UseSelf {
+ msrv: Option<RustcVersion>,
+ stack: Vec<StackItem>,
+}
+
+impl UseSelf {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Self::default()
+ }
+ }
+}
+
+#[derive(Debug)]
+enum StackItem {
+ Check {
+ impl_id: LocalDefId,
+ in_body: u32,
+ types_to_skip: FxHashSet<HirId>,
+ },
+ NoCheck,
+}
+
+impl_lint_pass!(UseSelf => [USE_SELF]);
+
+const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
+
+impl<'tcx> LateLintPass<'tcx> for UseSelf {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
+ if matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ // skip over `ItemKind::OpaqueTy` in order to lint `foo() -> impl <..>`
+ return;
+ }
+ // We push the self types of `impl`s on a stack here. Only the top type on the stack is
+ // relevant for linting, since this is the self type of the `impl` we're currently in. To
+ // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
+ // we're in an `impl` or nested item, that we don't want to lint
+ let stack_item = if_chain! {
+ if let ItemKind::Impl(Impl { self_ty, .. }) = item.kind;
+ if let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind;
+ let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
+ if parameters.as_ref().map_or(true, |params| {
+ !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
+ });
++ if !item.span.from_expansion();
+ if !is_from_proc_macro(cx, item); // expensive, should be last check
+ then {
+ StackItem::Check {
+ impl_id: item.owner_id.def_id,
+ in_body: 0,
+ types_to_skip: std::iter::once(self_ty.hir_id).collect(),
+ }
+ } else {
+ StackItem::NoCheck
+ }
+ };
+ self.stack.push(stack_item);
+ }
+
+ fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
+ if !matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ self.stack.pop();
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+ // We want to skip types in trait `impl`s that aren't declared as `Self` in the trait
+ // declaration. The collection of those types is all this method implementation does.
+ if_chain! {
+ if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind;
+ if let Some(&mut StackItem::Check {
+ impl_id,
+ ref mut types_to_skip,
+ ..
+ }) = self.stack.last_mut();
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_id);
+ then {
+ // `self_ty` is the semantic self type of `impl <trait> for <type>`. This cannot be
+ // `Self`.
+ let self_ty = impl_trait_ref.self_ty();
+
+ // `trait_method_sig` is the signature of the function, how it is declared in the
+ // trait, not in the impl of the trait.
+ let trait_method = cx
+ .tcx
+ .associated_item(impl_item.owner_id)
+ .trait_item_def_id
+ .expect("impl method matches a trait method");
+ let trait_method_sig = cx.tcx.fn_sig(trait_method);
+ let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
+
+ // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the
+ // implementation of the trait.
+ let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output {
+ Some(&**ty)
+ } else {
+ None
+ };
+ let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty);
+
+ // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
+ //
+ // `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the
+ // trait declaration. This is used to check if `Self` was used in the trait
+ // declaration.
+ //
+ // If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed
+ // to `Self`), we want to skip linting that type and all subtypes of it. This
+ // avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`, in an `impl Trait
+ // for u8`, when the trait always uses `Vec<u8>`.
+ //
+ // See also https://github.com/rust-lang/rust-clippy/issues/2894.
+ for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) {
+ if trait_sem_ty.walk().any(|inner| inner == self_ty.into()) {
+ let mut visitor = SkipTyCollector::default();
+ visitor.visit_ty(impl_hir_ty);
+ types_to_skip.extend(visitor.types_to_skip);
+ }
+ }
+ }
+ }
+ }
+
+ fn check_body(&mut self, _: &LateContext<'_>, _: &hir::Body<'_>) {
+ // `hir_ty_to_ty` cannot be called in `Body`s or it will panic (sometimes). But in bodies
+ // we can use `cx.typeck_results.node_type(..)` to get the `ty::Ty` from a `hir::Ty`.
+ // However the `node_type()` method can *only* be called in bodies.
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_add(1);
+ }
+ }
+
+ fn check_body_post(&mut self, _: &LateContext<'_>, _: &hir::Body<'_>) {
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_sub(1);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
+ if_chain! {
+ if !hir_ty.span.from_expansion();
+ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check {
+ impl_id,
+ in_body,
+ ref types_to_skip,
+ }) = self.stack.last();
+ if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
+ if !matches!(
+ path.res,
+ Res::SelfTyParam { .. }
+ | Res::SelfTyAlias { .. }
+ | Res::Def(DefKind::TyParam, _)
+ );
+ if !types_to_skip.contains(&hir_ty.hir_id);
+ let ty = if in_body > 0 {
+ cx.typeck_results().node_type(hir_ty.hir_id)
+ } else {
+ hir_ty_to_ty(cx.tcx, hir_ty)
+ };
+ if same_type_and_consts(ty, cx.tcx.type_of(impl_id));
+ then {
+ span_lint(cx, hir_ty.span);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+ if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id);
+ then {} else { return; }
+ }
+ match expr.kind {
- if let Res::Def(DefKind::Ctor(ctor_of, _), ..) = path.res {
- match ctor_of {
- CtorOf::Variant => lint_path_to_variant(cx, path),
- CtorOf::Struct => span_lint(cx, path.span),
- }
- }
++ ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path),
+ ExprKind::Call(fun, _) => {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind {
- // unit enum variants (`Enum::A`)
- ExprKind::Path(QPath::Resolved(_, path)) => lint_path_to_variant(cx, path),
++ check_path(cx, path);
+ }
+ },
- match path.res {
- Res::Def(DefKind::Ctor(ctor_of, _), ..) => match ctor_of {
- CtorOf::Variant => lint_path_to_variant(cx, path),
- CtorOf::Struct => span_lint(cx, path.span),
- },
- Res::Def(DefKind::Variant, ..) => lint_path_to_variant(cx, path),
- Res::Def(DefKind::Struct, ..) => span_lint(cx, path.span),
- _ => ()
- }
++ ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path),
+ _ => (),
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
+ if_chain! {
+ if !pat.span.from_expansion();
+ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+ // get the path from the pattern
+ if let PatKind::Path(QPath::Resolved(_, path))
+ | PatKind::TupleStruct(QPath::Resolved(_, path), _, _)
+ | PatKind::Struct(QPath::Resolved(_, path), _, _) = pat.kind;
+ if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id);
+ then {
++ check_path(cx, path);
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[derive(Default)]
+struct SkipTyCollector {
+ types_to_skip: Vec<HirId>,
+}
+
+impl<'tcx> Visitor<'tcx> for SkipTyCollector {
+ fn visit_infer(&mut self, inf: &hir::InferArg) {
+ self.types_to_skip.push(inf.hir_id);
+
+ walk_inf(self, inf);
+ }
+ fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) {
+ self.types_to_skip.push(hir_ty.hir_id);
+
+ walk_ty(self, hir_ty);
+ }
+}
+
+fn span_lint(cx: &LateContext<'_>, span: Span) {
+ span_lint_and_sugg(
+ cx,
+ USE_SELF,
+ span,
+ "unnecessary structure name repetition",
+ "use the applicable keyword",
+ "Self".to_owned(),
+ Applicability::MachineApplicable,
+ );
+}
+
++fn check_path(cx: &LateContext<'_>, path: &Path<'_>) {
++ match path.res {
++ Res::Def(DefKind::Ctor(CtorOf::Variant, _) | DefKind::Variant, ..) => {
++ lint_path_to_variant(cx, path);
++ },
++ Res::Def(DefKind::Ctor(CtorOf::Struct, _) | DefKind::Struct, ..) => span_lint(cx, path.span),
++ _ => (),
++ }
++}
++
+fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
+ if let [.., self_seg, _variant] = path.segments {
+ let span = path
+ .span
+ .with_hi(self_seg.args().span_ext().unwrap_or(self_seg.ident.span).hi());
+ span_lint(cx, span);
+ }
+}
--- /dev/null
- pub fn reason(&self) -> Option<&str> {
+//! Read configurations files.
+
+#![allow(clippy::module_name_repetitions)]
+
+use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
+use serde::Deserialize;
+use std::error::Error;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::{cmp, env, fmt, fs, io, iter};
+
+#[rustfmt::skip]
+const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
+ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
+ "DirectX",
+ "ECMAScript",
+ "GPLv2", "GPLv3",
+ "GitHub", "GitLab",
+ "IPv4", "IPv6",
+ "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
+ "NaN", "NaNs",
+ "OAuth", "GraphQL",
+ "OCaml",
+ "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
+ "WebGL",
+ "TensorFlow",
+ "TrueType",
+ "iOS", "macOS", "FreeBSD",
+ "TeX", "LaTeX", "BibTeX", "BibLaTeX",
+ "MinGW",
+ "CamelCase",
+];
+const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
+
+/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
+#[derive(Clone, Debug, Deserialize)]
+pub struct Rename {
+ pub path: String,
+ pub rename: String,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum DisallowedPath {
+ Simple(String),
+ WithReason { path: String, reason: Option<String> },
+}
+
+impl DisallowedPath {
+ pub fn path(&self) -> &str {
+ let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
+
+ path
+ }
+
- } => Some(reason),
++ pub fn reason(&self) -> Option<String> {
+ match self {
+ Self::WithReason {
+ reason: Some(reason), ..
- /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP.
++ } => Some(format!("{reason} (from clippy.toml)")),
+ _ => None,
+ }
+ }
+}
+
+/// Conf with parse errors
+#[derive(Default)]
+pub struct TryConf {
+ pub conf: Conf,
+ pub errors: Vec<Box<dyn Error>>,
+ pub warnings: Vec<Box<dyn Error>>,
+}
+
+impl TryConf {
+ fn from_error(error: impl Error + 'static) -> Self {
+ Self {
+ conf: Conf::default(),
+ errors: vec![Box::new(error)],
+ warnings: vec![],
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ConfError(String);
+
+impl fmt::Display for ConfError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ <String as fmt::Display>::fmt(&self.0, f)
+ }
+}
+
+impl Error for ConfError {}
+
+fn conf_error(s: impl Into<String>) -> Box<dyn Error> {
+ Box::new(ConfError(s.into()))
+}
+
+macro_rules! define_Conf {
+ ($(
+ $(#[doc = $doc:literal])+
+ $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
+ ($name:ident: $ty:ty = $default:expr),
+ )*) => {
+ /// Clippy lint configuration
+ pub struct Conf {
+ $($(#[doc = $doc])+ pub $name: $ty,)*
+ }
+
+ mod defaults {
+ $(pub fn $name() -> $ty { $default })*
+ }
+
+ impl Default for Conf {
+ fn default() -> Self {
+ Self { $($name: defaults::$name(),)* }
+ }
+ }
+
+ impl<'de> Deserialize<'de> for TryConf {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
+ deserializer.deserialize_map(ConfVisitor)
+ }
+ }
+
+ #[derive(Deserialize)]
+ #[serde(field_identifier, rename_all = "kebab-case")]
+ #[allow(non_camel_case_types)]
+ enum Field { $($name,)* third_party, }
+
+ struct ConfVisitor;
+
+ impl<'de> Visitor<'de> for ConfVisitor {
+ type Value = TryConf;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("Conf")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
+ let mut errors = Vec::new();
+ let mut warnings = Vec::new();
+ $(let mut $name = None;)*
+ // could get `Field` here directly, but get `str` first for diagnostics
+ while let Some(name) = map.next_key::<&str>()? {
+ match Field::deserialize(name.into_deserializer())? {
+ $(Field::$name => {
+ $(warnings.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)?
+ match map.next_value() {
+ Err(e) => errors.push(conf_error(e.to_string())),
+ Ok(value) => match $name {
+ Some(_) => errors.push(conf_error(format!("duplicate field `{}`", name))),
+ None => {
+ $name = Some(value);
+ // $new_conf is the same as one of the defined `$name`s, so
+ // this variable is defined in line 2 of this function.
+ $(match $new_conf {
+ Some(_) => errors.push(conf_error(concat!(
+ "duplicate field `", stringify!($new_conf),
+ "` (provided as `", stringify!($name), "`)"
+ ))),
+ None => $new_conf = $name.clone(),
+ })?
+ },
+ }
+ }
+ })*
+ // white-listed; ignore
+ Field::third_party => drop(map.next_value::<IgnoredAny>())
+ }
+ }
+ let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
+ Ok(TryConf { conf, errors, warnings })
+ }
+ }
+
+ #[cfg(feature = "internal")]
+ pub mod metadata {
+ use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
+
+ macro_rules! wrap_option {
+ () => (None);
+ ($x:literal) => (Some($x));
+ }
+
+ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
+ vec![
+ $(
+ {
+ let deprecation_reason = wrap_option!($($dep)?);
+
+ ClippyConfiguration::new(
+ stringify!($name),
+ stringify!($ty),
+ format!("{:?}", super::defaults::$name()),
+ concat!($($doc, '\n',)*),
+ deprecation_reason,
+ )
+ },
+ )+
+ ]
+ }
+ }
+ };
+}
+
+define_Conf! {
+ /// Lint: Arithmetic.
+ ///
+ /// Suppress checking of the passed type names.
+ (arithmetic_side_effects_allowed: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
+ /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
+ ///
+ /// Suppress lints whenever the suggested change would cause breakage for other crates.
+ (avoid_breaking_exported_api: bool = true),
- /// Whether `expect` should be allowed in test functions
++ /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION.
+ ///
+ /// The minimum rust version that the project supports
+ (msrv: Option<String> = None),
+ /// DEPRECATED LINT: BLACKLISTED_NAME.
+ ///
+ /// Use the Disallowed Names lint instead
+ #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
+ (blacklisted_names: Vec<String> = Vec::new()),
+ /// Lint: COGNITIVE_COMPLEXITY.
+ ///
+ /// The maximum cognitive complexity a function can have
+ (cognitive_complexity_threshold: u64 = 25),
+ /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
+ ///
+ /// Use the Cognitive Complexity lint instead.
+ #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
+ (cyclomatic_complexity_threshold: u64 = 25),
+ /// Lint: DISALLOWED_NAMES.
+ ///
+ /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
+ /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
+ /// default configuration of Clippy. By default any configuration will replace the default value.
+ (disallowed_names: Vec<String> = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()),
+ /// Lint: DOC_MARKDOWN.
+ ///
+ /// The list of words this lint should not consider as identifiers needing ticks. The value
+ /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
+ /// default configuration of Clippy. By default any configuraction will replace the default value. For example:
+ /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
+ /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
+ ///
+ /// Default list:
+ (doc_valid_idents: Vec<String> = super::DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()),
+ /// Lint: TOO_MANY_ARGUMENTS.
+ ///
+ /// The maximum number of argument a function or method can have
+ (too_many_arguments_threshold: u64 = 7),
+ /// Lint: TYPE_COMPLEXITY.
+ ///
+ /// The maximum complexity a type can have
+ (type_complexity_threshold: u64 = 250),
+ /// Lint: MANY_SINGLE_CHAR_NAMES.
+ ///
+ /// The maximum number of single char bindings a scope may have
+ (single_char_binding_names_threshold: u64 = 4),
+ /// Lint: BOXED_LOCAL, USELESS_VEC.
+ ///
+ /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
+ (too_large_for_stack: u64 = 200),
+ /// Lint: ENUM_VARIANT_NAMES.
+ ///
+ /// The minimum number of enum variants for the lints about variant names to trigger
+ (enum_variant_name_threshold: u64 = 3),
+ /// Lint: LARGE_ENUM_VARIANT.
+ ///
+ /// The maximum size of an enum's variant to avoid box suggestion
+ (enum_variant_size_threshold: u64 = 200),
+ /// Lint: VERBOSE_BIT_MASK.
+ ///
+ /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
+ (verbose_bit_mask_threshold: u64 = 1),
+ /// Lint: DECIMAL_LITERAL_REPRESENTATION.
+ ///
+ /// The lower bound for linting decimal literals
+ (literal_representation_threshold: u64 = 16384),
+ /// Lint: TRIVIALLY_COPY_PASS_BY_REF.
+ ///
+ /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
+ (trivial_copy_size_limit: Option<u64> = None),
+ /// Lint: LARGE_TYPE_PASS_BY_MOVE.
+ ///
+ /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
+ (pass_by_value_size_limit: u64 = 256),
+ /// Lint: TOO_MANY_LINES.
+ ///
+ /// The maximum number of lines a function or method can have
+ (too_many_lines_threshold: u64 = 100),
+ /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS.
+ ///
+ /// The maximum allowed size for arrays on the stack
+ (array_size_threshold: u64 = 512_000),
+ /// Lint: VEC_BOX.
+ ///
+ /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
+ (vec_box_size_threshold: u64 = 4096),
+ /// Lint: TYPE_REPETITION_IN_BOUNDS.
+ ///
+ /// The maximum number of bounds a trait can have to be linted
+ (max_trait_bounds: u64 = 3),
+ /// Lint: STRUCT_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool fields a struct can have
+ (max_struct_bools: u64 = 3),
+ /// Lint: FN_PARAMS_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool parameters a function can have
+ (max_fn_params_bools: u64 = 3),
+ /// Lint: WILDCARD_IMPORTS.
+ ///
+ /// Whether to allow certain wildcard imports (prelude, super in tests).
+ (warn_on_all_wildcard_imports: bool = false),
+ /// Lint: DISALLOWED_MACROS.
+ ///
+ /// The list of disallowed macros, written as fully qualified paths.
+ (disallowed_macros: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
+ /// Lint: DISALLOWED_METHODS.
+ ///
+ /// The list of disallowed methods, written as fully qualified paths.
+ (disallowed_methods: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
+ /// Lint: DISALLOWED_TYPES.
+ ///
+ /// The list of disallowed types, written as fully qualified paths.
+ (disallowed_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
+ /// Lint: UNREADABLE_LITERAL.
+ ///
+ /// Should the fraction of a decimal be linted to include separators.
+ (unreadable_literal_lint_fractions: bool = true),
+ /// Lint: UPPER_CASE_ACRONYMS.
+ ///
+ /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
+ (upper_case_acronyms_aggressive: bool = false),
++ /// Lint: MANUAL_LET_ELSE.
++ ///
++ /// Whether the matches should be considered by the lint, and whether there should
++ /// be filtering for common types.
++ (matches_for_let_else: crate::manual_let_else::MatchLintBehaviour =
++ crate::manual_let_else::MatchLintBehaviour::WellKnownTypes),
+ /// Lint: _CARGO_COMMON_METADATA.
+ ///
+ /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
+ (cargo_ignore_publish: bool = false),
+ /// Lint: NONSTANDARD_MACRO_BRACES.
+ ///
+ /// Enforce the named macros always use the braces specified.
+ ///
+ /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
+ /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
+ /// `crate_name::macro_name` and one with just the macro name.
+ (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
+ /// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
+ ///
+ /// The list of imports to always rename, a fully qualified path followed by the rename.
+ (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()),
+ /// Lint: DISALLOWED_SCRIPT_IDENTS.
+ ///
+ /// The list of unicode scripts allowed to be used in the scope.
+ (allowed_scripts: Vec<String> = vec!["Latin".to_string()]),
+ /// Lint: NON_SEND_FIELDS_IN_SEND_TY.
+ ///
+ /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
+ (enable_raw_pointer_heuristic_for_send: bool = true),
+ /// Lint: INDEX_REFUTABLE_SLICE.
+ ///
+ /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
+ /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
+ /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
+ (max_suggested_slice_pattern_length: u64 = 3),
+ /// Lint: AWAIT_HOLDING_INVALID_TYPE
+ (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
+ /// Lint: LARGE_INCLUDE_FILE.
+ ///
+ /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
+ (max_include_file_size: u64 = 1_000_000),
+ /// Lint: EXPECT_USED.
+ ///
- /// Whether `unwrap` should be allowed in test functions
++ /// Whether `expect` should be allowed within `#[cfg(test)]`
+ (allow_expect_in_tests: bool = false),
+ /// Lint: UNWRAP_USED.
+ ///
- /// Lint: RESULT_LARGE_ERR
++ /// Whether `unwrap` should be allowed in test cfg
+ (allow_unwrap_in_tests: bool = false),
+ /// Lint: DBG_MACRO.
+ ///
+ /// Whether `dbg!` should be allowed in test functions
+ (allow_dbg_in_tests: bool = false),
++ /// Lint: PRINT_STDOUT, PRINT_STDERR.
++ ///
++ /// Whether print macros (ex. `println!`) should be allowed in test functions
++ (allow_print_in_tests: bool = false),
++ /// Lint: RESULT_LARGE_ERR.
+ ///
+ /// The maximum size of the `Err`-variant in a `Result` returned from a function
+ (large_error_threshold: u64 = 128),
++ /// Lint: MUTABLE_KEY.
++ ///
++ /// A list of paths to types that should be treated like `Arc`, i.e. ignored but
++ /// for the generic parameters for determining interior mutability
++ (ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()])),
+}
+
+/// Search for the configuration file.
++///
++/// # Errors
++///
++/// Returns any unexpected filesystem error encountered when searching for the config file
+pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
+ /// Possible filename to search for.
+ const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
+
+ // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
+ // If neither of those exist, use ".".
+ let mut current = env::var_os("CLIPPY_CONF_DIR")
+ .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
+ .map_or_else(|| PathBuf::from("."), PathBuf::from);
+
+ let mut found_config: Option<PathBuf> = None;
+
+ loop {
+ for config_file_name in &CONFIG_FILE_NAMES {
+ if let Ok(config_file) = current.join(config_file_name).canonicalize() {
+ match fs::metadata(&config_file) {
+ Err(e) if e.kind() == io::ErrorKind::NotFound => {},
+ Err(e) => return Err(e),
+ Ok(md) if md.is_dir() => {},
+ Ok(_) => {
+ // warn if we happen to find two config files #8323
+ if let Some(ref found_config_) = found_config {
+ eprintln!(
+ "Using config file `{}`\nWarning: `{}` will be ignored.",
+ found_config_.display(),
+ config_file.display(),
+ );
+ } else {
+ found_config = Some(config_file);
+ }
+ },
+ }
+ }
+ }
+
+ if found_config.is_some() {
+ return Ok(found_config);
+ }
+
+ // If the current directory has no parent, we're done searching.
+ if !current.pop() {
+ return Ok(None);
+ }
+ }
+}
+
+/// Read the `toml` configuration file.
+///
+/// In case of error, the function tries to continue as much as possible.
+pub fn read(path: &Path) -> TryConf {
+ let content = match fs::read_to_string(path) {
+ Err(e) => return TryConf::from_error(e),
+ Ok(content) => content,
+ };
+ match toml::from_str::<TryConf>(&content) {
+ Ok(mut conf) => {
+ extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
+ extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
+
+ conf
+ },
+ Err(e) => TryConf::from_error(e),
+ }
+}
+
+fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
+ if vec.contains(&"..".to_string()) {
+ vec.extend(default.iter().map(ToString::to_string));
+ }
+}
+
+const SEPARATOR_WIDTH: usize = 4;
+
+// Check whether the error is "unknown field" and, if so, list the available fields sorted and at
+// least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it.
+pub fn format_error(error: Box<dyn Error>) -> String {
+ let s = error.to_string();
+
+ if_chain! {
+ if error.downcast::<toml::de::Error>().is_ok();
+ if let Some((prefix, mut fields, suffix)) = parse_unknown_field_message(&s);
+ then {
+ use fmt::Write;
+
+ fields.sort_unstable();
+
+ let (rows, column_widths) = calculate_dimensions(&fields);
+
+ let mut msg = String::from(prefix);
+ for row in 0..rows {
+ writeln!(msg).unwrap();
+ for (column, column_width) in column_widths.iter().copied().enumerate() {
+ let index = column * rows + row;
+ let field = fields.get(index).copied().unwrap_or_default();
+ write!(
+ msg,
+ "{:SEPARATOR_WIDTH$}{field:column_width$}",
+ " "
+ )
+ .unwrap();
+ }
+ }
+ write!(msg, "\n{suffix}").unwrap();
+ msg
+ } else {
+ s
+ }
+ }
+}
+
+// `parse_unknown_field_message` will become unnecessary if
+// https://github.com/alexcrichton/toml-rs/pull/364 is merged.
+fn parse_unknown_field_message(s: &str) -> Option<(&str, Vec<&str>, &str)> {
+ // An "unknown field" message has the following form:
+ // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y
+ // ^^ ^^^^ ^^
+ if_chain! {
+ if s.starts_with("unknown field");
+ let slices = s.split("`, `").collect::<Vec<_>>();
+ let n = slices.len();
+ if n >= 2;
+ if let Some((prefix, first_field)) = slices[0].rsplit_once(" `");
+ if let Some((last_field, suffix)) = slices[n - 1].split_once("` ");
+ then {
+ let fields = iter::once(first_field)
+ .chain(slices[1..n - 1].iter().copied())
+ .chain(iter::once(last_field))
+ .collect::<Vec<_>>();
+ Some((prefix, fields, suffix))
+ } else {
+ None
+ }
+ }
+}
+
+fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
+ let columns = env::var("CLIPPY_TERMINAL_WIDTH")
+ .ok()
+ .and_then(|s| <usize as FromStr>::from_str(&s).ok())
+ .map_or(1, |terminal_width| {
+ let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
+ cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
+ });
+
+ let rows = (fields.len() + (columns - 1)) / columns;
+
+ let column_widths = (0..columns)
+ .map(|column| {
+ if column < columns - 1 {
+ (0..rows)
+ .map(|row| {
+ let index = column * rows + row;
+ let field = fields.get(index).copied().unwrap_or_default();
+ field.len()
+ })
+ .max()
+ .unwrap()
+ } else {
+ // Avoid adding extra space to the last column.
+ 0
+ }
+ })
+ .collect::<Vec<_>>();
+
+ (rows, column_widths)
+}
--- /dev/null
- use clippy_utils::{def_path_res, is_expn_of, match_def_path, paths};
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::match_type;
- if let Some(def_id) = def_path_res(cx, module, None).opt_def_id() {
++use clippy_utils::{def_path_def_ids, is_expn_of, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty::{self};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Symbol;
+
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for interning symbols that have already been pre-interned and defined as constants.
+ ///
+ /// ### Why is this bad?
+ /// It's faster and easier to use the symbol constant.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let _ = sym!(f32);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// let _ = sym::f32;
+ /// ```
+ pub INTERNING_DEFINED_SYMBOL,
+ internal,
+ "interning a symbol that is pre-interned and defined as a constant"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary conversion from Symbol to a string.
+ ///
+ /// ### Why is this bad?
+ /// It's faster use symbols directly instead of strings.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// symbol.as_str() == "clippy";
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// symbol == sym::clippy;
+ /// ```
+ pub UNNECESSARY_SYMBOL_STR,
+ internal,
+ "unnecessary conversion between Symbol and string"
+}
+
+#[derive(Default)]
+pub struct InterningDefinedSymbol {
+ // Maps the symbol value to the constant DefId.
+ symbol_map: FxHashMap<u32, DefId>,
+}
+
+impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]);
+
+impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if !self.symbol_map.is_empty() {
+ return;
+ }
+
+ for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
++ for def_id in def_path_def_ids(cx, module) {
+ for item in cx.tcx.module_children(def_id).iter() {
+ if_chain! {
+ if let Res::Def(DefKind::Const, item_def_id) = item.res;
+ let ty = cx.tcx.type_of(item_def_id);
+ if match_type(cx, ty, &paths::SYMBOL);
+ if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id);
+ if let Ok(value) = value.to_u32();
+ then {
+ self.symbol_map.insert(value, item_def_id);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = &expr.kind;
+ if let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind();
+ if match_def_path(cx, *def_id, &paths::SYMBOL_INTERN);
+ if let Some(Constant::Str(arg)) = constant_simple(cx, cx.typeck_results(), arg);
+ let value = Symbol::intern(&arg).as_u32();
+ if let Some(&def_id) = self.symbol_map.get(&value);
+ then {
+ span_lint_and_sugg(
+ cx,
+ INTERNING_DEFINED_SYMBOL,
+ is_expn_of(expr.span, "sym").unwrap_or(expr.span),
+ "interning a defined symbol",
+ "try",
+ cx.tcx.def_path_str(def_id),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ if let ExprKind::Binary(op, left, right) = expr.kind {
+ if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) {
+ let data = [
+ (left, self.symbol_str_expr(left, cx)),
+ (right, self.symbol_str_expr(right, cx)),
+ ];
+ match data {
+ // both operands are a symbol string
+ [(_, Some(left)), (_, Some(right))] => {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary `Symbol` to string conversion",
+ "try",
+ format!(
+ "{} {} {}",
+ left.as_symbol_snippet(cx),
+ op.node.as_str(),
+ right.as_symbol_snippet(cx),
+ ),
+ Applicability::MachineApplicable,
+ );
+ },
+ // one of the operands is a symbol string
+ [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => {
+ // creating an owned string for comparison
+ if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary string allocation",
+ "try",
+ format!("{}.as_str()", symbol.as_symbol_snippet(cx)),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ // nothing found
+ [(_, None), (_, None)] => {},
+ }
+ }
+ }
+ }
+}
+
+impl InterningDefinedSymbol {
+ fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
+ static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD];
+ static SYMBOL_STR_PATHS: &[&[&str]] = &[
+ &paths::SYMBOL_AS_STR,
+ &paths::SYMBOL_TO_IDENT_STRING,
+ &paths::TO_STRING_METHOD,
+ ];
+ let call = if_chain! {
+ if let ExprKind::AddrOf(_, _, e) = expr.kind;
+ if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
+ then { e } else { expr }
+ };
+ if_chain! {
+ // is a method call
+ if let ExprKind::MethodCall(_, item, [], _) = call.kind;
+ if let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id);
+ let ty = cx.typeck_results().expr_ty(item);
+ // ...on either an Ident or a Symbol
+ if let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) {
+ Some(false)
+ } else if match_type(cx, ty, &paths::IDENT) {
+ Some(true)
+ } else {
+ None
+ };
+ // ...which converts it to a string
+ let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
+ if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path));
+ then {
+ let is_to_owned = path.last().unwrap().ends_with("string");
+ return Some(SymbolStrExpr::Expr {
+ item,
+ is_ident,
+ is_to_owned,
+ });
+ }
+ }
+ // is a string constant
+ if let Some(Constant::Str(s)) = constant_simple(cx, cx.typeck_results(), expr) {
+ let value = Symbol::intern(&s).as_u32();
+ // ...which matches a symbol constant
+ if let Some(&def_id) = self.symbol_map.get(&value) {
+ return Some(SymbolStrExpr::Const(def_id));
+ }
+ }
+ None
+ }
+}
+
+enum SymbolStrExpr<'tcx> {
+ /// a string constant with a corresponding symbol constant
+ Const(DefId),
+ /// a "symbol to string" expression like `symbol.as_str()`
+ Expr {
+ /// part that evaluates to `Symbol` or `Ident`
+ item: &'tcx Expr<'tcx>,
+ is_ident: bool,
+ /// whether an owned `String` is created like `to_ident_string()`
+ is_to_owned: bool,
+ },
+}
+
+impl<'tcx> SymbolStrExpr<'tcx> {
+ /// Returns a snippet that evaluates to a `Symbol` and is const if possible
+ fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> {
+ match *self {
+ Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(),
+ Self::Expr { item, is_ident, .. } => {
+ let mut snip = snippet(cx, item.span.source_callsite(), "..");
+ if is_ident {
+ // get `Ident.name`
+ snip.to_mut().push_str(".name");
+ }
+ snip
+ },
+ }
+ }
+}
--- /dev/null
- use rustc_hir::def::{DefKind, Res};
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::def_path_res;
+use clippy_utils::diagnostics::span_lint;
+use if_chain::if_chain;
+use rustc_hir as hir;
- if def_path_res(cx, path, None) != Res::Err {
++use rustc_hir::def::DefKind;
+use rustc_hir::Item;
+use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, fast_reject::SimplifiedTypeGen, FloatTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the paths module for invalid paths.
+ ///
+ /// ### Why is this bad?
+ /// It indicates a bug in the code.
+ ///
+ /// ### Example
+ /// None.
+ pub INVALID_PATHS,
+ internal,
+ "invalid path"
+}
+
+declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let local_def_id = &cx.tcx.parent_module(item.hir_id());
+ let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
+ if_chain! {
+ if mod_name.as_str() == "paths";
+ if let hir::ItemKind::Const(ty, body_id) = item.kind;
+ let ty = hir_ty_to_ty(cx.tcx, ty);
+ if let ty::Array(el_ty, _) = &ty.kind();
+ if let ty::Ref(_, el_ty, _) = &el_ty.kind();
+ if el_ty.is_str();
+ let body = cx.tcx.hir().body(body_id);
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
+ let path: Vec<&str> = path
+ .iter()
+ .map(|x| {
+ if let Constant::Str(s) = x {
+ s.as_str()
+ } else {
+ // We checked the type of the constant above
+ unreachable!()
+ }
+ })
+ .collect();
+ if !check_path(cx, &path[..]);
+ then {
+ span_lint(cx, INVALID_PATHS, item.span, "invalid path");
+ }
+ }
+ }
+}
+
+// This is not a complete resolver for paths. It works on all the paths currently used in the paths
+// module. That's all it does and all it needs to do.
+pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
++ if !def_path_res(cx, path).is_empty() {
+ return true;
+ }
+
+ // Some implementations can't be found by `path_to_res`, particularly inherent
+ // implementations of native types. Check lang items.
+ let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
+ let lang_items = cx.tcx.lang_items();
+ // This list isn't complete, but good enough for our current list of paths.
+ let incoherent_impls = [
+ SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F32),
+ SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F64),
+ SimplifiedTypeGen::SliceSimplifiedType,
+ SimplifiedTypeGen::StrSimplifiedType,
+ ]
+ .iter()
+ .flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter().copied());
+ for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) {
+ let lang_item_path = cx.get_def_path(item_def_id);
+ if path_syms.starts_with(&lang_item_path) {
+ if let [item] = &path_syms[lang_item_path.len()..] {
+ if matches!(
+ cx.tcx.def_kind(item_def_id),
+ DefKind::Mod | DefKind::Enum | DefKind::Trait
+ ) {
+ for child in cx.tcx.module_children(item_def_id) {
+ if child.ident.name == *item {
+ return true;
+ }
+ }
+ } else {
+ for child in cx.tcx.associated_item_def_ids(item_def_id) {
+ if cx.tcx.item_name(*child) == *item {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ false
+}
--- /dev/null
- pub(super) fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &hir::Ty<'_>) -> bool {
+use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::macros::root_macro_call_first_node;
+use clippy_utils::{is_lint_allowed, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast as ast;
+use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::Symbol;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Ensures every lint is associated to a `LintPass`.
+ ///
+ /// ### Why is this bad?
+ /// The compiler only knows lints via a `LintPass`. Without
+ /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
+ /// know the name of the lint.
+ ///
+ /// ### Known problems
+ /// Only checks for lints associated using the
+ /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// declare_lint! { pub LINT_1, ... }
+ /// declare_lint! { pub LINT_2, ... }
+ /// declare_lint! { pub FORGOTTEN_LINT, ... }
+ /// // ...
+ /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
+ /// // missing FORGOTTEN_LINT
+ /// ```
+ pub LINT_WITHOUT_LINT_PASS,
+ internal,
+ "declaring a lint without associating it in a LintPass"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cases of an auto-generated lint without an updated description,
+ /// i.e. `default lint description`.
+ ///
+ /// ### Why is this bad?
+ /// Indicates that the lint is not finished.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
+ /// ```
+ pub DEFAULT_LINT,
+ internal,
+ "found 'default lint description' in a lint declaration"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for invalid `clippy::version` attributes.
+ ///
+ /// Valid values are:
+ /// * "pre 1.29.0"
+ /// * any valid semantic version
+ pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
+ internal,
+ "found an invalid `clippy::version` attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for declared clippy lints without the `clippy::version` attribute.
+ ///
+ pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
+ internal,
+ "found clippy lint without `clippy::version` attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cases of an auto-generated deprecated lint without an updated reason,
+ /// i.e. `"default deprecation note"`.
+ ///
+ /// ### Why is this bad?
+ /// Indicates that the documentation is incomplete.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// declare_deprecated_lint! {
+ /// /// ### What it does
+ /// /// Nothing. This lint has been deprecated.
+ /// ///
+ /// /// ### Deprecation reason
+ /// /// TODO
+ /// #[clippy::version = "1.63.0"]
+ /// pub COOL_LINT,
+ /// "default deprecation note"
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// declare_deprecated_lint! {
+ /// /// ### What it does
+ /// /// Nothing. This lint has been deprecated.
+ /// ///
+ /// /// ### Deprecation reason
+ /// /// This lint has been replaced by `cooler_lint`
+ /// #[clippy::version = "1.63.0"]
+ /// pub COOL_LINT,
+ /// "this lint has been replaced by `cooler_lint`"
+ /// }
+ /// ```
+ pub DEFAULT_DEPRECATION_REASON,
+ internal,
+ "found 'default deprecation note' in a deprecated lint declaration"
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct LintWithoutLintPass {
+ declared_lints: FxHashMap<Symbol, Span>,
+ registered_lints: FxHashSet<Symbol>,
+}
+
+impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
+
+impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
+ || is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
+ {
+ return;
+ }
+
+ if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
+ let is_lint_ref_ty = is_lint_ref_type(cx, ty);
+ if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
+ check_invalid_clippy_version_attribute(cx, item);
+
+ let expr = &cx.tcx.hir().body(body_id).value;
+ let fields;
+ if is_lint_ref_ty {
+ if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
+ && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind {
+ fields = struct_fields;
+ } else {
+ return;
+ }
+ } else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
+ fields = struct_fields;
+ } else {
+ return;
+ }
+
+ let field = fields
+ .iter()
+ .find(|f| f.ident.as_str() == "desc")
+ .expect("lints must have a description field");
+
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Str(ref sym, _),
+ ..
+ }) = field.expr.kind
+ {
+ let sym_str = sym.as_str();
+ if is_lint_ref_ty {
+ if sym_str == "default lint description" {
+ span_lint(
+ cx,
+ DEFAULT_LINT,
+ item.span,
+ &format!("the lint `{}` has the default lint description", item.ident.name),
+ );
+ }
+
+ self.declared_lints.insert(item.ident.name, item.span);
+ } else if sym_str == "default deprecation note" {
+ span_lint(
+ cx,
+ DEFAULT_DEPRECATION_REASON,
+ item.span,
+ &format!("the lint `{}` has the default deprecation reason", item.ident.name),
+ );
+ }
+ }
+ }
+ } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
+ if !matches!(
+ cx.tcx.item_name(macro_call.def_id).as_str(),
+ "impl_lint_pass" | "declare_lint_pass"
+ ) {
+ return;
+ }
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: None,
+ items: impl_item_refs,
+ ..
+ }) = item.kind
+ {
+ let mut collector = LintCollector {
+ output: &mut self.registered_lints,
+ cx,
+ };
+ let body_id = cx.tcx.hir().body_owned_by(
+ cx.tcx.hir().local_def_id(
+ impl_item_refs
+ .iter()
+ .find(|iiref| iiref.ident.as_str() == "get_lints")
+ .expect("LintPass needs to implement get_lints")
+ .id
+ .hir_id(),
+ ),
+ );
+ collector.visit_expr(cx.tcx.hir().body(body_id).value);
+ }
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
+ return;
+ }
+
+ for (lint_name, &lint_span) in &self.declared_lints {
+ // When using the `declare_tool_lint!` macro, the original `lint_span`'s
+ // file points to "<rustc macros>".
+ // `compiletest-rs` thinks that's an error in a different file and
+ // just ignores it. This causes the test in compile-fail/lint_pass
+ // not able to capture the error.
+ // Therefore, we need to climb the macro expansion tree and find the
+ // actual span that invoked `declare_tool_lint!`:
+ let lint_span = lint_span.ctxt().outer_expn_data().call_site;
+
+ if !self.registered_lints.contains(lint_name) {
+ span_lint(
+ cx,
+ LINT_WITHOUT_LINT_PASS,
+ lint_span,
+ &format!("the lint `{lint_name}` is not added to any `LintPass`"),
+ );
+ }
+ }
+ }
+}
+
++pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let TyKind::Rptr(
+ _,
+ MutTy {
+ ty: inner,
+ mutbl: Mutability::Not,
+ },
+ ) = ty.kind
+ {
+ if let TyKind::Path(ref path) = inner.kind {
+ if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
+ return match_def_path(cx, def_id, &paths::LINT);
+ }
+ }
+ }
+
+ false
+}
+
+fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
+ if let Some(value) = extract_clippy_version_value(cx, item) {
+ // The `sym!` macro doesn't work as it only expects a single token.
+ // It's better to keep it this way and have a direct `Symbol::intern` call here.
+ if value == Symbol::intern("pre 1.29.0") {
+ return;
+ }
+
+ if RustcVersion::parse(value.as_str()).is_err() {
+ span_lint_and_help(
+ cx,
+ INVALID_CLIPPY_VERSION_ATTRIBUTE,
+ item.span,
+ "this item has an invalid `clippy::version` attribute",
+ None,
+ "please use a valid semantic version, see `doc/adding_lints.md`",
+ );
+ }
+ } else {
+ span_lint_and_help(
+ cx,
+ MISSING_CLIPPY_VERSION_ATTRIBUTE,
+ item.span,
+ "this lint is missing the `clippy::version` attribute or version value",
+ None,
+ "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
+ );
+ }
+}
+
+/// This function extracts the version value of a `clippy::version` attribute if the given value has
+/// one
+pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ attrs.iter().find_map(|attr| {
+ if_chain! {
+ // Identify attribute
+ if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind;
+ if let [tool_name, attr_name] = &attr_kind.item.path.segments[..];
+ if tool_name.ident.name == sym::clippy;
+ if attr_name.ident.name == sym::version;
+ if let Some(version) = attr.value_str();
+ then { Some(version) } else { None }
+ }
+ })
+}
+
+struct LintCollector<'a, 'tcx> {
+ output: &'a mut FxHashSet<Symbol>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) {
+ if path.segments.len() == 1 {
+ self.output.insert(path.segments[0].ident.name);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- use clippy_utils::{def_path_res, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
- use rustc_middle::ty::{self, AssocKind, DefIdTree, Ty};
++use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
- use rustc_span::symbol::{Ident, Symbol};
++use rustc_middle::ty::{self, DefIdTree, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
- if let Some(def_id) = inherent_def_path_res(cx, &segments[..]);
++use rustc_span::symbol::Symbol;
+use rustc_span::Span;
+
+use std::str;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
+ ///
+ /// ### Why is this bad?
+ /// The path for an item is subject to change and is less efficient to look up than a
+ /// diagnostic item or a `LangItem`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// utils::match_type(cx, ty, &paths::VEC)
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
+ /// ```
+ pub UNNECESSARY_DEF_PATH,
+ internal,
+ "using a def path when a diagnostic item or a `LangItem` is available"
+}
+
+impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+
+#[derive(Default)]
+pub struct UnnecessaryDefPath {
+ array_def_ids: FxHashSet<(DefId, Span)>,
+ linted_def_ids: FxHashSet<DefId>,
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
+ return;
+ }
+
+ match expr.kind {
+ ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span),
+ ExprKind::Array(elements) => self.check_array(cx, elements, expr.span),
+ _ => {},
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ for &(def_id, span) in &self.array_def_ids {
+ if self.linted_def_ids.contains(&def_id) {
+ continue;
+ }
+
+ let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) {
+ ("diagnostic item", format!("sym::{sym}"))
+ } else if let Some(sym) = get_lang_item_name(cx, def_id) {
+ ("language item", format!("LangItem::{sym}"))
+ } else {
+ continue;
+ };
+
+ span_lint_and_help(
+ cx,
+ UNNECESSARY_DEF_PATH,
+ span,
+ &format!("hardcoded path to a {msg}"),
+ None,
+ &format!("convert all references to use `{sugg}`"),
+ );
+ }
+ }
+}
+
+impl UnnecessaryDefPath {
+ #[allow(clippy::too_many_lines)]
+ fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) {
+ enum Item {
+ LangItem(&'static str),
+ DiagnosticItem(Symbol),
+ }
+ static PATHS: &[&[&str]] = &[
+ &["clippy_utils", "match_def_path"],
+ &["clippy_utils", "match_trait_method"],
+ &["clippy_utils", "ty", "match_type"],
+ &["clippy_utils", "is_expr_path_def_path"],
+ ];
+
+ if_chain! {
+ if let [cx_arg, def_arg, args @ ..] = args;
+ if let ExprKind::Path(path) = &func.kind;
+ if let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id();
+ if let Some(which_path) = match_any_def_paths(cx, id, PATHS);
+ let item_arg = if which_path == 4 { &args[1] } else { &args[0] };
+ // Extract the path to the matched type
+ if let Some(segments) = path_to_matched_type(cx, item_arg);
+ let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
- if let Some(def_id) = inherent_def_path_res(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
++ if let Some(def_id) = def_path_def_ids(cx, &segments[..]).next();
+ then {
+ // Check if the target item is a diagnostic item or LangItem.
+ #[rustfmt::skip]
+ let (msg, item) = if let Some(item_name)
+ = cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id)
+ {
+ (
+ "use of a def path to a diagnostic item",
+ Item::DiagnosticItem(*item_name),
+ )
+ } else if let Some(item_name) = get_lang_item_name(cx, def_id) {
+ (
+ "use of a def path to a `LangItem`",
+ Item::LangItem(item_name),
+ )
+ } else {
+ return;
+ };
+
+ let has_ctor = match cx.tcx.def_kind(def_id) {
+ DefKind::Struct => {
+ let variant = cx.tcx.adt_def(def_id).non_enum_variant();
+ variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+ },
+ DefKind::Variant => {
+ let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id);
+ variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+ },
+ _ => false,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app);
+ let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app);
+ let (sugg, with_note) = match (which_path, item) {
+ // match_def_path
+ (0, Item::DiagnosticItem(item)) => (
+ format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"),
+ has_ctor,
+ ),
+ (0, Item::LangItem(item)) => (
+ format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"),
+ has_ctor,
+ ),
+ // match_trait_method
+ (1, Item::DiagnosticItem(item)) => {
+ (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false)
+ },
+ // match_type
+ (2, Item::DiagnosticItem(item)) => (
+ format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+ false,
+ ),
+ (2, Item::LangItem(item)) => (
+ format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"),
+ false,
+ ),
+ // is_expr_path_def_path
+ (3, Item::DiagnosticItem(item)) if has_ctor => (
+ format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",),
+ false,
+ ),
+ (3, Item::LangItem(item)) if has_ctor => (
+ format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",),
+ false,
+ ),
+ (3, Item::DiagnosticItem(item)) => (
+ format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+ false,
+ ),
+ (3, Item::LangItem(item)) => (
+ format!(
+ "path_res({cx_snip}, {def_snip}).opt_def_id()\
+ .map_or(false, |id| {cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some(id))",
+ ),
+ false,
+ ),
+ _ => return,
+ };
+
+ span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| {
+ diag.span_suggestion(span, "try", sugg, app);
+ if with_note {
+ diag.help(
+ "if this `DefId` came from a constructor expression or pattern then the \
+ parent `DefId` should be used instead",
+ );
+ }
+ });
+
+ self.linted_def_ids.insert(def_id);
+ }
+ }
+ }
+
+ fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) {
+ let Some(path) = path_from_array(elements) else { return };
+
- let &alloc = alloc.provenance().values().next()?;
++ for def_id in def_path_def_ids(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
+ self.array_def_ids.insert((def_id, span));
+ }
+ }
+}
+
+fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<String>> {
+ match peel_hir_expr_refs(expr).0.kind {
+ ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Local(hir_id) => {
+ let parent_id = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(Local { init: Some(init), .. })) = cx.tcx.hir().find(parent_id) {
+ path_to_matched_type(cx, init)
+ } else {
+ None
+ }
+ },
+ Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
+ cx,
+ cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
+ cx.tcx.type_of(def_id),
+ ),
+ Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
+ ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => {
+ read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id))
+ },
+ _ => None,
+ },
+ _ => None,
+ },
+ ExprKind::Array(exprs) => path_from_array(exprs),
+ _ => None,
+ }
+}
+
+fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> {
+ let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() {
- // def_path_res will match field names before anything else, but for this we want to match
- // inherent functions first.
- fn inherent_def_path_res(cx: &LateContext<'_>, segments: &[&str]) -> Option<DefId> {
- def_path_res(cx, segments, None).opt_def_id().map(|def_id| {
- if cx.tcx.def_kind(def_id) == DefKind::Field {
- let method_name = *segments.last().unwrap();
- cx.tcx
- .def_key(def_id)
- .parent
- .and_then(|parent_idx| {
- cx.tcx
- .inherent_impls(DefId {
- index: parent_idx,
- krate: def_id.krate,
- })
- .iter()
- .find_map(|impl_id| {
- cx.tcx.associated_items(*impl_id).find_by_name_and_kind(
- cx.tcx,
- Ident::from_str(method_name),
- AssocKind::Fn,
- *impl_id,
- )
- })
- })
- .map_or(def_id, |item| item.def_id)
- } else {
- def_id
- }
- })
- }
-
++ let &alloc = alloc.provenance().ptrs().values().next()?;
+ if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
+ (alloc.inner(), ty)
+ } else {
+ return None;
+ }
+ } else {
+ (alloc, ty)
+ };
+
+ if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
+ && let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
+ && ty.is_str()
+ {
+ alloc
+ .provenance()
++ .ptrs()
+ .values()
+ .map(|&alloc| {
+ if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
+ let alloc = alloc.inner();
+ str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()))
+ .ok().map(ToOwned::to_owned)
+ } else {
+ None
+ }
+ })
+ .collect()
+ } else {
+ None
+ }
+}
+
+fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> {
+ exprs
+ .iter()
+ .map(|expr| {
+ if let ExprKind::Lit(lit) = &expr.kind {
+ if let LitKind::Str(sym, _) = lit.node {
+ return Some((*sym.as_str()).to_owned());
+ }
+ }
+
+ None
+ })
+ .collect()
+}
+
+fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> {
+ if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) {
+ Some(lang_item.variant_name())
+ } else {
+ None
+ }
+}
--- /dev/null
- sym::print_macro | sym::println_macro => {
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn, MacroCall};
+use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
++use clippy_utils::{is_in_cfg_test, is_in_test_function};
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, HirIdMap, Impl, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `println!("")` to
+ /// print a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `println!()`, which is simpler.
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// println!();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINTLN_EMPTY_STRING,
+ style,
+ "using `println!(\"\")` with an empty string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `print!()` with a format
+ /// string that ends in a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `println!()` instead, which appends the
+ /// newline.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "World";
+ /// print!("Hello {}!\n", name);
+ /// ```
+ /// use println!() instead
+ /// ```rust
+ /// # let name = "World";
+ /// println!("Hello {}!", name);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_WITH_NEWLINE,
+ style,
+ "using `print!()` with a format string that ends in a single newline"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for printing on *stdout*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// People often print on *stdout* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// ### Known problems
+ /// Only catches `print!` and `println!` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("Hello world!");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_STDOUT,
+ restriction,
+ "printing on stdout"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for printing on *stderr*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// People often print on *stderr* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// ### Known problems
+ /// Only catches `eprint!` and `eprintln!` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// eprintln!("Hello world!");
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub PRINT_STDERR,
+ restriction,
+ "printing on stderr"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Debug` formatting. The purpose of this
+ /// lint is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// The purpose of the `Debug` trait is to facilitate
+ /// debugging Rust code. It should not be used in user-facing output.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = "bar";
+ /// println!("{:?}", foo);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USE_DEBUG,
+ restriction,
+ "use of `Debug`-based formatting"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about the use of literals as `print!`/`println!` args.
+ ///
+ /// ### Why is this bad?
+ /// Using literals as `println!` args is inefficient
+ /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
+ /// (i.e., just put the literal in the format string)
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("{}", "foo");
+ /// ```
+ /// use the literal without formatting:
+ /// ```rust
+ /// println!("foo");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_LITERAL,
+ style,
+ "printing a literal with a format string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `writeln!(buf, "")` to
+ /// print a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `writeln!(buf)`, which is simpler.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// writeln!(buf, "");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// writeln!(buf);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITELN_EMPTY_STRING,
+ style,
+ "using `writeln!(buf, \"\")` with an empty string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `write!()` with a format
+ /// string that
+ /// ends in a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `writeln!()` instead, which appends the
+ /// newline.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// # let name = "World";
+ /// write!(buf, "Hello {}!\n", name);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// # let name = "World";
+ /// writeln!(buf, "Hello {}!", name);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITE_WITH_NEWLINE,
+ style,
+ "using `write!()` with a format string that ends in a single newline"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about the use of literals as `write!`/`writeln!` args.
+ ///
+ /// ### Why is this bad?
+ /// Using literals as `writeln!` args is inefficient
+ /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
+ /// (i.e., just put the literal in the format string)
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// writeln!(buf, "{}", "foo");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// writeln!(buf, "foo");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITE_LITERAL,
+ style,
+ "writing a literal with a format string"
+}
+
+#[derive(Default)]
+pub struct Write {
+ in_debug_impl: bool,
++ allow_print_in_tests: bool,
++}
++
++impl Write {
++ pub fn new(allow_print_in_tests: bool) -> Self {
++ Self {
++ allow_print_in_tests,
++ ..Default::default()
++ }
++ }
+}
+
+impl_lint_pass!(Write => [
+ PRINT_WITH_NEWLINE,
+ PRINTLN_EMPTY_STRING,
+ PRINT_STDOUT,
+ PRINT_STDERR,
+ USE_DEBUG,
+ PRINT_LITERAL,
+ WRITE_WITH_NEWLINE,
+ WRITELN_EMPTY_STRING,
+ WRITE_LITERAL,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Write {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_debug_impl(cx, item) {
+ self.in_debug_impl = true;
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_debug_impl(cx, item) {
+ self.in_debug_impl = false;
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { return };
+ let Some(name) = diag_name.as_str().strip_suffix("_macro") else { return };
+
+ let is_build_script = cx
+ .sess()
+ .opts
+ .crate_name
+ .as_ref()
+ .map_or(false, |crate_name| crate_name == "build_script_build");
+
++ let allowed_in_tests = self.allow_print_in_tests
++ && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id));
+ match diag_name {
- sym::eprint_macro | sym::eprintln_macro => {
++ sym::print_macro | sym::println_macro if !allowed_in_tests => {
+ if !is_build_script {
+ span_lint(cx, PRINT_STDOUT, macro_call.span, &format!("use of `{name}!`"));
+ }
+ },
++ sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => {
+ span_lint(cx, PRINT_STDERR, macro_call.span, &format!("use of `{name}!`"));
+ },
+ sym::write_macro | sym::writeln_macro => {},
+ _ => return,
+ }
+
+ let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn) else { return };
+
+ // ignore `writeln!(w)` and `write!(v, some_macro!())`
+ if format_args.format_string.span.from_expansion() {
+ return;
+ }
+
+ match diag_name {
+ sym::print_macro | sym::eprint_macro | sym::write_macro => {
+ check_newline(cx, &format_args, ¯o_call, name);
+ },
+ sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
+ check_empty_string(cx, &format_args, ¯o_call, name);
+ },
+ _ => {},
+ }
+
+ check_literal(cx, &format_args, name);
+
+ if !self.in_debug_impl {
+ for arg in &format_args.args {
+ if arg.format.r#trait == sym::Debug {
+ span_lint(cx, USE_DEBUG, arg.span, "use of `Debug`-based formatting");
+ }
+ }
+ }
+ }
+}
+fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind
+ && let Some(trait_id) = trait_ref.trait_def_id()
+ {
+ cx.tcx.is_diagnostic_item(sym::Debug, trait_id)
+ } else {
+ false
+ }
+}
+
+fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_call: &MacroCall, name: &str) {
+ let format_string_parts = &format_args.format_string.parts;
+ let mut format_string_span = format_args.format_string.span;
+
+ let Some(last) = format_string_parts.last() else { return };
+
+ let count_vertical_whitespace = || {
+ format_string_parts
+ .iter()
+ .flat_map(|part| part.as_str().chars())
+ .filter(|ch| matches!(ch, '\r' | '\n'))
+ .count()
+ };
+
+ if last.as_str().ends_with('\n')
+ // ignore format strings with other internal vertical whitespace
+ && count_vertical_whitespace() == 1
+
+ // ignore trailing arguments: `print!("Issue\n{}", 1265);`
+ && format_string_parts.len() > format_args.args.len()
+ {
+ let lint = if name == "write" {
+ format_string_span = expand_past_previous_comma(cx, format_string_span);
+
+ WRITE_WITH_NEWLINE
+ } else {
+ PRINT_WITH_NEWLINE
+ };
+
+ span_lint_and_then(
+ cx,
+ lint,
+ macro_call.span,
+ &format!("using `{name}!()` with a format string that ends in a single newline"),
+ |diag| {
+ let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!');
+ let Some(format_snippet) = snippet_opt(cx, format_string_span) else { return };
+
+ if format_string_parts.len() == 1 && last.as_str() == "\n" {
+ // print!("\n"), write!(f, "\n")
+
+ diag.multipart_suggestion(
+ &format!("use `{name}ln!` instead"),
+ vec![(name_span, format!("{name}ln")), (format_string_span, String::new())],
+ Applicability::MachineApplicable,
+ );
+ } else if format_snippet.ends_with("\\n\"") {
+ // print!("...\n"), write!(f, "...\n")
+
+ let hi = format_string_span.hi();
+ let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1));
+
+ diag.multipart_suggestion(
+ &format!("use `{name}ln!` instead"),
+ vec![(name_span, format!("{name}ln")), (newline_span, String::new())],
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+}
+
+fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_call: &MacroCall, name: &str) {
+ if let [part] = &format_args.format_string.parts[..]
+ && let mut span = format_args.format_string.span
+ && part.as_str() == "\n"
+ {
+ let lint = if name == "writeln" {
+ span = expand_past_previous_comma(cx, span);
+
+ WRITELN_EMPTY_STRING
+ } else {
+ PRINTLN_EMPTY_STRING
+ };
+
+ span_lint_and_then(
+ cx,
+ lint,
+ macro_call.span,
+ &format!("empty string literal in `{name}!`"),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "remove the empty string",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+}
+
+fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &str) {
+ let mut counts = HirIdMap::<usize>::default();
+ for param in format_args.params() {
+ *counts.entry(param.value.hir_id).or_default() += 1;
+ }
+
+ for arg in &format_args.args {
+ let value = arg.param.value;
+
+ if counts[&value.hir_id] == 1
+ && arg.format.is_default()
+ && let ExprKind::Lit(lit) = &value.kind
+ && !value.span.from_expansion()
+ && let Some(value_string) = snippet_opt(cx, value.span)
+ {
+ let (replacement, replace_raw) = match lit.node {
+ LitKind::Str(..) => extract_str_literal(&value_string),
+ LitKind::Char(ch) => (
+ match ch {
+ '"' => "\\\"",
+ '\'' => "'",
+ _ => &value_string[1..value_string.len() - 1],
+ }
+ .to_string(),
+ false,
+ ),
+ LitKind::Bool(b) => (b.to_string(), false),
+ _ => continue,
+ };
+
+ let lint = if name.starts_with("write") {
+ WRITE_LITERAL
+ } else {
+ PRINT_LITERAL
+ };
+
+ let format_string_is_raw = format_args.format_string.style.is_some();
+ let replacement = match (format_string_is_raw, replace_raw) {
+ (false, false) => Some(replacement),
+ (false, true) => Some(replacement.replace('"', "\\\"").replace('\\', "\\\\")),
+ (true, false) => match conservative_unescape(&replacement) {
+ Ok(unescaped) => Some(unescaped),
+ Err(UnescapeErr::Lint) => None,
+ Err(UnescapeErr::Ignore) => continue,
+ },
+ (true, true) => {
+ if replacement.contains(['#', '"']) {
+ None
+ } else {
+ Some(replacement)
+ }
+ },
+ };
+
+ span_lint_and_then(
+ cx,
+ lint,
+ value.span,
+ "literal with an empty format string",
+ |diag| {
+ if let Some(replacement) = replacement
+ // `format!("{}", "a")`, `format!("{named}", named = "b")
+ // ~~~~~ ~~~~~~~~~~~~~
+ && let Some(value_span) = format_args.value_with_prev_comma_span(value.hir_id)
+ {
+ let replacement = replacement.replace('{', "{{").replace('}', "}}");
+ diag.multipart_suggestion(
+ "try this",
+ vec![(arg.span, replacement), (value_span, String::new())],
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+ }
+}
+
+/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
+///
+/// `r#"a"#` -> (`a`, true)
+///
+/// `"b"` -> (`b`, false)
+fn extract_str_literal(literal: &str) -> (String, bool) {
+ let (literal, raw) = match literal.strip_prefix('r') {
+ Some(stripped) => (stripped.trim_matches('#'), true),
+ None => (literal, false),
+ };
+
+ (literal[1..literal.len() - 1].to_string(), raw)
+}
+
+enum UnescapeErr {
+ /// Should still be linted, can be manually resolved by author, e.g.
+ ///
+ /// ```ignore
+ /// print!(r"{}", '"');
+ /// ```
+ Lint,
+ /// Should not be linted, e.g.
+ ///
+ /// ```ignore
+ /// print!(r"{}", '\r');
+ /// ```
+ Ignore,
+}
+
+/// Unescape a normal string into a raw string
+fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
+ let mut unescaped = String::with_capacity(literal.len());
+ let mut chars = literal.chars();
+ let mut err = false;
+
+ while let Some(ch) = chars.next() {
+ match ch {
+ '#' => err = true,
+ '\\' => match chars.next() {
+ Some('\\') => unescaped.push('\\'),
+ Some('"') => err = true,
+ _ => return Err(UnescapeErr::Ignore),
+ },
+ _ => unescaped.push(ch),
+ }
+ }
+
+ if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
+}
--- /dev/null
- version = "0.1.66"
+[package]
+name = "clippy_utils"
++version = "0.1.67"
+edition = "2021"
+publish = false
+
+[dependencies]
+arrayvec = { version = "0.7", default-features = false }
+if_chain = "1.0"
+itertools = "0.10.1"
+rustc-semver = "1.1"
+
+[features]
+deny-warnings = []
+internal = []
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- MethodCall(box ast::MethodCall { seg: ls, receiver: lr, args: la, .. }),
- MethodCall(box ast::MethodCall { seg: rs, receiver: rr, args: ra, .. })
- ) => {
- eq_path_seg(ls, rs) && eq_expr(lr, rr) && over(la, ra, |l, r| eq_expr(l, r))
- },
+//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
+//!
+//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
+
+#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use crate::{both, over};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, *};
+use rustc_span::symbol::Ident;
+use std::mem;
+
+pub mod ident_iter;
+pub use ident_iter::IdentIter;
+
+pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
+ use BinOpKind::*;
+ matches!(
+ kind,
+ Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
+ )
+}
+
+/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
+pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
+}
+
+pub fn eq_id(l: Ident, r: Ident) -> bool {
+ l.name == r.name
+}
+
+pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
+ use PatKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_pat(l, r),
+ (_, Paren(r)) => eq_pat(l, r),
+ (Wild, Wild) | (Rest, Rest) => true,
+ (Lit(l), Lit(r)) => eq_expr(l, r),
+ (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
+ (Range(lf, lt, le), Range(rf, rt, re)) => {
+ eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
+ },
+ (Box(l), Box(r))
+ | (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
+ | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
+ (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => {
+ eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r))
+ },
+ (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
+ lr == rr && eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat)
+ },
+ (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
+ match (l, r) {
+ (RangeEnd::Excluded, RangeEnd::Excluded) => true,
+ (RangeEnd::Included(l), RangeEnd::Included(r)) => {
+ matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_pat(&l.pat, &r.pat)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_qself(l: &P<QSelf>, r: &P<QSelf>) -> bool {
+ l.position == r.position && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_maybe_qself(l: &Option<P<QSelf>>, r: &Option<P<QSelf>>) -> bool {
+ match (l, r) {
+ (Some(l), Some(r)) => eq_qself(l, r),
+ (None, None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_path(l: &Path, r: &Path) -> bool {
+ over(&l.segments, &r.segments, eq_path_seg)
+}
+
+pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
+ eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
+}
+
+pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
+ match (l, r) {
+ (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => over(&l.args, &r.args, eq_angle_arg),
+ (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
+ over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
+ match (l, r) {
+ (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
+ (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
+ match (l, r) {
+ (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
+ (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
+ (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
+ _ => false,
+ }
+}
+
+pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
+ both(l, r, |l, r| eq_expr(l, r))
+}
+
+pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
+ match (l, r) {
+ (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
+ (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
+ use ExprKind::*;
+ if !over(&l.attrs, &r.attrs, eq_attr) {
+ return false;
+ }
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_expr(l, r),
+ (_, Paren(r)) => eq_expr(l, r),
+ (Err, Err) => true,
+ (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
+ (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
+ (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
+ (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
+ (
- })
++ MethodCall(box ast::MethodCall {
++ seg: ls,
++ receiver: lr,
++ args: la,
++ ..
++ }),
++ MethodCall(box ast::MethodCall {
++ seg: rs,
++ receiver: rr,
++ args: ra,
++ ..
++ }),
++ ) => eq_path_seg(ls, rs) && eq_expr(lr, rr) && over(la, ra, |l, r| eq_expr(l, r)),
+ (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
+ (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
+ (Lit(l), Lit(r)) => l == r,
+ (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
+ (Let(lp, le, _), Let(rp, re, _)) => eq_pat(lp, rp) && eq_expr(le, re),
+ (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
+ (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
+ (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
+ eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
+ },
+ (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
+ (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
+ (TryBlock(l), TryBlock(r)) => eq_block(l, r),
+ (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
+ (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
+ (Continue(ll), Continue(rl)) => eq_label(ll, rl),
+ (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
+ (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
+ (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
+ (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm),
+ (
+ Closure(box ast::Closure {
+ binder: lb,
+ capture_clause: lc,
+ asyncness: la,
+ movability: lm,
+ fn_decl: lf,
+ body: le,
+ ..
+ }),
+ Closure(box ast::Closure {
+ binder: rb,
+ capture_clause: rc,
+ asyncness: ra,
+ movability: rm,
+ fn_decl: rf,
+ body: re,
+ ..
++ }),
+ ) => {
+ eq_closure_binder(lb, rb)
+ && lc == rc
+ && la.is_async() == ra.is_async()
+ && lm == rm
+ && eq_fn_decl(lf, rf)
+ && eq_expr(le, re)
+ },
+ (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
+ (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
+ (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (Struct(lse), Struct(rse)) => {
+ eq_maybe_qself(&lse.qself, &rse.qself)
+ && eq_path(&lse.path, &rse.path)
+ && eq_struct_rest(&lse.rest, &rse.rest)
+ && unordered_over(&lse.fields, &rse.fields, eq_field)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_expr(&l.expr, &r.expr)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_expr(&l.body, &r.body)
+ && eq_expr_opt(&l.guard, &r.guard)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
+ both(l, r, |l, r| eq_id(l.ident, r.ident))
+}
+
+pub fn eq_block(l: &Block, r: &Block) -> bool {
+ l.rules == r.rules && over(&l.stmts, &r.stmts, eq_stmt)
+}
+
+pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
+ use StmtKind::*;
+ match (&l.kind, &r.kind) {
+ (Local(l), Local(r)) => {
+ eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
+ && eq_local_kind(&l.kind, &r.kind)
+ && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
+ (Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
+ (Empty, Empty) => true,
+ (MacCall(l), MacCall(r)) => {
+ l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool {
+ use LocalKind::*;
+ match (l, r) {
+ (Decl, Decl) => true,
+ (Init(l), Init(r)) => eq_expr(l, r),
+ (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re),
+ _ => false,
+ }
+}
+
+pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
+ eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
+}
+
+#[expect(clippy::too_many_lines)] // Just a big match statement
+pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
+ use ItemKind::*;
+ match (l, r) {
+ (ExternCrate(l), ExternCrate(r)) => l == r,
+ (Use(l), Use(r)) => eq_use_tree(l, r),
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (Mod(lu, lmk), Mod(ru, rmk)) => {
+ lu == ru
+ && match (lmk, rmk) {
+ (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
+ linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
+ },
+ (ModKind::Unloaded, ModKind::Unloaded) => true,
+ _ => false,
+ }
+ },
+ (ForeignMod(l), ForeignMod(r)) => {
+ both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
+ },
+ (
+ TyAlias(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ TyAlias(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (Enum(le, lg), Enum(re, rg)) => over(&le.variants, &re.variants, eq_variant) && eq_generics(lg, rg),
+ (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
+ eq_variant_data(lv, rv) && eq_generics(lg, rg)
+ },
+ (
+ Trait(box ast::Trait {
+ is_auto: la,
+ unsafety: lu,
+ generics: lg,
+ bounds: lb,
+ items: li,
+ }),
+ Trait(box ast::Trait {
+ is_auto: ra,
+ unsafety: ru,
+ generics: rg,
+ bounds: rb,
+ items: ri,
+ }),
+ ) => {
+ la == ra
+ && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, eq_generic_bound),
+ (
+ Impl(box ast::Impl {
+ unsafety: lu,
+ polarity: lp,
+ defaultness: ld,
+ constness: lc,
+ generics: lg,
+ of_trait: lot,
+ self_ty: lst,
+ items: li,
+ }),
+ Impl(box ast::Impl {
+ unsafety: ru,
+ polarity: rp,
+ defaultness: rd,
+ constness: rc,
+ generics: rg,
+ of_trait: rot,
+ self_ty: rst,
+ items: ri,
+ }),
+ ) => {
+ matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
+ && eq_defaultness(*ld, *rd)
+ && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
+ && eq_generics(lg, rg)
+ && both(lot, rot, |l, r| eq_path(&l.path, &r.path))
+ && eq_ty(lst, rst)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
+ _ => false,
+ }
+}
+
+pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
+ use ForeignItemKind::*;
+ match (l, r) {
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (
+ TyAlias(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ TyAlias(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
+ use AssocItemKind::*;
+ match (l, r) {
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (
+ Type(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ Type(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, eq_attr)
+ && eq_vis(&l.vis, &r.vis)
+ && eq_id(l.ident, r.ident)
+ && eq_variant_data(&l.data, &r.data)
+ && both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
+}
+
+pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
+ use VariantData::*;
+ match (l, r) {
+ (Unit(_), Unit(_)) => true,
+ (Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, eq_struct_field),
+ _ => false,
+ }
+}
+
+pub fn eq_struct_field(l: &FieldDef, r: &FieldDef) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, eq_attr)
+ && eq_vis(&l.vis, &r.vis)
+ && both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
+ && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
+ eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
+}
+
+pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
+ matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
+ && l.asyncness.is_async() == r.asyncness.is_async()
+ && matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
+ && eq_ext(&l.ext, &r.ext)
+}
+
+pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
+ over(&l.params, &r.params, eq_generic_param)
+ && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
+ eq_where_predicate(l, r)
+ })
+}
+
+pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
+ use WherePredicate::*;
+ match (l, r) {
+ (BoundPredicate(l), BoundPredicate(r)) => {
+ over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
+ && over(&l.bounds, &r.bounds, eq_generic_bound)
+ },
+ (RegionPredicate(l), RegionPredicate(r)) => {
+ eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound)
+ },
+ (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
+ _ => false,
+ }
+}
+
+pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
+ eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
+ eq_expr(&l.value, &r.value)
+}
+
+pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
+ use UseTreeKind::*;
+ match (l, r) {
+ (Glob, Glob) => true,
+ (Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
+ (Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
+ matches!(
+ (l, r),
+ (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
+ )
+}
+
+pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
+ use VisibilityKind::*;
+ match (&l.kind, &r.kind) {
+ (Public, Public) | (Inherited, Inherited) => true,
+ (Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
+ eq_fn_ret_ty(&l.output, &r.output)
+ && over(&l.inputs, &r.inputs, |l, r| {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_ty(&l.ty, &r.ty)
+ && over(&l.attrs, &r.attrs, eq_attr)
+ })
+}
+
+pub fn eq_closure_binder(l: &ClosureBinder, r: &ClosureBinder) -> bool {
+ match (l, r) {
+ (ClosureBinder::NotPresent, ClosureBinder::NotPresent) => true,
+ (ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) => {
+ lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r))
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
+ match (l, r) {
+ (FnRetTy::Default(_), FnRetTy::Default(_)) => true,
+ (FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
+ use TyKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_ty(l, r),
+ (_, Paren(r)) => eq_ty(l, r),
+ (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
+ (Slice(l), Slice(r)) => eq_ty(l, r),
+ (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
+ (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
+ (Rptr(ll, l), Rptr(rl, r)) => {
+ both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
+ },
+ (BareFn(l), BareFn(r)) => {
+ l.unsafety == r.unsafety
+ && eq_ext(&l.ext, &r.ext)
+ && over(&l.generic_params, &r.generic_params, eq_generic_param)
+ && eq_fn_decl(&l.decl, &r.decl)
+ },
+ (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound),
+ (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound),
+ (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
+ use Extern::*;
+ match (l, r) {
+ (None, None) | (Implicit(_), Implicit(_)) => true,
+ (Explicit(l, _), Explicit(r, _)) => eq_str_lit(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
+ l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
+}
+
+pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
+ eq_path(&l.trait_ref.path, &r.trait_ref.path)
+ && over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ })
+}
+
+pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
+ use GenericParamKind::*;
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && over(&l.bounds, &r.bounds, eq_generic_bound)
+ && match (&l.kind, &r.kind) {
+ (Lifetime, Lifetime) => true,
+ (Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
+ (
+ Const {
+ ty: lt,
+ kw_span: _,
+ default: ld,
+ },
+ Const {
+ ty: rt,
+ kw_span: _,
+ default: rd,
+ },
+ ) => eq_ty(lt, rt) && both(ld, rd, eq_anon_const),
+ _ => false,
+ }
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
+ use GenericBound::*;
+ match (l, r) {
+ (Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
+ (Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
+ _ => false,
+ }
+}
+
+fn eq_term(l: &Term, r: &Term) -> bool {
+ match (l, r) {
+ (Term::Ty(l), Term::Ty(r)) => eq_ty(l, r),
+ (Term::Const(l), Term::Const(r)) => eq_anon_const(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_constraint(l: &AssocConstraint, r: &AssocConstraint) -> bool {
+ use AssocConstraintKind::*;
+ eq_id(l.ident, r.ident)
+ && match (&l.kind, &r.kind) {
+ (Equality { term: l }, Equality { term: r }) => eq_term(l, r),
+ (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, eq_generic_bound),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
+ eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
+}
+
+pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
+ use AttrKind::*;
+ l.style == r.style
+ && match (&l.kind, &r.kind) {
+ (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
+ (Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_mac_args(&l.item.args, &r.item.args),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
+ use MacArgs::*;
+ match (l, r) {
+ (Empty, Empty) => true,
+ (Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
+ (Eq(_, MacArgsEq::Ast(le)), Eq(_, MacArgsEq::Ast(re))) => eq_expr(le, re),
+ (Eq(_, MacArgsEq::Hir(ll)), Eq(_, MacArgsEq::Hir(rl))) => ll.kind == rl.kind,
+ _ => false,
+ }
+}
--- /dev/null
- (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs,
- (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r,
+#![allow(clippy::float_cmp)]
+
+use crate::{clip, is_direct_expn_of, sext, unsext};
+use if_chain::if_chain;
+use rustc_ast::ast::{self, LitFloatType, LitKind};
+use rustc_data_structures::sync::Lrc;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::mir;
+use rustc_middle::mir::interpret::Scalar;
+use rustc_middle::ty::SubstsRef;
+use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt};
+use rustc_middle::{bug, span_bug};
+use rustc_span::symbol::Symbol;
+use std::cmp::Ordering::{self, Equal};
+use std::hash::{Hash, Hasher};
+use std::iter;
+
+/// A `LitKind`-like enum to fold constant `Expr`s into.
+#[derive(Debug, Clone)]
+pub enum Constant {
+ /// A `String` (e.g., "abc").
+ Str(String),
+ /// A binary string (e.g., `b"abc"`).
+ Binary(Lrc<[u8]>),
+ /// A single `char` (e.g., `'a'`).
+ Char(char),
+ /// An integer's bit representation.
+ Int(u128),
+ /// An `f32`.
+ F32(f32),
+ /// An `f64`.
+ F64(f64),
+ /// `true` or `false`.
+ Bool(bool),
+ /// An array of constants.
+ Vec(Vec<Constant>),
+ /// Also an array, but with only one constant, repeated N times.
+ Repeat(Box<Constant>, u64),
+ /// A tuple of constants.
+ Tuple(Vec<Constant>),
+ /// A raw pointer.
+ RawPtr(u128),
+ /// A reference
+ Ref(Box<Constant>),
+ /// A literal with syntax error.
+ Err,
+}
+
+impl PartialEq for Constant {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
- (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv,
- (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb,
++ (Self::Str(ls), Self::Str(rs)) => ls == rs,
++ (Self::Binary(l), Self::Binary(r)) => l == r,
+ (&Self::Char(l), &Self::Char(r)) => l == r,
+ (&Self::Int(l), &Self::Int(r)) => l == r,
+ (&Self::F64(l), &Self::F64(r)) => {
+ // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
+ // `Fw32 == Fw64`, so don’t compare them.
+ // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
+ l.to_bits() == r.to_bits()
+ },
+ (&Self::F32(l), &Self::F32(r)) => {
+ // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
+ // `Fw32 == Fw64`, so don’t compare them.
+ // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
+ f64::from(l).to_bits() == f64::from(r).to_bits()
+ },
+ (&Self::Bool(l), &Self::Bool(r)) => l == r,
+ (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r,
- (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)),
- (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)),
++ (Self::Repeat(lv, ls), Self::Repeat(rv, rs)) => ls == rs && lv == rv,
++ (Self::Ref(lb), Self::Ref(rb)) => *lb == *rb,
+ // TODO: are there inter-type equalities?
+ _ => false,
+ }
+ }
+}
+
+impl Hash for Constant {
+ fn hash<H>(&self, state: &mut H)
+ where
+ H: Hasher,
+ {
+ std::mem::discriminant(self).hash(state);
+ match *self {
+ Self::Str(ref s) => {
+ s.hash(state);
+ },
+ Self::Binary(ref b) => {
+ b.hash(state);
+ },
+ Self::Char(c) => {
+ c.hash(state);
+ },
+ Self::Int(i) => {
+ i.hash(state);
+ },
+ Self::F32(f) => {
+ f64::from(f).to_bits().hash(state);
+ },
+ Self::F64(f) => {
+ f.to_bits().hash(state);
+ },
+ Self::Bool(b) => {
+ b.hash(state);
+ },
+ Self::Vec(ref v) | Self::Tuple(ref v) => {
+ v.hash(state);
+ },
+ Self::Repeat(ref c, l) => {
+ c.hash(state);
+ l.hash(state);
+ },
+ Self::RawPtr(u) => {
+ u.hash(state);
+ },
+ Self::Ref(ref r) => {
+ r.hash(state);
+ },
+ Self::Err => {},
+ }
+ }
+}
+
+impl Constant {
+ pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option<Ordering> {
+ match (left, right) {
- (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)),
- (&Self::Tuple(ref l), &Self::Tuple(ref r)) if l.len() == r.len() => match *cmp_type.kind() {
++ (Self::Str(ls), Self::Str(rs)) => Some(ls.cmp(rs)),
++ (Self::Char(l), Self::Char(r)) => Some(l.cmp(r)),
+ (&Self::Int(l), &Self::Int(r)) => match *cmp_type.kind() {
+ ty::Int(int_ty) => Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))),
+ ty::Uint(_) => Some(l.cmp(&r)),
+ _ => bug!("Not an int type"),
+ },
+ (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r),
+ (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r),
- (&Self::Vec(ref l), &Self::Vec(ref r)) => {
- let cmp_type = match *cmp_type.kind() {
- ty::Array(ty, _) | ty::Slice(ty) => ty,
- _ => return None,
++ (Self::Bool(l), Self::Bool(r)) => Some(l.cmp(r)),
++ (Self::Tuple(l), Self::Tuple(r)) if l.len() == r.len() => match *cmp_type.kind() {
+ ty::Tuple(tys) if tys.len() == l.len() => l
+ .iter()
+ .zip(r)
+ .zip(tys)
+ .map(|((li, ri), cmp_type)| Self::partial_cmp(tcx, cmp_type, li, ri))
+ .find(|r| r.map_or(true, |o| o != Ordering::Equal))
+ .unwrap_or_else(|| Some(l.len().cmp(&r.len()))),
+ _ => None,
+ },
- (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => {
++ (Self::Vec(l), Self::Vec(r)) => {
++ let (ty::Array(cmp_type, _) | ty::Slice(cmp_type)) = *cmp_type.kind() else {
++ return None
+ };
+ iter::zip(l, r)
+ .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
+ .find(|r| r.map_or(true, |o| o != Ordering::Equal))
+ .unwrap_or_else(|| Some(l.len().cmp(&r.len())))
+ },
- (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(
++ (Self::Repeat(lv, ls), Self::Repeat(rv, rs)) => {
+ match Self::partial_cmp(
+ tcx,
+ match *cmp_type.kind() {
+ ty::Array(ty, _) => ty,
+ _ => return None,
+ },
+ lv,
+ rv,
+ ) {
+ Some(Equal) => Some(ls.cmp(rs)),
+ x => x,
+ }
+ },
- let ity = match *ty.kind() {
- ty::Int(ity) => ity,
- _ => return None,
- };
++ (Self::Ref(lb), Self::Ref(rb)) => Self::partial_cmp(
+ tcx,
+ match *cmp_type.kind() {
+ ty::Ref(_, ty, _) => ty,
+ _ => return None,
+ },
+ lb,
+ rb,
+ ),
+ // TODO: are there any useful inter-type orderings?
+ _ => None,
+ }
+ }
+
+ /// Returns the integer value or `None` if `self` or `val_type` is not integer type.
+ pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option<FullInt> {
+ if let Constant::Int(const_int) = *self {
+ match *val_type.kind() {
+ ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))),
+ ty::Uint(_) => Some(FullInt::U(const_int)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn peel_refs(mut self) -> Self {
+ while let Constant::Ref(r) = self {
+ self = *r;
+ }
+ self
+ }
+}
+
+/// Parses a `LitKind` to a `Constant`.
+pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
+ match *lit {
+ LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
+ LitKind::Byte(b) => Constant::Int(u128::from(b)),
+ LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)),
+ LitKind::Char(c) => Constant::Char(c),
+ LitKind::Int(n, _) => Constant::Int(n),
+ LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty {
+ ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()),
+ ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()),
+ },
+ LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() {
+ ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()),
+ ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()),
+ _ => bug!(),
+ },
+ LitKind::Bool(b) => Constant::Bool(b),
+ LitKind::Err => Constant::Err,
+ }
+}
+
+pub fn constant<'tcx>(
+ lcx: &LateContext<'tcx>,
+ typeck_results: &ty::TypeckResults<'tcx>,
+ e: &Expr<'_>,
+) -> Option<(Constant, bool)> {
+ let mut cx = ConstEvalLateContext {
+ lcx,
+ typeck_results,
+ param_env: lcx.param_env,
+ needed_resolution: false,
+ substs: lcx.tcx.intern_substs(&[]),
+ };
+ cx.expr(e).map(|cst| (cst, cx.needed_resolution))
+}
+
+pub fn constant_simple<'tcx>(
+ lcx: &LateContext<'tcx>,
+ typeck_results: &ty::TypeckResults<'tcx>,
+ e: &Expr<'_>,
+) -> Option<Constant> {
+ constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) })
+}
+
+pub fn constant_full_int<'tcx>(
+ lcx: &LateContext<'tcx>,
+ typeck_results: &ty::TypeckResults<'tcx>,
+ e: &Expr<'_>,
+) -> Option<FullInt> {
+ constant_simple(lcx, typeck_results, e)?.int_value(lcx, typeck_results.expr_ty(e))
+}
+
+#[derive(Copy, Clone, Debug, Eq)]
+pub enum FullInt {
+ S(i128),
+ U(u128),
+}
+
+impl PartialEq for FullInt {
+ #[must_use]
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+impl PartialOrd for FullInt {
+ #[must_use]
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for FullInt {
+ #[must_use]
+ fn cmp(&self, other: &Self) -> Ordering {
+ use FullInt::{S, U};
+
+ fn cmp_s_u(s: i128, u: u128) -> Ordering {
+ u128::try_from(s).map_or(Ordering::Less, |x| x.cmp(&u))
+ }
+
+ match (*self, *other) {
+ (S(s), S(o)) => s.cmp(&o),
+ (U(s), U(o)) => s.cmp(&o),
+ (S(s), U(o)) => cmp_s_u(s, o),
+ (U(s), S(o)) => cmp_s_u(o, s).reverse(),
+ }
+ }
+}
+
+/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`.
+pub fn constant_context<'a, 'tcx>(
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'a ty::TypeckResults<'tcx>,
+) -> ConstEvalLateContext<'a, 'tcx> {
+ ConstEvalLateContext {
+ lcx,
+ typeck_results,
+ param_env: lcx.param_env,
+ needed_resolution: false,
+ substs: lcx.tcx.intern_substs(&[]),
+ }
+}
+
+pub struct ConstEvalLateContext<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'a ty::TypeckResults<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ needed_resolution: bool,
+ substs: SubstsRef<'tcx>,
+}
+
+impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
+ /// Simple constant folding: Insert an expression, get a constant or none.
+ pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> {
+ match e.kind {
+ ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)),
+ ExprKind::Block(block, _) => self.block(block),
+ ExprKind::Lit(ref lit) => {
+ if is_direct_expn_of(e.span, "cfg").is_some() {
+ None
+ } else {
+ Some(lit_to_mir_constant(&lit.node, self.typeck_results.expr_ty_opt(e)))
+ }
+ },
+ ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec),
+ ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple),
+ ExprKind::Repeat(value, _) => {
+ let n = match self.typeck_results.expr_ty(e).kind() {
+ ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?,
+ _ => span_bug!(e.span, "typeck error"),
+ };
+ self.expr(value).map(|v| Constant::Repeat(Box::new(v), n))
+ },
+ ExprKind::Unary(op, operand) => self.expr(operand).and_then(|o| match op {
+ UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)),
+ UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)),
+ UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }),
+ }),
+ ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
+ ExprKind::Binary(op, left, right) => self.binop(op, left, right),
+ ExprKind::Call(callee, args) => {
+ // We only handle a few const functions for now.
+ if_chain! {
+ if args.is_empty();
+ if let ExprKind::Path(qpath) = &callee.kind;
+ let res = self.typeck_results.qpath_res(qpath, callee.hir_id);
+ if let Some(def_id) = res.opt_def_id();
+ let def_path = self.lcx.get_def_path(def_id);
+ let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).collect();
+ if let ["core", "num", int_impl, "max_value"] = *def_path;
+ then {
+ let value = match int_impl {
+ "<impl i8>" => i8::MAX as u128,
+ "<impl i16>" => i16::MAX as u128,
+ "<impl i32>" => i32::MAX as u128,
+ "<impl i64>" => i64::MAX as u128,
+ "<impl i128>" => i128::MAX as u128,
+ _ => return None,
+ };
+ Some(Constant::Int(value))
+ } else {
+ None
+ }
+ }
+ },
+ ExprKind::Index(arr, index) => self.index(arr, index),
+ ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
+ // TODO: add other expressions.
+ _ => None,
+ }
+ }
+
+ #[expect(clippy::cast_possible_wrap)]
+ fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
+ use self::Constant::{Bool, Int};
+ match *o {
+ Bool(b) => Some(Bool(!b)),
+ Int(value) => {
+ let value = !value;
+ match *ty.kind() {
+ ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))),
+ ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))),
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+ }
+
+ fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
+ use self::Constant::{Int, F32, F64};
+ match *o {
+ Int(value) => {
++ let ty::Int(ity) = *ty.kind() else { return None };
+ // sign extend
+ let value = sext(self.lcx.tcx, value, ity);
+ let value = value.checked_neg()?;
+ // clear unused bits
+ Some(Int(unsext(self.lcx.tcx, value, ity)))
+ },
+ F32(f) => Some(F32(-f)),
+ F64(f) => Some(F64(-f)),
+ _ => None,
+ }
+ }
+
+ /// Create `Some(Vec![..])` of all constants, unless there is any
+ /// non-constant part.
+ fn multi(&mut self, vec: &[Expr<'_>]) -> Option<Vec<Constant>> {
+ vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
+ }
+
+ /// Lookup a possibly constant expression from an `ExprKind::Path`.
+ fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant> {
+ let res = self.typeck_results.qpath_res(qpath, id);
+ match res {
+ Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
+ // Check if this constant is based on `cfg!(..)`,
+ // which is NOT constant for our purposes.
+ if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) &&
+ let Node::Item(&Item {
+ kind: ItemKind::Const(_, body_id),
+ ..
+ }) = node &&
+ let Node::Expr(&Expr {
+ kind: ExprKind::Lit(_),
+ span,
+ ..
+ }) = self.lcx.tcx.hir().get(body_id.hir_id) &&
+ is_direct_expn_of(span, "cfg").is_some() {
+ return None;
+ }
+
+ let substs = self.typeck_results.node_substs(id);
+ let substs = if self.substs.is_empty() {
+ substs
+ } else {
+ EarlyBinder(substs).subst(self.lcx.tcx, self.substs)
+ };
+
+ let result = self
+ .lcx
+ .tcx
+ .const_eval_resolve(
+ self.param_env,
+ mir::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs),
+ None,
+ )
+ .ok()
+ .map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?;
+ let result = miri_to_const(self.lcx.tcx, result);
+ if result.is_some() {
+ self.needed_resolution = true;
+ }
+ result
+ },
+ // FIXME: cover all usable cases.
+ _ => None,
+ }
+ }
+
+ fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> {
+ let lhs = self.expr(lhs);
+ let index = self.expr(index);
+
+ match (lhs, index) {
+ (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) {
+ Some(Constant::F32(x)) => Some(Constant::F32(*x)),
+ Some(Constant::F64(x)) => Some(Constant::F64(*x)),
+ _ => None,
+ },
+ (Some(Constant::Vec(vec)), _) => {
+ if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
+ match vec.get(0) {
+ Some(Constant::F32(x)) => Some(Constant::F32(*x)),
+ Some(Constant::F64(x)) => Some(Constant::F64(*x)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+ }
+
+ /// A block can only yield a constant if it only has one constant expression.
+ fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
+ if block.stmts.is_empty() {
+ block.expr.as_ref().and_then(|b| self.expr(b))
+ } else {
+ None
+ }
+ }
+
+ fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant> {
+ if let Some(Constant::Bool(b)) = self.expr(cond) {
+ if b {
+ self.expr(then)
+ } else {
+ otherwise.as_ref().and_then(|expr| self.expr(expr))
+ }
+ } else {
+ None
+ }
+ }
+
+ fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant> {
+ let l = self.expr(left)?;
+ let r = self.expr(right);
+ match (l, r) {
+ (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() {
+ ty::Int(ity) => {
+ let l = sext(self.lcx.tcx, l, ity);
+ let r = sext(self.lcx.tcx, r, ity);
+ let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity));
+ match op.node {
+ BinOpKind::Add => l.checked_add(r).map(zext),
+ BinOpKind::Sub => l.checked_sub(r).map(zext),
+ BinOpKind::Mul => l.checked_mul(r).map(zext),
+ BinOpKind::Div if r != 0 => l.checked_div(r).map(zext),
+ BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext),
+ BinOpKind::Shr => l.checked_shr(r.try_into().ok()?).map(zext),
+ BinOpKind::Shl => l.checked_shl(r.try_into().ok()?).map(zext),
+ BinOpKind::BitXor => Some(zext(l ^ r)),
+ BinOpKind::BitOr => Some(zext(l | r)),
+ BinOpKind::BitAnd => Some(zext(l & r)),
+ BinOpKind::Eq => Some(Constant::Bool(l == r)),
+ BinOpKind::Ne => Some(Constant::Bool(l != r)),
+ BinOpKind::Lt => Some(Constant::Bool(l < r)),
+ BinOpKind::Le => Some(Constant::Bool(l <= r)),
+ BinOpKind::Ge => Some(Constant::Bool(l >= r)),
+ BinOpKind::Gt => Some(Constant::Bool(l > r)),
+ _ => None,
+ }
+ },
+ ty::Uint(_) => match op.node {
+ BinOpKind::Add => l.checked_add(r).map(Constant::Int),
+ BinOpKind::Sub => l.checked_sub(r).map(Constant::Int),
+ BinOpKind::Mul => l.checked_mul(r).map(Constant::Int),
+ BinOpKind::Div => l.checked_div(r).map(Constant::Int),
+ BinOpKind::Rem => l.checked_rem(r).map(Constant::Int),
+ BinOpKind::Shr => l.checked_shr(r.try_into().ok()?).map(Constant::Int),
+ BinOpKind::Shl => l.checked_shl(r.try_into().ok()?).map(Constant::Int),
+ BinOpKind::BitXor => Some(Constant::Int(l ^ r)),
+ BinOpKind::BitOr => Some(Constant::Int(l | r)),
+ BinOpKind::BitAnd => Some(Constant::Int(l & r)),
+ BinOpKind::Eq => Some(Constant::Bool(l == r)),
+ BinOpKind::Ne => Some(Constant::Bool(l != r)),
+ BinOpKind::Lt => Some(Constant::Bool(l < r)),
+ BinOpKind::Le => Some(Constant::Bool(l <= r)),
+ BinOpKind::Ge => Some(Constant::Bool(l >= r)),
+ BinOpKind::Gt => Some(Constant::Bool(l > r)),
+ _ => None,
+ },
+ _ => None,
+ },
+ (Constant::F32(l), Some(Constant::F32(r))) => match op.node {
+ BinOpKind::Add => Some(Constant::F32(l + r)),
+ BinOpKind::Sub => Some(Constant::F32(l - r)),
+ BinOpKind::Mul => Some(Constant::F32(l * r)),
+ BinOpKind::Div => Some(Constant::F32(l / r)),
+ BinOpKind::Rem => Some(Constant::F32(l % r)),
+ BinOpKind::Eq => Some(Constant::Bool(l == r)),
+ BinOpKind::Ne => Some(Constant::Bool(l != r)),
+ BinOpKind::Lt => Some(Constant::Bool(l < r)),
+ BinOpKind::Le => Some(Constant::Bool(l <= r)),
+ BinOpKind::Ge => Some(Constant::Bool(l >= r)),
+ BinOpKind::Gt => Some(Constant::Bool(l > r)),
+ _ => None,
+ },
+ (Constant::F64(l), Some(Constant::F64(r))) => match op.node {
+ BinOpKind::Add => Some(Constant::F64(l + r)),
+ BinOpKind::Sub => Some(Constant::F64(l - r)),
+ BinOpKind::Mul => Some(Constant::F64(l * r)),
+ BinOpKind::Div => Some(Constant::F64(l / r)),
+ BinOpKind::Rem => Some(Constant::F64(l % r)),
+ BinOpKind::Eq => Some(Constant::Bool(l == r)),
+ BinOpKind::Ne => Some(Constant::Bool(l != r)),
+ BinOpKind::Lt => Some(Constant::Bool(l < r)),
+ BinOpKind::Le => Some(Constant::Bool(l <= r)),
+ BinOpKind::Ge => Some(Constant::Bool(l >= r)),
+ BinOpKind::Gt => Some(Constant::Bool(l > r)),
+ _ => None,
+ },
+ (l, r) => match (op.node, l, r) {
+ (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)),
+ (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)),
+ (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => {
+ Some(r)
+ },
+ (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)),
+ (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)),
+ (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)),
+ _ => None,
+ },
+ }
+ }
+}
+
+pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) -> Option<Constant> {
+ use rustc_middle::mir::interpret::ConstValue;
+ match result {
+ mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(int)), _) => {
+ match result.ty().kind() {
+ ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)),
+ ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))),
+ ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits(
+ int.try_into().expect("invalid f32 bit representation"),
+ ))),
+ ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(
+ int.try_into().expect("invalid f64 bit representation"),
+ ))),
+ ty::RawPtr(type_and_mut) => {
+ if let ty::Uint(_) = type_and_mut.ty.kind() {
+ return Some(Constant::RawPtr(int.assert_bits(int.size())));
+ }
+ None
+ },
+ // FIXME: implement other conversions.
+ _ => None,
+ }
+ },
+ mir::ConstantKind::Val(ConstValue::Slice { data, start, end }, _) => match result.ty().kind() {
+ ty::Ref(_, tam, _) => match tam.kind() {
+ ty::Str => String::from_utf8(
+ data.inner()
+ .inspect_with_uninit_and_ptr_outside_interpreter(start..end)
+ .to_owned(),
+ )
+ .ok()
+ .map(Constant::Str),
+ _ => None,
+ },
+ _ => None,
+ },
+ mir::ConstantKind::Val(ConstValue::ByRef { alloc, offset: _ }, _) => match result.ty().kind() {
+ ty::Array(sub_type, len) => match sub_type.kind() {
+ ty::Float(FloatTy::F32) => match len.kind().try_to_machine_usize(tcx) {
+ Some(len) => alloc
+ .inner()
+ .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap()))
+ .to_owned()
+ .array_chunks::<4>()
+ .map(|&chunk| Some(Constant::F32(f32::from_le_bytes(chunk))))
+ .collect::<Option<Vec<Constant>>>()
+ .map(Constant::Vec),
+ _ => None,
+ },
+ ty::Float(FloatTy::F64) => match len.kind().try_to_machine_usize(tcx) {
+ Some(len) => alloc
+ .inner()
+ .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap()))
+ .to_owned()
+ .array_chunks::<8>()
+ .map(|&chunk| Some(Constant::F64(f64::from_le_bytes(chunk))))
+ .collect::<Option<Vec<Constant>>>()
+ .map(Constant::Vec),
+ _ => None,
+ },
+ // FIXME: implement other array type conversions.
+ _ => None,
+ },
+ _ => None,
+ },
+ // FIXME: implement other conversions.
+ _ => None,
+ }
+}
--- /dev/null
- pub fn span_lint_and_help<'a, T: LintContext>(
- cx: &'a T,
+//! Clippy wrappers around rustc's diagnostic functions.
+//!
+//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding
+//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in
+//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added
+//! or renamed.
+//!
+//! Thank you!
+//! ~The `INTERNAL_METADATA_COLLECTOR` lint
+
+use rustc_errors::{Applicability, Diagnostic, MultiSpan};
+use rustc_hir::HirId;
+use rustc_lint::{LateContext, Lint, LintContext};
+use rustc_span::source_map::Span;
+use std::env;
+
+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!(
+ "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}",
+ &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
+ // extract just major + minor version and ignore patch versions
+ format!("rust-{}", n.rsplit_once('.').unwrap().1)
+ })
+ ));
+ }
+ }
+}
+
+/// Emit a basic lint message with a `msg` and a `span`.
+///
+/// This is the most primitive of our lint emission methods and can
+/// be a good way to get a new lint started.
+///
+/// Usually it's nicer to provide more context for lint messages.
+/// Be sure the output is understandable when you use this method.
+///
+/// # Example
+///
+/// ```ignore
+/// error: usage of mem::forget on Drop type
+/// --> $DIR/mem_forget.rs:17:5
+/// |
+/// 17 | std::mem::forget(seven);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
+ cx.struct_span_lint(lint, sp, msg, |diag| {
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+/// Same as `span_lint` but with an extra `help` message.
+///
+/// Use this if you want to provide some general help but
+/// can't provide a specific machine applicable suggestion.
+///
+/// The `help` message can be optionally attached to a `Span`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: constant division of 0.0 with 0.0 will always result in NaN
+/// --> $DIR/zero_div_zero.rs:6:25
+/// |
+/// 6 | let other_f64_nan = 0.0f64 / 0.0;
+/// | ^^^^^^^^^^^^
+/// |
+/// = help: consider using `f64::NAN` if you would like a constant representing NaN
+/// ```
- pub fn span_lint_and_note<'a, T: LintContext>(
- cx: &'a T,
++pub fn span_lint_and_help<T: LintContext>(
++ cx: &T,
+ lint: &'static Lint,
+ span: impl Into<MultiSpan>,
+ msg: &str,
+ help_span: Option<Span>,
+ help: &str,
+) {
+ cx.struct_span_lint(lint, span, msg, |diag| {
+ if let Some(help_span) = help_span {
+ diag.span_help(help_span, help);
+ } else {
+ diag.help(help);
+ }
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+/// Like `span_lint` but with a `note` section instead of a `help` message.
+///
+/// The `note` message is presented separately from the main lint message
+/// and is attached to a specific span:
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
+/// --> $DIR/drop_forget_ref.rs:10:5
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^^^^^^^^^
+/// |
+/// = note: `-D clippy::forget-ref` implied by `-D warnings`
+/// note: argument has type &SomeStruct
+/// --> $DIR/drop_forget_ref.rs:10:12
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^
+/// ```
- pub fn span_lint_and_sugg<'a, T: LintContext>(
- cx: &'a T,
++pub fn span_lint_and_note<T: LintContext>(
++ cx: &T,
+ lint: &'static Lint,
+ span: impl Into<MultiSpan>,
+ msg: &str,
+ note_span: Option<Span>,
+ note: &str,
+) {
+ cx.struct_span_lint(lint, span, msg, |diag| {
+ if let Some(note_span) = note_span {
+ diag.span_note(note_span, note);
+ } else {
+ diag.note(note);
+ }
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
+///
+/// If you need to customize your lint output a lot, use this function.
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+pub fn span_lint_and_then<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F)
+where
+ C: LintContext,
+ S: Into<MultiSpan>,
+ F: FnOnce(&mut Diagnostic),
+{
+ cx.struct_span_lint(lint, sp, msg, |diag| {
+ f(diag);
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |diag| {
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+pub fn span_lint_hir_and_then(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ hir_id: HirId,
+ sp: impl Into<MultiSpan>,
+ msg: &str,
+ f: impl FnOnce(&mut Diagnostic),
+) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |diag| {
+ f(diag);
+ docs_link(diag, lint);
+ diag
+ });
+}
+
+/// Add a span lint with a suggestion on how to fix it.
+///
+/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
+/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
+/// 2)"`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: This `.fold` can be more succinctly expressed as `.any`
+/// --> $DIR/methods.rs:390:13
+/// |
+/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
+/// |
+/// = note: `-D fold-any` implied by `-D warnings`
+/// ```
+#[cfg_attr(feature = "internal", allow(clippy::collapsible_span_lint_calls))]
++pub fn span_lint_and_sugg<T: LintContext>(
++ cx: &T,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ help: &str,
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ diag.span_suggestion(sp, help, sugg, applicability);
+ });
+}
+
+/// 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 Diagnostic, help_msg: &str, sugg: I)
+where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg);
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// rustfix currently doesn't support the automatic application of suggestions with
+/// 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 Diagnostic,
+ help_msg: &str,
+ applicability: Applicability,
+ sugg: I,
+) where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
+}
--- /dev/null
- PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
+use crate::consts::constant_simple;
+use crate::macros::macro_backtrace;
+use crate::source::snippet_opt;
+use rustc_ast::ast::InlineAsmTemplatePiece;
+use rustc_data_structures::fx::FxHasher;
+use rustc_hir::def::Res;
+use rustc_hir::HirIdMap;
+use rustc_hir::{
+ ArrayLen, BinOpKind, BindingAnnotation, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
+ GenericArgs, Guard, HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path,
- let (left, right) = match (
++ PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
+};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::TypeckResults;
+use rustc_span::{sym, Symbol};
+use std::hash::{Hash, Hasher};
+
+/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
+/// other conditions would make them equal.
+type SpanlessEqCallback<'a> = dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a;
+
+/// Type used to check whether two ast are the same. This is different from the
+/// operator `==` on ast types as this operator would compare true equality with
+/// ID and span.
+///
+/// Note that some expressions kinds are not considered but could be added.
+pub struct SpanlessEq<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<(&'tcx TypeckResults<'tcx>, &'tcx TypeckResults<'tcx>)>,
+ allow_side_effects: bool,
+ expr_fallback: Option<Box<SpanlessEqCallback<'a>>>,
+}
+
+impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results().map(|x| (x, x)),
+ allow_side_effects: true,
+ expr_fallback: None,
+ }
+ }
+
+ /// Consider expressions containing potential side effects as not equal.
+ #[must_use]
+ pub fn deny_side_effects(self) -> Self {
+ Self {
+ allow_side_effects: false,
+ ..self
+ }
+ }
+
+ #[must_use]
+ pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
+ Self {
+ expr_fallback: Some(Box::new(expr_fallback)),
+ ..self
+ }
+ }
+
+ /// Use this method to wrap comparisons that may involve inter-expression context.
+ /// See `self.locals`.
+ pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
+ HirEqInterExpr {
+ inner: self,
+ locals: HirIdMap::default(),
+ }
+ }
+
+ pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ self.inter_expr().eq_block(left, right)
+ }
+
+ 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)
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ self.inter_expr().eq_path_segments(left, right)
+ }
+}
+
+pub struct HirEqInterExpr<'a, 'b, 'tcx> {
+ inner: &'a mut SpanlessEq<'b, 'tcx>,
+
+ // When binding are declared, the binding ID in the left expression is mapped to the one on the
+ // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
+ // these blocks are considered equal since `x` is mapped to `y`.
+ pub locals: HirIdMap<HirId>,
+}
+
+impl HirEqInterExpr<'_, '_, '_> {
+ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&StmtKind::Local(l), &StmtKind::Local(r)) => {
+ // This additional check ensures that the type of the locals are equivalent even if the init
+ // expression or type have some inferred parts.
+ if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
+ let l_ty = typeck_lhs.pat_ty(l.pat);
+ let r_ty = typeck_rhs.pat_ty(r.pat);
+ if l_ty != r_ty {
+ return false;
+ }
+ }
+
+ // eq_pat adds the HirIds to the locals map. We therefor call it last to make sure that
+ // these only get added if the init and type is equal.
+ both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
+ && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
+ && both(&l.els, &r.els, |l, r| self.eq_block(l, r))
+ && self.eq_pat(l.pat, r.pat)
+ },
+ (&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r),
+ _ => false,
+ }
+ }
+
+ /// Checks whether two blocks are the same.
+ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ match (left.stmts, left.expr, right.stmts, right.expr) {
+ ([], None, [], None) => {
+ // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
+ // expanded to nothing, or the cfg attribute was used.
- ) {
- (Some(left), Some(right)) => (left, right),
- _ => return true,
- };
++ let (Some(left), Some(right)) = (
+ snippet_opt(self.inner.cx, left.span),
+ snippet_opt(self.inner.cx, right.span),
- (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
++ ) else { return true };
+ let mut left_pos = 0;
+ let left = tokenize(&left)
+ .map(|t| {
+ let end = left_pos + t.len as usize;
+ let s = &left[left_pos..end];
+ left_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ let mut right_pos = 0;
+ let right = tokenize(&right)
+ .map(|t| {
+ let end = right_pos + t.len as usize;
+ let s = &right[right_pos..end];
+ right_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ left.eq(right)
+ },
+ _ => {
+ over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r))
+ && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
+ },
+ }
+ }
+
+ fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
+ macro_backtrace(expr.span).last().map_or(false, |macro_call| {
+ matches!(
+ &self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id),
+ Some(sym::todo_macro | sym::unimplemented_macro)
+ )
+ })
+ }
+
+ pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool {
+ match (left, right) {
+ (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true,
+ (ArrayLen::Body(l_ct), ArrayLen::Body(r_ct)) => self.eq_body(l_ct.body, r_ct.body),
+ (_, _) => false,
+ }
+ }
+
+ pub fn eq_body(&mut self, left: BodyId, right: BodyId) -> bool {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.inner.maybe_typeck_results.replace((
+ self.inner.cx.tcx.typeck_body(left),
+ self.inner.cx.tcx.typeck_body(right),
+ ));
+ let res = self.eq_expr(
+ self.inner.cx.tcx.hir().body(left).value,
+ self.inner.cx.tcx.hir().body(right).value,
+ );
+ self.inner.maybe_typeck_results = old_maybe_typeck_results;
+ res
+ }
+
+ #[expect(clippy::similar_names)]
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
+ return false;
+ }
+
+ if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
+ if let (Some(l), Some(r)) = (
+ constant_simple(self.inner.cx, typeck_lhs, left),
+ constant_simple(self.inner.cx, typeck_rhs, right),
+ ) {
+ if l == r {
+ return true;
+ }
+ }
+ }
+
+ let is_eq = match (
+ reduce_exprkind(self.inner.cx, &left.kind),
+ reduce_exprkind(self.inner.cx, &right.kind),
+ ) {
+ (&ExprKind::AddrOf(lb, l_mut, le), &ExprKind::AddrOf(rb, r_mut, re)) => {
+ lb == rb && l_mut == r_mut && self.eq_expr(le, re)
+ },
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Assign(ll, lr, _), &ExprKind::Assign(rl, rr, _)) => {
+ self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::AssignOp(ref lo, ll, lr), &ExprKind::AssignOp(ref ro, rl, rr)) => {
+ self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::Block(l, _), &ExprKind::Block(r, _)) => self.eq_block(l, r),
+ (&ExprKind::Binary(l_op, ll, lr), &ExprKind::Binary(r_op, rl, rr)) => {
+ l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
+ l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ })
+ },
+ (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Box(l), &ExprKind::Box(r)) => self.eq_expr(l, r),
+ (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
+ self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Cast(lx, lt), &ExprKind::Cast(rx, rt)) | (&ExprKind::Type(lx, lt), &ExprKind::Type(rx, rt)) => {
+ self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
+ },
+ (&ExprKind::Field(l_f_exp, ref l_f_ident), &ExprKind::Field(r_f_exp, ref r_f_ident)) => {
+ l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
+ },
+ (&ExprKind::Index(la, li), &ExprKind::Index(ra, ri)) => self.eq_expr(la, ra) && self.eq_expr(li, ri),
+ (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => {
+ self.eq_expr(lc, rc) && self.eq_expr(lt, rt) && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Let(l), &ExprKind::Let(r)) => {
+ self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init)
+ },
- (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
- (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
++ (ExprKind::Lit(l), ExprKind::Lit(r)) => l.node == r.node,
+ (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => {
+ lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Match(le, la, ref ls), &ExprKind::Match(re, ra, ref rs)) => {
+ ls == rs
+ && self.eq_expr(le, re)
+ && over(la, ra, |l, r| {
+ self.eq_pat(l.pat, r.pat)
+ && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
+ && self.eq_expr(l.body, r.body)
+ })
+ },
+ (
+ &ExprKind::MethodCall(l_path, l_receiver, l_args, _),
+ &ExprKind::MethodCall(r_path, r_receiver, r_args, _),
+ ) => {
+ self.inner.allow_side_effects
+ && self.eq_path_segment(l_path, r_path)
+ && self.eq_expr(l_receiver, r_receiver)
+ && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Repeat(le, ll), &ExprKind::Repeat(re, rl)) => {
+ self.eq_expr(le, re) && self.eq_array_length(ll, rl)
+ },
- (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
++ (ExprKind::Ret(l), ExprKind::Ret(r)) => both(l, r, |l, r| self.eq_expr(l, r)),
++ (ExprKind::Path(l), ExprKind::Path(r)) => self.eq_qpath(l, r),
+ (&ExprKind::Struct(l_path, lf, ref lo), &ExprKind::Struct(r_path, rf, ref ro)) => {
+ self.eq_qpath(l_path, r_path)
+ && both(lo, ro, |l, r| self.eq_expr(l, r))
+ && over(lf, rf, |l, r| self.eq_expr_field(l, r))
+ },
+ (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
+ (&ExprKind::Unary(l_op, le), &ExprKind::Unary(r_op, re)) => l_op == r_op && self.eq_expr(le, re),
+ (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
+ (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re),
+ _ => false,
+ };
+ (is_eq && (!self.should_ignore(left) || !self.should_ignore(right)))
+ || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
+ }
+
+ fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
+ over(left, right, |l, r| self.eq_expr(l, r))
+ }
+
+ fn eq_expr_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_expr(left.expr, right.expr)
+ }
+
+ fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
+ match (left, right) {
+ (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
+ (Guard::IfLet(l), Guard::IfLet(r)) => {
+ self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init)
+ },
+ _ => false,
+ }
+ }
+
+ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
+ match (left, right) {
+ (GenericArg::Const(l), GenericArg::Const(r)) => self.eq_body(l.value.body, r.value.body),
+ (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
+ (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
+ (GenericArg::Infer(l_inf), GenericArg::Infer(r_inf)) => self.eq_ty(&l_inf.to_ty(), &r_inf.to_ty()),
+ _ => false,
+ }
+ }
+
+ fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
+ left.name == right.name
+ }
+
+ fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
+ let (PatField { ident: li, pat: lp, .. }, PatField { ident: ri, pat: rp, .. }) = (&left, &right);
+ li.name == ri.name && self.eq_pat(lp, rp)
+ }
+
+ /// Checks whether two patterns are the same.
+ fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&PatKind::Box(l), &PatKind::Box(r)) => self.eq_pat(l, r),
+ (&PatKind::Struct(ref lp, la, ..), &PatKind::Struct(ref rp, ra, ..)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat_field(l, r))
+ },
+ (&PatKind::TupleStruct(ref lp, la, ls), &PatKind::TupleStruct(ref rp, ra, rs)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
+ },
+ (&PatKind::Binding(lb, li, _, ref lp), &PatKind::Binding(rb, ri, _, ref rp)) => {
+ let eq = lb == rb && both(lp, rp, |l, r| self.eq_pat(l, r));
+ if eq {
+ self.locals.insert(li, ri);
+ }
+ eq
+ },
- (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
- l_mut.mutbl == r_mut.mutbl && self.eq_ty(l_mut.ty, r_mut.ty)
- },
- (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
++ (PatKind::Path(l), PatKind::Path(r)) => self.eq_qpath(l, r),
+ (&PatKind::Lit(l), &PatKind::Lit(r)) => self.eq_expr(l, r),
+ (&PatKind::Tuple(l, ls), &PatKind::Tuple(r, rs)) => ls == rs && over(l, r, |l, r| self.eq_pat(l, r)),
+ (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
+ both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
+ },
+ (&PatKind::Ref(le, ref lm), &PatKind::Ref(re, ref rm)) => lm == rm && self.eq_pat(le, re),
+ (&PatKind::Slice(ls, ref li, le), &PatKind::Slice(rs, ref ri, re)) => {
+ over(ls, rs, |l, r| self.eq_pat(l, r))
+ && over(le, re, |l, r| self.eq_pat(l, r))
+ && both(li, ri, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Wild, &PatKind::Wild) => true,
+ _ => false,
+ }
+ }
+
+ #[expect(clippy::similar_names)]
+ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
+ match (left, right) {
+ (&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => {
+ both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
+ },
+ (&QPath::TypeRelative(lty, lseg), &QPath::TypeRelative(rty, rseg)) => {
+ self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
+ },
+ (&QPath::LangItem(llang_item, ..), &QPath::LangItem(rlang_item, ..)) => llang_item == rlang_item,
+ _ => false,
+ }
+ }
+
+ 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,
+ _ => over(left.segments, right.segments, |l, r| self.eq_path_segment(l, r)),
+ }
+ }
+
+ fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
+ if !(left.parenthesized || right.parenthesized) {
+ over(left.args, right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
+ && over(left.bindings, right.bindings, |l, r| self.eq_type_binding(l, r))
+ } else if left.parenthesized && right.parenthesized {
+ over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
+ && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
+ self.eq_ty(l, r)
+ })
+ } else {
+ false
+ }
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
+ }
+
+ pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&TyKind::Slice(l_vec), &TyKind::Slice(r_vec)) => self.eq_ty(l_vec, r_vec),
+ (&TyKind::Array(lt, ll), &TyKind::Array(rt, rl)) => self.eq_ty(lt, rt) && self.eq_array_length(ll, rl),
- (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
++ (TyKind::Ptr(l_mut), TyKind::Ptr(r_mut)) => l_mut.mutbl == r_mut.mutbl && self.eq_ty(l_mut.ty, r_mut.ty),
++ (TyKind::Rptr(_, l_rmut), TyKind::Rptr(_, r_rmut)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(l_rmut.ty, r_rmut.ty)
+ },
++ (TyKind::Path(l), TyKind::Path(r)) => self.eq_qpath(l, r),
+ (&TyKind::Tup(l), &TyKind::Tup(r)) => over(l, r, |l, r| self.eq_ty(l, r)),
+ (&TyKind::Infer, &TyKind::Infer) => true,
+ _ => false,
+ }
+ }
+
+ fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_ty(left.ty(), right.ty())
+ }
+}
+
+/// Some simple reductions like `{ return }` => `return`
+fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> {
+ if let ExprKind::Block(block, _) = kind {
+ match (block.stmts, block.expr) {
+ // From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty
+ // block with an empty span.
+ ([], None) if block.span.is_empty() => &ExprKind::Tup(&[]),
+ // `{}` => `()`
+ ([], None) => match snippet_opt(cx, block.span) {
+ // Don't reduce if there are any tokens contained in the braces
+ Some(snip)
+ if tokenize(&snip)
+ .map(|t| t.kind)
+ .filter(|t| {
+ !matches!(
+ t,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) =>
+ {
+ kind
+ },
+ _ => &ExprKind::Tup(&[]),
+ },
+ ([], Some(expr)) => match expr.kind {
+ // `{ return .. }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ ([stmt], None) => match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ // `{ return ..; }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ _ => kind,
+ },
+ _ => kind,
+ }
+ } else {
+ kind
+ }
+}
+
+fn swap_binop<'a>(
+ binop: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
+ match binop {
+ BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
+ Some((binop, rhs, lhs))
+ },
+ BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
+ BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
+ BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
+ BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
+ BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Rem
+ | BinOpKind::Sub
+ | BinOpKind::Div
+ | BinOpKind::And
+ | BinOpKind::Or => None,
+ }
+}
+
+/// Checks if the two `Option`s are both `None` or some equal values as per
+/// `eq_fn`.
+pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ l.as_ref()
+ .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
+}
+
+/// Checks if two slices are equal as per `eq_fn`.
+pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
+}
+
+/// Counts how many elements of the slices are equal as per `eq_fn`.
+pub fn count_eq<X: Sized>(
+ left: &mut dyn Iterator<Item = X>,
+ right: &mut dyn Iterator<Item = X>,
+ mut eq_fn: impl FnMut(&X, &X) -> bool,
+) -> usize {
+ left.zip(right).take_while(|(l, r)| eq_fn(l, r)).count()
+}
+
+/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
+pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
+}
+
+/// Type used to hash an ast element. This is different from the `Hash` trait
+/// on ast types as this
+/// trait would consider IDs and spans.
+///
+/// All expressions kind are hashed, but some might have a weaker hash.
+pub struct SpanlessHash<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ s: FxHasher,
+}
+
+impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ s: FxHasher::default(),
+ }
+ }
+
+ pub fn finish(self) -> u64 {
+ self.s.finish()
+ }
+
+ pub fn hash_block(&mut self, b: &Block<'_>) {
+ for s in b.stmts {
+ self.hash_stmt(s);
+ }
+
+ if let Some(e) = b.expr {
+ self.hash_expr(e);
+ }
+
+ std::mem::discriminant(&b.rules).hash(&mut self.s);
+ }
+
+ #[expect(clippy::too_many_lines)]
+ pub fn hash_expr(&mut self, e: &Expr<'_>) {
+ let simple_const = self
+ .maybe_typeck_results
+ .and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
+
+ // const hashing may result in the same hash as some unrelated node, so add a sort of
+ // discriminant depending on which path we're choosing next
+ simple_const.hash(&mut self.s);
+ if simple_const.is_some() {
+ return;
+ }
+
+ std::mem::discriminant(&e.kind).hash(&mut self.s);
+
+ match e.kind {
+ ExprKind::AddrOf(kind, m, e) => {
+ std::mem::discriminant(&kind).hash(&mut self.s);
+ m.hash(&mut self.s);
+ self.hash_expr(e);
+ },
+ ExprKind::Continue(i) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Assign(l, r, _) => {
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::AssignOp(ref o, l, r) => {
+ std::mem::discriminant(&o.node).hash(&mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Block(b, _) => {
+ self.hash_block(b);
+ },
+ ExprKind::Binary(op, l, r) => {
+ std::mem::discriminant(&op.node).hash(&mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Break(i, ref j) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ if let Some(j) = *j {
+ self.hash_expr(j);
+ }
+ },
+ ExprKind::Box(e) | ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
+ self.hash_expr(e);
+ },
+ ExprKind::Call(fun, args) => {
+ self.hash_expr(fun);
+ self.hash_exprs(args);
+ },
+ ExprKind::Cast(e, ty) | ExprKind::Type(e, ty) => {
+ self.hash_expr(e);
+ self.hash_ty(ty);
+ },
+ ExprKind::Closure(&Closure {
+ capture_clause, body, ..
+ }) => {
+ std::mem::discriminant(&capture_clause).hash(&mut self.s);
+ // closures inherit TypeckResults
+ self.hash_expr(self.cx.tcx.hir().body(body).value);
+ },
+ ExprKind::Field(e, ref f) => {
+ self.hash_expr(e);
+ self.hash_name(f.name);
+ },
+ ExprKind::Index(a, i) => {
+ self.hash_expr(a);
+ self.hash_expr(i);
+ },
+ ExprKind::InlineAsm(asm) => {
+ for piece in asm.template {
+ match piece {
+ InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
+ InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span: _,
+ } => {
+ operand_idx.hash(&mut self.s);
+ modifier.hash(&mut self.s);
+ },
+ }
+ }
+ asm.options.hash(&mut self.s);
+ for (op, _op_sp) in asm.operands {
+ match op {
+ InlineAsmOperand::In { reg, expr } => {
+ reg.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::Out { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ if let Some(expr) = expr {
+ self.hash_expr(expr);
+ }
+ },
+ InlineAsmOperand::InOut { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::SplitInOut {
+ reg,
+ late,
+ in_expr,
+ out_expr,
+ } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(in_expr);
+ if let Some(out_expr) = out_expr {
+ self.hash_expr(out_expr);
+ }
+ },
+ InlineAsmOperand::Const { anon_const } | InlineAsmOperand::SymFn { anon_const } => {
+ self.hash_body(anon_const.body);
+ },
+ InlineAsmOperand::SymStatic { path, def_id: _ } => self.hash_qpath(path),
+ }
+ }
+ },
+ ExprKind::Let(Let { pat, init, ty, .. }) => {
+ self.hash_expr(init);
+ if let Some(ty) = ty {
+ self.hash_ty(ty);
+ }
+ self.hash_pat(pat);
+ },
+ ExprKind::Err => {},
+ ExprKind::Lit(ref l) => {
+ l.node.hash(&mut self.s);
+ },
+ ExprKind::Loop(b, ref i, ..) => {
+ self.hash_block(b);
+ if let Some(i) = *i {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::If(cond, then, ref else_opt) => {
+ self.hash_expr(cond);
+ self.hash_expr(then);
+ if let Some(e) = *else_opt {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Match(e, arms, ref s) => {
+ self.hash_expr(e);
+
+ for arm in arms {
+ self.hash_pat(arm.pat);
+ if let Some(ref e) = arm.guard {
+ self.hash_guard(e);
+ }
+ self.hash_expr(arm.body);
+ }
+
+ s.hash(&mut self.s);
+ },
+ ExprKind::MethodCall(path, receiver, args, ref _fn_span) => {
+ self.hash_name(path.ident.name);
+ self.hash_expr(receiver);
+ self.hash_exprs(args);
+ },
+ ExprKind::ConstBlock(ref l_id) => {
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Repeat(e, len) => {
+ self.hash_expr(e);
+ self.hash_array_length(len);
+ },
+ ExprKind::Ret(ref e) => {
+ if let Some(e) = *e {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
+ },
+ ExprKind::Struct(path, fields, ref expr) => {
+ self.hash_qpath(path);
+
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_expr(f.expr);
+ }
+
+ if let Some(e) = *expr {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Tup(tup) => {
+ self.hash_exprs(tup);
+ },
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
+ ExprKind::Unary(lop, le) => {
+ std::mem::discriminant(&lop).hash(&mut self.s);
+ self.hash_expr(le);
+ },
+ }
+ }
+
+ pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
+ for e in e {
+ self.hash_expr(e);
+ }
+ }
+
+ pub fn hash_name(&mut self, n: Symbol) {
+ n.hash(&mut self.s);
+ }
+
+ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
+ match *p {
+ QPath::Resolved(_, path) => {
+ self.hash_path(path);
+ },
+ QPath::TypeRelative(_, path) => {
+ self.hash_name(path.ident.name);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ std::mem::discriminant(&lang_item).hash(&mut self.s);
+ },
+ }
+ // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
+ }
+
+ pub fn hash_pat(&mut self, pat: &Pat<'_>) {
+ std::mem::discriminant(&pat.kind).hash(&mut self.s);
+ match pat.kind {
+ PatKind::Binding(BindingAnnotation(by_ref, mutability), _, _, pat) => {
+ std::mem::discriminant(&by_ref).hash(&mut self.s);
+ std::mem::discriminant(&mutability).hash(&mut self.s);
+ if let Some(pat) = pat {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Box(pat) => self.hash_pat(pat),
+ PatKind::Lit(expr) => self.hash_expr(expr),
+ PatKind::Or(pats) => {
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Path(ref qpath) => self.hash_qpath(qpath),
+ PatKind::Range(s, e, i) => {
+ if let Some(s) = s {
+ self.hash_expr(s);
+ }
+ if let Some(e) = e {
+ self.hash_expr(e);
+ }
+ std::mem::discriminant(&i).hash(&mut self.s);
+ },
+ PatKind::Ref(pat, mu) => {
+ self.hash_pat(pat);
+ std::mem::discriminant(&mu).hash(&mut self.s);
+ },
+ PatKind::Slice(l, m, r) => {
+ for pat in l {
+ self.hash_pat(pat);
+ }
+ if let Some(pat) = m {
+ self.hash_pat(pat);
+ }
+ for pat in r {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Struct(ref qpath, fields, e) => {
+ self.hash_qpath(qpath);
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_pat(f.pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::Tuple(pats, e) => {
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::TupleStruct(ref qpath, pats, e) => {
+ self.hash_qpath(qpath);
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::Wild => {},
+ }
+ }
+
+ pub fn hash_path(&mut self, path: &Path<'_>) {
+ match path.res {
+ // constant hash since equality is dependant on inter-expression context
+ // e.g. The expressions `if let Some(x) = foo() {}` and `if let Some(y) = foo() {}` are considered equal
+ // even though the binding names are different and they have different `HirId`s.
+ Res::Local(_) => 1_usize.hash(&mut self.s),
+ _ => {
+ for seg in path.segments {
+ self.hash_name(seg.ident.name);
+ self.hash_generic_args(seg.args().args);
+ }
+ },
+ }
+ }
+
+ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
+ std::mem::discriminant(&b.kind).hash(&mut self.s);
+
+ match &b.kind {
+ StmtKind::Local(local) => {
+ self.hash_pat(local.pat);
+ if let Some(init) = local.init {
+ self.hash_expr(init);
+ }
+ if let Some(els) = local.els {
+ self.hash_block(els);
+ }
+ },
+ StmtKind::Item(..) => {},
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_guard(&mut self, g: &Guard<'_>) {
+ match g {
+ Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
+ std::mem::discriminant(&lifetime.name).hash(&mut self.s);
+ if let LifetimeName::Param(param_id, ref name) = lifetime.name {
+ std::mem::discriminant(name).hash(&mut self.s);
+ param_id.hash(&mut self.s);
+ match name {
+ ParamName::Plain(ref ident) => {
+ ident.name.hash(&mut self.s);
+ },
+ ParamName::Fresh | ParamName::Error => {},
+ }
+ }
+ }
+
+ pub fn hash_ty(&mut self, ty: &Ty<'_>) {
+ std::mem::discriminant(&ty.kind).hash(&mut self.s);
+ self.hash_tykind(&ty.kind);
+ }
+
+ pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
+ match ty {
+ TyKind::Slice(ty) => {
+ self.hash_ty(ty);
+ },
+ &TyKind::Array(ty, len) => {
+ self.hash_ty(ty);
+ self.hash_array_length(len);
+ },
+ TyKind::Ptr(ref mut_ty) => {
+ self.hash_ty(mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::Rptr(lifetime, ref mut_ty) => {
+ self.hash_lifetime(lifetime);
+ self.hash_ty(mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::BareFn(bfn) => {
+ bfn.unsafety.hash(&mut self.s);
+ bfn.abi.hash(&mut self.s);
+ for arg in bfn.decl.inputs {
+ self.hash_ty(arg);
+ }
+ std::mem::discriminant(&bfn.decl.output).hash(&mut self.s);
+ match bfn.decl.output {
+ FnRetTy::DefaultReturn(_) => {},
+ FnRetTy::Return(ty) => {
+ self.hash_ty(ty);
+ },
+ }
+ bfn.decl.c_variadic.hash(&mut self.s);
+ },
+ TyKind::Tup(ty_list) => {
+ for ty in *ty_list {
+ self.hash_ty(ty);
+ }
+ },
+ TyKind::Path(ref qpath) => self.hash_qpath(qpath),
+ TyKind::OpaqueDef(_, arg_list, in_trait) => {
+ self.hash_generic_args(arg_list);
+ in_trait.hash(&mut self.s);
+ },
+ TyKind::TraitObject(_, lifetime, _) => {
+ self.hash_lifetime(lifetime);
+ },
+ TyKind::Typeof(anon_const) => {
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Err | TyKind::Infer | TyKind::Never => {},
+ }
+ }
+
+ pub fn hash_array_length(&mut self, length: ArrayLen) {
+ match length {
+ ArrayLen::Infer(..) => {},
+ ArrayLen::Body(anon_const) => self.hash_body(anon_const.body),
+ }
+ }
+
+ pub fn hash_body(&mut self, body_id: BodyId) {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
+ self.hash_expr(self.cx.tcx.hir().body(body_id).value);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
+ for arg in arg_list {
+ match *arg {
+ GenericArg::Lifetime(l) => self.hash_lifetime(l),
+ GenericArg::Type(ty) => self.hash_ty(ty),
+ GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
+ GenericArg::Infer(ref inf) => self.hash_ty(&inf.to_ty()),
+ }
+ }
+ }
+}
+
+pub fn hash_stmt(cx: &LateContext<'_>, s: &Stmt<'_>) -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_stmt(s);
+ h.finish()
+}
+
++pub fn is_bool(ty: &Ty<'_>) -> bool {
++ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
++ matches!(path.res, Res::PrimTy(PrimTy::Bool))
++ } else {
++ false
++ }
++}
++
+pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(e);
+ h.finish()
+}
--- /dev/null
- both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
+#![feature(array_chunks)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(let_chains)]
+#![feature(lint_reasons)]
+#![feature(never_type)]
+#![feature(once_cell)]
+#![feature(rustc_private)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
+// warn on the same lints as `clippy_lints`
+#![warn(trivial_casts, trivial_numeric_casts)]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// 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_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_attr;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_typeck;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir_dataflow;
+extern crate rustc_parse_format;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+
+#[macro_use]
+pub mod sym_helper;
+
+pub mod ast_utils;
+pub mod attrs;
+mod check_proc_macro;
+pub mod comparisons;
+pub mod consts;
+pub mod diagnostics;
+pub mod eager_or_lazy;
+pub mod higher;
+mod hir_utils;
+pub mod macros;
+pub mod mir;
+pub mod msrvs;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod qualify_min_const_fn;
+pub mod source;
+pub mod str_utils;
+pub mod sugg;
+pub mod ty;
+pub mod usage;
+pub mod visitors;
+
+pub use self::attrs::*;
+pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
+pub use self::hir_utils::{
- use rustc_hir as hir;
- use rustc_hir::def::{DefKind, Namespace, Res};
- use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
++ both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
+};
+
+use core::ops::ControlFlow;
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
+use std::sync::OnceLock;
+use std::sync::{Mutex, MutexGuard};
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, LitKind};
+use rustc_ast::Attribute;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::unhash::UnhashMap;
- def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness,
- Destination, Expr, ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind,
- LangItem, Local, MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy,
- QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
++use rustc_hir::def::{DefKind, Res};
++use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
+use rustc_hir::hir_id::{HirIdMap, HirIdSet};
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
+use rustc_hir::{
- use rustc_span::source_map::original_sp;
++ self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination,
++ Expr, ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local,
++ MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
++ TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
+};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::ty as rustc_ty;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::binding::BindingMode;
+use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{
+ ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType,
+ PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
+};
+use rustc_middle::ty::{
+ layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture,
+};
+use rustc_middle::ty::{FloatTy, IntTy, UintTy};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+use rustc_span::hygiene::{ExpnKind, MacroKind};
- use rustc_span::symbol::{kw, Symbol};
- use rustc_span::{Span, DUMMY_SP};
+use rustc_span::source_map::SourceMap;
+use rustc_span::sym;
- pub fn is_path_lang_item<'tcx>(
- cx: &LateContext<'_>,
- maybe_path: &impl MaybePath<'tcx>,
- lang_item: LangItem,
- ) -> bool {
++use rustc_span::symbol::{kw, Ident, Symbol};
++use rustc_span::Span;
+use rustc_target::abi::Integer;
+
+use crate::consts::{constant, Constant};
+use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
+use crate::visitors::for_each_expr;
+
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+ if let Ok(version) = RustcVersion::parse(msrv) {
+ return Some(version);
+ } else if let Some(sess) = sess {
+ if let Some(span) = span {
+ sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
+ }
+ }
+ None
+}
+
+pub fn meets_msrv(msrv: Option<RustcVersion>, lint_msrv: RustcVersion) -> bool {
+ msrv.map_or(true, |msrv| msrv.meets(lint_msrv))
+}
+
+#[macro_export]
+macro_rules! extract_msrv_attr {
+ ($context:ident) => {
+ fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
+ let sess = rustc_lint::LintContext::sess(cx);
+ match $crate::get_unique_inner_attr(sess, attrs, "msrv") {
+ Some(msrv_attr) => {
+ if let Some(msrv) = msrv_attr.value_str() {
+ self.msrv = $crate::parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
+ } else {
+ sess.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+ },
+ _ => (),
+ }
+ }
+ };
+}
+
+/// If the given expression is a local binding, find the initializer expression.
+/// If that initializer expression is another local binding, find its initializer again.
+/// This process repeats as long as possible (but usually no more than once). Initializer
+/// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`]
+/// instead.
+///
+/// Examples:
+/// ```
+/// let abc = 1;
+/// // ^ output
+/// let def = abc;
+/// dbg!(def);
+/// // ^^^ input
+///
+/// // or...
+/// let abc = 1;
+/// let def = abc + 2;
+/// // ^^^^^^^ output
+/// dbg!(def);
+/// // ^^^ input
+/// ```
+pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
+ while let Some(init) = path_to_local(expr)
+ .and_then(|id| find_binding_init(cx, id))
+ .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
+ {
+ expr = init;
+ }
+ expr
+}
+
+/// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable.
+/// By only considering immutable bindings, we guarantee that the returned expression represents the
+/// value of the binding wherever it is referenced.
+///
+/// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned.
+/// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the
+/// canonical binding `HirId`.
+pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
+ let hir = cx.tcx.hir();
+ if_chain! {
+ if let Some(Node::Pat(pat)) = hir.find(hir_id);
+ if matches!(pat.kind, PatKind::Binding(BindingAnnotation::NONE, ..));
+ let parent = hir.get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = hir.find(parent);
+ then {
+ return local.init;
+ }
+ }
+ None
+}
+
+/// Returns `true` if the given `NodeId` is inside a constant context
+///
+/// # Example
+///
+/// ```rust,ignore
+/// if in_constant(cx, expr.hir_id) {
+/// // Do something
+/// }
+/// ```
+pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(id).def_id;
+ match cx.tcx.hir().get_by_def_id(parent_id) {
+ Node::Item(&Item {
+ kind: ItemKind::Const(..) | ItemKind::Static(..),
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ ..
+ })
+ | Node::AnonConst(_) => true,
+ Node::Item(&Item {
+ kind: ItemKind::Fn(ref sig, ..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(ref sig, _),
+ ..
+ }) => sig.header.constness == Constness::Const,
+ _ => false,
+ }
+}
+
+/// Checks if a `Res` refers to a constructor of a `LangItem`
+/// For example, use this to check whether a function call or a pattern is `Some(..)`.
+pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool {
+ if let Res::Def(DefKind::Ctor(..), id) = res
+ && let Some(lang_id) = cx.tcx.lang_items().get(lang_item)
+ && let Some(id) = cx.tcx.opt_parent(id)
+ {
+ id == lang_id
+ } else {
+ false
+ }
+}
+
+pub fn is_res_diagnostic_ctor(cx: &LateContext<'_>, res: Res, diag_item: Symbol) -> bool {
+ if let Res::Def(DefKind::Ctor(..), id) = res
+ && let Some(id) = cx.tcx.opt_parent(id)
+ {
+ cx.tcx.is_diagnostic_item(diag_item, id)
+ } else {
+ false
+ }
+}
+
+/// Checks if a `QPath` resolves to a constructor of a diagnostic item.
+pub fn is_diagnostic_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, diagnostic_item: Symbol) -> bool {
+ if let QPath::Resolved(_, path) = qpath {
+ if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res {
+ return cx.tcx.is_diagnostic_item(diagnostic_item, cx.tcx.parent(ctor_id));
+ }
+ }
+ false
+}
+
+/// Checks if the `DefId` matches the given diagnostic item or it's constructor.
+pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool {
+ let did = match cx.tcx.def_kind(did) {
+ DefKind::Ctor(..) => cx.tcx.parent(did),
+ // Constructors for types in external crates seem to have `DefKind::Variant`
+ DefKind::Variant => match cx.tcx.opt_parent(did) {
+ Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
+ _ => did,
+ },
+ _ => did,
+ };
+
+ cx.tcx.is_diagnostic_item(item, did)
+}
+
+/// Checks if the `DefId` matches the given `LangItem` or it's constructor.
+pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool {
+ let did = match cx.tcx.def_kind(did) {
+ DefKind::Ctor(..) => cx.tcx.parent(did),
+ // Constructors for types in external crates seem to have `DefKind::Variant`
+ DefKind::Variant => match cx.tcx.opt_parent(did) {
+ Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
+ _ => did,
+ },
+ _ => did,
+ };
+
+ cx.tcx.lang_items().get(item) == Some(did)
+}
+
+pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
+ matches!(
+ expr.kind,
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: None,
+ ..
+ },
+ _
+ ) | ExprKind::Tup([])
+ )
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild(pat: &Pat<'_>) -> bool {
+ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+/// This is a deprecated function, consider using [`is_trait_method`].
+pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ let trt_id = cx.tcx.trait_of_item(def_id);
+ trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
+}
+
+/// Checks if a method is defined in an impl of a diagnostic item
+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());
+ }
+ }
+ false
+}
+
+/// Checks if a method is in a diagnostic item trait
+pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
+ return cx.tcx.is_diagnostic_item(diag_item, trait_did);
+ }
+ false
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ cx.typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
+}
+
+/// Checks if the given expression is a path referring an item on the trait
+/// that is marked with the given diagnostic item.
+///
+/// For checking method call expressions instead of path expressions, use
+/// [`is_trait_method`].
+///
+/// For example, this can be used to find if an expression like `u64::default`
+/// refers to an item of the trait `Default`, which is associated with the
+/// `diag_item` of `sym::Default`.
+pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ if let hir::ExprKind::Path(ref qpath) = expr.kind {
+ cx.qpath_res(qpath, expr.hir_id)
+ .opt_def_id()
+ .map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item))
+ } else {
+ false
+ }
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, seg) => seg,
+ QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
+ }
+}
+
+pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
+ last_path_segment(qpath)
+ .args
+ .map_or(&[][..], |a| a.args)
+ .iter()
+ .filter_map(|a| match a {
+ hir::GenericArg::Type(ty) => Some(*ty),
+ _ => None,
+ })
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// Matches a `QPath` against a slice of segment string literals.
+///
+/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
+/// `rustc_hir::QPath`.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_qpath(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
+ match *path {
+ QPath::Resolved(_, path) => match_path(path, segments),
+ QPath::TypeRelative(ty, segment) => match ty.kind {
+ TyKind::Path(ref inner_path) => {
+ if let [prefix @ .., end] = segments {
+ if match_qpath(inner_path, prefix) {
+ return segment.ident.name.as_str() == *end;
+ }
+ }
+ false
+ },
+ _ => false,
+ },
+ QPath::LangItem(..) => false,
+ }
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
+///
+/// Please use `is_path_diagnostic_item` if the target is a diagnostic item.
+pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
+ path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if
+/// it matches the given lang item.
- fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
- let single = |ty| tcx.incoherent_impls(ty).iter().copied();
- let empty = || [].iter().copied();
- match name {
- "bool" => single(BoolSimplifiedType),
- "char" => single(CharSimplifiedType),
- "str" => single(StrSimplifiedType),
- "array" => single(ArraySimplifiedType),
- "slice" => single(SliceSimplifiedType),
++pub fn is_path_lang_item<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>, lang_item: LangItem) -> bool {
+ path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.lang_items().get(lang_item) == Some(id))
+}
+
+/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if
+/// it matches the given diagnostic item.
+pub fn is_path_diagnostic_item<'tcx>(
+ cx: &LateContext<'_>,
+ maybe_path: &impl MaybePath<'tcx>,
+ diag_item: Symbol,
+) -> bool {
+ path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// Matches a `Path` against a slice of segment string literals.
+///
+/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
+/// `rustc_hir::Path`.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// if match_path(&trait_ref.path, &paths::HASH) {
+/// // This is the `std::hash::Hash` trait.
+/// }
+///
+/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
+/// // This is a `rustc_middle::lint::Lint`.
+/// }
+/// ```
+pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// If the expression is a path to a local, returns the canonical `HirId` of the local.
+pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
+ if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
+ if let Res::Local(id) = path.res {
+ return Some(id);
+ }
+ }
+ None
+}
+
+/// Returns true if the expression is a path to a local with the specified `HirId`.
+/// Use this function to see if an expression matches a function argument or a match binding.
+pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
+ path_to_local(expr) == Some(id)
+}
+
+pub trait MaybePath<'hir> {
+ fn hir_id(&self) -> HirId;
+ fn qpath_opt(&self) -> Option<&QPath<'hir>>;
+}
+
+macro_rules! maybe_path {
+ ($ty:ident, $kind:ident) => {
+ impl<'hir> MaybePath<'hir> for hir::$ty<'hir> {
+ fn hir_id(&self) -> HirId {
+ self.hir_id
+ }
+ fn qpath_opt(&self) -> Option<&QPath<'hir>> {
+ match &self.kind {
+ hir::$kind::Path(qpath) => Some(qpath),
+ _ => None,
+ }
+ }
+ }
+ };
+}
+maybe_path!(Expr, ExprKind);
+maybe_path!(Pat, PatKind);
+maybe_path!(Ty, TyKind);
+
+/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err`
+pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res {
+ match maybe_path.qpath_opt() {
+ None => Res::Err,
+ Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()),
+ }
+}
+
+/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID
+pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<DefId> {
+ path_res(cx, maybe_path).opt_def_id()
+}
+
- "const_ptr" => single(PtrSimplifiedType(Mutability::Not)),
- "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)),
- "isize" => single(IntSimplifiedType(IntTy::Isize)),
- "i8" => single(IntSimplifiedType(IntTy::I8)),
- "i16" => single(IntSimplifiedType(IntTy::I16)),
- "i32" => single(IntSimplifiedType(IntTy::I32)),
- "i64" => single(IntSimplifiedType(IntTy::I64)),
- "i128" => single(IntSimplifiedType(IntTy::I128)),
- "usize" => single(UintSimplifiedType(UintTy::Usize)),
- "u8" => single(UintSimplifiedType(UintTy::U8)),
- "u16" => single(UintSimplifiedType(UintTy::U16)),
- "u32" => single(UintSimplifiedType(UintTy::U32)),
- "u64" => single(UintSimplifiedType(UintTy::U64)),
- "u128" => single(UintSimplifiedType(UintTy::U128)),
- "f32" => single(FloatSimplifiedType(FloatTy::F32)),
- "f64" => single(FloatSimplifiedType(FloatTy::F64)),
- _ => empty(),
- }
- }
-
- /// Resolves a def path like `std::vec::Vec`. `namespace_hint` can be supplied to disambiguate
- /// between `std::vec` the module and `std::vec` the macro
- ///
- /// This function is expensive and should be used sparingly.
- pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<Namespace>) -> Res {
- fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str, matches_ns: impl Fn(Res) -> bool) -> Option<Res> {
- match tcx.def_kind(def_id) {
- DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx
- .module_children(def_id)
- .iter()
- .find(|item| item.ident.name.as_str() == name && matches_ns(item.res.expect_non_local()))
- .map(|child| child.res.expect_non_local()),
- DefKind::Impl => tcx
- .associated_item_def_ids(def_id)
- .iter()
- .copied()
- .find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name)
- .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)),
- DefKind::Struct | DefKind::Union => tcx
- .adt_def(def_id)
- .non_enum_variant()
- .fields
- .iter()
- .find(|f| f.name.as_str() == name)
- .map(|f| Res::Def(DefKind::Field, f.did)),
- _ => None,
++fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
++ let ty = match name {
++ "bool" => BoolSimplifiedType,
++ "char" => CharSimplifiedType,
++ "str" => StrSimplifiedType,
++ "array" => ArraySimplifiedType,
++ "slice" => SliceSimplifiedType,
+ // FIXME: rustdoc documents these two using just `pointer`.
+ //
+ // Maybe this is something we should do here too.
- fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
++ "const_ptr" => PtrSimplifiedType(Mutability::Not),
++ "mut_ptr" => PtrSimplifiedType(Mutability::Mut),
++ "isize" => IntSimplifiedType(IntTy::Isize),
++ "i8" => IntSimplifiedType(IntTy::I8),
++ "i16" => IntSimplifiedType(IntTy::I16),
++ "i32" => IntSimplifiedType(IntTy::I32),
++ "i64" => IntSimplifiedType(IntTy::I64),
++ "i128" => IntSimplifiedType(IntTy::I128),
++ "usize" => UintSimplifiedType(UintTy::Usize),
++ "u8" => UintSimplifiedType(UintTy::U8),
++ "u16" => UintSimplifiedType(UintTy::U16),
++ "u32" => UintSimplifiedType(UintTy::U32),
++ "u64" => UintSimplifiedType(UintTy::U64),
++ "u128" => UintSimplifiedType(UintTy::U128),
++ "f32" => FloatSimplifiedType(FloatTy::F32),
++ "f64" => FloatSimplifiedType(FloatTy::F64),
++ _ => return [].iter().copied(),
++ };
++
++ tcx.incoherent_impls(ty).iter().copied()
++}
++
++fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
++ match tcx.def_kind(def_id) {
++ DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx
++ .module_children(def_id)
++ .iter()
++ .filter(|item| item.ident.name == name)
++ .map(|child| child.res.expect_non_local())
++ .collect(),
++ DefKind::Impl => tcx
++ .associated_item_def_ids(def_id)
++ .iter()
++ .copied()
++ .filter(|assoc_def_id| tcx.item_name(*assoc_def_id) == name)
++ .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id))
++ .collect(),
++ _ => Vec::new(),
++ }
++}
++
++fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symbol) -> Vec<Res> {
++ let hir = tcx.hir();
++
++ let root_mod;
++ let item_kind = match hir.find_by_def_id(local_id) {
++ Some(Node::Crate(r#mod)) => {
++ root_mod = ItemKind::Mod(r#mod);
++ &root_mod
++ },
++ Some(Node::Item(item)) => &item.kind,
++ _ => return Vec::new(),
++ };
++
++ let res = |ident: Ident, owner_id: OwnerId| {
++ if ident.name == name {
++ let def_id = owner_id.to_def_id();
++ Some(Res::Def(tcx.def_kind(def_id), def_id))
++ } else {
++ None
+ }
++ };
++
++ match item_kind {
++ ItemKind::Mod(r#mod) => r#mod
++ .item_ids
++ .iter()
++ .filter_map(|&item_id| res(hir.item(item_id).ident, item_id.owner_id))
++ .collect(),
++ ItemKind::Impl(r#impl) => r#impl
++ .items
++ .iter()
++ .filter_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id))
++ .collect(),
++ ItemKind::Trait(.., trait_item_refs) => trait_item_refs
++ .iter()
++ .filter_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id))
++ .collect(),
++ _ => Vec::new(),
++ }
++}
++
++fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
++ if let Some(local_id) = def_id.as_local() {
++ local_item_children_by_name(tcx, local_id, name)
++ } else {
++ non_local_item_children_by_name(tcx, def_id, name)
+ }
++}
+
- .find(|&num| tcx.crate_name(num).as_str() == name)
++/// Resolves a def path like `std::vec::Vec`.
++///
++/// Can return multiple resolutions when there are multiple versions of the same crate, e.g.
++/// `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0.
++///
++/// Also returns multiple results when there are mulitple paths under the same name e.g. `std::vec`
++/// would have both a [`DefKind::Mod`] and [`DefKind::Macro`].
++///
++/// This function is expensive and should be used sparingly.
++pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Vec<Res> {
++ fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator<Item = DefId> + '_ {
+ tcx.crates(())
+ .iter()
+ .copied()
- let (base, path) = match *path {
++ .filter(move |&num| tcx.crate_name(num) == name)
+ .map(CrateNum::as_def_id)
+ }
+
- return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
++ let tcx = cx.tcx;
++
++ let (base, mut path) = match *path {
+ [primitive] => {
- _ => return Res::Err,
++ return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)];
+ },
+ [base, ref path @ ..] => (base, path),
- let tcx = cx.tcx;
- let starts = find_primitive(tcx, base)
- .chain(find_crate(tcx, base))
++ _ => return Vec::new(),
+ };
- for first in starts {
- let last = path
- .iter()
- .copied()
- .enumerate()
- // for each segment, find the child item
- .try_fold(first, |res, (idx, segment)| {
- let matches_ns = |res: Res| {
- // If at the last segment in the path, respect the namespace hint
- if idx == path.len() - 1 {
- match namespace_hint {
- Some(ns) => res.matches_ns(ns),
- None => true,
- }
- } else {
- res.matches_ns(Namespace::TypeNS)
- }
- };
-
- let def_id = res.def_id();
- if let Some(item) = item_child_by_name(tcx, def_id, segment, matches_ns) {
- Some(item)
- } else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
- // it is not a child item so check inherent impl items
- tcx.inherent_impls(def_id)
- .iter()
- .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment, matches_ns))
- } else {
- None
- }
- });
++
++ let base_sym = Symbol::intern(base);
++
++ let local_crate = if tcx.crate_name(LOCAL_CRATE) == base_sym {
++ Some(LOCAL_CRATE.as_def_id())
++ } else {
++ None
++ };
++
++ let starts = find_primitive_impls(tcx, base)
++ .chain(find_crates(tcx, base_sym))
++ .chain(local_crate)
+ .map(|id| Res::Def(tcx.def_kind(id), id));
+
- if let Some(last) = last {
- return last;
- }
++ let mut resolutions: Vec<Res> = starts.collect();
+
- Res::Err
++ while let [segment, rest @ ..] = path {
++ path = rest;
++ let segment = Symbol::intern(segment);
++
++ resolutions = resolutions
++ .into_iter()
++ .filter_map(|res| res.opt_def_id())
++ .flat_map(|def_id| {
++ // When the current def_id is e.g. `struct S`, check the impl items in
++ // `impl S { ... }`
++ let inherent_impl_children = tcx
++ .inherent_impls(def_id)
++ .iter()
++ .flat_map(|&impl_def_id| item_children_by_name(tcx, impl_def_id, segment));
++
++ let direct_children = item_children_by_name(tcx, def_id, segment);
++
++ inherent_impl_children.chain(direct_children)
++ })
++ .collect();
+ }
+
- match def_path_res(cx, path, Some(Namespace::TypeNS)) {
++ resolutions
++}
++
++/// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`].
++pub fn def_path_def_ids(cx: &LateContext<'_>, path: &[&str]) -> impl Iterator<Item = DefId> {
++ def_path_res(cx, path).into_iter().filter_map(|res| res.opt_def_id())
+}
+
+/// Convenience function to get the `DefId` of a trait by path.
+/// It could be a trait or trait alias.
+///
+/// This function is expensive and should be used sparingly.
+pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
- }
++ def_path_res(cx, path).into_iter().find_map(|res| match res {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
- return std_types_symbols
- .iter()
- .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string());
++ })
+}
+
+/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
+///
+/// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
+///
+/// ```rust
+/// struct Point(isize, isize);
+///
+/// impl std::ops::Add for Point {
+/// type Output = Self;
+///
+/// fn add(self, other: Self) -> Self {
+/// Point(0, 0)
+/// }
+/// }
+/// ```
+pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) -> Option<&'tcx TraitRef<'tcx>> {
+ // Get the implemented trait for the current function
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+ let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
+ if_chain! {
+ if parent_impl != hir::CRATE_OWNER_ID;
+ if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl.def_id);
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ then {
+ return impl_.of_trait.as_ref();
+ }
+ }
+ None
+}
+
+/// This method will return tuple of projection stack and root of the expression,
+/// used in `can_mut_borrow_both`.
+///
+/// For example, if `e` represents the `v[0].a.b[x]`
+/// this method will return a tuple, composed of a `Vec`
+/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
+/// and an `Expr` for root of them, `v`
+fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
+ let mut result = vec![];
+ let root = loop {
+ match e.kind {
+ ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
+ result.push(e);
+ e = ep;
+ },
+ _ => break e,
+ };
+ };
+ result.reverse();
+ (result, root)
+}
+
+/// Gets the mutability of the custom deref adjustment, if any.
+pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
+ cx.typeck_results()
+ .expr_adjustments(e)
+ .iter()
+ .find_map(|a| match a.kind {
+ Adjust::Deref(Some(d)) => Some(Some(d.mutbl)),
+ Adjust::Deref(None) => None,
+ _ => Some(None),
+ })
+ .and_then(|x| x)
+}
+
+/// Checks if two expressions can be mutably borrowed simultaneously
+/// and they aren't dependent on borrowing same thing twice
+pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
+ let (s1, r1) = projection_stack(e1);
+ let (s2, r2) = projection_stack(e2);
+ if !eq_expr_value(cx, r1, r2) {
+ return true;
+ }
+ if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
+ return false;
+ }
+
+ for (x1, x2) in s1.iter().zip(s2.iter()) {
+ if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
+ return false;
+ }
+
+ match (&x1.kind, &x2.kind) {
+ (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
+ if i1 != i2 {
+ return true;
+ }
+ },
+ (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
+ if !eq_expr_value(cx, i1, i2) {
+ return false;
+ }
+ },
+ _ => return false,
+ }
+ }
+ false
+}
+
+/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
+/// constructor from the std library
+fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
+ let std_types_symbols = &[
+ sym::Vec,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::HashMap,
+ sym::BTreeMap,
+ sym::HashSet,
+ sym::BTreeSet,
+ sym::BinaryHeap,
+ ];
+
+ if let QPath::TypeRelative(_, method) = path {
+ if method.ident.name == sym::new {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
- pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
++ return std_types_symbols.iter().any(|&symbol| {
++ cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string()
++ });
+ }
+ }
+ }
+ }
+ false
+}
+
+/// Return true if the expr is equal to `Default::default` when evaluated.
+pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ if is_diag_trait_item(cx, repl_def_id, sym::Default)
+ || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
+ then { true } else { false }
+ }
+}
+
+/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
+/// It doesn't cover all cases, for example indirect function calls (some of std
+/// functions are supported) but it is the best we have.
+pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(false) | LitKind::Int(0, _) => true,
+ LitKind::Str(s, _) => s.is_empty(),
+ _ => false,
+ },
+ ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
+ ExprKind::Repeat(x, ArrayLen::Body(len)) => if_chain! {
+ if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind;
+ if let LitKind::Int(v, _) = const_lit.node;
+ if v <= 32 && is_default_equivalent(cx, x);
+ then {
+ true
+ }
+ else {
+ false
+ }
+ },
+ ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func),
+ ExprKind::Call(from_func, [ref arg]) => is_default_equivalent_from(cx, from_func, arg),
+ ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone),
+ ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
+ _ => false,
+ }
+}
+
+fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
+ if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind &&
+ seg.ident.name == sym::from
+ {
+ match arg.kind {
+ ExprKind::Lit(hir::Lit {
+ node: LitKind::Str(ref sym, _),
+ ..
+ }) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String),
+ ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec),
+ ExprKind::Repeat(_, ArrayLen::Body(len)) => {
+ if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind &&
+ let LitKind::Int(v, _) = const_lit.node
+ {
+ return v == 0 && is_path_diagnostic_item(cx, ty, sym::Vec);
+ }
+ }
+ _ => (),
+ }
+ }
+ false
+}
+
+/// Checks if the top level expression can be moved into a closure as is.
+/// Currently checks for:
+/// * Break/Continue outside the given loop HIR ids.
+/// * Yield/Return statements.
+/// * Inline assembly.
+/// * Usages of a field of a local where the type of the local can be partially moved.
+///
+/// For example, given the following function:
+///
+/// ```
+/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) {
+/// for item in iter {
+/// let s = item.1;
+/// if item.0 > 10 {
+/// continue;
+/// } else {
+/// s.clear();
+/// }
+/// }
+/// }
+/// ```
+///
+/// When called on the expression `item.0` this will return false unless the local `item` is in the
+/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it
+/// isn't always safe to move into a closure when only a single field is needed.
+///
+/// When called on the `continue` expression this will return false unless the outer loop expression
+/// is in the `loop_ids` set.
+///
+/// Note that this check is not recursive, so passing the `if` expression will always return true
+/// even though sub-expressions might return false.
+pub fn can_move_expr_to_closure_no_visit<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ loop_ids: &[HirId],
+ ignore_locals: &HirIdSet,
+) -> bool {
+ match expr.kind {
+ ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
+ | ExprKind::Continue(Destination { target_id: Ok(id), .. })
+ if loop_ids.contains(&id) =>
+ {
+ true
+ },
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Yield(..)
+ | ExprKind::InlineAsm(_) => false,
+ // Accessing a field of a local value can only be done if the type isn't
+ // partially moved.
+ ExprKind::Field(
+ &Expr {
+ hir_id,
+ kind:
+ ExprKind::Path(QPath::Resolved(
+ _,
+ Path {
+ res: Res::Local(local_id),
+ ..
+ },
+ )),
+ ..
+ },
+ _,
+ ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
+ // TODO: check if the local has been partially moved. Assume it has for now.
+ false
+ },
+ _ => true,
+ }
+}
+
+/// How a local is captured by a closure
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum CaptureKind {
+ Value,
+ Ref(Mutability),
+}
+impl CaptureKind {
+ pub fn is_imm_ref(self) -> bool {
+ self == Self::Ref(Mutability::Not)
+ }
+}
+impl std::ops::BitOr for CaptureKind {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self::Output {
+ match (self, rhs) {
+ (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
+ (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
+ | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
+ (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
+ }
+ }
+}
+impl std::ops::BitOrAssign for CaptureKind {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs;
+ }
+}
+
+/// Given an expression referencing a local, determines how it would be captured in a closure.
+/// Note as this will walk up to parent expressions until the capture can be determined it should
+/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
+/// function argument (other than a receiver).
- /// Extends the span to the beginning of the spans line, incl. whitespaces.
- ///
- /// ```rust
- /// let x = ();
- /// // ^^
- /// // will be converted to
- /// let x = ();
- /// // ^^^^^^^^^^^^^^
- /// ```
- fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
- let span = original_sp(span, DUMMY_SP);
- let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
- let line_no = source_map_and_line.line;
- let line_start = source_map_and_line.sf.lines(|lines| lines[line_no]);
- span.with_lo(line_start)
- }
-
++pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind {
+ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
+ let mut capture = CaptureKind::Ref(Mutability::Not);
+ pat.each_binding_or_first(&mut |_, id, span, _| match cx
+ .typeck_results()
+ .extract_binding_mode(cx.sess(), id, span)
+ .unwrap()
+ {
+ BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
+ capture = CaptureKind::Value;
+ },
+ BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
+ capture = CaptureKind::Ref(Mutability::Mut);
+ },
+ _ => (),
+ });
+ capture
+ }
+
+ debug_assert!(matches!(
+ e.kind,
+ ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
+ ));
+
+ let mut child_id = e.hir_id;
+ let mut capture = CaptureKind::Value;
+ let mut capture_expr_ty = e;
+
+ for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) {
+ if let [
+ Adjustment {
+ kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
+ target,
+ },
+ ref adjust @ ..,
+ ] = *cx
+ .typeck_results()
+ .adjustments()
+ .get(child_id)
+ .map_or(&[][..], |x| &**x)
+ {
+ if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
+ *adjust.last().map_or(target, |a| a.target).kind()
+ {
+ return CaptureKind::Ref(mutability);
+ }
+ }
+
+ match parent {
+ Node::Expr(e) => match e.kind {
+ ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
+ ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
+ ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
+ return CaptureKind::Ref(Mutability::Mut);
+ },
+ ExprKind::Field(..) => {
+ if capture == CaptureKind::Value {
+ capture_expr_ty = e;
+ }
+ },
+ ExprKind::Let(let_expr) => {
+ let mutability = match pat_capture_kind(cx, let_expr.pat) {
+ CaptureKind::Value => Mutability::Not,
+ CaptureKind::Ref(m) => m,
+ };
+ return CaptureKind::Ref(mutability);
+ },
+ ExprKind::Match(_, arms, _) => {
+ let mut mutability = Mutability::Not;
+ for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
+ match capture {
+ CaptureKind::Value => break,
+ CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
+ CaptureKind::Ref(Mutability::Not) => (),
+ }
+ }
+ return CaptureKind::Ref(mutability);
+ },
+ _ => break,
+ },
+ Node::Local(l) => match pat_capture_kind(cx, l.pat) {
+ CaptureKind::Value => break,
+ capture @ CaptureKind::Ref(_) => return capture,
+ },
+ _ => break,
+ }
+
+ child_id = parent_id;
+ }
+
+ if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
+ // Copy types are never automatically captured by value.
+ CaptureKind::Ref(Mutability::Not)
+ } else {
+ capture
+ }
+}
+
+/// Checks if the expression can be moved into a closure as is. This will return a list of captures
+/// if so, otherwise, `None`.
+pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ // Stack of potential break targets contained in the expression.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+ /// Whether this expression can be turned into a closure.
+ allow_closure: bool,
+ /// Locals which need to be captured, and whether they need to be by value, reference, or
+ /// mutable reference.
+ captures: HirIdMap<CaptureKind>,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.allow_closure {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
+ if !self.locals.contains(&l) {
+ let cap = capture_local_usage(self.cx, e);
+ self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
+ }
+ },
+ ExprKind::Closure { .. } => {
+ let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id);
+ for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
+ let local_id = match capture.place.base {
+ PlaceBase::Local(id) => id,
+ PlaceBase::Upvar(var) => var.var_path.hir_id,
+ _ => continue,
+ };
+ if !self.locals.contains(&local_id) {
+ let capture = match capture.info.capture_kind {
+ UpvarCapture::ByValue => CaptureKind::Value,
+ UpvarCapture::ByRef(kind) => match kind {
+ BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
+ BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
+ CaptureKind::Ref(Mutability::Mut)
+ },
+ },
+ };
+ self.captures
+ .entry(local_id)
+ .and_modify(|e| *e |= capture)
+ .or_insert(capture);
+ }
+ }
+ },
+ ExprKind::Loop(b, ..) => {
+ self.loops.push(e.hir_id);
+ self.visit_block(b);
+ self.loops.pop();
+ },
+ _ => {
+ self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
+ walk_expr(self, e);
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+ }
+
+ let mut v = V {
+ cx,
+ allow_closure: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ captures: HirIdMap::default(),
+ };
+ v.visit_expr(expr);
+ v.allow_closure.then_some(v.captures)
+}
+
+/// Arguments of a method: the receiver and all the additional arguments.
+pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
+
+/// Returns the method names and argument list of nested method call expressions that make up
+/// `expr`. method/span lists are sorted with the most recent call first.
+pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
+ let mut method_names = Vec::with_capacity(max_depth);
+ let mut arg_lists = Vec::with_capacity(max_depth);
+ let mut spans = Vec::with_capacity(max_depth);
+
+ let mut current = expr;
+ for _ in 0..max_depth {
+ if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind {
+ if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
+ break;
+ }
+ method_names.push(path.ident.name);
+ arg_lists.push((*receiver, &**args));
+ spans.push(path.ident.span);
+ current = receiver;
+ } else {
+ break;
+ }
+ }
+
+ (method_names, arg_lists, spans)
+}
+
+/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
+///
+/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
+/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
+/// containing the `Expr`s for
+/// `.bar()` and `.baz()`
+pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
+ let mut current = expr;
+ let mut matched = Vec::with_capacity(methods.len());
+ for method_name in methods.iter().rev() {
+ // method chains are stored last -> first
+ if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
+ if path.ident.name.as_str() == *method_name {
+ if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
+ return None;
+ }
+ matched.push((receiver, args)); // build up `matched` backwards
+ current = receiver; // go to parent expression
+ } else {
+ return None;
+ }
+ } else {
+ return None;
+ }
+ }
+ // Reverse `matched` so that it is in the same order as `methods`.
+ matched.reverse();
+ Some(matched)
+}
+
+/// Returns `true` if the provided `def_id` is an entrypoint to a program.
+pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx
+ .entry_fn(())
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id)
+}
+
+/// Returns `true` if the expression is in the program's `#[panic_handler]`.
+pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+ Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl()
+}
+
+/// Gets the name of the item the expression is in, if available.
+pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
+ let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id).def_id;
+ match cx.tcx.hir().find_by_def_id(parent_id) {
+ Some(
+ Node::Item(Item { ident, .. })
+ | Node::TraitItem(TraitItem { ident, .. })
+ | Node::ImplItem(ImplItem { ident, .. }),
+ ) => Some(ident.name),
+ _ => None,
+ }
+}
+
+pub struct ContainsName {
+ pub name: Symbol,
+ pub result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ fn visit_name(&mut self, name: Symbol) {
+ if self.name == name {
+ self.result = true;
+ }
+ }
+}
+
+/// Checks if an `Expr` contains a certain name.
+pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
+ let mut cn = ContainsName { name, result: false };
+ cn.visit_expr(expr);
+ cn.result
+}
+
+/// Returns `true` if `expr` contains a return expression
+pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
+ for_each_expr(expr, |e| {
+ if matches!(e.kind, hir::ExprKind::Ret(..)) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ .is_some()
+}
+
- pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+/// Gets the parent node, if any.
+pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
+ tcx.hir().parent_iter(id).next().map(|(_, node)| node)
+}
+
+/// Gets the parent expression, if any –- this is useful to constrain a lint.
+pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ get_parent_expr_for_hir(cx, e.hir_id)
+}
+
+/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
+/// constraint lints
+pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
+ match get_parent_node(cx.tcx, hir_id) {
+ Some(Node::Expr(parent)) => Some(parent),
+ _ => None,
+ }
+}
+
+pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
+ let map = &cx.tcx.hir();
+ let enclosing_node = map
+ .get_enclosing_scope(hir_id)
+ .and_then(|enclosing_id| map.find(enclosing_id));
+ enclosing_node.and_then(|node| match node {
+ Node::Block(block) => Some(block),
+ Node::Item(&Item {
+ kind: ItemKind::Fn(_, _, eid),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(_, eid),
+ ..
+ }) => match cx.tcx.hir().body(eid).value.kind {
+ ExprKind::Block(block, _) => Some(block),
+ _ => None,
+ },
+ _ => None,
+ })
+}
+
+/// Gets the loop or closure enclosing the given expression, if any.
+pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &Expr<'_>,
+) -> Option<&'tcx Expr<'tcx>> {
+ for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
+ match node {
+ Node::Expr(e) => match e.kind {
+ ExprKind::Closure { .. } => {
+ if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
+ && subs.as_closure().kind() == ClosureKind::FnOnce
+ {
+ continue;
+ }
+ let is_once = walk_to_expr_usage(cx, e, |node, id| {
+ let Node::Expr(e) = node else {
+ return None;
+ };
+ match e.kind {
+ ExprKind::Call(f, _) if f.hir_id == id => Some(()),
+ ExprKind::Call(f, args) => {
+ let i = args.iter().position(|arg| arg.hir_id == id)?;
+ let sig = expr_sig(cx, f)?;
+ let predicates = sig
+ .predicates_id()
+ .map_or(cx.param_env, |id| cx.tcx.param_env(id))
+ .caller_bounds();
+ sig.input(i).and_then(|ty| {
+ ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
+ })
+ },
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ let i = std::iter::once(receiver)
+ .chain(args.iter())
+ .position(|arg| arg.hir_id == id)?;
+ let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
+ let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i];
+ ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
+ },
+ _ => None,
+ }
+ })
+ .is_some();
+ if !is_once {
+ return Some(e);
+ }
+ },
+ ExprKind::Loop(..) => return Some(e),
+ _ => (),
+ },
+ Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
+ _ => break,
+ }
+ }
+ None
+}
+
+/// Gets the parent node if it's an impl block.
+pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
+ match tcx.hir().parent_iter(id).next() {
+ Some((
+ _,
+ Node::Item(Item {
+ kind: ItemKind::Impl(imp),
+ ..
+ }),
+ )) => Some(imp),
+ _ => None,
+ }
+}
+
+/// Removes blocks around an expression, only if the block contains just one expression
+/// and no statements. Unsafe blocks are not removed.
+///
+/// Examples:
+/// * `{}` -> `{}`
+/// * `{ x }` -> `x`
+/// * `{{ x }}` -> `x`
+/// * `{ x; }` -> `{ x; }`
+/// * `{ x; y }` -> `{ x; y }`
+/// * `{ unsafe { x } }` -> `unsafe { x }`
+pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
+ while let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(inner),
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ },
+ _,
+ ) = expr.kind
+ {
+ expr = inner;
+ }
+ expr
+}
+
+/// Removes blocks around an expression, only if the block contains just one expression
+/// or just one expression statement with a semicolon. Unsafe blocks are not removed.
+///
+/// Examples:
+/// * `{}` -> `{}`
+/// * `{ x }` -> `x`
+/// * `{ x; }` -> `x`
+/// * `{{ x; }}` -> `x`
+/// * `{ x; y }` -> `{ x; y }`
+/// * `{ unsafe { x } }` -> `unsafe { x }`
+pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
+ while let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(inner),
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ }
+ | Block {
+ stmts:
+ [
+ Stmt {
+ kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
+ ..
+ },
+ ],
+ expr: None,
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ },
+ _,
+ ) = expr.kind
+ {
+ expr = inner;
+ }
+ expr
+}
+
+/// Checks if the given expression is the else clause of either an `if` or `if let` expression.
+pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let mut iter = tcx.hir().parent_iter(expr.hir_id);
+ match iter.next() {
+ Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::If(_, _, Some(else_expr)),
+ ..
+ }),
+ )) => else_expr.hir_id == expr.hir_id,
+ _ => false,
+ }
+}
+
+/// Checks whether the given expression is a constant integer of the given value.
+/// unlike `is_integer_literal`, this version does const folding
+pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
+ if is_integer_literal(e, value) {
+ return true;
+ }
+ let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id);
+ if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
+ return value == v;
+ }
+ false
+}
+
+/// Checks whether the given expression is a constant literal of the given value.
+pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
+ // FIXME: use constant folding
+ if let ExprKind::Lit(ref spanned) = expr.kind {
+ if let LitKind::Int(v, _) = spanned.node {
+ return v == value;
+ }
+ }
+ false
+}
+
+/// Returns `true` if the given `Expr` has been coerced before.
+///
+/// Examples of coercions can be found in the Nomicon at
+/// <https://doc.rust-lang.org/nomicon/coercions.html>.
+///
+/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_hir_analysis::check::coercion` for
+/// more information on adjustments and coercions.
+pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ cx.typeck_results().adjustments().get(e.hir_id).is_some()
+}
+
+/// Returns the pre-expansion span if this comes from an expansion of the
+/// macro `name`.
+/// See also [`is_direct_expn_of`].
+#[must_use]
+pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
+ loop {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+
+ span = new_span;
+ } else {
+ return None;
+ }
+ }
+}
+
+/// Returns the pre-expansion span if the span directly comes from an expansion
+/// of the macro `name`.
+/// The difference with [`is_expn_of`] is that in
+/// ```rust
+/// # macro_rules! foo { ($name:tt!$args:tt) => { $name!$args } }
+/// # macro_rules! bar { ($e:expr) => { $e } }
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// from `bar!` by `is_direct_expn_of`.
+#[must_use]
+pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+ }
+
+ None
+}
+
+/// Convenience function to get the return type of a function.
+pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
+ cx.tcx.erase_late_bound_regions(ret_ty)
+}
+
+/// Convenience function to get the nth argument type of a function.
+pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId, nth: usize) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let arg = cx.tcx.fn_sig(fn_def_id).input(nth);
+ cx.tcx.erase_late_bound_regions(arg)
+}
+
+/// Checks if an expression is constructing a tuple-like enum variant or struct
+pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(fun, _) = expr.kind {
+ if let ExprKind::Path(ref qp) = fun.kind {
+ let res = cx.qpath_res(qp, fun.hir_id);
+ return match res {
+ def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
+ def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
+ _ => false,
+ };
+ }
+ }
+ false
+}
+
+/// Returns `true` if a pattern is refutable.
+// TODO: should be implemented using rustc/mir_build/thir machinery
+pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
+ fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
+ matches!(
+ cx.qpath_res(qpath, id),
+ def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
+ )
+ }
+
+ fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
+ i.into_iter().any(|pat| is_refutable(cx, pat))
+ }
+
+ match pat.kind {
+ PatKind::Wild => false,
+ PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
+ PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat),
+ PatKind::Lit(..) | PatKind::Range(..) => true,
+ PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
+ PatKind::Or(pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats)
+ },
+ PatKind::Tuple(pats, _) => are_refutable(cx, pats),
+ PatKind::Struct(ref qpath, fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats),
+ PatKind::Slice(head, middle, tail) => {
+ match &cx.typeck_results().node_type(pat.hir_id).kind() {
+ rustc_ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call
+/// the function once on the given pattern.
+pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
+ if let PatKind::Or(pats) = pat.kind {
+ pats.iter().for_each(f);
+ } else {
+ f(pat);
+ }
+}
+
+pub fn is_self(slf: &Param<'_>) -> bool {
+ if let PatKind::Binding(.., name, _) = slf.pat.kind {
+ name.name == kw::SelfLower
+ } else {
+ false
+ }
+}
+
+pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
+ if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
+ if let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res {
+ return true;
+ }
+ }
+ false
+}
+
+pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
+ (0..decl.inputs.len()).map(move |i| &body.params[i])
+}
+
+/// Checks if a given expression is a match expression expanded from the `?`
+/// operator or the `try` macro.
+pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind;
+ if ddpos.as_opt_usize().is_none();
+ if is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk);
+ if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
+ if path_to_local_id(arm.body, hir_id);
+ then {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultErr)
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if *source == MatchSource::TryDesugar {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
+ then {
+ return Some(expr);
+ }
+ }
+ }
+
+ None
+}
+
+/// Returns `true` if the lint is allowed in the current context. This is useful for
+/// skipping long running code when it's unnecessary
+///
+/// This function should check the lint level for the same node, that the lint will
+/// be emitted at. If the information is buffered to be emitted at a later point, please
+/// make sure to use `span_lint_hir` functions to emit the lint. This ensures that
+/// expectations at the checked nodes will be fulfilled.
+pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
+ cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
+}
+
+pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
+ while let PatKind::Ref(subpat, _) = pat.kind {
+ pat = subpat;
+ }
+ pat
+}
+
+pub fn int_bits(tcx: TyCtxt<'_>, ity: rustc_ty::IntTy) -> u64 {
+ Integer::from_int_ty(&tcx, ity).size().bits()
+}
+
+#[expect(clippy::cast_possible_wrap)]
+/// Turn a constant int byte representation into an i128
+pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::IntTy) -> i128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as i128) << amt) >> amt
+}
+
+#[expect(clippy::cast_sign_loss)]
+/// clip unused bytes
+pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: rustc_ty::IntTy) -> u128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as u128) << amt) >> amt
+}
+
+/// clip unused bytes
+pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 {
+ let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
+pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool {
+ attrs.iter().any(|attr| attr.has_name(symbol))
+}
+
++pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
++ has_attr(cx.tcx.hir().attrs(hir_id), sym::repr)
++}
++
+pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
+ if has_attr(map.attrs(enclosing_node), symbol) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.get_parent_item(enclosing_node).into();
+ }
+
+ false
+}
+
+pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
+ any_parent_has_attr(tcx, node, sym::automatically_derived)
+}
+
+/// Matches a function call with the given path and returns the arguments.
+///
+/// Usage:
+///
+/// ```rust,ignore
+/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
+/// ```
+/// This function is deprecated. Use [`match_function_call_with_def_id`].
+pub fn match_function_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ path: &[&str],
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ if match_def_path(cx, fun_def_id, path);
+ then {
+ return Some(args);
+ }
+ };
+ None
+}
+
+pub fn match_function_call_with_def_id<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ fun_def_id: DefId,
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id);
+ then {
+ return Some(args);
+ }
+ };
+ None
+}
+
+/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
+/// any.
+///
+/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items.
+pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
+ let search_path = cx.get_def_path(did);
+ paths
+ .iter()
+ .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
+}
+
+/// Checks if the given `DefId` matches the path.
- matches!(kind, FnKind::ItemFn(_, _, header) if header.asyncness.is_async())
++pub fn match_def_path(cx: &LateContext<'_>, did: DefId, syms: &[&str]) -> bool {
+ // We should probably move to Symbols in Clippy as well rather than interning every time.
+ let path = cx.get_def_path(did);
+ syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
+}
+
+/// Checks if the given `DefId` matches the `libc` item.
+pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
+ let path = cx.get_def_path(did);
+ // libc is meant to be used as a flat list of names, but they're all actually defined in different
+ // modules based on the target platform. Ignore everything but crate name and the item name.
+ path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name)
+}
+
+/// Returns the list of condition expressions and the list of blocks in a
+/// sequence of `if/else`.
+/// E.g., this returns `([a, b], [c, d, e])` for the expression
+/// `if a { c } else if b { d } else { e }`.
+pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
+ let mut conds = Vec::new();
+ let mut blocks: Vec<&Block<'_>> = Vec::new();
+
+ while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
+ conds.push(cond);
+ if let ExprKind::Block(block, _) = then.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(else_expr) = r#else {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(block, _) = expr.kind {
+ blocks.push(block);
+ }
+ }
+
+ (conds, blocks)
+}
+
+/// Checks if the given function kind is an async function.
+pub fn is_async_fn(kind: FnKind<'_>) -> bool {
++ match kind {
++ FnKind::ItemFn(_, _, header) => header.asyncness == IsAsync::Async,
++ FnKind::Method(_, sig) => sig.header.asyncness == IsAsync::Async,
++ FnKind::Closure => false,
++ }
+}
+
+/// Peels away all the compiler generated code surrounding the body of an async function,
+pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Call(
+ _,
+ &[
+ Expr {
+ kind: ExprKind::Closure(&Closure { body, .. }),
+ ..
+ },
+ ],
+ ) = body.value.kind
+ {
+ if let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr:
+ Some(Expr {
+ kind: ExprKind::DropTemps(expr),
+ ..
+ }),
+ ..
+ },
+ _,
+ ) = tcx.hir().body(body).value.kind
+ {
+ return Some(expr);
+ }
+ };
+ None
+}
+
+// check if expr is calling method or function with #[must_use] attribute
+pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let did = match expr.kind {
+ ExprKind::Call(path, _) => if_chain! {
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id);
+ then {
+ Some(did)
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+
+ did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use))
+}
+
+/// Checks if an expression represents the identity function
+/// Only examines closures and `std::convert::identity`
+pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Checks if a function's body represents the identity function. Looks for bodies of the form:
+ /// * `|x| x`
+ /// * `|x| return x`
+ /// * `|x| { return x }`
+ /// * `|x| { return x; }`
+ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
+ let id = if_chain! {
+ if let [param] = func.params;
+ if let PatKind::Binding(_, id, _, _) = param.pat.kind;
+ then {
+ id
+ } else {
+ return false;
+ }
+ };
+
+ let mut expr = func.value;
+ loop {
+ match expr.kind {
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
+ | ExprKind::Ret(Some(e)) => expr = e,
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
+ if_chain! {
+ if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
+ if let ExprKind::Ret(Some(ret_val)) = e.kind;
+ then {
+ expr = ret_val;
+ } else {
+ return false;
+ }
+ }
+ },
+ _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
+ }
+ }
+ }
+
+ match expr.kind {
+ ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)),
+ _ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)),
+ }
+}
+
+/// Gets the node where an expression is either used, or it's type is unified with another branch.
+/// Returns both the node and the `HirId` of the closest child node.
+pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
+ let mut child_id = expr.hir_id;
+ let mut iter = tcx.hir().parent_iter(child_id);
+ loop {
+ match iter.next() {
+ None => break None,
+ Some((id, Node::Block(_))) => child_id = id,
+ Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
+ Some((_, Node::Expr(expr))) => match expr.kind {
+ ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
+ ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
+ ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
+ _ => break Some((Node::Expr(expr), child_id)),
+ },
+ Some((_, node)) => break Some((node, child_id)),
+ }
+ }
+}
+
+/// Checks if the result of an expression is used, or it's type is unified with another branch.
+pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ !matches!(
+ get_expr_use_or_unification_node(tcx, expr),
+ None | Some((
+ Node::Stmt(Stmt {
+ kind: StmtKind::Expr(_)
+ | StmtKind::Semi(_)
+ | StmtKind::Local(Local {
+ pat: Pat {
+ kind: PatKind::Wild,
+ ..
+ },
+ ..
+ }),
+ ..
+ }),
+ _
+ ))
+ )
+}
+
+/// Checks if the expression is the final expression returned from a block.
+pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
+}
+
+pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
+ if !is_no_std_crate(cx) {
+ Some("std")
+ } else if !is_no_core_crate(cx) {
+ Some("core")
+ } else {
+ None
+ }
+}
+
+pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ normal.item.path == sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
+pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ normal.item.path == sym::no_core
+ } else {
+ false
+ }
+ })
+}
+
+/// Check if parent of a hir node is a trait implementation block.
+/// For example, `f` in
+/// ```rust
+/// # struct S;
+/// # trait Trait { fn f(); }
+/// impl Trait for S {
+/// fn f() {}
+/// }
+/// ```
+pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ }
+}
+
+/// Check if it's even possible to satisfy the `where` clause for the item.
+///
+/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example:
+///
+/// ```ignore
+/// fn foo() where i32: Iterator {
+/// for _ in 2i32 {}
+/// }
+/// ```
+pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
+ use rustc_trait_selection::traits;
+ let predicates = cx
+ .tcx
+ .predicates_of(did)
+ .predicates
+ .iter()
+ .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
+ traits::impossible_predicates(
+ cx.tcx,
+ traits::elaborate_predicates(cx.tcx, predicates)
+ .map(|o| o.predicate)
+ .collect::<Vec<_>>(),
+ )
+}
+
+/// Returns the `DefId` of the callee if the given expression is a function or method call.
+pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
+ match &expr.kind {
+ ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(qpath),
+ hir_id: path_hir_id,
+ ..
+ },
+ ..,
+ ) => {
+ // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or
+ // deref to fn pointers, dyn Fn, impl Fn - #8850
+ if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
+ cx.typeck_results().qpath_res(qpath, *path_hir_id)
+ {
+ Some(id)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+/// Returns `Option<String>` where String is a textual representation of the type encapsulated in
+/// the slice iff the given expression is a slice of primitives (as defined in the
+/// `is_recursively_primitive_type` function) and `None` otherwise.
+pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
+ let expr_kind = expr_type.kind();
+ let is_primitive = match expr_kind {
+ rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
+ rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
+ if let rustc_ty::Slice(element_type) = inner_ty.kind() {
+ is_recursively_primitive_type(*element_type)
+ } else {
+ unreachable!()
+ }
+ },
+ _ => false,
+ };
+
+ if is_primitive {
+ // if we have wrappers like Array, Slice or Tuple, print these
+ // and get the type enclosed in the slice ref
+ match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
+ rustc_ty::Slice(..) => return Some("slice".into()),
+ rustc_ty::Array(..) => return Some("array".into()),
+ rustc_ty::Tuple(..) => return Some("tuple".into()),
+ _ => {
+ // is_recursively_primitive_type() should have taken care
+ // of the rest and we can rely on the type that is found
+ let refs_peeled = expr_type.peel_refs();
+ return Some(refs_peeled.walk().last().unwrap().to_string());
+ },
+ }
+ }
+ None
+}
+
+/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
+/// `hash` must be comformed with `eq`
+pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
+where
+ Hash: Fn(&T) -> u64,
+ Eq: Fn(&T, &T) -> bool,
+{
+ match exprs {
+ [a, b] if eq(a, b) => return vec![(a, b)],
+ _ if exprs.len() <= 2 => return vec![],
+ _ => {},
+ }
+
+ let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+ let mut map: UnhashMap<u64, Vec<&_>> =
+ UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+
+ for expr in exprs {
+ match map.entry(hash(expr)) {
+ Entry::Occupied(mut o) => {
+ for o in o.get() {
+ if eq(o, expr) {
+ match_expr_list.push((o, expr));
+ }
+ }
+ o.get_mut().push(expr);
+ },
+ Entry::Vacant(v) => {
+ v.insert(vec![expr]);
+ },
+ }
+ }
+
+ match_expr_list
+}
+
+/// Peels off all references on the pattern. Returns the underlying pattern and the number of
+/// references removed.
+pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+ fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
+ if let PatKind::Ref(pat, _) = pat.kind {
+ peel(pat, count + 1)
+ } else {
+ (pat, count)
+ }
+ }
+ peel(pat, 0)
+}
+
+/// Peels of expressions while the given closure returns `Some`.
+pub fn peel_hir_expr_while<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+ mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
+) -> &'tcx Expr<'tcx> {
+ while let Some(e) = f(expr) {
+ expr = e;
+ }
+ expr
+}
+
+/// Peels off up to the given number of references on the expression. Returns the underlying
+/// expression and the number of references removed.
+pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ let mut remaining = count;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
+ remaining -= 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count - remaining)
+}
+
+/// Peels off all references on the expression. Returns the underlying expression and the number of
+/// references removed.
+pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
+ let mut count = 0;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
+ count += 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count)
+}
+
+/// Peels off all references on the type. Returns the underlying type and the number of references
+/// removed.
+pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
+ let mut count = 0;
+ loop {
+ match &ty.kind {
+ TyKind::Rptr(_, ref_ty) => {
+ ty = ref_ty.ty;
+ count += 1;
+ },
+ _ => break (ty, count),
+ }
+ }
+}
+
+/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
+/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
+pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+ loop {
+ match expr.kind {
+ ExprKind::AddrOf(_, _, e) => expr = e,
+ ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ if let Res::Def(_, def_id) = path.res {
+ return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
+ }
+ }
+ false
+}
+
+static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalDefId, Vec<Symbol>>>> = OnceLock::new();
+
+fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool {
+ let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
+ let mut map: MutexGuard<'_, FxHashMap<LocalDefId, Vec<Symbol>>> = cache.lock().unwrap();
+ let value = map.entry(module);
+ match value {
+ Entry::Occupied(entry) => f(entry.get()),
+ Entry::Vacant(entry) => {
+ let mut names = Vec::new();
+ for id in tcx.hir().module_items(module) {
+ if matches!(tcx.def_kind(id.owner_id), DefKind::Const)
+ && let item = tcx.hir().item(id)
+ && let ItemKind::Const(ty, _body) = item.kind {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ // We could also check for the type name `test::TestDescAndFn`
+ if let Res::Def(DefKind::Struct, _) = path.res {
+ let has_test_marker = tcx
+ .hir()
+ .attrs(item.hir_id())
+ .iter()
+ .any(|a| a.has_name(sym::rustc_test_marker));
+ if has_test_marker {
+ names.push(item.ident.name);
+ }
+ }
+ }
+ }
+ }
+ names.sort_unstable();
+ f(entry.insert(names))
+ },
+ }
+}
+
+/// Checks if the function containing the given `HirId` is a `#[test]` function
+///
+/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
+pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
+ with_test_item_names(tcx, tcx.parent_module(id), |names| {
+ tcx.hir()
+ .parent_iter(id)
+ // Since you can nest functions we need to collect all until we leave
+ // function scope
+ .any(|(_id, node)| {
+ if let Node::Item(item) = node {
+ if let ItemKind::Fn(_, _, _) = item.kind {
+ // Note that we have sorted the item names in the visitor,
+ // so the binary_search gets the same as `contains`, but faster.
+ return names.binary_search(&item.ident.name).is_ok();
+ }
+ }
+ false
+ })
+ })
+}
+
+/// Checks if the item containing the given `HirId` has `#[cfg(test)]` attribute applied
+///
+/// Note: Add `// compile-flags: --test` to UI tests with a `#[cfg(test)]` function
+pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
+ fn is_cfg_test(attr: &Attribute) -> bool {
+ if attr.has_name(sym::cfg)
+ && let Some(items) = attr.meta_item_list()
+ && let [item] = &*items
+ && item.has_name(sym::test)
+ {
+ true
+ } else {
+ false
+ }
+ }
+ tcx.hir()
+ .parent_iter(id)
+ .flat_map(|(parent_id, _)| tcx.hir().attrs(parent_id))
+ .any(is_cfg_test)
+}
+
+/// Checks whether item either has `test` attribute applied, or
+/// is a module with `test` in its name.
+///
+/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
+pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
+ is_in_test_function(tcx, item.hir_id())
+ || matches!(item.kind, ItemKind::Mod(..))
+ && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
+}
+
+/// Walks the HIR tree from the given expression, up to the node where the value produced by the
+/// expression is consumed. Calls the function for every node encountered this way until it returns
+/// `Some`.
+///
+/// This allows walking through `if`, `match`, `break`, block expressions to find where the value
+/// produced by the expression is consumed.
+pub fn walk_to_expr_usage<'tcx, T>(
+ cx: &LateContext<'tcx>,
+ e: &Expr<'tcx>,
+ mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>,
+) -> Option<T> {
+ let map = cx.tcx.hir();
+ let mut iter = map.parent_iter(e.hir_id);
+ let mut child_id = e.hir_id;
+
+ while let Some((parent_id, parent)) = iter.next() {
+ if let Some(x) = f(parent, child_id) {
+ return Some(x);
+ }
+ let parent = match parent {
+ Node::Expr(e) => e,
+ Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
+ child_id = parent_id;
+ continue;
+ },
+ Node::Arm(a) if a.body.hir_id == child_id => {
+ child_id = parent_id;
+ continue;
+ },
+ _ => return None,
+ };
+ match parent.kind {
+ ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
+ ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
+ child_id = id;
+ iter = map.parent_iter(id);
+ },
+ ExprKind::Block(..) => child_id = parent_id,
+ _ => return None,
+ }
+ }
+ None
+}
+
+/// Checks whether a given span has any comment token
+/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
+pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
+ let Ok(snippet) = sm.span_to_snippet(span) else { return false };
+ return tokenize(&snippet).any(|token| {
+ matches!(
+ token.kind,
+ TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
+ )
+ });
+}
+
+/// Return all the comments a given span contains
+/// Comments are returned wrapped with their relevant delimiters
+pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
+ let snippet = sm.span_to_snippet(span).unwrap_or_default();
+ let mut comments_buf: Vec<String> = Vec::new();
+ let mut index: usize = 0;
+
+ for token in tokenize(&snippet) {
+ let token_range = index..(index + token.len as usize);
+ index += token.len as usize;
+ match token.kind {
+ TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => {
+ if let Some(comment) = snippet.get(token_range) {
+ comments_buf.push(comment.to_string());
+ }
+ },
+ _ => (),
+ }
+ }
+
+ comments_buf.join("\n")
+}
+
+macro_rules! op_utils {
+ ($($name:ident $assign:ident)*) => {
+ /// Binary operation traits like `LangItem::Add`
+ pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
+
+ /// Operator-Assign traits like `LangItem::AddAssign`
+ pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
+
+ /// Converts `BinOpKind::Add` to `(LangItem::Add, LangItem::AddAssign)`, for example
+ pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
+ match kind {
+ $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
+ _ => None,
+ }
+ }
+ };
+}
+
+op_utils! {
+ Add AddAssign
+ Sub SubAssign
+ Mul MulAssign
+ Div DivAssign
+ Rem RemAssign
+ BitXor BitXorAssign
+ BitAnd BitAndAssign
+ BitOr BitOrAssign
+ Shl ShlAssign
+ Shr ShrAssign
+}
--- /dev/null
- 1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
+use rustc_semver::RustcVersion;
+
+macro_rules! msrv_aliases {
+ ($($major:literal,$minor:literal,$patch:literal {
+ $($name:ident),* $(,)?
+ })*) => {
+ $($(
+ pub const $name: RustcVersion = RustcVersion::new($major, $minor, $patch);
+ )*)*
+ };
+}
+
+// names may refer to stabilized feature flags or library items
+msrv_aliases! {
++ 1,65,0 { LET_ELSE }
+ 1,62,0 { BOOL_THEN_SOME }
+ 1,58,0 { FORMAT_ARGS_CAPTURE }
+ 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
+ 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
- 1,47,0 { TAU }
++ 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
+ 1,50,0 { BOOL_THEN, CLAMP }
++ 1,47,0 { TAU, IS_ASCII_DIGIT_CONST }
+ 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, 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, REM_EUCLID }
+ 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
+ 1,36,0 { ITERATOR_COPIED }
+ 1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
+ 1,34,0 { TRY_FROM }
+ 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
+ 1,28,0 { FROM_BOOL }
+ 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
+ 1,24,0 { IS_ASCII_DIGIT }
+ 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
+ 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
+ 1,16,0 { STR_REPEAT }
++ 1,55,0 { SEEK_REWIND }
+}
--- /dev/null
+//! This module contains paths to types and functions Clippy needs to know
+//! about.
+//!
+//! Whenever possible, please consider diagnostic items over hardcoded paths.
+//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
+
+#[cfg(feature = "internal")]
+pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
+#[cfg(feature = "internal")]
+pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
+ ["rustc_lint_defs", "Applicability", "Unspecified"],
+ ["rustc_lint_defs", "Applicability", "HasPlaceholders"],
+ ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
+ ["rustc_lint_defs", "Applicability", "MachineApplicable"],
+];
+#[cfg(feature = "internal")]
+pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
+pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
+pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"];
+pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
+pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"];
+pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
+pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
+pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
+pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
+pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
+pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
+#[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"];
+pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
+pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
+pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
+pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"];
+#[cfg(feature = "internal")]
+pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
+#[cfg(feature = "internal")]
+pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
+pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
+pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
+pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
+pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
+#[cfg(feature = "internal")]
+pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
+#[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 MEM_SWAP: [&str; 3] = ["core", "mem", "swap"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
+pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
+pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
+pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
+pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
+pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
+pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekable"];
+pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
+#[cfg_attr(not(unix), allow(clippy::invalid_paths))]
+pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
+pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
+pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
+pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
+pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
+pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
+pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
+pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
+pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
+pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
+pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
+pub const PTR_UNALIGNED_VOLATILE_LOAD: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_load"];
+pub const PTR_UNALIGNED_VOLATILE_STORE: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_store"];
+pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
+pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
+pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
+pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
+pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+#[cfg(feature = "internal")]
+pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
+pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
+pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
+pub const SLICE_GET: [&str; 4] = ["core", "slice", "<impl [T]>", "get"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_INTO: [&str; 4] = ["core", "slice", "<impl [T]>", "iter"];
+pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
+pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
++pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
++pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"];
++pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"];
+pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
+pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
+pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"];
+pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"];
+pub const STR_CHARS: [&str; 4] = ["core", "str", "<impl str>", "chars"];
+pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
+pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
+pub const STR_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"];
+pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
+pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
+#[cfg(feature = "internal")]
+pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
+#[cfg(feature = "internal")]
+pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
+#[cfg(feature = "internal")]
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
+pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
+pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
+pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
+pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
+pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
+pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
+pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];
+pub const INSTANT_NOW: [&str; 4] = ["std", "time", "Instant", "now"];
+pub const INSTANT: [&str; 3] = ["std", "time", "Instant"];
--- /dev/null
- pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<RustcVersion>) -> McfResult {
+// This code used to be a part of `rustc` but moved to Clippy as a result of
+// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some
+// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
+// differ from the time of `rustc` even if the name stays the same.
+
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_middle::mir::{
+ Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
+ Terminator, TerminatorKind,
+};
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use std::borrow::Cow;
+
+type McfResult = Result<(), (Span, Cow<'static, str>)>;
+
- fn check_terminator<'a, 'tcx>(
++pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: Option<RustcVersion>) -> McfResult {
+ let def_id = body.source.def_id();
+ let mut current = def_id;
+ loop {
+ let predicates = tcx.predicates_of(current);
+ for (predicate, _) in predicates.predicates {
+ match predicate.kind().skip_binder() {
+ ty::PredicateKind::RegionOutlives(_)
+ | ty::PredicateKind::TypeOutlives(_)
+ | ty::PredicateKind::WellFormed(_)
+ | 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:#?}"),
+ }
+ }
+ match predicates.parent {
+ Some(parent) => current = parent,
+ None => break,
+ }
+ }
+
+ for local in &body.local_decls {
+ check_ty(tcx, local.ty, local.source_info.span)?;
+ }
+ // impl trait is gone in MIR, so check the return type manually
+ check_ty(
+ tcx,
+ tcx.fn_sig(def_id).output().skip_binder(),
+ body.local_decls.iter().next().unwrap().source_info.span,
+ )?;
+
+ for bb in body.basic_blocks.iter() {
+ check_terminator(tcx, body, bb.terminator(), msrv)?;
+ for stmt in &bb.statements {
+ check_statement(tcx, body, def_id, stmt)?;
+ }
+ }
+ Ok(())
+}
+
+fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
+ for arg in ty.walk() {
+ let ty = match arg.unpack() {
+ GenericArgKind::Type(ty) => ty,
+
+ // No constraints on lifetimes or constants, except potentially
+ // constants' types, but `walk` will get to them as well.
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
+ };
+
+ match ty.kind() {
+ ty::Ref(_, _, hir::Mutability::Mut) => {
+ return Err((span, "mutable references in const fn are unstable".into()));
+ },
+ ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
+ ty::FnPtr(..) => {
+ return Err((span, "function pointers in const fn are unstable".into()));
+ },
+ ty::Dynamic(preds, _, _) => {
+ for pred in preds.iter() {
+ match pred.skip_binder() {
+ ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ },
+ ty::ExistentialPredicate::Trait(trait_ref) => {
+ if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ }
+ },
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ Ok(())
+}
+
+fn check_rvalue<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ def_id: DefId,
+ rvalue: &Rvalue<'tcx>,
+ span: Span,
+) -> McfResult {
+ match rvalue {
+ Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
+ Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+ check_place(tcx, *place, span, body)
+ },
+ Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body),
+ Rvalue::Repeat(operand, _)
+ | Rvalue::Use(operand)
+ | Rvalue::Cast(
+ CastKind::PointerFromExposedAddress
+ | CastKind::IntToInt
+ | CastKind::FloatToInt
+ | CastKind::IntToFloat
+ | CastKind::FloatToFloat
+ | CastKind::FnPtrToPtr
+ | CastKind::PtrToPtr
+ | CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer),
+ operand,
+ _,
+ ) => check_operand(tcx, operand, span, body),
+ Rvalue::Cast(
+ CastKind::Pointer(
+ PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
+ ),
+ _,
+ _,
+ ) => Err((span, "function pointer casts are not allowed in const fn".into())),
+ Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
+ let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
+ deref_ty.ty
+ } else {
+ // We cannot allow this for now.
+ return Err((span, "unsizing casts are only allowed for references right now".into()));
+ };
+ let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
+ if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
+ check_operand(tcx, op, span, body)?;
+ // Casting/coercing things to slices is fine.
+ Ok(())
+ } else {
+ // We just can't allow trait objects until we have figured out trait method calls.
+ Err((span, "unsizing casts are not allowed in const fn".into()))
+ }
+ },
+ Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => {
+ Err((span, "casting pointers to ints is unstable in const fn".into()))
+ },
+ Rvalue::Cast(CastKind::DynStar, _, _) => {
+ // FIXME(dyn-star)
+ unimplemented!()
+ },
+ // binops are fine on integers
+ Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
+ check_operand(tcx, lhs, span, body)?;
+ check_operand(tcx, rhs, span, body)?;
+ let ty = lhs.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() || ty.is_char() {
+ Ok(())
+ } else {
+ Err((
+ span,
+ "only int, `bool` and `char` operations are stable in const fn".into(),
+ ))
+ }
+ },
+ Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()),
+ Rvalue::UnaryOp(_, operand) => {
+ let ty = operand.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() {
+ check_operand(tcx, operand, span, body)
+ } else {
+ Err((span, "only int and `bool` operations are stable in const fn".into()))
+ }
+ },
+ Rvalue::Aggregate(_, operands) => {
+ for operand in operands {
+ check_operand(tcx, operand, span, body)?;
+ }
+ Ok(())
+ },
+ }
+}
+
+fn check_statement<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ def_id: DefId,
+ statement: &Statement<'tcx>,
+) -> McfResult {
+ let span = statement.source_info.span;
+ match &statement.kind {
+ StatementKind::Assign(box (place, rval)) => {
+ check_place(tcx, *place, span, body)?;
+ check_rvalue(tcx, body, def_id, rval, span)
+ },
+
+ StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body),
+ // just an assignment
+ StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
+ check_place(tcx, **place, span, body)
+ },
+
+ StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body),
+
+ StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(
+ rustc_middle::mir::CopyNonOverlapping { dst, src, count },
+ )) => {
+ check_operand(tcx, dst, span, body)?;
+ check_operand(tcx, src, span, body)?;
+ check_operand(tcx, count, span, body)
+ },
+ // These are all NOPs
+ StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Retag { .. }
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Coverage(..)
+ | StatementKind::Nop => Ok(()),
+ }
+}
+
+fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ match operand {
+ Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
+ Operand::Constant(c) => match c.check_static_ptr(tcx) {
+ Some(_) => Err((span, "cannot access `static` items in const fn".into())),
+ None => Ok(()),
+ },
+ }
+}
+
+fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ let mut cursor = place.projection.as_ref();
+ while let [ref proj_base @ .., elem] = *cursor {
+ cursor = proj_base;
+ match elem {
+ ProjectionElem::Field(..) => {
+ let base_ty = Place::ty_from(place.local, proj_base, body, tcx).ty;
+ if let Some(def) = base_ty.ty_adt_def() {
+ // No union field accesses in `const fn`
+ if def.is_union() {
+ return Err((span, "accessing union fields is unstable".into()));
+ }
+ }
+ },
+ ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::OpaqueCast(..)
+ | ProjectionElem::Downcast(..)
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Deref
+ | ProjectionElem::Index(_) => {},
+ }
+ }
+
+ Ok(())
+}
+
- body: &'a Body<'tcx>,
++fn check_terminator<'tcx>(
+ tcx: TyCtxt<'tcx>,
++ body: &Body<'tcx>,
+ terminator: &Terminator<'tcx>,
+ msrv: Option<RustcVersion>,
+) -> McfResult {
+ let span = terminator.source_info.span;
+ match &terminator.kind {
+ TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::Return
+ | TerminatorKind::Resume
+ | TerminatorKind::Unreachable => Ok(()),
+
+ TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
+ TerminatorKind::DropAndReplace { place, value, .. } => {
+ check_place(tcx, *place, span, body)?;
+ check_operand(tcx, value, span, body)
+ },
+
+ TerminatorKind::SwitchInt {
+ discr,
+ switch_ty: _,
+ targets: _,
+ } => check_operand(tcx, discr, span, body),
+
+ TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
+ TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
+ Err((span, "const fn generators are unstable".into()))
+ },
+
+ TerminatorKind::Call {
+ func,
+ args,
+ from_hir_call: _,
+ destination: _,
+ target: _,
+ cleanup: _,
+ fn_span: _,
+ } => {
+ let fn_ty = func.ty(body, tcx);
+ if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
+ if !is_const_fn(tcx, fn_def_id, msrv) {
+ return Err((
+ span,
+ format!(
+ "can only call other `const fn` within a `const fn`, \
+ but `{func:?}` is not stable as `const fn`",
+ )
+ .into(),
+ ));
+ }
+
+ // HACK: This is to "unstabilize" the `transmute` intrinsic
+ // within const fns. `transmute` is allowed in all other const contexts.
+ // This won't really scale to more intrinsics or functions. Let's allow const
+ // transmutes in const fn before we add more hacks to this.
+ if tcx.is_intrinsic(fn_def_id) && tcx.item_name(fn_def_id) == sym::transmute {
+ return Err((
+ span,
+ "can only call `transmute` from const items, not `const fn`".into(),
+ ));
+ }
+
+ check_operand(tcx, func, span, body)?;
+
+ for arg in args {
+ check_operand(tcx, arg, span, body)?;
+ }
+ Ok(())
+ } else {
+ Err((span, "can only call other const fns within const fn".into()))
+ }
+ },
+
+ TerminatorKind::Assert {
+ cond,
+ expected: _,
+ msg: _,
+ target: _,
+ cleanup: _,
+ } => check_operand(tcx, cond, span, body),
+
+ TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
+ }
+}
+
+fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bool {
+ tcx.is_const_fn(def_id)
+ && tcx.lookup_const_stability(def_id).map_or(true, |const_stab| {
+ if let rustc_attr::StabilityLevel::Stable { since, .. } = const_stab.level {
+ // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
+ // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
+ // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
+
+ // HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev. `rustc-semver`
+ // doesn't accept the `-dev` version number so we have to strip it
+ // off.
+ let short_version = since
+ .as_str()
+ .split('-')
+ .next()
+ .expect("rustc_attr::StabilityLevel::Stable::since` is empty");
+
+ let since = rustc_span::Symbol::intern(short_version);
+
+ crate::meets_msrv(
+ msrv,
+ RustcVersion::parse(since.as_str()).unwrap_or_else(|err| {
+ panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{since}`, {err:?}")
+ }),
+ )
+ } else {
+ // Unstable const fn with the feature enabled.
+ msrv.is_none()
+ }
+ })
+}
--- /dev/null
- use crate::line_span;
+//! Utils for extracting, inspecting or transforming source code
+
+#![allow(clippy::module_name_repetitions)]
+
- use rustc_span::source_map::SourceMap;
- use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_span::hygiene;
++use rustc_span::source_map::{original_sp, SourceMap};
++use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
+use std::borrow::Cow;
+
+/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
+/// Also takes an `Option<String>` which can be put inside the braces.
+pub fn expr_block<'a, T: LintContext>(
+ cx: &T,
+ expr: &Expr<'_>,
+ option: Option<String>,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let code = snippet_block(cx, expr.span, default, indent_relative_to);
+ let string = option.unwrap_or_default();
+ if expr.span.from_expansion() {
+ Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
+ } else if let ExprKind::Block(_, _) = expr.kind {
+ Cow::Owned(format!("{code}{string}"))
+ } else if string.is_empty() {
+ Cow::Owned(format!("{{ {code} }}"))
+ } else {
+ Cow::Owned(format!("{{\n{code};\n{string}\n}}"))
+ }
+}
+
+/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
+/// line.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^
+/// ```
+pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
+}
+
+fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
+ let line_span = line_span(cx, span);
+ snippet_opt(cx, line_span).and_then(|snip| {
+ snip.find(|c: char| !c.is_whitespace())
+ .map(|pos| line_span.lo() + BytePos::from_usize(pos))
+ })
+}
+
++/// Extends the span to the beginning of the spans line, incl. whitespaces.
++///
++/// ```rust
++/// let x = ();
++/// // ^^
++/// // will be converted to
++/// let x = ();
++/// // ^^^^^^^^^^^^^^
++/// ```
++fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
++ let span = original_sp(span, DUMMY_SP);
++ let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
++ let line_no = source_map_and_line.line;
++ let line_start = source_map_and_line.sf.lines(|lines| lines[line_no]);
++ span.with_lo(line_start)
++}
++
+/// Returns the indentation of the line of a span
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^ -- will return 0
+/// let x = ();
+/// // ^^ -- will return 4
+/// ```
+pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
+ snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
+}
+
+/// Gets a snippet of the indentation of the line of a span
+pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ snippet_opt(cx, line_span(cx, span)).map(|mut s| {
+ let len = s.len() - s.trim_start().len();
+ s.truncate(len);
+ s
+ })
+}
+
+// If the snippet is empty, it's an attribute that was inserted during macro
+// expansion and we want to ignore those, because they could come from external
+// sources that the user has no control over.
+// For some reason these attributes don't have any expansion info on them, so
+// we have to check it this way until there is a better way.
+pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
+ if let Some(snippet) = snippet_opt(cx, span) {
+ if snippet.is_empty() {
+ return false;
+ }
+ }
+ true
+}
+
+/// Returns the position just before rarrow
+///
+/// ```rust,ignore
+/// fn into(self) -> () {}
+/// ^
+/// // in case of unformatted code
+/// fn into2(self)-> () {}
+/// ^
+/// fn into3(self) -> () {}
+/// ^
+/// ```
+pub fn position_before_rarrow(s: &str) -> Option<usize> {
+ s.rfind("->").map(|rpos| {
+ let mut rpos = rpos;
+ let chars: Vec<char> = s.chars().collect();
+ while rpos > 1 {
+ if let Some(c) = chars.get(rpos - 1) {
+ if c.is_whitespace() {
+ rpos -= 1;
+ continue;
+ }
+ }
+ break;
+ }
+ rpos
+ })
+}
+
+/// Reindent a multiline string with possibility of ignoring the first line.
+#[expect(clippy::needless_pass_by_value)]
+pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
+ let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
+ let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
+ reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
+}
+
+fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
+ let x = s
+ .lines()
+ .skip(usize::from(ignore_first))
+ .filter_map(|l| {
+ if l.is_empty() {
+ None
+ } else {
+ // ignore empty lines
+ Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
+ }
+ })
+ .min()
+ .unwrap_or(0);
+ let indent = indent.unwrap_or(0);
+ s.lines()
+ .enumerate()
+ .map(|(i, l)| {
+ if (ignore_first && i == 0) || l.is_empty() {
+ l.to_owned()
+ } else if x > indent {
+ l.split_at(x - indent).1.to_owned()
+ } else {
+ " ".repeat(indent - x) + l
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n")
+}
+
+/// Converts a span to a code snippet if available, otherwise returns the default.
+///
+/// This is useful if you want to provide suggestions for your lint or more generally, if you want
+/// to convert a given `Span` to a `str`. To create suggestions consider using
+/// [`snippet_with_applicability`] to ensure that the applicability stays correct.
+///
+/// # Example
+/// ```rust,ignore
+/// // Given two spans one for `value` and one for the `init` expression.
+/// let value = Vec::new();
+/// // ^^^^^ ^^^^^^^^^^
+/// // span1 span2
+///
+/// // The snipped call would return the corresponding code snippet
+/// snippet(cx, span1, "..") // -> "value"
+/// snippet(cx, span2, "..") // -> "Vec::new()"
+/// ```
+pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
+}
+
+/// Same as [`snippet`], but it adapts the applicability level by following rules:
+///
+/// - Applicability level `Unspecified` will never be changed.
+/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
+/// `HasPlaceholders`
+pub fn snippet_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ if *applicability != Applicability::Unspecified && span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ snippet_opt(cx, span).map_or_else(
+ || {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Cow::Borrowed(default)
+ },
+ From::from,
+ )
+}
+
+/// Same as `snippet`, but should only be used when it's clear that the input span is
+/// not a macro argument.
+pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet(cx, span.source_callsite(), default)
+}
+
+/// Converts a span to a code snippet. Returns `None` if not available.
+pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ cx.sess().source_map().span_to_snippet(span).ok()
+}
+
+/// Converts a span (from a block) to a code snippet if available, otherwise use default.
+///
+/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
+/// things which need to be printed as such.
+///
+/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
+/// resulting snippet of the given span.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", None)
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// }
+/// ```
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", Some(if_expr.span))
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// } // aligned with `if`
+/// ```
+/// Note that the first line of the snippet always has 0 indentation.
+pub fn snippet_block<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let snip = snippet(cx, span, default);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_block`, but adapts the applicability level by the rules of
+/// `snippet_with_applicability`.
+pub fn snippet_block_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ let snip = snippet_with_applicability(cx, span, default, applicability);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
+/// will result in the macro call, rather then the expansion, if the span is from a child context.
+/// If the span is not from a child context, it will be used directly instead.
+///
+/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
+/// would result in `box []`. If given the context of the address of expression, this function will
+/// correctly get a snippet of `vec![]`.
+///
+/// This will also return whether or not the snippet is a macro call.
+pub fn snippet_with_context<'a>(
+ cx: &LateContext<'_>,
+ span: Span,
+ outer: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> (Cow<'a, str>, bool) {
+ let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
+ || {
+ // The span is from a macro argument, and the outer context is the macro using the argument
+ if *applicability != Applicability::Unspecified {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ // TODO: get the argument span.
+ (span, false)
+ },
+ |outer_span| (outer_span, span.ctxt() != outer),
+ );
+
+ (
+ snippet_with_applicability(cx, span, default, applicability),
+ is_macro_call,
+ )
+}
+
+/// Walks the span up to the target context, thereby returning the macro call site if the span is
+/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
+/// case of the span being in a macro expansion, but the target context is from expanding a macro
+/// argument.
+///
+/// Given the following
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { f($e) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
+/// containing `0` as the context is the same as the outer context.
+///
+/// This will traverse through multiple macro calls. Given the following:
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
+/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`.
+pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
+ let outer_span = hygiene::walk_chain(span, outer);
+ (outer_span.ctxt() == outer).then_some(outer_span)
+}
+
+/// Removes block comments from the given `Vec` of lines.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// without_block_comments(vec!["/*", "foo", "*/"]);
+/// // => vec![]
+///
+/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
+/// // => vec!["bar"]
+/// ```
+pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
+ let mut without = vec![];
+
+ let mut nest_level = 0;
+
+ for line in lines {
+ if line.contains("/*") {
+ nest_level += 1;
+ continue;
+ } else if line.contains("*/") {
+ nest_level -= 1;
+ continue;
+ }
+
+ if nest_level == 0 {
+ without.push(line);
+ }
+ }
+
+ without
+}
+
+/// Trims the whitespace from the start and the end of the span.
+pub fn trim_span(sm: &SourceMap, span: Span) -> Span {
+ let data = span.data();
+ let sf: &_ = &sm.lookup_source_file(data.lo);
+ let Some(src) = sf.src.as_deref() else {
+ return span;
+ };
+ let Some(snip) = &src.get((data.lo - sf.start_pos).to_usize()..(data.hi - sf.start_pos).to_usize()) else {
+ return span;
+ };
+ let trim_start = snip.len() - snip.trim_start().len();
+ let trim_end = snip.len() - snip.trim_end().len();
+ SpanData {
+ lo: data.lo + BytePos::from_usize(trim_start),
+ hi: data.hi - BytePos::from_usize(trim_end),
+ ctxt: data.ctxt,
+ parent: data.parent,
+ }
+ .span()
+}
+
+/// Expand a span to include a preceding comma
+/// ```rust,ignore
+/// writeln!(o, "") -> writeln!(o, "")
+/// ^^ ^^^^
+/// ```
+pub fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span {
+ let extended = cx.sess().source_map().span_extend_to_prev_char(span, ',', true);
+ extended.with_lo(extended.lo() - BytePos(1))
+}
+
+#[cfg(test)]
+mod test {
+ use super::{reindent_multiline, without_block_comments};
+
+ #[test]
+ fn test_reindent_multiline_single_line() {
+ assert_eq!("", reindent_multiline("".into(), false, None));
+ assert_eq!("...", reindent_multiline("...".into(), false, None));
+ assert_eq!("...", reindent_multiline(" ...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", reindent_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_lines_deeper() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline("\
+ if x {
+ y
+ } else {
+ z
+ }".into(), true, Some(8)));
+ }
+
+ #[test]
+ fn test_without_block_comments_lines_without_block_comments() {
+ let result = without_block_comments(vec!["/*", "", "*/"]);
+ println!("result: {result:?}");
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
+ assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
+
+ let result = without_block_comments(vec!["/* rust", "", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* one-line comment */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["foo", "bar", "baz"]);
+ assert_eq!(result, vec!["foo", "bar", "baz"]);
+ }
+}
--- /dev/null
- pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option<DerefClosure> {
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::source::{
+ snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
+};
+use crate::ty::expr_sig;
+use crate::{get_parent_expr_for_hir, higher};
+use rustc_ast::util::parser::AssocOp;
+use rustc_ast::{ast, token};
+use rustc_ast_pretty::pprust::token_kind_to_string;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
+use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{EarlyContext, LateContext, LintContext};
+use rustc_middle::hir::place::ProjectionKind;
+use rustc_middle::mir::{FakeReadCause, Mutability};
+use rustc_middle::ty;
+use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
+use std::borrow::Cow;
+use std::fmt::{Display, Write as _};
+use std::ops::{Add, Neg, Not, Sub};
+
+/// A helper type to build suggestion correctly handling parentheses.
+#[derive(Clone, Debug, PartialEq)]
+pub enum Sugg<'a> {
+ /// An expression that never needs parentheses such as `1337` or `[0; 42]`.
+ NonParen(Cow<'a, str>),
+ /// An expression that does not fit in other variants.
+ MaybeParen(Cow<'a, str>),
+ /// A binary operator expression, including `as`-casts and explicit type
+ /// coercion.
+ BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
+}
+
+/// Literal constant `0`, for convenience.
+pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
+/// Literal constant `1`, for convenience.
+pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
+/// a constant represents an empty string, for convenience.
+pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
+
+impl Display for Sugg<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ match *self {
+ Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
+ Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
+ }
+ }
+}
+
+#[expect(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
+impl<'a> Sugg<'a> {
+ /// Prepare a suggestion from an expression.
+ pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
+ let get_snippet = |span| snippet(cx, span, "");
+ snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet))
+ }
+
+ /// Convenience function around `hir_opt` for suggestions with a default
+ /// text.
+ pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
+ }
+
+ /// Same as `hir`, but it adapts the applicability level by following rules:
+ ///
+ /// - Applicability level `Unspecified` will never be changed.
+ /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+ /// - If the default value is used and the applicability level is `MachineApplicable`, change it
+ /// to
+ /// `HasPlaceholders`
+ pub fn hir_with_applicability(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ Self::hir_opt(cx, expr).unwrap_or_else(|| {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Sugg::NonParen(Cow::Borrowed(default))
+ })
+ }
+
+ /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
+ pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ let get_snippet = |span| snippet_with_macro_callsite(cx, span, default);
+ Self::hir_from_snippet(expr, get_snippet)
+ }
+
+ /// Same as `hir`, but first walks the span up to the given context. This will result in the
+ /// macro call, rather then the expansion, if the span is from a child context. If the span is
+ /// not from a child context, it will be used directly instead.
+ ///
+ /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR
+ /// node would result in `box []`. If given the context of the address of expression, this
+ /// function will correctly get a snippet of `vec![]`.
+ pub fn hir_with_context(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ ctxt: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if expr.span.ctxt() == ctxt {
+ Self::hir_from_snippet(expr, |span| snippet(cx, span, default))
+ } else {
+ let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
+ Sugg::NonParen(snip)
+ }
+ }
+
+ /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
+ /// function variants of `Sugg`, since these use different snippet functions.
+ fn hir_from_snippet(expr: &hir::Expr<'_>, get_snippet: impl Fn(Span) -> Cow<'a, str>) -> Self {
+ if let Some(range) = higher::Range::hir(expr) {
+ let op = match range.limits {
+ ast::RangeLimits::HalfOpen => AssocOp::DotDot,
+ ast::RangeLimits::Closed => AssocOp::DotDotEq,
+ };
+ let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
+ let end = range.end.map_or("".into(), |expr| get_snippet(expr.span));
+
+ return Sugg::BinOp(op, start, end);
+ }
+
+ match expr.kind {
+ hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Let(..)
+ | hir::ExprKind::Closure { .. }
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Match(..) => Sugg::MaybeParen(get_snippet(expr.span)),
+ hir::ExprKind::Continue(..)
+ | hir::ExprKind::Yield(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Call(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Index(..)
+ | hir::ExprKind::InlineAsm(..)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Lit(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Path(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::Err => Sugg::NonParen(get_snippet(expr.span)),
+ hir::ExprKind::DropTemps(inner) => Self::hir_from_snippet(inner, get_snippet),
+ hir::ExprKind::Assign(lhs, rhs, _) => {
+ Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span))
+ },
+ hir::ExprKind::AssignOp(op, lhs, rhs) => {
+ Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span))
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp(
+ AssocOp::from_ast_binop(op.node.into()),
+ get_snippet(lhs.span),
+ get_snippet(rhs.span),
+ ),
+ hir::ExprKind::Cast(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)),
+ hir::ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::Colon, get_snippet(lhs.span), get_snippet(ty.span)),
+ }
+ }
+
+ /// Prepare a suggestion from an expression.
+ pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
+ use rustc_ast::ast::RangeLimits;
+
+ let snippet_without_expansion = |cx, span: Span, default| {
+ if span.from_expansion() {
+ snippet_with_macro_callsite(cx, span, default)
+ } else {
+ snippet(cx, span, default)
+ }
+ };
+
+ match expr.kind {
+ ast::ExprKind::AddrOf(..)
+ | ast::ExprKind::Box(..)
+ | ast::ExprKind::Closure { .. }
+ | ast::ExprKind::If(..)
+ | ast::ExprKind::Let(..)
+ | ast::ExprKind::Unary(..)
+ | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet_without_expansion(cx, expr.span, default)),
+ ast::ExprKind::Async(..)
+ | ast::ExprKind::Block(..)
+ | ast::ExprKind::Break(..)
+ | ast::ExprKind::Call(..)
+ | ast::ExprKind::Continue(..)
+ | ast::ExprKind::Yield(..)
+ | ast::ExprKind::Field(..)
+ | ast::ExprKind::ForLoop(..)
+ | ast::ExprKind::Index(..)
+ | ast::ExprKind::InlineAsm(..)
+ | ast::ExprKind::ConstBlock(..)
+ | ast::ExprKind::Lit(..)
+ | ast::ExprKind::IncludedBytes(..)
+ | ast::ExprKind::Loop(..)
+ | ast::ExprKind::MacCall(..)
+ | ast::ExprKind::MethodCall(..)
+ | ast::ExprKind::Paren(..)
+ | ast::ExprKind::Underscore
+ | ast::ExprKind::Path(..)
+ | ast::ExprKind::Repeat(..)
+ | ast::ExprKind::Ret(..)
+ | ast::ExprKind::Yeet(..)
+ | ast::ExprKind::Struct(..)
+ | ast::ExprKind::Try(..)
+ | ast::ExprKind::TryBlock(..)
+ | ast::ExprKind::Tup(..)
+ | ast::ExprKind::Array(..)
+ | ast::ExprKind::While(..)
+ | ast::ExprKind::Await(..)
+ | ast::ExprKind::Err => Sugg::NonParen(snippet_without_expansion(cx, expr.span, default)),
+ ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
+ AssocOp::DotDot,
+ lhs.as_ref()
+ .map_or("".into(), |lhs| snippet_without_expansion(cx, lhs.span, default)),
+ rhs.as_ref()
+ .map_or("".into(), |rhs| snippet_without_expansion(cx, rhs.span, default)),
+ ),
+ ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
+ AssocOp::DotDotEq,
+ lhs.as_ref()
+ .map_or("".into(), |lhs| snippet_without_expansion(cx, lhs.span, default)),
+ rhs.as_ref()
+ .map_or("".into(), |rhs| snippet_without_expansion(cx, rhs.span, default)),
+ ),
+ ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
+ AssocOp::Assign,
+ snippet_without_expansion(cx, lhs.span, default),
+ snippet_without_expansion(cx, rhs.span, default),
+ ),
+ ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
+ astbinop2assignop(op),
+ snippet_without_expansion(cx, lhs.span, default),
+ snippet_without_expansion(cx, rhs.span, default),
+ ),
+ ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
+ AssocOp::from_ast_binop(op.node),
+ snippet_without_expansion(cx, lhs.span, default),
+ snippet_without_expansion(cx, rhs.span, default),
+ ),
+ ast::ExprKind::Cast(ref lhs, ref ty) => Sugg::BinOp(
+ AssocOp::As,
+ snippet_without_expansion(cx, lhs.span, default),
+ snippet_without_expansion(cx, ty.span, default),
+ ),
+ ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
+ AssocOp::Colon,
+ snippet_without_expansion(cx, lhs.span, default),
+ snippet_without_expansion(cx, ty.span, default),
+ ),
+ }
+ }
+
+ /// Convenience method to create the `<lhs> && <rhs>` suggestion.
+ pub fn and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::And, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> & <rhs>` suggestion.
+ pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::BitAnd, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> as <rhs>` suggestion.
+ pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
+ make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
+ }
+
+ /// Convenience method to create the `&<expr>` suggestion.
+ pub fn addr(self) -> Sugg<'static> {
+ make_unop("&", self)
+ }
+
+ /// Convenience method to create the `&mut <expr>` suggestion.
+ pub fn mut_addr(self) -> Sugg<'static> {
+ make_unop("&mut ", self)
+ }
+
+ /// Convenience method to create the `*<expr>` suggestion.
+ pub fn deref(self) -> Sugg<'static> {
+ make_unop("*", self)
+ }
+
+ /// Convenience method to create the `&*<expr>` suggestion. Currently this
+ /// is needed because `sugg.deref().addr()` produces an unnecessary set of
+ /// parentheses around the deref.
+ pub fn addr_deref(self) -> Sugg<'static> {
+ make_unop("&*", self)
+ }
+
+ /// Convenience method to create the `&mut *<expr>` suggestion. Currently
+ /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
+ /// set of parentheses around the deref.
+ pub fn mut_addr_deref(self) -> Sugg<'static> {
+ make_unop("&mut *", self)
+ }
+
+ /// Convenience method to transform suggestion into a return call
+ pub fn make_return(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("return {self}")))
+ }
+
+ /// Convenience method to transform suggestion into a block
+ /// where the suggestion is a trailing expression
+ pub fn blockify(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("{{ {self} }}")))
+ }
+
+ /// Convenience method to prefix the expression with the `async` keyword.
+ /// Can be used after `blockify` to create an async block.
+ pub fn asyncify(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("async {self}")))
+ }
+
+ /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
+ /// suggestion.
+ pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
+ match limit {
+ ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
+ ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
+ }
+ }
+
+ /// Adds parentheses to any expression that might need them. Suitable to the
+ /// `self` argument of a method call
+ /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
+ #[must_use]
+ pub fn maybe_par(self) -> Self {
+ match self {
+ Sugg::NonParen(..) => self,
+ // `(x)` and `(x).y()` both don't need additional parens.
+ Sugg::MaybeParen(sugg) => {
+ if has_enclosing_paren(&sugg) {
+ Sugg::MaybeParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({sugg})").into())
+ }
+ },
+ Sugg::BinOp(op, lhs, rhs) => {
+ let sugg = binop_to_string(op, &lhs, &rhs);
+ Sugg::NonParen(format!("({sugg})").into())
+ },
+ }
+ }
+}
+
+/// Generates a string from the operator and both sides.
+fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
+ match op {
+ AssocOp::Add
+ | AssocOp::Subtract
+ | AssocOp::Multiply
+ | AssocOp::Divide
+ | AssocOp::Modulus
+ | AssocOp::LAnd
+ | AssocOp::LOr
+ | AssocOp::BitXor
+ | AssocOp::BitAnd
+ | AssocOp::BitOr
+ | AssocOp::ShiftLeft
+ | AssocOp::ShiftRight
+ | AssocOp::Equal
+ | AssocOp::Less
+ | AssocOp::LessEqual
+ | AssocOp::NotEqual
+ | AssocOp::Greater
+ | AssocOp::GreaterEqual => {
+ format!(
+ "{lhs} {} {rhs}",
+ op.to_ast_binop().expect("Those are AST ops").to_string()
+ )
+ },
+ AssocOp::Assign => format!("{lhs} = {rhs}"),
+ AssocOp::AssignOp(op) => {
+ format!("{lhs} {}= {rhs}", token_kind_to_string(&token::BinOp(op)))
+ },
+ AssocOp::As => format!("{lhs} as {rhs}"),
+ AssocOp::DotDot => format!("{lhs}..{rhs}"),
+ AssocOp::DotDotEq => format!("{lhs}..={rhs}"),
+ AssocOp::Colon => format!("{lhs}: {rhs}"),
+ }
+}
+
+/// Return `true` if `sugg` is enclosed in parenthesis.
+pub fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
+ let mut chars = sugg.as_ref().chars();
+ if chars.next() == Some('(') {
+ let mut depth = 1;
+ for c in &mut chars {
+ if c == '(' {
+ depth += 1;
+ } else if c == ')' {
+ depth -= 1;
+ }
+ if depth == 0 {
+ break;
+ }
+ }
+ chars.next().is_none()
+ } else {
+ false
+ }
+}
+
+/// Copied from the rust standard library, and then edited
+macro_rules! forward_binop_impls_to_ref {
+ (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
+ impl $imp<$t> for &$t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(self, &other)
+ }
+ }
+
+ impl $imp<&$t> for $t {
+ type Output = $o;
+
+ fn $method(self, other: &$t) -> $o {
+ $imp::$method(&self, other)
+ }
+ }
+
+ impl $imp for $t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(&self, &other)
+ }
+ }
+ };
+}
+
+impl Add for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Add, self, rhs)
+ }
+}
+
+impl Sub for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Sub, self, rhs)
+ }
+}
+
+forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
+forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
+
+impl Neg for Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn neg(self) -> Sugg<'static> {
+ make_unop("-", self)
+ }
+}
+
+impl<'a> Not for Sugg<'a> {
+ type Output = Sugg<'a>;
+ fn not(self) -> Sugg<'a> {
+ use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
+
+ if let Sugg::BinOp(op, lhs, rhs) = self {
+ let to_op = match op {
+ Equal => NotEqual,
+ NotEqual => Equal,
+ Less => GreaterEqual,
+ GreaterEqual => Less,
+ Greater => LessEqual,
+ LessEqual => Greater,
+ _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
+ };
+ Sugg::BinOp(to_op, lhs, rhs)
+ } else {
+ make_unop("!", self)
+ }
+ }
+}
+
+/// Helper type to display either `foo` or `(foo)`.
+struct ParenHelper<T> {
+ /// `true` if parentheses are needed.
+ paren: bool,
+ /// The main thing to display.
+ wrapped: T,
+}
+
+impl<T> ParenHelper<T> {
+ /// Builds a `ParenHelper`.
+ fn new(paren: bool, wrapped: T) -> Self {
+ Self { paren, wrapped }
+ }
+}
+
+impl<T: Display> Display for ParenHelper<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ if self.paren {
+ write!(f, "({})", self.wrapped)
+ } else {
+ self.wrapped.fmt(f)
+ }
+ }
+}
+
+/// Builds the string for `<op><expr>` adding parenthesis when necessary.
+///
+/// For convenience, the operator is taken as a string because all unary
+/// operators have the same
+/// precedence.
+pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
+ Sugg::MaybeParen(format!("{op}{}", expr.maybe_par()).into())
+}
+
+/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
+///
+/// Precedence of shift operator relative to other arithmetic operation is
+/// often confusing so
+/// parenthesis will always be added for a mix of these.
+pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ /// Returns `true` if the operator is a shift operator `<<` or `>>`.
+ fn is_shift(op: AssocOp) -> bool {
+ matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+ }
+
+ /// Returns `true` if the operator is an arithmetic operator
+ /// (i.e., `+`, `-`, `*`, `/`, `%`).
+ fn is_arith(op: AssocOp) -> bool {
+ matches!(
+ op,
+ AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+ )
+ }
+
+ /// Returns `true` if the operator `op` needs parenthesis with the operator
+ /// `other` in the direction `dir`.
+ fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
+ other.precedence() < op.precedence()
+ || (other.precedence() == op.precedence()
+ && ((op != other && associativity(op) != dir)
+ || (op == other && associativity(op) != Associativity::Both)))
+ || is_shift(op) && is_arith(other)
+ || is_shift(other) && is_arith(op)
+ }
+
+ let lhs_paren = if let Sugg::BinOp(lop, _, _) = *lhs {
+ needs_paren(op, lop, Associativity::Left)
+ } else {
+ false
+ };
+
+ let rhs_paren = if let Sugg::BinOp(rop, _, _) = *rhs {
+ needs_paren(op, rop, Associativity::Right)
+ } else {
+ false
+ };
+
+ let lhs = ParenHelper::new(lhs_paren, lhs).to_string();
+ let rhs = ParenHelper::new(rhs_paren, rhs).to_string();
+ Sugg::BinOp(op, lhs.into(), rhs.into())
+}
+
+/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
+pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+/// Operator associativity.
+enum Associativity {
+ /// The operator is both left-associative and right-associative.
+ Both,
+ /// The operator is left-associative.
+ Left,
+ /// The operator is not associative.
+ None,
+ /// The operator is right-associative.
+ Right,
+}
+
+/// Returns the associativity/fixity of an operator. The difference with
+/// `AssocOp::fixity` is that an operator can be both left and right associative
+/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
+///
+/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
+/// they are considered
+/// associative.
+#[must_use]
+fn associativity(op: AssocOp) -> Associativity {
+ use rustc_ast::util::parser::AssocOp::{
+ Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
+ GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+ };
+
+ match op {
+ Assign | AssignOp(_) => Associativity::Right,
+ Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
+ Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
+ | Subtract => Associativity::Left,
+ DotDot | DotDotEq => Associativity::None,
+ }
+}
+
+/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
+fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
+ use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
+
+ AssocOp::AssignOp(match op.node {
+ hir::BinOpKind::Add => Plus,
+ hir::BinOpKind::BitAnd => And,
+ hir::BinOpKind::BitOr => Or,
+ hir::BinOpKind::BitXor => Caret,
+ hir::BinOpKind::Div => Slash,
+ hir::BinOpKind::Mul => Star,
+ hir::BinOpKind::Rem => Percent,
+ hir::BinOpKind::Shl => Shl,
+ hir::BinOpKind::Shr => Shr,
+ hir::BinOpKind::Sub => Minus,
+
+ hir::BinOpKind::And
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
+fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
+ use rustc_ast::ast::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ use rustc_ast::token::BinOpToken;
+
+ AssocOp::AssignOp(match op.node {
+ Add => BinOpToken::Plus,
+ BitAnd => BinOpToken::And,
+ BitOr => BinOpToken::Or,
+ BitXor => BinOpToken::Caret,
+ Div => BinOpToken::Slash,
+ Mul => BinOpToken::Star,
+ Rem => BinOpToken::Percent,
+ Shl => BinOpToken::Shl,
+ Shr => BinOpToken::Shr,
+ Sub => BinOpToken::Minus,
+ And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Returns the indentation before `span` if there are nothing but `[ \t]`
+/// before it on its line.
+fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ let lo = cx.sess().source_map().lookup_char_pos(span.lo());
+ lo.file
+ .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
+ .and_then(|line| {
+ if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
+ // We can mix char and byte positions here because we only consider `[ \t]`.
+ if lo.col == CharPos(pos) {
+ Some(line[..pos].into())
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+}
+
+/// 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.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
+ /// ```
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ );
+
+ /// Suggest to add an item before another.
+ ///
+ /// The item should not be indented (except for inner indentation).
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_prepend_item(cx, item,
+ /// "fn foo() {
+ /// bar();
+ /// }");
+ /// ```
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
+
+ /// Suggest to completely remove an item.
+ ///
+ /// This will remove an item and all following whitespace until the next non-whitespace
+ /// character. This should work correctly if item is on the same indentation level as the
+ /// following item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_remove_item(cx, item, "remove this")
+ /// ```
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
+}
+
+impl<T: LintContext> DiagnosticExt<T> for rustc_errors::Diagnostic {
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ ) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ self.span_suggestion(span, msg, format!("{attr}\n{indent}"), applicability);
+ }
+ }
+
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ let mut first = true;
+ let new_item = new_item
+ .lines()
+ .map(|l| {
+ if first {
+ first = false;
+ format!("{l}\n")
+ } else {
+ format!("{indent}{l}\n")
+ }
+ })
+ .collect::<String>();
+
+ self.span_suggestion(span, msg, format!("{new_item}\n{indent}"), applicability);
+ }
+ }
+
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
+ let mut remove_span = item;
+ let fmpos = cx.sess().source_map().lookup_byte_offset(remove_span.hi());
+
+ if let Some(ref src) = fmpos.sf.src {
+ let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
+
+ if let Some(non_whitespace_offset) = non_whitespace_offset {
+ remove_span = remove_span
+ .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")));
+ }
+ }
+
+ self.span_suggestion(remove_span, msg, "", applicability);
+ }
+}
+
+/// Suggestion results for handling closure
+/// args dereferencing and borrowing
+pub struct DerefClosure {
+ /// confidence on the built suggestion
+ pub applicability: Applicability,
+ /// gradually built suggestion
+ pub suggestion: String,
+}
+
+/// Build suggestion gradually by handling closure arg specific usages,
+/// such as explicit deref and borrowing cases.
+/// Returns `None` if no such use cases have been triggered in closure body
+///
+/// note: this only works on single line immutable closures with exactly one input parameter.
++pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option<DerefClosure> {
+ if let hir::ExprKind::Closure(&Closure { fn_decl, body, .. }) = closure.kind {
+ let closure_body = cx.tcx.hir().body(body);
+ // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`)
+ // a type annotation is present if param `kind` is different from `TyKind::Infer`
+ let closure_arg_is_type_annotated_double_ref = if let TyKind::Rptr(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
+ {
+ matches!(ty.kind, TyKind::Rptr(_, MutTy { .. }))
+ } else {
+ false
+ };
+
+ let mut visitor = DerefDelegate {
+ cx,
+ closure_span: closure.span,
+ closure_arg_is_type_annotated_double_ref,
+ next_pos: closure.span.lo(),
+ suggestion_start: String::new(),
+ applicability: Applicability::MachineApplicable,
+ };
+
+ let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
+ let infcx = cx.tcx.infer_ctxt().build();
+ ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
+ .consume_body(closure_body);
+
+ if !visitor.suggestion_start.is_empty() {
+ return Some(DerefClosure {
+ applicability: visitor.applicability,
+ suggestion: visitor.finish(),
+ });
+ }
+ }
+ None
+}
+
+/// Visitor struct used for tracking down
+/// dereferencing and borrowing of closure's args
+struct DerefDelegate<'a, 'tcx> {
+ /// The late context of the lint
+ cx: &'a LateContext<'tcx>,
+ /// The span of the input closure to adapt
+ closure_span: Span,
+ /// Indicates if the arg of the closure is a type annotated double reference
+ closure_arg_is_type_annotated_double_ref: bool,
+ /// last position of the span to gradually build the suggestion
+ next_pos: BytePos,
+ /// starting part of the gradually built suggestion
+ suggestion_start: String,
+ /// confidence on the built suggestion
+ applicability: Applicability,
+}
+
+impl<'tcx> DerefDelegate<'_, 'tcx> {
+ /// build final suggestion:
+ /// - create the ending part of suggestion
+ /// - concatenate starting and ending parts
+ /// - potentially remove needless borrowing
+ pub fn finish(&mut self) -> String {
+ let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None);
+ let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability);
+ let sugg = format!("{}{end_snip}", self.suggestion_start);
+ if self.closure_arg_is_type_annotated_double_ref {
+ sugg.replacen('&', "", 1)
+ } else {
+ sugg
+ }
+ }
+
+ /// indicates whether the function from `parent_expr` takes its args by double reference
+ fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
+ let ty = match parent_expr.kind {
+ ExprKind::MethodCall(_, receiver, call_args, _) => {
+ if let Some(sig) = self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(parent_expr.hir_id)
+ .map(|did| self.cx.tcx.fn_sig(did).skip_binder())
+ {
+ std::iter::once(receiver)
+ .chain(call_args.iter())
+ .position(|arg| arg.hir_id == cmt_hir_id)
+ .map(|i| sig.inputs()[i])
+ } else {
+ return false;
+ }
+ },
+ ExprKind::Call(func, call_args) => {
+ if let Some(sig) = expr_sig(self.cx, func) {
+ call_args
+ .iter()
+ .position(|arg| arg.hir_id == cmt_hir_id)
+ .and_then(|i| sig.input(i))
+ .map(ty::Binder::skip_binder)
+ } else {
+ return false;
+ }
+ },
+ _ => return false,
+ };
+
+ ty.map_or(false, |ty| matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
+ if let PlaceBase::Local(id) = cmt.place.base {
+ let map = self.cx.tcx.hir();
+ let span = map.span(cmt.hir_id);
+ let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
+ let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
+
+ // identifier referring to the variable currently triggered (i.e.: `fp`)
+ let ident_str = map.name(id).to_string();
+ // full identifier that includes projection (i.e.: `fp.field`)
+ let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
+
+ if cmt.place.projections.is_empty() {
+ // handle item without any projection, that needs an explicit borrowing
+ // i.e.: suggest `&x` instead of `x`
+ let _ = write!(self.suggestion_start, "{start_snip}&{ident_str}");
+ } else {
+ // cases where a parent `Call` or `MethodCall` is using the item
+ // i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()`
+ //
+ // Note about method calls:
+ // - compiler automatically dereference references if the target type is a reference (works also for
+ // function call)
+ // - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and
+ // no projection should be suggested
+ if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) {
+ match &parent_expr.kind {
+ // given expression is the self argument and will be handled completely by the compiler
+ // i.e.: `|x| x.is_something()`
+ ExprKind::MethodCall(_, self_expr, ..) if self_expr.hir_id == cmt.hir_id => {
+ let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
+ self.next_pos = span.hi();
+ return;
+ },
+ // item is used in a call
+ // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
+ ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, _, [call_args @ ..], _) => {
+ let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
+ let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
+
+ if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
+ // suggest ampersand if call function is taking args by double reference
+ let takes_arg_by_double_ref =
+ self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id);
+
+ // compiler will automatically dereference field or index projection, so no need
+ // to suggest ampersand, but full identifier that includes projection is required
+ let has_field_or_index_projection =
+ cmt.place.projections.iter().any(|proj| {
+ matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index)
+ });
+
+ // no need to bind again if the function doesn't take arg by double ref
+ // and if the item is already a double ref
+ let ident_sugg = if !call_args.is_empty()
+ && !takes_arg_by_double_ref
+ && (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection)
+ {
+ let ident = if has_field_or_index_projection {
+ ident_str_with_proj
+ } else {
+ ident_str
+ };
+ format!("{start_snip}{ident}")
+ } else {
+ format!("{start_snip}&{ident_str}")
+ };
+ self.suggestion_start.push_str(&ident_sugg);
+ self.next_pos = span.hi();
+ return;
+ }
+
+ self.applicability = Applicability::Unspecified;
+ },
+ _ => (),
+ }
+ }
+
+ let mut replacement_str = ident_str;
+ let mut projections_handled = false;
+ cmt.place.projections.iter().enumerate().for_each(|(i, proj)| {
+ match proj.kind {
+ // Field projection like `|v| v.foo`
+ // no adjustment needed here, as field projections are handled by the compiler
+ ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() {
+ ty::Adt(..) | ty::Tuple(_) => {
+ replacement_str = ident_str_with_proj.clone();
+ projections_handled = true;
+ },
+ _ => (),
+ },
+ // Index projection like `|x| foo[x]`
+ // the index is dropped so we can't get it to build the suggestion,
+ // so the span is set-up again to get more code, using `span.hi()` (i.e.: `foo[x]`)
+ // instead of `span.lo()` (i.e.: `foo`)
+ ProjectionKind::Index => {
+ let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None);
+ start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
+ replacement_str.clear();
+ projections_handled = true;
+ },
+ // note: unable to trigger `Subslice` kind in tests
+ ProjectionKind::Subslice => (),
+ ProjectionKind::Deref => {
+ // Explicit derefs are typically handled later on, but
+ // some items do not need explicit deref, such as array accesses,
+ // so we mark them as already processed
+ // i.e.: don't suggest `*sub[1..4].len()` for `|sub| sub[1..4].len() == 3`
+ if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() {
+ if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) {
+ projections_handled = true;
+ }
+ }
+ },
+ }
+ });
+
+ // handle `ProjectionKind::Deref` by removing one explicit deref
+ // if no special case was detected (i.e.: suggest `*x` instead of `**x`)
+ if !projections_handled {
+ let last_deref = cmt
+ .place
+ .projections
+ .iter()
+ .rposition(|proj| proj.kind == ProjectionKind::Deref);
+
+ if let Some(pos) = last_deref {
+ let mut projections = cmt.place.projections.clone();
+ projections.truncate(pos);
+
+ for item in projections {
+ if item.kind == ProjectionKind::Deref {
+ replacement_str = format!("*{replacement_str}");
+ }
+ }
+ }
+ }
+
+ let _ = write!(self.suggestion_start, "{start_snip}{replacement_str}");
+ }
+ self.next_pos = span.hi();
+ }
+ }
+
+ fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+#[cfg(test)]
+mod test {
+ use super::Sugg;
+
+ use rustc_ast::util::parser::AssocOp;
+ use std::borrow::Cow;
+
+ const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
+
+ #[test]
+ fn make_return_transform_sugg_into_a_return_call() {
+ assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
+ }
+
+ #[test]
+ fn blockify_transforms_sugg_into_a_block() {
+ assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
+ }
+
+ #[test]
+ fn binop_maybe_par() {
+ let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into());
+ assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
+
+ let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into());
+ assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
+ }
+ #[test]
+ fn not_op() {
+ use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual};
+
+ fn test_not(op: AssocOp, correct: &str) {
+ let sugg = Sugg::BinOp(op, "x".into(), "y".into());
+ assert_eq!((!sugg).to_string(), correct);
+ }
+
+ // Invert the comparison operator.
+ test_not(Equal, "x != y");
+ test_not(NotEqual, "x == y");
+ test_not(Less, "x >= y");
+ test_not(LessEqual, "x > y");
+ test_not(Greater, "x <= y");
+ test_not(GreaterEqual, "x < y");
+
+ // Other operators are inverted like !(..).
+ test_not(Add, "!(x + y)");
+ test_not(LAnd, "!(x && y)");
+ test_not(LOr, "!(x || y)");
+ }
+}
--- /dev/null
- self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy,
- Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, VariantDef, VariantDiscr,
+//! Util methods for [`rustc_middle::ty`]
+
+#![allow(clippy::module_name_repetitions)]
+
+use core::ops::ControlFlow;
+use rustc_ast::ast::Mutability;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::mir::interpret::{ConstValue, Scalar};
+use rustc_middle::ty::{
- /// Attempts to read the given constant as though it were an an enum value.
++ self, AdtDef, AssocKind, Binder, BoundRegion, DefIdTree, FnSig, GenericParamDefKind, IntTy, List, ParamEnv,
++ Predicate, PredicateKind, ProjectionTy, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable,
++ TypeVisitable, TypeVisitor, UintTy, VariantDef, VariantDiscr,
+};
+use rustc_middle::ty::{GenericArg, GenericArgKind};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span, Symbol};
+use rustc_target::abi::{Size, VariantIdx};
+use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::query::normalize::AtExt;
+use std::iter;
+
+use crate::{match_def_path, path_res, paths};
+
+// Checks if the given type implements copy.
+pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ ty.is_copy_modulo_regions(cx.tcx, cx.param_env)
+}
+
+/// This checks whether a given type is known to implement Debug.
+pub fn has_debug_impl<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ cx.tcx
+ .get_diagnostic_item(sym::Debug)
+ .map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
+}
+
+/// Checks whether a type can be partially moved.
+pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if has_drop(cx, ty) || is_copy(cx, ty) {
+ return false;
+ }
+ match ty.kind() {
+ ty::Param(_) => false,
+ ty::Adt(def, subs) => def.all_fields().any(|f| !is_copy(cx, f.ty(cx.tcx, subs))),
+ _ => true,
+ }
+}
+
+/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
+/// constructor.
+pub fn contains_adt_constructor<'tcx>(ty: Ty<'tcx>, adt: AdtDef<'tcx>) -> bool {
+ ty.walk().any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
++/// Walks into `ty` and returns `true` if any inner type is an instance of the given type, or adt
++/// constructor of the same type.
++///
++/// This method also recurses into opaque type predicates, so call it with `impl Trait<U>` and `U`
++/// will also return `true`.
++pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, needle: Ty<'tcx>) -> bool {
++ ty.walk().any(|inner| match inner.unpack() {
++ GenericArgKind::Type(inner_ty) => {
++ if inner_ty == needle {
++ return true;
++ }
++
++ if inner_ty.ty_adt_def() == needle.ty_adt_def() {
++ return true;
++ }
++
++ if let ty::Opaque(def_id, _) = *inner_ty.kind() {
++ for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
++ match predicate.kind().skip_binder() {
++ // For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through
++ // and check substituions to find `U`.
++ ty::PredicateKind::Trait(trait_predicate) => {
++ if trait_predicate
++ .trait_ref
++ .substs
++ .types()
++ .skip(1) // Skip the implicit `Self` generic parameter
++ .any(|ty| contains_ty_adt_constructor_opaque(cx, ty, needle))
++ {
++ return true;
++ }
++ },
++ // For `impl Trait<Assoc=U>`, it will register a predicate of `<T as Trait>::Assoc = U`,
++ // so we check the term for `U`.
++ ty::PredicateKind::Projection(projection_predicate) => {
++ if let ty::TermKind::Ty(ty) = projection_predicate.term.unpack() {
++ if contains_ty_adt_constructor_opaque(cx, ty, needle) {
++ return true;
++ }
++ };
++ },
++ _ => (),
++ }
++ }
++ }
++
++ false
++ },
++ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
++ })
++}
++
+/// Resolves `<T as Iterator>::Item` for `T`
+/// Do not invoke without first verifying that the type implements `Iterator`
+pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ cx.tcx
+ .get_diagnostic_item(sym::Iterator)
+ .and_then(|iter_did| get_associated_type(cx, ty, iter_did, "Item"))
+}
+
+/// Returns the associated type `name` for `ty` as an implementation of `trait_id`.
+/// Do not invoke without first verifying that the type implements the trait.
+pub fn get_associated_type<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ name: &str,
+) -> Option<Ty<'tcx>> {
+ cx.tcx
+ .associated_items(trait_id)
+ .find_by_name_and_kind(cx.tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id)
+ .and_then(|assoc| {
+ let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[]));
+ cx.tcx.try_normalize_erasing_regions(cx.param_env, proj).ok()
+ })
+}
+
+/// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type
+/// implements a trait marked with a diagnostic item use [`implements_trait`].
+///
+/// For a further exploitation what diagnostic items are see [diagnostic items] in
+/// rustc-dev-guide.
+///
+/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
+pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> {
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.get_diagnostic_name(adt.did()),
+ _ => None,
+ }
+}
+
+/// Returns true if ty has `iter` or `iter_mut` methods
+pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
+ // FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
+ // exists and has the desired signature. Unfortunately FnCtxt is not exported
+ // so we can't use its `lookup_method` method.
+ let into_iter_collections: &[Symbol] = &[
+ sym::Vec,
+ sym::Option,
+ sym::Result,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::BinaryHeap,
+ sym::HashSet,
+ sym::HashMap,
+ sym::PathBuf,
+ sym::Path,
+ sym::Receiver,
+ ];
+
+ let ty_to_check = match probably_ref_ty.kind() {
+ ty::Ref(_, ty_to_check, _) => *ty_to_check,
+ _ => probably_ref_ty,
+ };
+
+ 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(),
+ _ => return None,
+ };
+
+ for &name in into_iter_collections {
+ if cx.tcx.is_diagnostic_item(name, def_id) {
+ return Some(cx.tcx.item_name(def_id));
+ }
+ }
+ None
+}
+
+/// Checks whether a type implements a trait.
+/// The function returns false in case the type contains an inference variable.
+///
+/// See:
+/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
+/// * [Common tools for writing lints] for an example how to use this function and other options.
+///
+/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
+pub fn implements_trait<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ implements_trait_with_env(cx.tcx, cx.param_env, ty, trait_id, ty_params)
+}
+
+/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
+pub fn implements_trait_with_env<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ param_env: ParamEnv<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ // Clippy shouldn't have infer types
+ assert!(!ty.needs_infer());
+
+ let ty = tcx.erase_regions(ty);
+ if ty.has_escaping_bound_vars() {
+ return false;
+ }
+ let ty_params = tcx.mk_substs(ty_params.iter());
+ let infcx = tcx.infer_ctxt().build();
+ infcx
+ .type_implements_trait(trait_id, ty, ty_params, param_env)
+ .must_apply_modulo_regions()
+}
+
+/// Checks whether this type implements `Drop`.
+pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.ty_adt_def() {
+ Some(def) => def.has_dtor(cx.tcx),
+ None => false,
+ }
+}
+
+// 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, _) => cx.tcx.has_attr(adt.did(), sym::must_use),
+ ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use),
+ 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
+ // because we don't want to lint functions returning empty arrays
+ is_must_use_ty(cx, *ty)
+ },
+ ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)),
+ ty::Opaque(def_id, _) => {
+ for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
+ if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() {
+ if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ ty::Dynamic(binder, _, _) => {
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
+ if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize
+// this function can be removed once the `normalize` method does not panic when normalization does
+// not succeed
+/// Checks if `Ty` is normalizable. This function is useful
+/// to avoid crashes on `layout_of`.
+pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
+ is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default())
+}
+
+fn is_normalizable_helper<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ ty: Ty<'tcx>,
+ cache: &mut FxHashMap<Ty<'tcx>, bool>,
+) -> bool {
+ if let Some(&cached_result) = cache.get(&ty) {
+ return cached_result;
+ }
+ // prevent recursive loops, false-negative is better than endless loop leading to stack overflow
+ cache.insert(ty, false);
+ let infcx = cx.tcx.infer_ctxt().build();
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ let result = if infcx.at(&cause, param_env).normalize(ty).is_ok() {
+ match ty.kind() {
+ ty::Adt(def, substs) => def.variants().iter().all(|variant| {
+ variant
+ .fields
+ .iter()
+ .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache))
+ }),
+ _ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
+ GenericArgKind::Type(inner_ty) if inner_ty != ty => {
+ is_normalizable_helper(cx, param_env, inner_ty, cache)
+ },
+ _ => true, // if inner_ty == ty, we've already checked it
+ }),
+ }
+ } else {
+ false
+ };
+ cache.insert(ty, result);
+ result
+}
+
+/// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any
+/// integer or floating-point number type). For checking aggregation of primitive types (e.g.
+/// tuples and slices of primitive type) see `is_recursively_primitive_type`
+pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool {
+ matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_))
+}
+
+/// Returns `true` if the given type is a primitive (a `bool` or `char`, any integer or
+/// floating-point number type, a `str`, or an array, slice, or tuple of those types).
+pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
+ match *ty.kind() {
+ ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
+ ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true,
+ ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
+ ty::Tuple(inner_types) => inner_types.iter().all(is_recursively_primitive_type),
+ _ => false,
+ }
+}
+
+/// Checks if the type is a reference equals to a diagnostic item
+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()),
+ _ => false,
+ },
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a diagnostic item. To check if a type implements a
+/// trait marked with a diagnostic item use [`implements_trait`].
+///
+/// For a further exploitation what diagnostic items are see [diagnostic items] in
+/// rustc-dev-guide.
+///
+/// ---
+///
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
+///
+/// [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()),
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a lang item.
+///
+/// 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().get(lang_item) == Some(adt.did()),
+ _ => false,
+ }
+}
+
+/// Return `true` if the passed `typ` is `isize` or `usize`.
+pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
+ matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+}
+
+/// Checks if type is struct, enum or union type with the given def path.
+///
+/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
+/// 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),
+ _ => false,
+ }
+}
+
+/// Checks if the drop order for a type matters. Some std types implement drop solely to
+/// deallocate memory. For these types, and composites containing them, changing the drop order
+/// won't result in any observable side effects.
+pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ fn needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
+ if !seen.insert(ty) {
+ return false;
+ }
+ if !ty.has_significant_drop(cx.tcx, cx.param_env) {
+ false
+ }
+ // Check for std types which implement drop, but only for memory allocation.
+ else if is_type_lang_item(cx, ty, LangItem::OwnedBox)
+ || matches!(
+ get_type_diagnostic_name(cx, ty),
+ Some(sym::HashSet | sym::Rc | sym::Arc | sym::cstring_type)
+ )
+ || match_type(cx, ty, &paths::WEAK_RC)
+ || match_type(cx, ty, &paths::WEAK_ARC)
+ {
+ // Check all of the generic arguments.
+ if let ty::Adt(_, subs) = ty.kind() {
+ subs.types().any(|ty| needs_ordered_drop_inner(cx, ty, seen))
+ } else {
+ true
+ }
+ } else if !cx
+ .tcx
+ .lang_items()
+ .drop_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+ {
+ // This type doesn't implement drop, so no side effects here.
+ // Check if any component type has any.
+ match ty.kind() {
+ ty::Tuple(fields) => fields.iter().any(|ty| needs_ordered_drop_inner(cx, ty, seen)),
+ ty::Array(ty, _) => needs_ordered_drop_inner(cx, *ty, seen),
+ ty::Adt(adt, subs) => adt
+ .all_fields()
+ .map(|f| f.ty(cx.tcx, subs))
+ .any(|ty| needs_ordered_drop_inner(cx, ty, seen)),
+ _ => true,
+ }
+ } else {
+ true
+ }
+ }
+
+ needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
+}
+
+/// Peels off all references on the type. Returns the underlying type and the number of references
+/// removed.
+pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
+ if let ty::Ref(_, ty, _) = ty.kind() {
+ peel(*ty, count + 1)
+ } else {
+ (ty, count)
+ }
+ }
+ peel(ty, 0)
+}
+
+/// Peels off all references on the type. Returns the underlying type, the number of references
+/// removed, and whether the pointer is ultimately mutable or not.
+pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
+ fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) {
+ match ty.kind() {
+ ty::Ref(_, ty, Mutability::Mut) => f(*ty, count + 1, mutability),
+ ty::Ref(_, ty, Mutability::Not) => f(*ty, count + 1, Mutability::Not),
+ _ => (ty, count, mutability),
+ }
+ }
+ f(ty, 0, Mutability::Mut)
+}
+
+/// Returns `true` if the given type is an `unsafe` function.
+pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe,
+ _ => false,
+ }
+}
+
+/// Returns the base type for HIR references and pointers.
+pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
+ match ty.kind {
+ TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(mut_ty.ty),
+ _ => ty,
+ }
+}
+
+/// Returns the base type for references and raw pointers, and count reference
+/// depth.
+pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
+ match ty.kind() {
+ ty::Ref(_, ty, _) => inner(*ty, depth + 1),
+ _ => (ty, depth),
+ }
+ }
+ inner(ty, 0)
+}
+
+/// Returns `true` if types `a` and `b` are same types having same `Const` generic args,
+/// otherwise returns `false`
+pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
+ match (&a.kind(), &b.kind()) {
+ (&ty::Adt(did_a, substs_a), &ty::Adt(did_b, substs_b)) => {
+ if did_a != did_b {
+ return false;
+ }
+
+ substs_a
+ .iter()
+ .zip(substs_b.iter())
+ .all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) {
+ (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b,
+ (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => {
+ same_type_and_consts(type_a, type_b)
+ },
+ _ => true,
+ })
+ },
+ _ => a == b,
+ }
+}
+
+/// Checks if a given type looks safe to be uninitialized.
+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()),
+ _ => false,
+ }
+}
+
+/// Gets an iterator over all predicates which apply to the given item.
+pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(Predicate<'_>, Span)> {
+ let mut next_id = Some(id);
+ iter::from_fn(move || {
+ next_id.take().map(|id| {
+ let preds = tcx.predicates_of(id);
+ next_id = preds.parent;
+ preds.predicates.iter()
+ })
+ })
+ .flatten()
+}
+
+/// A signature for a function like type.
+#[derive(Clone, Copy)]
+pub enum ExprFnSig<'tcx> {
+ Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
+ Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),
+ Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>, Option<DefId>),
+}
+impl<'tcx> ExprFnSig<'tcx> {
+ /// Gets the argument type at the given offset. This will return `None` when the index is out of
+ /// bounds only for variadic functions, otherwise this will panic.
+ pub fn input(self, i: usize) -> Option<Binder<'tcx, Ty<'tcx>>> {
+ match self {
+ Self::Sig(sig, _) => {
+ if sig.c_variadic() {
+ sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose()
+ } else {
+ Some(sig.input(i))
+ }
+ },
+ Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])),
+ Self::Trait(inputs, _, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])),
+ }
+ }
+
+ /// Gets the argument type at the given offset. For closures this will also get the type as
+ /// written. This will return `None` when the index is out of bounds only for variadic
+ /// functions, otherwise this will panic.
+ pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> {
+ match self {
+ Self::Sig(sig, _) => {
+ if sig.c_variadic() {
+ sig.inputs()
+ .map_bound(|inputs| inputs.get(i).copied())
+ .transpose()
+ .map(|arg| (None, arg))
+ } else {
+ Some((None, sig.input(i)))
+ }
+ },
+ Self::Closure(decl, sig) => Some((
+ decl.and_then(|decl| decl.inputs.get(i)),
+ sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
+ )),
+ Self::Trait(inputs, _, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))),
+ }
+ }
+
+ /// Gets the result type, if one could be found. Note that the result type of a trait may not be
+ /// specified.
+ pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
+ match self {
+ Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()),
+ Self::Trait(_, output, _) => output,
+ }
+ }
+
+ pub fn predicates_id(&self) -> Option<DefId> {
+ if let ExprFnSig::Sig(_, id) | ExprFnSig::Trait(_, _, id) = *self {
+ id
+ } else {
+ None
+ }
+ }
+}
+
+/// If the expression is function like, get the signature for it.
+pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> {
+ if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) {
+ Some(ExprFnSig::Sig(cx.tcx.fn_sig(id), Some(id)))
+ } else {
+ ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs())
+ }
+}
+
+/// If the type is function like, get the signature for it.
+pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
+ if ty.is_box() {
+ return ty_sig(cx, ty.boxed_ty());
+ }
+ match *ty.kind() {
+ ty::Closure(id, subs) => {
+ let decl = id
+ .as_local()
+ .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id)));
+ Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
+ },
+ ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))),
+ ty::Opaque(id, _) => sig_from_bounds(cx, ty, cx.tcx.item_bounds(id), cx.tcx.opt_parent(id)),
+ ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
+ ty::Dynamic(bounds, _, _) => {
+ let lang_items = cx.tcx.lang_items();
+ match bounds.principal() {
+ Some(bound)
+ if Some(bound.def_id()) == lang_items.fn_trait()
+ || Some(bound.def_id()) == lang_items.fn_once_trait()
+ || Some(bound.def_id()) == lang_items.fn_mut_trait() =>
+ {
+ let output = bounds
+ .projection_bounds()
+ .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
+ .map(|p| p.map_bound(|p| p.term.ty().unwrap()));
+ Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output, None))
+ },
+ _ => None,
+ }
+ },
+ ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) {
+ Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty),
+ _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None)),
+ },
+ ty::Param(_) => sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None),
+ _ => None,
+ }
+}
+
+fn sig_from_bounds<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ predicates: &'tcx [Predicate<'tcx>],
+ predicates_id: Option<DefId>,
+) -> Option<ExprFnSig<'tcx>> {
+ let mut inputs = None;
+ let mut output = None;
+ let lang_items = cx.tcx.lang_items();
+
+ for pred in predicates {
+ match pred.kind().skip_binder() {
+ PredicateKind::Trait(p)
+ if (lang_items.fn_trait() == Some(p.def_id())
+ || lang_items.fn_mut_trait() == Some(p.def_id())
+ || lang_items.fn_once_trait() == Some(p.def_id()))
+ && p.self_ty() == ty =>
+ {
+ let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
+ if inputs.map_or(false, |inputs| i != inputs) {
+ // Multiple different fn trait impls. Is this even allowed?
+ return None;
+ }
+ inputs = Some(i);
+ },
+ PredicateKind::Projection(p)
+ if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
+ && p.projection_ty.self_ty() == ty =>
+ {
+ if output.is_some() {
+ // Multiple different fn trait impls. Is this even allowed?
+ return None;
+ }
+ output = Some(pred.kind().rebind(p.term.ty().unwrap()));
+ },
+ _ => (),
+ }
+ }
+
+ inputs.map(|ty| ExprFnSig::Trait(ty, output, predicates_id))
+}
+
+fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> {
+ let mut inputs = None;
+ let mut output = None;
+ let lang_items = cx.tcx.lang_items();
+
+ for (pred, _) in cx
+ .tcx
+ .bound_explicit_item_bounds(ty.item_def_id)
+ .subst_iter_copied(cx.tcx, ty.substs)
+ {
+ match pred.kind().skip_binder() {
+ PredicateKind::Trait(p)
+ if (lang_items.fn_trait() == Some(p.def_id())
+ || lang_items.fn_mut_trait() == Some(p.def_id())
+ || lang_items.fn_once_trait() == Some(p.def_id())) =>
+ {
+ let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
+
+ if inputs.map_or(false, |inputs| inputs != i) {
+ // Multiple different fn trait impls. Is this even allowed?
+ return None;
+ }
+ inputs = Some(i);
+ },
+ PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => {
+ if output.is_some() {
+ // Multiple different fn trait impls. Is this even allowed?
+ return None;
+ }
+ output = pred.kind().rebind(p.term.ty()).transpose();
+ },
+ _ => (),
+ }
+ }
+
+ inputs.map(|ty| ExprFnSig::Trait(ty, output, None))
+}
+
+#[derive(Clone, Copy)]
+pub enum EnumValue {
+ Unsigned(u128),
+ Signed(i128),
+}
+impl core::ops::Add<u32> for EnumValue {
+ type Output = Self;
+ fn add(self, n: u32) -> Self::Output {
+ match self {
+ Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)),
+ Self::Signed(x) => Self::Signed(x + i128::from(n)),
+ }
+ }
+}
+
++/// Attempts to read the given constant as though it were an enum value.
+#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
+pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
+ if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
+ match tcx.type_of(id).kind() {
+ ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() {
+ 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
+ 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
+ 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
+ 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
+ 16 => value.assert_bits(Size::from_bytes(16)) as i128,
+ _ => return None,
+ })),
+ ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() {
+ 1 => value.assert_bits(Size::from_bytes(1)),
+ 2 => value.assert_bits(Size::from_bytes(2)),
+ 4 => value.assert_bits(Size::from_bytes(4)),
+ 8 => value.assert_bits(Size::from_bytes(8)),
+ 16 => value.assert_bits(Size::from_bytes(16)),
+ _ => return None,
+ })),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
+
+/// Gets the value of the given variant.
+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.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()),
+ },
+ }
+}
+
+/// Check if the given type is either `core::ffi::c_void`, `std::os::raw::c_void`, or one of the
+/// 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 sym::libc | sym::core | sym::std = krate
+ && name.as_str() == "c_void"
+ {
+ true
+ } else {
+ false
+ }
+}
+
+pub fn for_each_top_level_late_bound_region<B>(
+ ty: Ty<'_>,
+ f: impl FnMut(BoundRegion) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ struct V<F> {
+ index: u32,
+ f: F,
+ }
+ impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> {
+ type BreakTy = B;
+ fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+ if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index {
+ (self.f)(bound)
+ } else {
+ ControlFlow::Continue(())
+ }
+ }
+ fn visit_binder<T: TypeVisitable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> {
+ self.index += 1;
+ let res = t.super_visit_with(self);
+ self.index -= 1;
+ res
+ }
+ }
+ ty.visit_with(&mut V { index: 0, f })
+}
+
++pub struct AdtVariantInfo {
++ pub ind: usize,
++ pub size: u64,
++
++ /// (ind, size)
++ pub fields_size: Vec<(usize, u64)>,
++}
++
++impl AdtVariantInfo {
++ /// Returns ADT variants ordered by size
++ pub fn new<'tcx>(cx: &LateContext<'tcx>, adt: AdtDef<'tcx>, subst: &'tcx List<GenericArg<'tcx>>) -> Vec<Self> {
++ let mut variants_size = adt
++ .variants()
++ .iter()
++ .enumerate()
++ .map(|(i, variant)| {
++ let mut fields_size = variant
++ .fields
++ .iter()
++ .enumerate()
++ .map(|(i, f)| (i, approx_ty_size(cx, f.ty(cx.tcx, subst))))
++ .collect::<Vec<_>>();
++ fields_size.sort_by(|(_, a_size), (_, b_size)| (a_size.cmp(b_size)));
++
++ Self {
++ ind: i,
++ size: fields_size.iter().map(|(_, size)| size).sum(),
++ fields_size,
++ }
++ })
++ .collect::<Vec<_>>();
++ variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
++ variants_size
++ }
++}
++
+/// Gets the struct or enum variant from the given `Res`
+pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> {
+ match res {
+ Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()),
+ Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)),
+ Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()),
+ Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => {
+ let var_id = cx.tcx.parent(id);
+ Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id))
+ },
+ Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()),
+ _ => None,
+ }
+}
+
+/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`.
+pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [Predicate<'_>]) -> bool {
+ let ty::Param(ty) = *ty.kind() else {
+ return false;
+ };
+ let lang = tcx.lang_items();
+ let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id))
+ = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait())
+ else {
+ return false;
+ };
+ predicates
+ .iter()
+ .try_fold(false, |found, p| {
+ if let PredicateKind::Trait(p) = p.kind().skip_binder()
+ && let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
+ && ty.index == self_ty.index
+ {
+ // This should use `super_traits_of`, but that's a private function.
+ if p.trait_ref.def_id == fn_once_id {
+ return Some(true);
+ } else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id {
+ return None;
+ }
+ }
+ Some(found)
+ })
+ .unwrap_or(false)
+}
+
+/// Comes up with an "at least" guesstimate for the type's size, not taking into
+/// account the layout of type parameters.
+pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
+ use rustc_middle::ty::layout::LayoutOf;
+ if !is_normalizable(cx, cx.param_env, ty) {
+ return 0;
+ }
+ match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) {
+ (Ok(size), _) => size,
+ (Err(_), ty::Tuple(list)) => list.as_substs().types().map(|t| approx_ty_size(cx, t)).sum(),
+ (Err(_), ty::Array(t, n)) => {
+ n.try_eval_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t)
+ },
+ (Err(_), ty::Adt(def, subst)) if def.is_struct() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .sum::<u64>()
+ })
+ .sum(),
+ (Err(_), ty::Adt(def, subst)) if def.is_enum() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .sum::<u64>()
+ })
+ .max()
+ .unwrap_or_default(),
+ (Err(_), ty::Adt(def, subst)) if def.is_union() => def
+ .variants()
+ .iter()
+ .map(|v| {
+ v.fields
+ .iter()
+ .map(|field| approx_ty_size(cx, field.ty(cx.tcx, subst)))
+ .max()
+ .unwrap_or_default()
+ })
+ .max()
+ .unwrap_or_default(),
+ (Err(_), _) => 0,
+ }
+}
++
++/// Makes the projection type for the named associated type in the given impl or trait impl.
++///
++/// This function is for associated types which are "known" to exist, and as such, will only return
++/// `None` when debug assertions are disabled in order to prevent ICE's. With debug assertions
++/// enabled this will check that the named associated type exists, the correct number of
++/// substitutions are given, and that the correct kinds of substitutions are given (lifetime,
++/// constant or type). This will not check if type normalization would succeed.
++pub fn make_projection<'tcx>(
++ tcx: TyCtxt<'tcx>,
++ container_id: DefId,
++ assoc_ty: Symbol,
++ substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
++) -> Option<ProjectionTy<'tcx>> {
++ fn helper<'tcx>(
++ tcx: TyCtxt<'tcx>,
++ container_id: DefId,
++ assoc_ty: Symbol,
++ substs: SubstsRef<'tcx>,
++ ) -> Option<ProjectionTy<'tcx>> {
++ let Some(assoc_item) = tcx
++ .associated_items(container_id)
++ .find_by_name_and_kind(tcx, Ident::with_dummy_span(assoc_ty), AssocKind::Type, container_id)
++ else {
++ debug_assert!(false, "type `{assoc_ty}` not found in `{container_id:?}`");
++ return None;
++ };
++ #[cfg(debug_assertions)]
++ {
++ let generics = tcx.generics_of(assoc_item.def_id);
++ let generic_count = generics.parent_count + generics.params.len();
++ let params = generics
++ .parent
++ .map_or([].as_slice(), |id| &*tcx.generics_of(id).params)
++ .iter()
++ .chain(&generics.params)
++ .map(|x| &x.kind);
++
++ debug_assert!(
++ generic_count == substs.len(),
++ "wrong number of substs for `{:?}`: found `{}` expected `{}`.\n\
++ note: the expected parameters are: {:#?}\n\
++ the given arguments are: `{:#?}`",
++ assoc_item.def_id,
++ substs.len(),
++ generic_count,
++ params.map(GenericParamDefKind::descr).collect::<Vec<_>>(),
++ substs,
++ );
++
++ if let Some((idx, (param, arg))) = params
++ .clone()
++ .zip(substs.iter().map(GenericArg::unpack))
++ .enumerate()
++ .find(|(_, (param, arg))| {
++ !matches!(
++ (param, arg),
++ (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
++ | (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
++ | (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_))
++ )
++ })
++ {
++ debug_assert!(
++ false,
++ "mismatched subst type at index {}: expected a {}, found `{:?}`\n\
++ note: the expected parameters are {:#?}\n\
++ the given arguments are {:#?}",
++ idx,
++ param.descr(),
++ arg,
++ params.map(GenericParamDefKind::descr).collect::<Vec<_>>(),
++ substs,
++ );
++ }
++ }
++
++ Some(ProjectionTy {
++ substs,
++ item_def_id: assoc_item.def_id,
++ })
++ }
++ helper(
++ tcx,
++ container_id,
++ assoc_ty,
++ tcx.mk_substs(substs.into_iter().map(Into::into)),
++ )
++}
++
++/// Normalizes the named associated type in the given impl or trait impl.
++///
++/// This function is for associated types which are "known" to be valid with the given
++/// substitutions, and as such, will only return `None` when debug assertions are disabled in order
++/// to prevent ICE's. With debug assertions enabled this will check that that type normalization
++/// succeeds as well as everything checked by `make_projection`.
++pub fn make_normalized_projection<'tcx>(
++ tcx: TyCtxt<'tcx>,
++ param_env: ParamEnv<'tcx>,
++ container_id: DefId,
++ assoc_ty: Symbol,
++ substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
++) -> Option<Ty<'tcx>> {
++ fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: ProjectionTy<'tcx>) -> Option<Ty<'tcx>> {
++ #[cfg(debug_assertions)]
++ if let Some((i, subst)) = ty
++ .substs
++ .iter()
++ .enumerate()
++ .find(|(_, subst)| subst.has_late_bound_regions())
++ {
++ debug_assert!(
++ false,
++ "substs contain late-bound region at index `{i}` which can't be normalized.\n\
++ use `TyCtxt::erase_late_bound_regions`\n\
++ note: subst is `{subst:#?}`",
++ );
++ return None;
++ }
++ match tcx.try_normalize_erasing_regions(param_env, tcx.mk_projection(ty.item_def_id, ty.substs)) {
++ Ok(ty) => Some(ty),
++ Err(e) => {
++ debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}");
++ None
++ },
++ }
++ }
++ helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
++}
--- /dev/null
- fn fake_read(
- &mut self,
- _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
- _: FakeReadCause,
- _: HirId,
- ) {}
+use crate as utils;
+use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
+use core::ops::ControlFlow;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirIdSet;
+use rustc_hir::{Expr, ExprKind, HirId, Node};
+use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty;
+
+/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
+pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
+ let mut delegate = MutVarsDelegate {
+ used_mutably: HirIdSet::default(),
+ skip: false,
+ };
+ let infcx = cx.tcx.infer_ctxt().build();
+ ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ expr.hir_id.owner.def_id,
+ cx.param_env,
+ cx.typeck_results(),
+ )
+ .walk_expr(expr);
+
+ if delegate.skip {
+ return None;
+ }
+ Some(delegate.used_mutably)
+}
+
+pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
+ mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
+}
+
+struct MutVarsDelegate {
+ used_mutably: HirIdSet,
+ skip: bool,
+}
+
+impl<'tcx> MutVarsDelegate {
+ fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
+ match cat.place.base {
+ PlaceBase::Local(id) => {
+ self.used_mutably.insert(id);
+ },
+ PlaceBase::Upvar(_) => {
+ //FIXME: This causes false negatives. We can't get the `NodeId` from
+ //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
+ //`while`-body, not just the ones in the condition.
+ self.skip = true;
+ },
+ _ => {},
+ }
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
+ if bk == ty::BorrowKind::MutBorrow {
+ self.update(cmt);
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ self.update(cmt);
+ }
+
++ fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+pub struct ParamBindingIdCollector {
+ pub binding_hir_ids: Vec<hir::HirId>,
+}
+impl<'tcx> ParamBindingIdCollector {
+ fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
+ let mut hir_ids: Vec<hir::HirId> = Vec::new();
+ for param in body.params.iter() {
+ let mut finder = ParamBindingIdCollector {
+ binding_hir_ids: Vec::new(),
+ };
+ finder.visit_param(param);
+ for hir_id in &finder.binding_hir_ids {
+ hir_ids.push(*hir_id);
+ }
+ }
+ hir_ids
+ }
+}
+impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
+ self.binding_hir_ids.push(hir_id);
+ }
+ intravisit::walk_pat(self, pat);
+ }
+}
+
+pub struct BindingUsageFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ binding_ids: Vec<hir::HirId>,
+ usage_found: bool,
+}
+impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
+ pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
+ let mut finder = BindingUsageFinder {
+ cx,
+ binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
+ usage_found: false,
+ };
+ finder.visit_body(body);
+ finder.usage_found
+ }
+}
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if !self.usage_found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ if let hir::def::Res::Local(id) = path.res {
+ if self.binding_ids.contains(&id) {
+ self.usage_found = true;
+ }
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
+ for_each_expr(expression, |e| {
+ match e.kind {
+ ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
+ // Something special could be done here to handle while or for loop
+ // desugaring, as this will detect a break if there's a while loop
+ // or a for loop inside the expression.
+ _ if e.span.from_expansion() => ControlFlow::Break(()),
+ _ => ControlFlow::Continue(()),
+ }
+ })
+ .is_some()
+}
+
+pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
+ let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false };
+
+ // for _ in 1..3 {
+ // local
+ // }
+ //
+ // let closure = || local;
+ // closure();
+ // closure();
+ let in_loop_or_closure = cx
+ .tcx
+ .hir()
+ .parent_iter(after.hir_id)
+ .take_while(|&(id, _)| id != block.hir_id)
+ .any(|(_, node)| {
+ matches!(
+ node,
+ Node::Expr(Expr {
+ kind: ExprKind::Loop(..) | ExprKind::Closure { .. },
+ ..
+ })
+ )
+ });
+ if in_loop_or_closure {
+ return true;
+ }
+
+ let mut past_expr = false;
+ for_each_expr_with_closures(cx, block, |e| {
+ if e.hir_id == after.hir_id {
+ past_expr = true;
+ ControlFlow::Continue(Descend::No)
+ } else if past_expr && utils::path_to_local_id(e, local_id) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(Descend::Yes)
+ }
+ })
+ .is_some()
+}
--- /dev/null
--- /dev/null
++[package]
++name = "declare_clippy_lint"
++version = "0.1.67"
++edition = "2021"
++publish = false
++
++[lib]
++proc-macro = true
++
++[dependencies]
++itertools = "0.10.1"
++quote = "1.0.21"
++syn = "1.0.100"
--- /dev/null
--- /dev/null
++#![feature(let_chains)]
++#![cfg_attr(feature = "deny-warnings", deny(warnings))]
++
++use proc_macro::TokenStream;
++use quote::{format_ident, quote};
++use syn::parse::{Parse, ParseStream};
++use syn::{parse_macro_input, Attribute, Error, Ident, Lit, LitStr, Meta, Result, Token};
++
++fn parse_attr<const LEN: usize>(path: [&'static str; LEN], attr: &Attribute) -> Option<LitStr> {
++ if let Meta::NameValue(name_value) = attr.parse_meta().ok()? {
++ let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident);
++
++ if itertools::equal(path_idents, path)
++ && let Lit::Str(lit) = name_value.lit
++ {
++ return Some(lit);
++ }
++ }
++
++ None
++}
++
++struct ClippyLint {
++ attrs: Vec<Attribute>,
++ explanation: String,
++ name: Ident,
++ category: Ident,
++ description: LitStr,
++}
++
++impl Parse for ClippyLint {
++ fn parse(input: ParseStream) -> Result<Self> {
++ let attrs = input.call(Attribute::parse_outer)?;
++
++ let mut in_code = false;
++ let mut explanation = String::new();
++ let mut version = None;
++ for attr in &attrs {
++ if let Some(lit) = parse_attr(["doc"], attr) {
++ let value = lit.value();
++ let line = value.strip_prefix(' ').unwrap_or(&value);
++
++ if line.starts_with("```") {
++ explanation += "```\n";
++ in_code = !in_code;
++ } else if !(in_code && line.starts_with("# ")) {
++ explanation += line;
++ explanation.push('\n');
++ }
++ } else if let Some(lit) = parse_attr(["clippy", "version"], attr) {
++ if let Some(duplicate) = version.replace(lit) {
++ return Err(Error::new_spanned(duplicate, "duplicate clippy::version"));
++ }
++ } else {
++ return Err(Error::new_spanned(attr, "unexpected attribute"));
++ }
++ }
++
++ input.parse::<Token![pub]>()?;
++ let name = input.parse()?;
++ input.parse::<Token![,]>()?;
++
++ let category = input.parse()?;
++ input.parse::<Token![,]>()?;
++
++ let description = input.parse()?;
++
++ Ok(Self {
++ attrs,
++ explanation,
++ name,
++ category,
++ description,
++ })
++ }
++}
++
++/// Macro used to declare a Clippy lint.
++///
++/// Every lint declaration consists of 4 parts:
++///
++/// 1. The documentation, which is used for the website and `cargo clippy --explain`
++/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
++/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
++/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
++/// 4. The `description` that contains a short explanation on what's wrong with code where the
++/// lint is triggered.
++///
++/// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are
++/// enabled by default. As said in the README.md of this repository, if the lint level mapping
++/// changes, please update README.md.
++///
++/// # Example
++///
++/// ```
++/// use rustc_session::declare_tool_lint;
++///
++/// declare_clippy_lint! {
++/// /// ### What it does
++/// /// Checks for ... (describe what the lint matches).
++/// ///
++/// /// ### Why is this bad?
++/// /// Supply the reason for linting the code.
++/// ///
++/// /// ### Example
++/// /// ```rust
++/// /// Insert a short example of code that triggers the lint
++/// /// ```
++/// ///
++/// /// Use instead:
++/// /// ```rust
++/// /// Insert a short example of improved code that doesn't trigger the lint
++/// /// ```
++/// #[clippy::version = "1.65.0"]
++/// pub LINT_NAME,
++/// pedantic,
++/// "description"
++/// }
++/// ```
++/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
++#[proc_macro]
++pub fn declare_clippy_lint(input: TokenStream) -> TokenStream {
++ let ClippyLint {
++ attrs,
++ explanation,
++ name,
++ category,
++ description,
++ } = parse_macro_input!(input as ClippyLint);
++
++ let mut category = category.to_string();
++
++ let level = format_ident!(
++ "{}",
++ match category.as_str() {
++ "correctness" => "Deny",
++ "style" | "suspicious" | "complexity" | "perf" | "internal_warn" => "Warn",
++ "pedantic" | "restriction" | "cargo" | "nursery" | "internal" => "Allow",
++ _ => panic!("unknown category {category}"),
++ },
++ );
++
++ let info = if category == "internal_warn" {
++ None
++ } else {
++ let info_name = format_ident!("{name}_INFO");
++
++ (&mut category[0..1]).make_ascii_uppercase();
++ let category_variant = format_ident!("{category}");
++
++ Some(quote! {
++ pub(crate) static #info_name: &'static crate::LintInfo = &crate::LintInfo {
++ lint: &#name,
++ category: crate::LintCategory::#category_variant,
++ explanation: #explanation,
++ };
++ })
++ };
++
++ let output = quote! {
++ declare_tool_lint! {
++ #(#attrs)*
++ pub clippy::#name,
++ #level,
++ #description,
++ report_in_external_macro: true
++ }
++
++ #info
++ };
++
++ TokenStream::from(output)
++}
--- /dev/null
- /// check if the latest modification of the logfile is older than the modification date of the
- /// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck
- fn lintcheck_needs_rerun(lintcheck_logs_path: &Path, paths: [&Path; 2]) -> bool {
- if !lintcheck_logs_path.exists() {
- return true;
- }
-
- let clippy_modified: std::time::SystemTime = {
- let [cargo, driver] = paths.map(|p| {
- std::fs::metadata(p)
- .expect("failed to get metadata of file")
- .modified()
- .expect("failed to get modification date")
- });
- // the oldest modification of either of the binaries
- std::cmp::max(cargo, driver)
- };
-
- let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_logs_path)
- .expect("failed to get metadata of file")
- .modified()
- .expect("failed to get modification date");
-
- // time is represented in seconds since X
- // logs_modified 2 and clippy_modified 5 means clippy binary is older and we need to recheck
- logs_modified < clippy_modified
- }
-
+// Run clippy on a fixed set of crates and collect the warnings.
+// This helps observing the impact clippy changes have on a set of real-world code (and not just our
+// testsuite).
+//
+// When a new lint is introduced, we can search the results for new warnings and check for false
+// positives.
+
+#![allow(clippy::collapsible_else_if)]
+
+mod config;
+mod driver;
+mod recursive;
+
+use crate::config::LintcheckConfig;
+use crate::recursive::LintcheckServer;
+
+use std::collections::{HashMap, HashSet};
+use std::env;
+use std::env::consts::EXE_SUFFIX;
+use std::fmt::Write as _;
+use std::fs;
+use std::io::ErrorKind;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread;
+use std::time::Duration;
+
+use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
+use cargo_metadata::Message;
+use rayon::prelude::*;
+use serde::{Deserialize, Serialize};
+use walkdir::{DirEntry, WalkDir};
+
+const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
+const LINTCHECK_SOURCES: &str = "target/lintcheck/sources";
+
+/// List of sources to check, loaded from a .toml file
+#[derive(Debug, Serialize, Deserialize)]
+struct SourceList {
+ crates: HashMap<String, TomlCrate>,
+ #[serde(default)]
+ recursive: RecursiveOptions,
+}
+
+#[derive(Debug, Serialize, Deserialize, Default)]
+struct RecursiveOptions {
+ ignore: HashSet<String>,
+}
+
+/// A crate source stored inside the .toml
+/// will be translated into on one of the `CrateSource` variants
+#[derive(Debug, Serialize, Deserialize)]
+struct TomlCrate {
+ name: String,
+ versions: Option<Vec<String>>,
+ git_url: Option<String>,
+ git_hash: Option<String>,
+ path: Option<String>,
+ options: Option<Vec<String>>,
+}
+
+/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
+/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
+#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)]
+enum CrateSource {
+ CratesIo {
+ name: String,
+ version: String,
+ options: Option<Vec<String>>,
+ },
+ Git {
+ name: String,
+ url: String,
+ commit: String,
+ options: Option<Vec<String>>,
+ },
+ Path {
+ name: String,
+ path: PathBuf,
+ options: Option<Vec<String>>,
+ },
+}
+
+/// Represents the actual source code of a crate that we ran "cargo clippy" on
+#[derive(Debug)]
+struct Crate {
+ version: String,
+ name: String,
+ // path to the extracted sources that clippy can check
+ path: PathBuf,
+ options: Option<Vec<String>>,
+}
+
+/// A single warning that clippy issued while checking a `Crate`
+#[derive(Debug)]
+struct ClippyWarning {
+ crate_name: String,
+ file: String,
+ line: usize,
+ column: usize,
+ lint_type: String,
+ message: String,
+ is_ice: bool,
+}
+
+#[allow(unused)]
+impl ClippyWarning {
+ fn new(diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option<Self> {
+ let lint_type = diag.code?.code;
+ if !(lint_type.contains("clippy") || diag.message.contains("clippy"))
+ || diag.message.contains("could not read cargo metadata")
+ {
+ return None;
+ }
+
+ let span = diag.spans.into_iter().find(|span| span.is_primary)?;
+
+ let file = if let Ok(stripped) = Path::new(&span.file_name).strip_prefix(env!("CARGO_HOME")) {
+ format!("$CARGO_HOME/{}", stripped.display())
+ } else {
+ format!(
+ "target/lintcheck/sources/{}-{}/{}",
+ crate_name, crate_version, span.file_name
+ )
+ };
+
+ Some(Self {
+ crate_name: crate_name.to_owned(),
+ file,
+ line: span.line_start,
+ column: span.column_start,
+ lint_type,
+ message: diag.message,
+ is_ice: diag.level == DiagnosticLevel::Ice,
+ })
+ }
+
+ fn to_output(&self, markdown: bool) -> String {
+ let file_with_pos = format!("{}:{}:{}", &self.file, &self.line, &self.column);
+ if markdown {
+ let mut file = self.file.clone();
+ if !file.starts_with('$') {
+ file.insert_str(0, "../");
+ }
+
+ let mut output = String::from("| ");
+ let _ = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line);
+ let _ = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message);
+ output.push('\n');
+ output
+ } else {
+ format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.message)
+ }
+ }
+}
+
+#[allow(clippy::result_large_err)]
+fn get(path: &str) -> Result<ureq::Response, ureq::Error> {
+ const MAX_RETRIES: u8 = 4;
+ let mut retries = 0;
+ loop {
+ match ureq::get(path).call() {
+ Ok(res) => return Ok(res),
+ Err(e) if retries >= MAX_RETRIES => return Err(e),
+ Err(ureq::Error::Transport(e)) => eprintln!("Error: {e}"),
+ Err(e) => return Err(e),
+ }
+ eprintln!("retrying in {retries} seconds...");
+ thread::sleep(Duration::from_secs(u64::from(retries)));
+ retries += 1;
+ }
+}
+
+impl CrateSource {
+ /// Makes the sources available on the disk for clippy to check.
+ /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
+ /// copies a local folder
+ fn download_and_extract(&self) -> Crate {
+ match self {
+ CrateSource::CratesIo { name, version, options } => {
+ let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
+ let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
+
+ // url to download the crate from crates.io
+ let url = format!("https://crates.io/api/v1/crates/{name}/{version}/download");
+ println!("Downloading and extracting {name} {version} from {url}");
+ create_dirs(&krate_download_dir, &extract_dir);
+
+ let krate_file_path = krate_download_dir.join(format!("{name}-{version}.crate.tar.gz"));
+ // don't download/extract if we already have done so
+ if !krate_file_path.is_file() {
+ // create a file path to download and write the crate data into
+ let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap();
+ let mut krate_req = get(&url).unwrap().into_reader();
+ // copy the crate into the file
+ std::io::copy(&mut krate_req, &mut krate_dest).unwrap();
+
+ // unzip the tarball
+ let ungz_tar = flate2::read::GzDecoder::new(std::fs::File::open(&krate_file_path).unwrap());
+ // extract the tar archive
+ let mut archive = tar::Archive::new(ungz_tar);
+ archive.unpack(&extract_dir).expect("Failed to extract!");
+ }
+ // crate is extracted, return a new Krate object which contains the path to the extracted
+ // sources that clippy can check
+ Crate {
+ version: version.clone(),
+ name: name.clone(),
+ path: extract_dir.join(format!("{name}-{version}/")),
+ options: options.clone(),
+ }
+ },
+ CrateSource::Git {
+ name,
+ url,
+ commit,
+ options,
+ } => {
+ let repo_path = {
+ let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
+ // add a -git suffix in case we have the same crate from crates.io and a git repo
+ repo_path.push(format!("{name}-git"));
+ repo_path
+ };
+ // clone the repo if we have not done so
+ if !repo_path.is_dir() {
+ println!("Cloning {url} and checking out {commit}");
+ if !Command::new("git")
+ .arg("clone")
+ .arg(url)
+ .arg(&repo_path)
+ .status()
+ .expect("Failed to clone git repo!")
+ .success()
+ {
+ eprintln!("Failed to clone {url} into {}", repo_path.display());
+ }
+ }
+ // check out the commit/branch/whatever
+ if !Command::new("git")
+ .args(["-c", "advice.detachedHead=false"])
+ .arg("checkout")
+ .arg(commit)
+ .current_dir(&repo_path)
+ .status()
+ .expect("Failed to check out commit")
+ .success()
+ {
+ eprintln!("Failed to checkout {commit} of repo at {}", repo_path.display());
+ }
+
+ Crate {
+ version: commit.clone(),
+ name: name.clone(),
+ path: repo_path,
+ options: options.clone(),
+ }
+ },
+ CrateSource::Path { name, path, options } => {
+ fn is_cache_dir(entry: &DirEntry) -> bool {
+ std::fs::read(entry.path().join("CACHEDIR.TAG"))
+ .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
+ .unwrap_or(false)
+ }
+
+ // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
+ // The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
+ // as a result of this filter.
+ let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
+ if dest_crate_root.exists() {
+ println!("Deleting existing directory at {dest_crate_root:?}");
+ std::fs::remove_dir_all(&dest_crate_root).unwrap();
+ }
+
+ println!("Copying {path:?} to {dest_crate_root:?}");
+
+ for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
+ let entry = entry.unwrap();
+ let entry_path = entry.path();
+ let relative_entry_path = entry_path.strip_prefix(path).unwrap();
+ let dest_path = dest_crate_root.join(relative_entry_path);
+ let metadata = entry_path.symlink_metadata().unwrap();
+
+ if metadata.is_dir() {
+ std::fs::create_dir(dest_path).unwrap();
+ } else if metadata.is_file() {
+ std::fs::copy(entry_path, dest_path).unwrap();
+ }
+ }
+
+ Crate {
+ version: String::from("local"),
+ name: name.clone(),
+ path: dest_crate_root,
+ options: options.clone(),
+ }
+ },
+ }
+ }
+}
+
+impl Crate {
+ /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
+ /// issued
+ #[allow(clippy::too_many_arguments)]
+ fn run_clippy_lints(
+ &self,
+ cargo_clippy_path: &Path,
+ clippy_driver_path: &Path,
+ target_dir_index: &AtomicUsize,
+ total_crates_to_lint: usize,
+ config: &LintcheckConfig,
+ lint_filter: &Vec<String>,
+ server: &Option<LintcheckServer>,
+ ) -> Vec<ClippyWarning> {
+ // advance the atomic index by one
+ let index = target_dir_index.fetch_add(1, Ordering::SeqCst);
+ // "loop" the index within 0..thread_limit
+ let thread_index = index % config.max_jobs;
+ let perc = (index * 100) / total_crates_to_lint;
+
+ if config.max_jobs == 1 {
+ println!(
+ "{}/{} {}% Linting {} {}",
+ index, total_crates_to_lint, perc, &self.name, &self.version
+ );
+ } else {
+ println!(
+ "{}/{} {}% Linting {} {} in target dir {:?}",
+ index, total_crates_to_lint, perc, &self.name, &self.version, thread_index
+ );
+ }
+
+ let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
+
+ let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir");
+
+ let mut cargo_clippy_args = if config.fix {
+ vec!["--fix", "--"]
+ } else {
+ vec!["--", "--message-format=json", "--"]
+ };
+
+ let mut clippy_args = Vec::<&str>::new();
+ if let Some(options) = &self.options {
+ for opt in options {
+ clippy_args.push(opt);
+ }
+ } else {
+ clippy_args.extend(["-Wclippy::pedantic", "-Wclippy::cargo"]);
+ }
+
+ if lint_filter.is_empty() {
+ clippy_args.push("--cap-lints=warn");
+ } else {
+ clippy_args.push("--cap-lints=allow");
+ clippy_args.extend(lint_filter.iter().map(std::string::String::as_str));
+ }
+
+ if let Some(server) = server {
+ let target = shared_target_dir.join("recursive");
+
+ // `cargo clippy` is a wrapper around `cargo check` that mainly sets `RUSTC_WORKSPACE_WRAPPER` to
+ // `clippy-driver`. We do the same thing here with a couple changes:
+ //
+ // `RUSTC_WRAPPER` is used instead of `RUSTC_WORKSPACE_WRAPPER` so that we can lint all crate
+ // dependencies rather than only workspace members
+ //
+ // The wrapper is set to the `lintcheck` so we can force enable linting and ignore certain crates
+ // (see `crate::driver`)
+ let status = Command::new("cargo")
+ .arg("check")
+ .arg("--quiet")
+ .current_dir(&self.path)
+ .env("CLIPPY_ARGS", clippy_args.join("__CLIPPY_HACKERY__"))
+ .env("CARGO_TARGET_DIR", target)
+ .env("RUSTC_WRAPPER", env::current_exe().unwrap())
+ // Pass the absolute path so `crate::driver` can find `clippy-driver`, as it's executed in various
+ // different working directories
+ .env("CLIPPY_DRIVER", clippy_driver_path)
+ .env("LINTCHECK_SERVER", server.local_addr.to_string())
+ .status()
+ .expect("failed to run cargo");
+
+ assert_eq!(status.code(), Some(0));
+
+ return Vec::new();
+ }
+
+ cargo_clippy_args.extend(clippy_args);
+
+ let all_output = Command::new(&cargo_clippy_path)
+ // use the looping index to create individual target dirs
+ .env("CARGO_TARGET_DIR", shared_target_dir.join(format!("_{thread_index:?}")))
+ .args(&cargo_clippy_args)
+ .current_dir(&self.path)
+ .output()
+ .unwrap_or_else(|error| {
+ panic!(
+ "Encountered error:\n{:?}\ncargo_clippy_path: {}\ncrate path:{}\n",
+ error,
+ &cargo_clippy_path.display(),
+ &self.path.display()
+ );
+ });
+ let stdout = String::from_utf8_lossy(&all_output.stdout);
+ let stderr = String::from_utf8_lossy(&all_output.stderr);
+ let status = &all_output.status;
+
+ if !status.success() {
+ eprintln!(
+ "\nWARNING: bad exit status after checking {} {} \n",
+ self.name, self.version
+ );
+ }
+
+ if config.fix {
+ if let Some(stderr) = stderr
+ .lines()
+ .find(|line| line.contains("failed to automatically apply fixes suggested by rustc to crate"))
+ {
+ let subcrate = &stderr[63..];
+ println!(
+ "ERROR: failed to apply some suggetion to {} / to (sub)crate {subcrate}",
+ self.name
+ );
+ }
+ // fast path, we don't need the warnings anyway
+ return Vec::new();
+ }
+
+ // get all clippy warnings and ICEs
+ let warnings: Vec<ClippyWarning> = Message::parse_stream(stdout.as_bytes())
+ .filter_map(|msg| match msg {
+ Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message, &self.name, &self.version),
+ _ => None,
+ })
+ .collect();
+
+ warnings
+ }
+}
+
+/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
+fn build_clippy() {
+ let status = Command::new("cargo")
+ .arg("build")
+ .status()
+ .expect("Failed to build clippy!");
+ if !status.success() {
+ eprintln!("Error: Failed to compile Clippy!");
+ std::process::exit(1);
+ }
+}
+
+/// Read a `lintcheck_crates.toml` file
+fn read_crates(toml_path: &Path) -> (Vec<CrateSource>, RecursiveOptions) {
+ let toml_content: String =
+ std::fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
+ let crate_list: SourceList =
+ toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{e}", toml_path.display()));
+ // parse the hashmap of the toml file into a list of crates
+ let tomlcrates: Vec<TomlCrate> = crate_list.crates.into_values().collect();
+
+ // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
+ // multiple Cratesources)
+ let mut crate_sources = Vec::new();
+ for tk in tomlcrates {
+ if let Some(ref path) = tk.path {
+ crate_sources.push(CrateSource::Path {
+ name: tk.name.clone(),
+ path: PathBuf::from(path),
+ options: tk.options.clone(),
+ });
+ } else if let Some(ref versions) = tk.versions {
+ // if we have multiple versions, save each one
+ for ver in versions.iter() {
+ crate_sources.push(CrateSource::CratesIo {
+ name: tk.name.clone(),
+ version: ver.to_string(),
+ options: tk.options.clone(),
+ });
+ }
+ } else if tk.git_url.is_some() && tk.git_hash.is_some() {
+ // otherwise, we should have a git source
+ crate_sources.push(CrateSource::Git {
+ name: tk.name.clone(),
+ url: tk.git_url.clone().unwrap(),
+ commit: tk.git_hash.clone().unwrap(),
+ options: tk.options.clone(),
+ });
+ } else {
+ panic!("Invalid crate source: {tk:?}");
+ }
+
+ // if we have a version as well as a git data OR only one git data, something is funky
+ if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some())
+ || tk.git_hash.is_some() != tk.git_url.is_some()
+ {
+ eprintln!("tomlkrate: {tk:?}");
+ assert_eq!(
+ tk.git_hash.is_some(),
+ tk.git_url.is_some(),
+ "Error: Encountered TomlCrate with only one of git_hash and git_url!"
+ );
+ assert!(
+ tk.path.is_none() || (tk.git_hash.is_none() && tk.versions.is_none()),
+ "Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields"
+ );
+ unreachable!("Failed to translate TomlCrate into CrateSource!");
+ }
+ }
+ // sort the crates
+ crate_sources.sort();
+
+ (crate_sources, crate_list.recursive)
+}
+
+/// Generate a short list of occurring lints-types and their count
+fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
+ // count lint type occurrences
+ let mut counter: HashMap<&String, usize> = HashMap::new();
+ clippy_warnings
+ .iter()
+ .for_each(|wrn| *counter.entry(&wrn.lint_type).or_insert(0) += 1);
+
+ // collect into a tupled list for sorting
+ let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
+ // sort by "000{count} {clippy::lintname}"
+ // to not have a lint with 200 and 2 warnings take the same spot
+ stats.sort_by_key(|(lint, count)| format!("{count:0>4}, {lint}"));
+
+ let mut header = String::from("| lint | count |\n");
+ header.push_str("| -------------------------------------------------- | ----- |\n");
+ let stats_string = stats
+ .iter()
+ .map(|(lint, count)| format!("| {lint:<50} | {count:>4} |\n"))
+ .fold(header, |mut table, line| {
+ table.push_str(&line);
+ table
+ });
+
+ (stats_string, counter)
+}
+
- // if the clippy bin is newer than our logs, throw away target dirs to force clippy to
- // refresh the logs
- if lintcheck_needs_rerun(
- &config.lintcheck_results_path,
- [&cargo_clippy_path, &clippy_driver_path],
- ) {
- let shared_target_dir = "target/lintcheck/shared_target_dir";
- // if we get an Err here, the shared target dir probably does simply not exist
- if let Ok(metadata) = std::fs::metadata(shared_target_dir) {
- if metadata.is_dir() {
- println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir...");
- std::fs::remove_dir_all(shared_target_dir)
- .expect("failed to remove target/lintcheck/shared_target_dir");
- }
- }
- }
-
+#[allow(clippy::too_many_lines)]
+fn main() {
+ // We're being executed as a `RUSTC_WRAPPER` as part of `--recursive`
+ if let Ok(addr) = env::var("LINTCHECK_SERVER") {
+ driver::drive(&addr);
+ }
+
+ // assert that we launch lintcheck from the repo root (via cargo lintcheck)
+ if std::fs::metadata("lintcheck/Cargo.toml").is_err() {
+ eprintln!("lintcheck needs to be run from clippy's repo root!\nUse `cargo lintcheck` alternatively.");
+ std::process::exit(3);
+ }
+
+ let config = LintcheckConfig::new();
+
+ println!("Compiling clippy...");
+ build_clippy();
+ println!("Done compiling");
+
+ let cargo_clippy_path = fs::canonicalize(format!("target/debug/cargo-clippy{EXE_SUFFIX}")).unwrap();
+ let clippy_driver_path = fs::canonicalize(format!("target/debug/clippy-driver{EXE_SUFFIX}")).unwrap();
+
- fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive").unwrap_or_default();
+ // assert that clippy is found
+ assert!(
+ cargo_clippy_path.is_file(),
+ "target/debug/cargo-clippy binary not found! {}",
+ cargo_clippy_path.display()
+ );
+
+ let clippy_ver = std::process::Command::new(&cargo_clippy_path)
+ .arg("--version")
+ .output()
+ .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
+ .expect("could not get clippy version!");
+
+ // download and extract the crates, then run clippy on them and collect clippy's warnings
+ // flatten into one big list of warnings
+
+ let (crates, recursive_options) = read_crates(&config.sources_toml_path);
+ let old_stats = read_stats_from_file(&config.lintcheck_results_path);
+
+ let counter = AtomicUsize::new(1);
+ let lint_filter: Vec<String> = config
+ .lint_filter
+ .iter()
+ .map(|filter| {
+ let mut filter = filter.clone();
+ filter.insert_str(0, "--force-warn=");
+ filter
+ })
+ .collect();
+
+ let crates: Vec<Crate> = crates
+ .into_iter()
+ .filter(|krate| {
+ if let Some(only_one_crate) = &config.only {
+ let name = match krate {
+ CrateSource::CratesIo { name, .. }
+ | CrateSource::Git { name, .. }
+ | CrateSource::Path { name, .. } => name,
+ };
+
+ name == only_one_crate
+ } else {
+ true
+ }
+ })
+ .map(|krate| krate.download_and_extract())
+ .collect();
+
+ if crates.is_empty() {
+ eprintln!(
+ "ERROR: could not find crate '{}' in lintcheck/lintcheck_crates.toml",
+ config.only.unwrap(),
+ );
+ std::process::exit(1);
+ }
+
+ // run parallel with rayon
+
+ // This helps when we check many small crates with dep-trees that don't have a lot of branches in
+ // order to achieve some kind of parallelism
+
+ rayon::ThreadPoolBuilder::new()
+ .num_threads(config.max_jobs)
+ .build_global()
+ .unwrap();
+
+ let server = config.recursive.then(|| {
++ let _ = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive");
+
+ LintcheckServer::spawn(recursive_options)
+ });
+
+ let mut clippy_warnings: Vec<ClippyWarning> = crates
+ .par_iter()
+ .flat_map(|krate| {
+ krate.run_clippy_lints(
+ &cargo_clippy_path,
+ &clippy_driver_path,
+ &counter,
+ crates.len(),
+ &config,
+ &lint_filter,
+ &server,
+ )
+ })
+ .collect();
+
+ if let Some(server) = server {
+ clippy_warnings.extend(server.warnings());
+ }
+
+ // if we are in --fix mode, don't change the log files, terminate here
+ if config.fix {
+ return;
+ }
+
+ // generate some stats
+ let (stats_formatted, new_stats) = gather_stats(&clippy_warnings);
+
+ // grab crashes/ICEs, save the crate name and the ice message
+ let ices: Vec<(&String, &String)> = clippy_warnings
+ .iter()
+ .filter(|warning| warning.is_ice)
+ .map(|w| (&w.crate_name, &w.message))
+ .collect();
+
+ let mut all_msgs: Vec<String> = clippy_warnings
+ .iter()
+ .map(|warn| warn.to_output(config.markdown))
+ .collect();
+ all_msgs.sort();
+ all_msgs.push("\n\n### Stats:\n\n".into());
+ all_msgs.push(stats_formatted);
+
+ // save the text into lintcheck-logs/logs.txt
+ let mut text = clippy_ver; // clippy version number on top
+ text.push_str("\n### Reports\n\n");
+ if config.markdown {
+ text.push_str("| file | lint | message |\n");
+ text.push_str("| --- | --- | --- |\n");
+ }
+ write!(text, "{}", all_msgs.join("")).unwrap();
+ text.push_str("\n\n### ICEs:\n");
+ for (cratename, msg) in &ices {
+ let _ = write!(text, "{cratename}: '{msg}'");
+ }
+
+ println!("Writing logs to {}", config.lintcheck_results_path.display());
+ fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap();
+ fs::write(&config.lintcheck_results_path, text).unwrap();
+
+ print_stats(old_stats, new_stats, &config.lint_filter);
+}
+
+/// read the previous stats from the lintcheck-log file
+fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
+ let file_content: String = match std::fs::read_to_string(file_path).ok() {
+ Some(content) => content,
+ None => {
+ return HashMap::new();
+ },
+ };
+
+ let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
+
+ lines
+ .iter()
+ .skip_while(|line| line.as_str() != "### Stats:")
+ // Skipping the table header and the `Stats:` label
+ .skip(4)
+ .take_while(|line| line.starts_with("| "))
+ .filter_map(|line| {
+ let mut spl = line.split('|');
+ // Skip the first `|` symbol
+ spl.next();
+ if let (Some(lint), Some(count)) = (spl.next(), spl.next()) {
+ Some((lint.trim().to_string(), count.trim().parse::<usize>().unwrap()))
+ } else {
+ None
+ }
+ })
+ .collect::<HashMap<String, usize>>()
+}
+
+/// print how lint counts changed between runs
+fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &Vec<String>) {
+ let same_in_both_hashmaps = old_stats
+ .iter()
+ .filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val))
+ .map(|(k, v)| (k.to_string(), *v))
+ .collect::<Vec<(String, usize)>>();
+
+ let mut old_stats_deduped = old_stats;
+ let mut new_stats_deduped = new_stats;
+
+ // remove duplicates from both hashmaps
+ for (k, v) in &same_in_both_hashmaps {
+ assert!(old_stats_deduped.remove(k) == Some(*v));
+ assert!(new_stats_deduped.remove(k) == Some(*v));
+ }
+
+ println!("\nStats:");
+
+ // list all new counts (key is in new stats but not in old stats)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _)| old_stats_deduped.get::<str>(new_key).is_none())
+ .for_each(|(new_key, new_value)| {
+ println!("{new_key} 0 => {new_value}");
+ });
+
+ // list all changed counts (key is in both maps but value differs)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(new_key).is_some())
+ .for_each(|(new_key, new_val)| {
+ let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
+ println!("{new_key} {old_val} => {new_val}");
+ });
+
+ // list all gone counts (key is in old status but not in new stats)
+ old_stats_deduped
+ .iter()
+ .filter(|(old_key, _)| new_stats_deduped.get::<&String>(old_key).is_none())
+ .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
+ .for_each(|(old_key, old_value)| {
+ println!("{old_key} {old_value} => 0");
+ });
+}
+
+/// Create necessary directories to run the lintcheck tool.
+///
+/// # Panics
+///
+/// This function panics if creating one of the dirs fails.
+fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
+ std::fs::create_dir("target/lintcheck/").unwrap_or_else(|err| {
+ assert_eq!(
+ err.kind(),
+ ErrorKind::AlreadyExists,
+ "cannot create lintcheck target dir"
+ );
+ });
+ std::fs::create_dir(krate_download_dir).unwrap_or_else(|err| {
+ assert_eq!(err.kind(), ErrorKind::AlreadyExists, "cannot create crate download dir");
+ });
+ std::fs::create_dir(extract_dir).unwrap_or_else(|err| {
+ assert_eq!(
+ err.kind(),
+ ErrorKind::AlreadyExists,
+ "cannot create crate extraction dir"
+ );
+ });
+}
+
+/// Returns the path to the Clippy project directory
+#[must_use]
+fn clippy_project_root() -> &'static Path {
+ Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()
+}
+
+#[test]
+fn lintcheck_test() {
+ let args = [
+ "run",
+ "--target-dir",
+ "lintcheck/target",
+ "--manifest-path",
+ "./lintcheck/Cargo.toml",
+ "--",
+ "--crates-toml",
+ "lintcheck/test_sources.toml",
+ ];
+ let status = std::process::Command::new("cargo")
+ .args(args)
+ .current_dir("..") // repo root
+ .status();
+ //.output();
+
+ assert!(status.unwrap().success());
+}
--- /dev/null
- channel = "nightly-2022-10-20"
- components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
+[toolchain]
++channel = "nightly-2022-11-21"
++components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
--- /dev/null
- let conf = clippy_lints::read_conf(sess);
+#![feature(rustc_private)]
++#![feature(let_chains)]
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// 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_driver;
+extern crate rustc_errors;
+extern crate rustc_interface;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_interface::interface;
+use rustc_session::parse::ParseSess;
+use rustc_span::symbol::Symbol;
+use rustc_tools_util::VersionInfo;
+
+use std::borrow::Cow;
+use std::env;
+use std::ops::Deref;
+use std::panic;
+use std::path::Path;
+use std::process::exit;
+use std::sync::LazyLock;
+
+/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
+/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
+fn arg_value<'a, T: Deref<Target = str>>(
+ args: &'a [T],
+ find_arg: &str,
+ pred: impl Fn(&str) -> bool,
+) -> Option<&'a str> {
+ let mut args = args.iter().map(Deref::deref);
+ while let Some(arg) = args.next() {
+ let mut arg = arg.splitn(2, '=');
+ if arg.next() != Some(find_arg) {
+ continue;
+ }
+
+ match arg.next().or_else(|| args.next()) {
+ Some(v) if pred(v) => return Some(v),
+ _ => {},
+ }
+ }
+ None
+}
+
+#[test]
+fn test_arg_value() {
+ let args = &["--bar=bar", "--foobar", "123", "--foo"];
+
+ assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None);
+ assert_eq!(arg_value(args, "--bar", |_| false), None);
+ assert_eq!(arg_value(args, "--bar", |_| true), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123"));
+ assert_eq!(arg_value(args, "--foobar", |p| p.contains("12")), Some("123"));
+ assert_eq!(arg_value(args, "--foo", |_| true), None);
+}
+
+fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option<String>) {
+ parse_sess.env_depinfo.get_mut().insert((
+ Symbol::intern("CLIPPY_ARGS"),
+ args_env_var.as_deref().map(Symbol::intern),
+ ));
+}
+
++/// Track files that may be accessed at runtime in `file_depinfo` so that cargo will re-run clippy
++/// when any of them are modified
++fn track_files(parse_sess: &mut ParseSess, conf_path_string: Option<String>) {
++ let file_depinfo = parse_sess.file_depinfo.get_mut();
++
++ // Used by `clippy::cargo` lints and to determine the MSRV. `cargo clippy` executes `clippy-driver`
++ // with the current directory set to `CARGO_MANIFEST_DIR` so a relative path is fine
++ if Path::new("Cargo.toml").exists() {
++ file_depinfo.insert(Symbol::intern("Cargo.toml"));
++ }
++
++ // `clippy.toml`
++ if let Some(path) = conf_path_string {
++ file_depinfo.insert(Symbol::intern(&path));
++ }
++
++ // During development track the `clippy-driver` executable so that cargo will re-run clippy whenever
++ // it is rebuilt
++ if cfg!(debug_assertions)
++ && let Ok(current_exe) = env::current_exe()
++ && let Some(current_exe) = current_exe.to_str()
++ {
++ file_depinfo.insert(Symbol::intern(current_exe));
++ }
++}
++
+struct DefaultCallbacks;
+impl rustc_driver::Callbacks for DefaultCallbacks {}
+
+/// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of
+/// `CLIPPY_ARGS` environment variable.
+struct RustcCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for RustcCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
+ }));
+ }
+}
+
+struct ClippyCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for ClippyCallbacks {
+ // JUSTIFICATION: necessary in clippy driver to set `mir_opt_level`
+ #[allow(rustc::bad_opt_access)]
+ fn config(&mut self, config: &mut interface::Config) {
++ let conf_path = clippy_lints::lookup_conf_file();
++ let conf_path_string = if let Ok(Some(path)) = &conf_path {
++ path.to_str().map(String::from)
++ } else {
++ None
++ };
++
+ let previous = config.register_lints.take();
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
++ track_files(parse_sess, conf_path_string);
+ }));
+ config.register_lints = Some(Box::new(move |sess, lint_store| {
+ // technically we're ~guaranteed that this is none but might as well call anything that
+ // is there already. Certainly it can't hurt.
+ if let Some(previous) = &previous {
+ (previous)(sess, lint_store);
+ }
+
- return rustc_driver::RunCompiler::new(&orig_args, &mut DefaultCallbacks).run();
++ let conf = clippy_lints::read_conf(sess, &conf_path);
+ clippy_lints::register_plugins(lint_store, sess, &conf);
+ clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf);
+ clippy_lints::register_renamed(lint_store);
+ }));
+
+ // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be
+ // run on the unoptimized MIR. On the other hand this results in some false negatives. If
+ // MIR passes can be enabled / disabled separately, we should figure out, what passes to
+ // use for Clippy.
+ config.opts.unstable_opts.mir_opt_level = Some(0);
+ }
+}
+
+fn display_help() {
+ println!(
+ "\
+Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<opts>...]
+
+Common options:
+ -h, --help Print this message
+ --rustc Pass all args to rustc
+ -V, --version Print version info and exit
+
+Other options are the same as `cargo check`.
+
+To allow or deny a lint from the command line you can use `cargo clippy --`
+with:
+
+ -W --warn OPT Set lint warnings
+ -A --allow OPT Set lint allowed
+ -D --deny OPT Set lint denied
+ -F --forbid OPT Set lint forbidden
+
+You can use tool lints to allow or deny lints from your code, eg.:
+
+ #[allow(clippy::needless_lifetimes)]
+"
+ );
+}
+
+const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new";
+
+type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static;
+static ICE_HOOK: LazyLock<Box<PanicCallback>> = LazyLock::new(|| {
+ let hook = panic::take_hook();
+ panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
+ hook
+});
+
+fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
+ // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
+ (*ICE_HOOK)(info);
+
+ // Separate the output with an empty line
+ eprintln!();
+
+ let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
+ let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
+ rustc_errors::ColorConfig::Auto,
+ None,
+ None,
+ fallback_bundle,
+ false,
+ false,
+ None,
+ false,
+ false,
+ ));
+ let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+
+ // a .span_bug or .bug call has already printed what
+ // it wants to print.
+ if !info.payload().is::<rustc_errors::ExplicitBug>() {
+ let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
+ handler.emit_diagnostic(&mut d);
+ }
+
+ let version_info = rustc_tools_util::get_version_info!();
+
+ let xs: Vec<Cow<'static, str>> = vec![
+ "the compiler unexpectedly panicked. this is a bug.".into(),
+ format!("we would appreciate a bug report: {bug_report_url}").into(),
+ format!("Clippy version: {version_info}").into(),
+ ];
+
+ for note in &xs {
+ handler.note_without_error(note.as_ref());
+ }
+
+ // If backtraces are enabled, also print the query stack
+ let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
+
+ let num_frames = if backtrace { None } else { Some(2) };
+
+ interface::try_print_query_stack(&handler, num_frames);
+}
+
+#[allow(clippy::too_many_lines)]
+pub fn main() {
+ rustc_driver::init_rustc_env_logger();
+ LazyLock::force(&ICE_HOOK);
+ exit(rustc_driver::catch_with_exit_code(move || {
+ let mut orig_args: Vec<String> = env::args().collect();
+
++ let sys_root_env = std::env::var("SYSROOT").ok();
++ let pass_sysroot_env_if_given = |args: &mut Vec<String>, sys_root_env| {
++ if let Some(sys_root) = sys_root_env {
++ args.extend(vec!["--sysroot".into(), sys_root]);
++ };
++ };
++
+ // make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc"
+ // for example `clippy-driver --rustc --version` will print the rustc version that clippy-driver
+ // uses
+ if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") {
+ orig_args.remove(pos);
+ orig_args[0] = "rustc".to_string();
+
- let mut args: Vec<String> = orig_args.clone();
++ let mut args: Vec<String> = orig_args.clone();
++ pass_sysroot_env_if_given(&mut args, sys_root_env);
++
++ return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run();
+ }
+
+ if orig_args.iter().any(|a| a == "--version" || a == "-V") {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{version_info}");
+ exit(0);
+ }
+
+ // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
+ // We're invoking the compiler programmatically, so we ignore this/
+ let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref());
+
+ if wrapper_mode {
+ // we still want to be able to invoke it normally though
+ orig_args.remove(1);
+ }
+
+ if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) {
+ display_help();
+ exit(0);
+ }
+
++ let mut args: Vec<String> = orig_args.clone();
++ pass_sysroot_env_if_given(&mut args, sys_root_env);
++
+ let mut no_deps = false;
+ let clippy_args_var = env::var("CLIPPY_ARGS").ok();
+ let clippy_args = clippy_args_var
+ .as_deref()
+ .unwrap_or_default()
+ .split("__CLIPPY_HACKERY__")
+ .filter_map(|s| match s {
+ "" => None,
+ "--no-deps" => {
+ no_deps = true;
+ None
+ },
+ _ => Some(s.to_string()),
+ })
+ .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
+ .collect::<Vec<String>>();
+
+ // We enable Clippy if one of the following conditions is met
+ // - IF Clippy is run on its test suite OR
+ // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN
+ // - IF `--no-deps` is not set (`!no_deps`) OR
+ // - IF `--no-deps` is set and Clippy is run on the specified primary package
+ let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some()
+ && arg_value(&orig_args, "--force-warn", |val| val.contains("clippy::")).is_none();
+ let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok();
+
+ let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package);
+ if clippy_enabled {
- rustc_driver::RunCompiler::new(&orig_args, &mut RustcCallbacks { clippy_args_var }).run()
+ args.extend(clippy_args);
+ rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run()
+ } else {
++ rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run()
+ }
+ }))
+}
--- /dev/null
- mod docs;
-
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use rustc_tools_util::VersionInfo;
+use std::env;
+use std::path::PathBuf;
+use std::process::{self, Command};
+
- docs::explain(&lint.strip_prefix("clippy::").unwrap_or(&lint).replace('-', "_"));
+const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<opts>...]
+
+Common options:
+ --no-deps Run Clippy only on the given crate, without linting the dependencies
+ --fix Automatically apply lint suggestions. This flag implies `--no-deps`
+ -h, --help Print this message
+ -V, --version Print version info and exit
+ --explain LINT Print the documentation for a given lint
+
+Other options are the same as `cargo check`.
+
+To allow or deny a lint from the command line you can use `cargo clippy --`
+with:
+
+ -W --warn OPT Set lint warnings
+ -A --allow OPT Set lint allowed
+ -D --deny OPT Set lint denied
+ -F --forbid OPT Set lint forbidden
+
+You can use tool lints to allow or deny lints from your code, eg.:
+
+ #[allow(clippy::needless_lifetimes)]
+"#;
+
+fn show_help() {
+ println!("{CARGO_CLIPPY_HELP}");
+}
+
+fn show_version() {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{version_info}");
+}
+
+pub fn main() {
+ // Check for version and help flags even when invoked as 'cargo-clippy'
+ if env::args().any(|a| a == "--help" || a == "-h") {
+ show_help();
+ return;
+ }
+
+ if env::args().any(|a| a == "--version" || a == "-V") {
+ show_version();
+ return;
+ }
+
+ if let Some(pos) = env::args().position(|a| a == "--explain") {
+ if let Some(mut lint) = env::args().nth(pos + 1) {
+ lint.make_ascii_lowercase();
++ clippy_lints::explain(&lint.strip_prefix("clippy::").unwrap_or(&lint).replace('-', "_"));
+ } else {
+ show_help();
+ }
+ return;
+ }
+
+ if let Err(code) = process(env::args().skip(2)) {
+ process::exit(code);
+ }
+}
+
+struct ClippyCmd {
+ cargo_subcommand: &'static str,
+ args: Vec<String>,
+ clippy_args: Vec<String>,
+}
+
+impl ClippyCmd {
+ fn new<I>(mut old_args: I) -> Self
+ where
+ I: Iterator<Item = String>,
+ {
+ let mut cargo_subcommand = "check";
+ let mut args = vec![];
+ let mut clippy_args: Vec<String> = vec![];
+
+ for arg in old_args.by_ref() {
+ match arg.as_str() {
+ "--fix" => {
+ cargo_subcommand = "fix";
+ continue;
+ },
+ "--no-deps" => {
+ clippy_args.push("--no-deps".into());
+ continue;
+ },
+ "--" => break,
+ _ => {},
+ }
+
+ args.push(arg);
+ }
+
+ clippy_args.append(&mut (old_args.collect()));
+ if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") {
+ clippy_args.push("--no-deps".into());
+ }
+
+ Self {
+ cargo_subcommand,
+ args,
+ clippy_args,
+ }
+ }
+
+ fn path() -> PathBuf {
+ let mut path = env::current_exe()
+ .expect("current executable path invalid")
+ .with_file_name("clippy-driver");
+
+ if cfg!(windows) {
+ path.set_extension("exe");
+ }
+
+ path
+ }
+
+ fn into_std_cmd(self) -> Command {
+ let mut cmd = Command::new("cargo");
+ let clippy_args: String = self
+ .clippy_args
+ .iter()
+ .map(|arg| format!("{arg}__CLIPPY_HACKERY__"))
+ .collect();
+
+ // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
+ let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
+
+ cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path())
+ .env("CLIPPY_ARGS", clippy_args)
+ .env("CLIPPY_TERMINAL_WIDTH", terminal_width.to_string())
+ .arg(self.cargo_subcommand)
+ .args(&self.args);
+
+ cmd
+ }
+}
+
+fn process<I>(old_args: I) -> Result<(), i32>
+where
+ I: Iterator<Item = String>,
+{
+ let cmd = ClippyCmd::new(old_args);
+
+ let mut cmd = cmd.into_std_cmd();
+
+ let exit_status = cmd
+ .spawn()
+ .expect("could not run cargo")
+ .wait()
+ .expect("failed to wait for cargo?");
+
+ if exit_status.success() {
+ Ok(())
+ } else {
+ Err(exit_status.code().unwrap_or(-1))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ClippyCmd;
+
+ #[test]
+ fn fix() {
+ let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!("fix", cmd.cargo_subcommand);
+ assert!(!cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
+ }
+
+ #[test]
+ fn fix_implies_no_deps() {
+ let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps"));
+ }
+
+ #[test]
+ fn no_deps_not_duplicated_with_fix() {
+ let args = "cargo clippy --fix -- --no-deps"
+ .split_whitespace()
+ .map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1);
+ }
+
+ #[test]
+ fn check() {
+ let args = "cargo clippy".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!("check", cmd.cargo_subcommand);
+ }
+}
--- /dev/null
- error: aborting due to previous error; 1 warning emitted
+warning: the MSRV in `clippy.toml` and `Cargo.toml` differ; using `1.59.0` from `clippy.toml`
+
+error: unnecessary structure name repetition
+ --> $DIR/main.rs:6:21
+ |
+LL | pub fn bar() -> Foo {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+note: the lint level is defined here
+ --> $DIR/main.rs:1:9
+ |
+LL | #![deny(clippy::use_self)]
+ | ^^^^^^^^^^^^^^^^
+
++error: unnecessary structure name repetition
++ --> $DIR/main.rs:7:9
++ |
++LL | Foo
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 2 previous errors; 1 warning emitted
+
--- /dev/null
- error: aborting due to previous error
+error: unnecessary structure name repetition
+ --> $DIR/main.rs:6:21
+ |
+LL | pub fn bar() -> Foo {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+note: the lint level is defined here
+ --> $DIR/main.rs:1:9
+ |
+LL | #![deny(clippy::use_self)]
+ | ^^^^^^^^^^^^^^^^
+
++error: unnecessary structure name repetition
++ --> $DIR/main.rs:7:9
++ |
++LL | Foo
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 2 previous errors
+
--- /dev/null
- error: aborting due to previous error
+error: unnecessary structure name repetition
+ --> $DIR/main.rs:6:21
+ |
+LL | pub fn bar() -> Foo {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+note: the lint level is defined here
+ --> $DIR/main.rs:1:9
+ |
+LL | #![deny(clippy::use_self)]
+ | ^^^^^^^^^^^^^^^^
+
++error: unnecessary structure name repetition
++ --> $DIR/main.rs:7:9
++ |
++LL | Foo
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 2 previous errors
+
--- /dev/null
- error: aborting due to previous error
+error: unnecessary structure name repetition
+ --> $DIR/main.rs:6:21
+ |
+LL | pub fn bar() -> Foo {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+note: the lint level is defined here
+ --> $DIR/main.rs:1:9
+ |
+LL | #![deny(clippy::use_self)]
+ | ^^^^^^^^^^^^^^^^
+
++error: unnecessary structure name repetition
++ --> $DIR/main.rs:7:9
++ |
++LL | Foo
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 2 previous errors
+
--- /dev/null
- error: aborting due to previous error
+error: unnecessary structure name repetition
+ --> $DIR/main.rs:11:21
+ |
+LL | pub fn bar() -> Foo {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+note: the lint level is defined here
+ --> $DIR/main.rs:6:9
+ |
+LL | #![deny(clippy::use_self)]
+ | ^^^^^^^^^^^^^^^^
+
++error: unnecessary structure name repetition
++ --> $DIR/main.rs:12:9
++ |
++LL | Foo
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 2 previous errors
+
--- /dev/null
+// rustc-env:RUST_BACKTRACE=0
+// normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo"
+// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs"
+// normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints"
++// normalize-stderr-test: "'rustc'" -> "'<unnamed>'"
+
+#![deny(clippy::internal)]
+#![allow(clippy::missing_clippy_version_attribute)]
+
+fn it_looks_like_you_are_trying_to_kill_clippy() {}
+
+fn main() {}
--- /dev/null
- thread 'rustc' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs:28:9
++thread '<unnamed>' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs:28:9
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+
+error: internal compiler error: unexpected panic
+
+note: the compiler unexpectedly panicked. this is a bug.
+
+note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new
+
+note: Clippy version: foo
+
+query stack during panic:
+end of query stack
--- /dev/null
- = note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
++error: hardcoded path to a language item
++ --> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40
++ |
++LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: convert all references to use `LangItem::DerefMut`
++ = note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
++
+error: hardcoded path to a diagnostic item
+ --> $DIR/unnecessary_def_path_hardcoded_path.rs:10:36
+ |
+LL | const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: convert all references to use `sym::Deref`
- error: hardcoded path to a language item
- --> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40
- |
- LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- |
- = help: convert all references to use `LangItem::DerefMut`
-
+
+error: hardcoded path to a diagnostic item
+ --> $DIR/unnecessary_def_path_hardcoded_path.rs:12:43
+ |
+LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: convert all references to use `sym::deref_method`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- use core::ops::Add;
+#![warn(clippy::arithmetic_side_effects)]
+
++use core::ops::{Add, Neg};
+
+#[derive(Clone, Copy)]
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+impl Add for Point {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ todo!()
+ }
+}
+
++impl Neg for Point {
++ type Output = Self;
++
++ fn neg(self) -> Self::Output {
++ todo!()
++ }
++}
++
+fn main() {
+ let _ = Point { x: 1, y: 0 } + Point { x: 2, y: 3 };
+
+ let point: Point = Point { x: 1, y: 0 };
+ let _ = point + point;
++ let _ = -point;
+}
--- /dev/null
- = note: strings are bad
+error: `std::string::String` may not be held across an `await` point per `clippy.toml`
+ --> $DIR/await_holding_invalid_type.rs:5:9
+ |
+LL | let _x = String::from("hello");
+ | ^^
+ |
- = note: strings are bad
++ = note: strings are bad (from clippy.toml)
+ = note: `-D clippy::await-holding-invalid-type` implied by `-D warnings`
+
+error: `std::net::Ipv4Addr` may not be held across an `await` point per `clippy.toml`
+ --> $DIR/await_holding_invalid_type.rs:10:9
+ |
+LL | let _x = Ipv4Addr::new(127, 0, 0, 1);
+ | ^^
+
+error: `std::string::String` may not be held across an `await` point per `clippy.toml`
+ --> $DIR/await_holding_invalid_type.rs:31:13
+ |
+LL | let _x = String::from("hi!");
+ | ^^
+ |
++ = note: strings are bad (from clippy.toml)
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- #[test]
- fn test_expect_option() {
- let opt = Some(0);
- let _ = opt.expect("");
- }
+// compile-flags: --test
+#![warn(clippy::expect_used)]
+
+fn expect_option() {
+ let opt = Some(0);
+ let _ = opt.expect("");
+}
+
+fn expect_result() {
+ let res: Result<u8, ()> = Ok(0);
+ let _ = res.expect("");
+}
+
+fn main() {
+ expect_option();
+ expect_result();
+}
+
- #[test]
- fn test_expect_result() {
- let res: Result<u8, ()> = Ok(0);
- let _ = res.expect("");
++#[cfg(test)]
++mod issue9612 {
++ // should not lint in `#[cfg(test)]` modules
++ #[test]
++ fn test_fn() {
++ let _a: u8 = 2.try_into().unwrap();
++ let _a: u8 = 3.try_into().expect("");
+
++ util();
++ }
++
++ fn util() {
++ let _a: u8 = 4.try_into().unwrap();
++ let _a: u8 = 5.try_into().expect("");
++ }
+}
--- /dev/null
- error: used `expect()` on `an Option` value
++error: used `expect()` on an `Option` value
+ --> $DIR/expect_used.rs:6:13
+ |
+LL | let _ = opt.expect("");
+ | ^^^^^^^^^^^^^^
+ |
+ = help: if this value is `None`, it will panic
+ = note: `-D clippy::expect-used` implied by `-D warnings`
+
- error: used `expect()` on `a Result` value
++error: used `expect()` on a `Result` value
+ --> $DIR/expect_used.rs:11:13
+ |
+LL | let _ = res.expect("");
+ | ^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Err`, it will panic
+
+error: aborting due to 2 previous errors
+
--- /dev/null
--- /dev/null
++ignore-interior-mutability = ["mut_key::Counted"]
--- /dev/null
--- /dev/null
++// compile-flags: --crate-name mut_key
++
++#![warn(clippy::mutable_key_type)]
++
++use std::cmp::{Eq, PartialEq};
++use std::collections::{HashMap, HashSet};
++use std::hash::{Hash, Hasher};
++use std::ops::Deref;
++use std::sync::atomic::{AtomicUsize, Ordering};
++
++struct Counted<T> {
++ count: AtomicUsize,
++ val: T,
++}
++
++impl<T: Clone> Clone for Counted<T> {
++ fn clone(&self) -> Self {
++ Self {
++ count: AtomicUsize::new(0),
++ val: self.val.clone(),
++ }
++ }
++}
++
++impl<T: PartialEq> PartialEq for Counted<T> {
++ fn eq(&self, other: &Self) -> bool {
++ self.val == other.val
++ }
++}
++impl<T: PartialEq + Eq> Eq for Counted<T> {}
++
++impl<T: Hash> Hash for Counted<T> {
++ fn hash<H: Hasher>(&self, state: &mut H) {
++ self.val.hash(state);
++ }
++}
++
++impl<T> Deref for Counted<T> {
++ type Target = T;
++
++ fn deref(&self) -> &T {
++ self.count.fetch_add(1, Ordering::AcqRel);
++ &self.val
++ }
++}
++
++// This is not linted because `"mut_key::Counted"` is in
++// `arc_like_types` in `clippy.toml`
++fn should_not_take_this_arg(_v: HashSet<Counted<String>>) {}
++
++fn main() {
++ should_not_take_this_arg(HashSet::new());
++}
--- /dev/null
--- /dev/null
++allow-print-in-tests = true
--- /dev/null
--- /dev/null
++// compile-flags: --test
++#![warn(clippy::print_stdout)]
++#![warn(clippy::print_stderr)]
++
++fn foo(n: u32) {
++ print!("{n}");
++ eprint!("{n}");
++}
++
++#[test]
++pub fn foo1() {
++ print!("{}", 1);
++ eprint!("{}", 1);
++}
++
++#[cfg(test)]
++fn foo3() {
++ print!("{}", 1);
++ eprint!("{}", 1);
++}
--- /dev/null
--- /dev/null
++error: use of `print!`
++ --> $DIR/print_macro.rs:6:5
++ |
++LL | print!("{n}");
++ | ^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::print-stdout` implied by `-D warnings`
++
++error: use of `eprint!`
++ --> $DIR/print_macro.rs:7:5
++ |
++LL | eprint!("{n}");
++ | ^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::print-stderr` implied by `-D warnings`
++
++error: aborting due to 2 previous errors
++
--- /dev/null
+disallowed-methods = [
+ # just a string is shorthand for path only
+ "std::iter::Iterator::sum",
+ "f32::clamp",
+ "slice::sort_unstable",
+ "futures::stream::select_all",
+ # can give path and reason with an inline table
+ { path = "regex::Regex::is_match", reason = "no matching allowed" },
+ # can use an inline table but omit reason
+ { path = "regex::Regex::new" },
++ # local paths
++ "conf_disallowed_methods::local_fn",
++ "conf_disallowed_methods::local_mod::f",
++ "conf_disallowed_methods::Struct::method",
++ "conf_disallowed_methods::Trait::provided_method",
++ "conf_disallowed_methods::Trait::implemented_method",
+]
--- /dev/null
++// compile-flags: --crate-name conf_disallowed_methods
++
+#![warn(clippy::disallowed_methods)]
+
+extern crate futures;
+extern crate regex;
+
+use futures::stream::{empty, select_all};
+use regex::Regex;
+
++fn local_fn() {}
++
++struct Struct;
++
++impl Struct {
++ fn method(&self) {}
++}
++
++trait Trait {
++ fn provided_method(&self) {}
++ fn implemented_method(&self);
++}
++
++impl Trait for Struct {
++ fn implemented_method(&self) {}
++}
++
++mod local_mod {
++ pub fn f() {}
++}
++
+fn main() {
+ let re = Regex::new(r"ab.*c").unwrap();
+ re.is_match("abc");
+
+ let mut a = vec![1, 2, 3, 4];
+ a.iter().sum::<i32>();
+
+ a.sort_unstable();
+
+ let _ = 2.0f32.clamp(3.0f32, 4.0f32);
+ let _ = 2.0f64.clamp(3.0f64, 4.0f64);
+
+ let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
+ let re = indirect(".").unwrap();
+
+ let in_call = Box::new(f32::clamp);
+ let in_method_call = ["^", "$"].into_iter().map(Regex::new);
+
+ // resolve ambiguity between `futures::stream::select_all` the module and the function
+ let same_name_as_module = select_all(vec![empty::<()>()]);
++
++ local_fn();
++ local_mod::f();
++ let s = Struct;
++ s.method();
++ s.provided_method();
++ s.implemented_method();
+}
--- /dev/null
- --> $DIR/conf_disallowed_methods.rs:10:14
+error: use of a disallowed method `regex::Regex::new`
- --> $DIR/conf_disallowed_methods.rs:11:5
++ --> $DIR/conf_disallowed_methods.rs:33:14
+ |
+LL | let re = Regex::new(r"ab.*c").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::disallowed-methods` implied by `-D warnings`
+
+error: use of a disallowed method `regex::Regex::is_match`
- --> $DIR/conf_disallowed_methods.rs:14:5
++ --> $DIR/conf_disallowed_methods.rs:34:5
+ |
+LL | re.is_match("abc");
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = note: no matching allowed (from clippy.toml)
+
+error: use of a disallowed method `std::iter::Iterator::sum`
- --> $DIR/conf_disallowed_methods.rs:16:5
++ --> $DIR/conf_disallowed_methods.rs:37:5
+ |
+LL | a.iter().sum::<i32>();
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `slice::sort_unstable`
- --> $DIR/conf_disallowed_methods.rs:18:13
++ --> $DIR/conf_disallowed_methods.rs:39:5
+ |
+LL | a.sort_unstable();
+ | ^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `f32::clamp`
- --> $DIR/conf_disallowed_methods.rs:21:61
++ --> $DIR/conf_disallowed_methods.rs:41:13
+ |
+LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `regex::Regex::new`
- --> $DIR/conf_disallowed_methods.rs:24:28
++ --> $DIR/conf_disallowed_methods.rs:44:61
+ |
+LL | let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
+ | ^^^^^^^^^^
+
+error: use of a disallowed method `f32::clamp`
- --> $DIR/conf_disallowed_methods.rs:25:53
++ --> $DIR/conf_disallowed_methods.rs:47:28
+ |
+LL | let in_call = Box::new(f32::clamp);
+ | ^^^^^^^^^^
+
+error: use of a disallowed method `regex::Regex::new`
- --> $DIR/conf_disallowed_methods.rs:28:31
++ --> $DIR/conf_disallowed_methods.rs:48:53
+ |
+LL | let in_method_call = ["^", "$"].into_iter().map(Regex::new);
+ | ^^^^^^^^^^
+
+error: use of a disallowed method `futures::stream::select_all`
- error: aborting due to 9 previous errors
++ --> $DIR/conf_disallowed_methods.rs:51:31
+ |
+LL | let same_name_as_module = select_all(vec![empty::<()>()]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
++error: use of a disallowed method `conf_disallowed_methods::local_fn`
++ --> $DIR/conf_disallowed_methods.rs:53:5
++ |
++LL | local_fn();
++ | ^^^^^^^^^^
++
++error: use of a disallowed method `conf_disallowed_methods::local_mod::f`
++ --> $DIR/conf_disallowed_methods.rs:54:5
++ |
++LL | local_mod::f();
++ | ^^^^^^^^^^^^^^
++
++error: use of a disallowed method `conf_disallowed_methods::Struct::method`
++ --> $DIR/conf_disallowed_methods.rs:56:5
++ |
++LL | s.method();
++ | ^^^^^^^^^^
++
++error: use of a disallowed method `conf_disallowed_methods::Trait::provided_method`
++ --> $DIR/conf_disallowed_methods.rs:57:5
++ |
++LL | s.provided_method();
++ | ^^^^^^^^^^^^^^^^^^^
++
++error: use of a disallowed method `conf_disallowed_methods::Trait::implemented_method`
++ --> $DIR/conf_disallowed_methods.rs:58:5
++ |
++LL | s.implemented_method();
++ | ^^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 14 previous errors
+
--- /dev/null
+error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of
+ allow-dbg-in-tests
+ allow-expect-in-tests
++ allow-print-in-tests
+ allow-unwrap-in-tests
+ allowed-scripts
+ arithmetic-side-effects-allowed
+ array-size-threshold
+ avoid-breaking-exported-api
+ await-holding-invalid-types
+ blacklisted-names
+ cargo-ignore-publish
+ cognitive-complexity-threshold
+ cyclomatic-complexity-threshold
+ disallowed-macros
+ disallowed-methods
+ disallowed-names
+ disallowed-types
+ doc-valid-idents
+ enable-raw-pointer-heuristic-for-send
+ enforced-import-renames
+ enum-variant-name-threshold
+ enum-variant-size-threshold
++ ignore-interior-mutability
+ large-error-threshold
+ literal-representation-threshold
++ matches-for-let-else
+ max-fn-params-bools
+ max-include-file-size
+ max-struct-bools
+ max-suggested-slice-pattern-length
+ max-trait-bounds
+ msrv
+ pass-by-value-size-limit
+ single-char-binding-names-threshold
+ standard-macro-braces
+ third-party
+ too-large-for-stack
+ too-many-arguments-threshold
+ too-many-lines-threshold
+ trivial-copy-size-limit
+ type-complexity-threshold
+ unreadable-literal-lint-fractions
+ upper-case-acronyms-aggressive
+ vec-box-size-threshold
+ verbose-bit-mask-threshold
+ warn-on-all-wildcard-imports
+ at line 5 column 1
+
+error: aborting due to previous error
+
--- /dev/null
- #[test]
- fn test() {
- let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
- let _ = boxed_slice.get(1).unwrap();
+// compile-flags: --test
+
+#![allow(unused_mut, clippy::get_first, clippy::from_iter_instead_of_collect)]
+#![warn(clippy::unwrap_used)]
+#![deny(clippy::get_unwrap)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::collections::VecDeque;
+
+struct GetFalsePositive {
+ arr: [u32; 3],
+}
+
+impl GetFalsePositive {
+ fn get(&self, pos: usize) -> Option<&u32> {
+ self.arr.get(pos)
+ }
+ fn get_mut(&mut self, pos: usize) -> Option<&mut u32> {
+ self.arr.get_mut(pos)
+ }
+}
+
+fn main() {
+ let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
+ let mut some_slice = &mut [0, 1, 2, 3];
+ let mut some_vec = vec![0, 1, 2, 3];
+ let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect();
+ let mut some_hashmap: HashMap<u8, char> = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut some_btreemap: BTreeMap<u8, char> = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut false_positive = GetFalsePositive { arr: [0, 1, 2] };
+
+ {
+ // Test `get().unwrap()`
+ let _ = boxed_slice.get(1).unwrap();
+ let _ = some_slice.get(0).unwrap();
+ let _ = some_vec.get(0).unwrap();
+ let _ = some_vecdeque.get(0).unwrap();
+ let _ = some_hashmap.get(&1).unwrap();
+ let _ = some_btreemap.get(&1).unwrap();
+ #[allow(clippy::unwrap_used)]
+ let _ = false_positive.get(0).unwrap();
+ // Test with deref
+ let _: u8 = *boxed_slice.get(1).unwrap();
+ }
+
+ {
+ // Test `get_mut().unwrap()`
+ *boxed_slice.get_mut(0).unwrap() = 1;
+ *some_slice.get_mut(0).unwrap() = 1;
+ *some_vec.get_mut(0).unwrap() = 1;
+ *some_vecdeque.get_mut(0).unwrap() = 1;
+ // Check false positives
+ #[allow(clippy::unwrap_used)]
+ {
+ *some_hashmap.get_mut(&1).unwrap() = 'b';
+ *some_btreemap.get_mut(&1).unwrap() = 'b';
+ *false_positive.get_mut(0).unwrap() = 1;
+ }
+ }
+
+ {
+ // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()`
+ let _ = some_vec.get(0..1).unwrap().to_vec();
+ let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ }
+}
+
++#[cfg(test)]
++mod issue9612 {
++ // should not lint in `#[cfg(test)]` modules
++ #[test]
++ fn test_fn() {
++ let _a: u8 = 2.try_into().unwrap();
++ let _a: u8 = 3.try_into().expect("");
++
++ util();
++ }
++
++ fn util() {
++ let _a: u8 = 4.try_into().unwrap();
++ let _a: u8 = 5.try_into().expect("");
++ // should still warn
++ let _ = Box::new([0]).get(1).unwrap();
++ }
+}
--- /dev/null
- error: used `unwrap()` on `an Option` value
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
+ |
+note: the lint level is defined here
+ --> $DIR/unwrap_used.rs:5:9
+ |
+LL | #![deny(clippy::get_unwrap)]
+ | ^^^^^^^^^^^^^^^^^^
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+ = note: `-D clippy::unwrap-used` implied by `-D warnings`
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:44:21
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:44:22
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:49:9
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:49:10
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:50:9
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:50:10
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:51:9
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:51:10
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:52:9
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:52:10
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/unwrap_used.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
- --> $DIR/unwrap_used.rs:72:13
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_used.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
- LL | let _ = boxed_slice.get(1).unwrap();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
++ --> $DIR/unwrap_used.rs:84:17
+ |
++LL | let _ = Box::new([0]).get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&Box::new([0])[1]`
+
+error: aborting due to 27 previous errors
+
--- /dev/null
- struct Bar(Vec<Box<u32>>);
- struct Baz(Vec<Box<(u32, u32)>>);
+struct S {
+ x: u64,
+}
+
+struct C {
+ y: u16,
+}
+
+struct Foo(Vec<Box<u8>>);
++struct Bar(Vec<Box<u16>>);
++struct Quux(Vec<Box<u32>>);
++struct Baz(Vec<Box<(u16, u16)>>);
+struct BarBaz(Vec<Box<S>>);
+struct FooBarBaz(Vec<Box<C>>);
+
+fn main() {}
--- /dev/null
- LL | struct Bar(Vec<Box<u32>>);
- | ^^^^^^^^^^^^^ help: try: `Vec<u32>`
+error: `Vec<T>` is already on the heap, the boxing is unnecessary
+ --> $DIR/test.rs:9:12
+ |
+LL | struct Foo(Vec<Box<u8>>);
+ | ^^^^^^^^^^^^ help: try: `Vec<u8>`
+ |
+ = note: `-D clippy::vec-box` implied by `-D warnings`
+
+error: `Vec<T>` is already on the heap, the boxing is unnecessary
+ --> $DIR/test.rs:10:12
+ |
- --> $DIR/test.rs:13:18
++LL | struct Bar(Vec<Box<u16>>);
++ | ^^^^^^^^^^^^^ help: try: `Vec<u16>`
+
+error: `Vec<T>` is already on the heap, the boxing is unnecessary
++ --> $DIR/test.rs:14:18
+ |
+LL | struct FooBarBaz(Vec<Box<C>>);
+ | ^^^^^^^^^^^ help: try: `Vec<C>`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- _n = -1;
- _n = -(-1);
+#![allow(
+ clippy::assign_op_pattern,
+ clippy::erasing_op,
+ clippy::identity_op,
+ clippy::op_ref,
+ clippy::unnecessary_owned_empty_strings,
+ arithmetic_overflow,
+ unconditional_panic
+)]
+#![feature(const_mut_refs, inline_const, saturating_int_impl)]
+#![warn(clippy::arithmetic_side_effects)]
+
+use core::num::{Saturating, Wrapping};
+
+pub struct Custom;
+
+macro_rules! impl_arith {
+ ( $( $_trait:ident, $ty:ty, $method:ident; )* ) => {
+ $(
+ impl core::ops::$_trait<$ty> for Custom {
+ type Output = Self;
+ fn $method(self, _: $ty) -> Self::Output { Self }
+ }
+ )*
+ }
+}
+
+impl_arith!(
+ Add, i32, add;
+ Div, i32, div;
+ Mul, i32, mul;
+ Sub, i32, sub;
+
+ Add, f64, add;
+ Div, f64, div;
+ Mul, f64, mul;
+ Sub, f64, sub;
+);
+
+pub fn association_with_structures_should_not_trigger_the_lint() {
+ enum Foo {
+ Bar = -2,
+ }
+
+ impl Trait for Foo {
+ const ASSOC: i32 = {
+ let _: [i32; 1 + 1];
+ fn foo() {}
+ 1 + 1
+ };
+ }
+
+ struct Baz([i32; 1 + 1]);
+
+ trait Trait {
+ const ASSOC: i32 = 1 + 1;
+ }
+
+ type Alias = [i32; 1 + 1];
+
+ union Qux {
+ field: [i32; 1 + 1],
+ }
+
+ let _: [i32; 1 + 1] = [0, 0];
+
+ let _: [i32; 1 + 1] = {
+ let a: [i32; 1 + 1] = [0, 0];
+ a
+ };
+}
+
+pub fn hard_coded_allowed() {
+ let _ = 1f32 + 1f32;
+ let _ = 1f64 + 1f64;
+
+ let _ = Saturating(0u32) + Saturating(0u32);
+ let _ = String::new() + "";
+ let _ = Wrapping(0u32) + Wrapping(0u32);
+
+ let saturating: Saturating<u32> = Saturating(0u32);
+ let string: String = String::new();
+ let wrapping: Wrapping<u32> = Wrapping(0u32);
+
+ let inferred_saturating = saturating + saturating;
+ let inferred_string = string + "";
+ let inferred_wrapping = wrapping + wrapping;
+
+ let _ = inferred_saturating + inferred_saturating;
+ let _ = inferred_string + "";
+ let _ = inferred_wrapping + inferred_wrapping;
+}
+
+#[rustfmt::skip]
+pub fn const_ops_should_not_trigger_the_lint() {
+ const _: i32 = { let mut n = 1; n += 1; n };
+ let _ = const { let mut n = 1; n += 1; n };
+
+ const _: i32 = { let mut n = 1; n = n + 1; n };
+ let _ = const { let mut n = 1; n = n + 1; n };
+
+ const _: i32 = { let mut n = 1; n = 1 + n; n };
+ let _ = const { let mut n = 1; n = 1 + n; n };
+
+ const _: i32 = 1 + 1;
+ let _ = const { 1 + 1 };
+
+ const _: i32 = { let mut n = 1; n = -1; n = -(-1); n = -n; n };
+ let _ = const { let mut n = 1; n = -1; n = -(-1); n = -n; n };
+}
+
+pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_trigger_the_lint() {
+ let mut _n = i32::MAX;
+
+ // Assign
+ _n += 0;
+ _n += &0;
+ _n -= 0;
+ _n -= &0;
+ _n /= 99;
+ _n /= &99;
+ _n %= 99;
+ _n %= &99;
+ _n *= 0;
+ _n *= &0;
+ _n *= 1;
+ _n *= &1;
+
+ // Binary
+ _n = _n + 0;
+ _n = _n + &0;
+ _n = 0 + _n;
+ _n = &0 + _n;
+ _n = _n - 0;
+ _n = _n - &0;
+ _n = 0 - _n;
+ _n = &0 - _n;
+ _n = _n / 99;
+ _n = _n / &99;
+ _n = _n % 99;
+ _n = _n % &99;
+ _n = _n * 0;
+ _n = _n * &0;
+ _n = 0 * _n;
+ _n = &0 * _n;
+ _n = _n * 1;
+ _n = _n * &1;
+ _n = 1 * _n;
+ _n = &1 * _n;
+ _n = 23 + 85;
+
+ // Unary
++ _n = -2147483647;
++ _n = -i32::MAX;
++ _n = -i32::MIN;
++ _n = -&2147483647;
++ _n = -&i32::MAX;
++ _n = -&i32::MIN;
+}
+
+pub fn runtime_ops() {
+ let mut _n = i32::MAX;
+
+ // Assign
+ _n += 1;
+ _n += &1;
+ _n -= 1;
+ _n -= &1;
+ _n /= 0;
+ _n /= &0;
+ _n %= 0;
+ _n %= &0;
+ _n *= 2;
+ _n *= &2;
+
+ // Binary
+ _n = _n + 1;
+ _n = _n + &1;
+ _n = 1 + _n;
+ _n = &1 + _n;
+ _n = _n - 1;
+ _n = _n - &1;
+ _n = 1 - _n;
+ _n = &1 - _n;
+ _n = _n / 0;
+ _n = _n / &0;
+ _n = _n % 0;
+ _n = _n % &0;
+ _n = _n * 2;
+ _n = _n * &2;
+ _n = 2 * _n;
+ _n = &2 * _n;
+ _n = 23 + &85;
+ _n = &23 + 85;
+ _n = &23 + &85;
+
+ // Custom
+ let _ = Custom + 0;
+ let _ = Custom + 1;
+ let _ = Custom + 2;
+ let _ = Custom + 0.0;
+ let _ = Custom + 1.0;
+ let _ = Custom + 2.0;
+ let _ = Custom - 0;
+ let _ = Custom - 1;
+ let _ = Custom - 2;
+ let _ = Custom - 0.0;
+ let _ = Custom - 1.0;
+ let _ = Custom - 2.0;
+ let _ = Custom / 0;
+ let _ = Custom / 1;
+ let _ = Custom / 2;
+ let _ = Custom / 0.0;
+ let _ = Custom / 1.0;
+ let _ = Custom / 2.0;
+ let _ = Custom * 0;
+ let _ = Custom * 1;
+ let _ = Custom * 2;
+ let _ = Custom * 0.0;
+ let _ = Custom * 1.0;
+ let _ = Custom * 2.0;
+
+ // Unary
+ _n = -_n;
+ _n = -&_n;
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/arithmetic_side_effects.rs:161:5
+error: arithmetic operation that can potentially result in unexpected side-effects
+ --> $DIR/arithmetic_side_effects.rs:78:13
+ |
+LL | let _ = String::new() + "";
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings`
+
+error: arithmetic operation that can potentially result in unexpected side-effects
+ --> $DIR/arithmetic_side_effects.rs:86:27
+ |
+LL | let inferred_string = string + "";
+ | ^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
+ --> $DIR/arithmetic_side_effects.rs:90:13
+ |
+LL | let _ = inferred_string + "";
+ | ^^^^^^^^^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:162:5
++ --> $DIR/arithmetic_side_effects.rs:165:5
+ |
+LL | _n += 1;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:163:5
++ --> $DIR/arithmetic_side_effects.rs:166:5
+ |
+LL | _n += &1;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:164:5
++ --> $DIR/arithmetic_side_effects.rs:167:5
+ |
+LL | _n -= 1;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:165:5
++ --> $DIR/arithmetic_side_effects.rs:168:5
+ |
+LL | _n -= &1;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:166:5
++ --> $DIR/arithmetic_side_effects.rs:169:5
+ |
+LL | _n /= 0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:167:5
++ --> $DIR/arithmetic_side_effects.rs:170:5
+ |
+LL | _n /= &0;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:168:5
++ --> $DIR/arithmetic_side_effects.rs:171:5
+ |
+LL | _n %= 0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:169:5
++ --> $DIR/arithmetic_side_effects.rs:172:5
+ |
+LL | _n %= &0;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:170:5
++ --> $DIR/arithmetic_side_effects.rs:173:5
+ |
+LL | _n *= 2;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:173:10
++ --> $DIR/arithmetic_side_effects.rs:174:5
+ |
+LL | _n *= &2;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:174:10
++ --> $DIR/arithmetic_side_effects.rs:177:10
+ |
+LL | _n = _n + 1;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:175:10
++ --> $DIR/arithmetic_side_effects.rs:178:10
+ |
+LL | _n = _n + &1;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:176:10
++ --> $DIR/arithmetic_side_effects.rs:179:10
+ |
+LL | _n = 1 + _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:177:10
++ --> $DIR/arithmetic_side_effects.rs:180:10
+ |
+LL | _n = &1 + _n;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:178:10
++ --> $DIR/arithmetic_side_effects.rs:181:10
+ |
+LL | _n = _n - 1;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:179:10
++ --> $DIR/arithmetic_side_effects.rs:182:10
+ |
+LL | _n = _n - &1;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:180:10
++ --> $DIR/arithmetic_side_effects.rs:183:10
+ |
+LL | _n = 1 - _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:181:10
++ --> $DIR/arithmetic_side_effects.rs:184:10
+ |
+LL | _n = &1 - _n;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:182:10
++ --> $DIR/arithmetic_side_effects.rs:185:10
+ |
+LL | _n = _n / 0;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:183:10
++ --> $DIR/arithmetic_side_effects.rs:186:10
+ |
+LL | _n = _n / &0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:184:10
++ --> $DIR/arithmetic_side_effects.rs:187:10
+ |
+LL | _n = _n % 0;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:185:10
++ --> $DIR/arithmetic_side_effects.rs:188:10
+ |
+LL | _n = _n % &0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:186:10
++ --> $DIR/arithmetic_side_effects.rs:189:10
+ |
+LL | _n = _n * 2;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:187:10
++ --> $DIR/arithmetic_side_effects.rs:190:10
+ |
+LL | _n = _n * &2;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:188:10
++ --> $DIR/arithmetic_side_effects.rs:191:10
+ |
+LL | _n = 2 * _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:189:10
++ --> $DIR/arithmetic_side_effects.rs:192:10
+ |
+LL | _n = &2 * _n;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:190:10
++ --> $DIR/arithmetic_side_effects.rs:193:10
+ |
+LL | _n = 23 + &85;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:191:10
++ --> $DIR/arithmetic_side_effects.rs:194:10
+ |
+LL | _n = &23 + 85;
+ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:194:13
++ --> $DIR/arithmetic_side_effects.rs:195:10
+ |
+LL | _n = &23 + &85;
+ | ^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:195:13
++ --> $DIR/arithmetic_side_effects.rs:198:13
+ |
+LL | let _ = Custom + 0;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:196:13
++ --> $DIR/arithmetic_side_effects.rs:199:13
+ |
+LL | let _ = Custom + 1;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:197:13
++ --> $DIR/arithmetic_side_effects.rs:200:13
+ |
+LL | let _ = Custom + 2;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:198:13
++ --> $DIR/arithmetic_side_effects.rs:201:13
+ |
+LL | let _ = Custom + 0.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:199:13
++ --> $DIR/arithmetic_side_effects.rs:202:13
+ |
+LL | let _ = Custom + 1.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:200:13
++ --> $DIR/arithmetic_side_effects.rs:203:13
+ |
+LL | let _ = Custom + 2.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:201:13
++ --> $DIR/arithmetic_side_effects.rs:204:13
+ |
+LL | let _ = Custom - 0;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:202:13
++ --> $DIR/arithmetic_side_effects.rs:205:13
+ |
+LL | let _ = Custom - 1;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:203:13
++ --> $DIR/arithmetic_side_effects.rs:206:13
+ |
+LL | let _ = Custom - 2;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:204:13
++ --> $DIR/arithmetic_side_effects.rs:207:13
+ |
+LL | let _ = Custom - 0.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:205:13
++ --> $DIR/arithmetic_side_effects.rs:208:13
+ |
+LL | let _ = Custom - 1.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:206:13
++ --> $DIR/arithmetic_side_effects.rs:209:13
+ |
+LL | let _ = Custom - 2.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:207:13
++ --> $DIR/arithmetic_side_effects.rs:210:13
+ |
+LL | let _ = Custom / 0;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:208:13
++ --> $DIR/arithmetic_side_effects.rs:211:13
+ |
+LL | let _ = Custom / 1;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:209:13
++ --> $DIR/arithmetic_side_effects.rs:212:13
+ |
+LL | let _ = Custom / 2;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:210:13
++ --> $DIR/arithmetic_side_effects.rs:213:13
+ |
+LL | let _ = Custom / 0.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:211:13
++ --> $DIR/arithmetic_side_effects.rs:214:13
+ |
+LL | let _ = Custom / 1.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:212:13
++ --> $DIR/arithmetic_side_effects.rs:215:13
+ |
+LL | let _ = Custom / 2.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:213:13
++ --> $DIR/arithmetic_side_effects.rs:216:13
+ |
+LL | let _ = Custom * 0;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:214:13
++ --> $DIR/arithmetic_side_effects.rs:217:13
+ |
+LL | let _ = Custom * 1;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:215:13
++ --> $DIR/arithmetic_side_effects.rs:218:13
+ |
+LL | let _ = Custom * 2;
+ | ^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:216:13
++ --> $DIR/arithmetic_side_effects.rs:219:13
+ |
+LL | let _ = Custom * 0.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:217:13
++ --> $DIR/arithmetic_side_effects.rs:220:13
+ |
+LL | let _ = Custom * 1.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:220:10
++ --> $DIR/arithmetic_side_effects.rs:221:13
+ |
+LL | let _ = Custom * 2.0;
+ | ^^^^^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:221:10
++ --> $DIR/arithmetic_side_effects.rs:224:10
+ |
+LL | _n = -_n;
+ | ^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:225:10
+ |
+LL | _n = -&_n;
+ | ^^^^
+
+error: aborting due to 58 previous errors
+
--- /dev/null
+#[macro_export]
+macro_rules! undocd_unsafe {
+ () => {
+ pub unsafe fn oy_vey() {
+ unimplemented!();
+ }
+ };
+}
++#[macro_export]
++macro_rules! undocd_safe {
++ () => {
++ pub fn vey_oy() {
++ unimplemented!();
++ }
++ };
++}
--- /dev/null
++// compile-flags: -W clippy::restriction
++
+#![warn(clippy::blanket_clippy_restriction_lints)]
+
+//! Test that the whole restriction group is not enabled
+#![warn(clippy::restriction)]
+#![deny(clippy::restriction)]
+#![forbid(clippy::restriction)]
+
+fn main() {}
--- /dev/null
- error: restriction lints are not meant to be all enabled
- --> $DIR/blanket_clippy_restriction_lints.rs:4:9
++error: `clippy::restriction` is not meant to be enabled as a group
++ |
++ = note: because of the command line `--warn clippy::restriction`
++ = help: enable the restriction lints you need individually
++ = note: `-D clippy::blanket-clippy-restriction-lints` implied by `-D warnings`
++
++error: `clippy::restriction` is not meant to be enabled as a group
++ --> $DIR/blanket_clippy_restriction_lints.rs:6:9
+ |
+LL | #![warn(clippy::restriction)]
+ | ^^^^^^^^^^^^^^^^^^^
+ |
- = help: try enabling only the lints you really need
- = note: `-D clippy::blanket-clippy-restriction-lints` implied by `-D warnings`
++ = help: enable the restriction lints you need individually
+
- error: restriction lints are not meant to be all enabled
- --> $DIR/blanket_clippy_restriction_lints.rs:5:9
++error: `clippy::restriction` is not meant to be enabled as a group
++ --> $DIR/blanket_clippy_restriction_lints.rs:7:9
+ |
+LL | #![deny(clippy::restriction)]
+ | ^^^^^^^^^^^^^^^^^^^
+ |
- = help: try enabling only the lints you really need
++ = help: enable the restriction lints you need individually
+
- error: restriction lints are not meant to be all enabled
- --> $DIR/blanket_clippy_restriction_lints.rs:6:11
++error: `clippy::restriction` is not meant to be enabled as a group
++ --> $DIR/blanket_clippy_restriction_lints.rs:8:11
+ |
+LL | #![forbid(clippy::restriction)]
+ | ^^^^^^^^^^^^^^^^^^^
+ |
- = help: try enabling only the lints you really need
++ = help: enable the restriction lints you need individually
+
- error: aborting due to 3 previous errors
++error: aborting due to 4 previous errors
+
--- /dev/null
+// run-rustfix
+
++#![feature(let_chains)]
+#![warn(clippy::bool_to_int_with_if)]
+#![allow(unused, dead_code, clippy::unnecessary_operation, clippy::no_effect)]
+
+fn main() {
+ let a = true;
+ let b = false;
+
+ let x = 1;
+ let y = 2;
+
+ // Should lint
+ // precedence
+ i32::from(a);
+ i32::from(!a);
+ i32::from(!a);
+ i32::from(a || b);
+ i32::from(cond(a, b));
+ i32::from(x + y < 4);
+
+ // if else if
+ if a {
+ 123
+ } else { i32::from(b) };
+
+ // if else if inverted
+ if a {
+ 123
+ } else { i32::from(!b) };
+
+ // Shouldn't lint
+
+ if a {
+ 1
+ } else if b {
+ 0
+ } else {
+ 3
+ };
+
+ if a {
+ 3
+ } else if b {
+ 1
+ } else {
+ -2
+ };
+
+ if a {
+ 3
+ } else {
+ 0
+ };
+ if a {
+ side_effect();
+ 1
+ } else {
+ 0
+ };
+ if a {
+ 1
+ } else {
+ side_effect();
+ 0
+ };
+
+ // multiple else ifs
+ if a {
+ 123
+ } else if b {
+ 1
+ } else if a | b {
+ 0
+ } else {
+ 123
+ };
+
++ pub const SHOULD_NOT_LINT: usize = if true { 1 } else { 0 };
++
+ some_fn(a);
+}
+
+// Lint returns and type inference
+fn some_fn(a: bool) -> u8 {
+ u8::from(a)
+}
+
+fn side_effect() {}
+
+fn cond(a: bool, b: bool) -> bool {
+ a || b
+}
++
++enum Enum {
++ A,
++ B,
++}
++
++fn if_let(a: Enum, b: Enum) {
++ if let Enum::A = a {
++ 1
++ } else {
++ 0
++ };
++
++ if let Enum::A = a && let Enum::B = b {
++ 1
++ } else {
++ 0
++ };
++}
--- /dev/null
+// run-rustfix
+
++#![feature(let_chains)]
+#![warn(clippy::bool_to_int_with_if)]
+#![allow(unused, dead_code, clippy::unnecessary_operation, clippy::no_effect)]
+
+fn main() {
+ let a = true;
+ let b = false;
+
+ let x = 1;
+ let y = 2;
+
+ // Should lint
+ // precedence
+ if a {
+ 1
+ } else {
+ 0
+ };
+ if a {
+ 0
+ } else {
+ 1
+ };
+ if !a {
+ 1
+ } else {
+ 0
+ };
+ if a || b {
+ 1
+ } else {
+ 0
+ };
+ if cond(a, b) {
+ 1
+ } else {
+ 0
+ };
+ if x + y < 4 {
+ 1
+ } else {
+ 0
+ };
+
+ // if else if
+ if a {
+ 123
+ } else if b {
+ 1
+ } else {
+ 0
+ };
+
+ // if else if inverted
+ if a {
+ 123
+ } else if b {
+ 0
+ } else {
+ 1
+ };
+
+ // Shouldn't lint
+
+ if a {
+ 1
+ } else if b {
+ 0
+ } else {
+ 3
+ };
+
+ if a {
+ 3
+ } else if b {
+ 1
+ } else {
+ -2
+ };
+
+ if a {
+ 3
+ } else {
+ 0
+ };
+ if a {
+ side_effect();
+ 1
+ } else {
+ 0
+ };
+ if a {
+ 1
+ } else {
+ side_effect();
+ 0
+ };
+
+ // multiple else ifs
+ if a {
+ 123
+ } else if b {
+ 1
+ } else if a | b {
+ 0
+ } else {
+ 123
+ };
+
++ pub const SHOULD_NOT_LINT: usize = if true { 1 } else { 0 };
++
+ some_fn(a);
+}
+
+// Lint returns and type inference
+fn some_fn(a: bool) -> u8 {
+ if a { 1 } else { 0 }
+}
+
+fn side_effect() {}
+
+fn cond(a: bool, b: bool) -> bool {
+ a || b
+}
++
++enum Enum {
++ A,
++ B,
++}
++
++fn if_let(a: Enum, b: Enum) {
++ if let Enum::A = a {
++ 1
++ } else {
++ 0
++ };
++
++ if let Enum::A = a && let Enum::B = b {
++ 1
++ } else {
++ 0
++ };
++}
--- /dev/null
- --> $DIR/bool_to_int_with_if.rs:15:5
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:20:5
++ --> $DIR/bool_to_int_with_if.rs:16:5
+ |
+LL | / if a {
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `i32::from(a)`
+ |
+ = note: `a as i32` or `a.into()` can also be valid options
+ = note: `-D clippy::bool-to-int-with-if` implied by `-D warnings`
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:25:5
++ --> $DIR/bool_to_int_with_if.rs:21:5
+ |
+LL | / if a {
+LL | | 0
+LL | | } else {
+LL | | 1
+LL | | };
+ | |_____^ help: replace with from: `i32::from(!a)`
+ |
+ = note: `!a as i32` or `(!a).into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:30:5
++ --> $DIR/bool_to_int_with_if.rs:26:5
+ |
+LL | / if !a {
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `i32::from(!a)`
+ |
+ = note: `!a as i32` or `(!a).into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:35:5
++ --> $DIR/bool_to_int_with_if.rs:31:5
+ |
+LL | / if a || b {
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `i32::from(a || b)`
+ |
+ = note: `(a || b) as i32` or `(a || b).into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:40:5
++ --> $DIR/bool_to_int_with_if.rs:36:5
+ |
+LL | / if cond(a, b) {
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `i32::from(cond(a, b))`
+ |
+ = note: `cond(a, b) as i32` or `cond(a, b).into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:49:12
++ --> $DIR/bool_to_int_with_if.rs:41:5
+ |
+LL | / if x + y < 4 {
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `i32::from(x + y < 4)`
+ |
+ = note: `(x + y < 4) as i32` or `(x + y < 4).into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:58:12
++ --> $DIR/bool_to_int_with_if.rs:50:12
+ |
+LL | } else if b {
+ | ____________^
+LL | | 1
+LL | | } else {
+LL | | 0
+LL | | };
+ | |_____^ help: replace with from: `{ i32::from(b) }`
+ |
+ = note: `b as i32` or `b.into()` can also be valid options
+
+error: boolean to int conversion using if
- --> $DIR/bool_to_int_with_if.rs:116:5
++ --> $DIR/bool_to_int_with_if.rs:59:12
+ |
+LL | } else if b {
+ | ____________^
+LL | | 0
+LL | | } else {
+LL | | 1
+LL | | };
+ | |_____^ help: replace with from: `{ i32::from(!b) }`
+ |
+ = note: `!b as i32` or `(!b).into()` can also be valid options
+
+error: boolean to int conversion using if
++ --> $DIR/bool_to_int_with_if.rs:119:5
+ |
+LL | if a { 1 } else { 0 }
+ | ^^^^^^^^^^^^^^^^^^^^^ help: replace with from: `u8::from(a)`
+ |
+ = note: `a as u8` or `a.into()` can also be valid options
+
+error: aborting due to 9 previous errors
+
--- /dev/null
+#![allow(clippy::all)]
+#![warn(clippy::cognitive_complexity)]
+#![allow(unused, unused_crate_dependencies)]
+
+#[rustfmt::skip]
+fn main() {
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+ if true {
+ println!("a");
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn kaboom() {
+ let n = 0;
+ 'a: for i in 0..20 {
+ 'b: for j in i..20 {
+ for k in j..20 {
+ if k == 5 {
+ break 'b;
+ }
+ if j == 3 && k == 6 {
+ continue 'a;
+ }
+ if k == j {
+ continue;
+ }
+ println!("bake");
+ }
+ }
+ println!("cake");
+ }
+}
+
+fn bloo() {
+ match 42 {
+ 0 => println!("hi"),
+ 1 => println!("hai"),
+ 2 => println!("hey"),
+ 3 => println!("hallo"),
+ 4 => println!("hello"),
+ 5 => println!("salut"),
+ 6 => println!("good morning"),
+ 7 => println!("good evening"),
+ 8 => println!("good afternoon"),
+ 9 => println!("good night"),
+ 10 => println!("bonjour"),
+ 11 => println!("hej"),
+ 12 => println!("hej hej"),
+ 13 => println!("greetings earthling"),
+ 14 => println!("take us to you leader"),
+ 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 => println!("take us to you leader"),
+ 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 53 => println!("there is no undefined behavior"),
+ 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 => println!("I know borrow-fu"),
+ _ => println!("bye"),
+ }
+}
+
+// Short circuiting operations don't increase the complexity of a function.
+// Note that the minimum complexity of a function is 1.
+#[clippy::cognitive_complexity = "1"]
+fn lots_of_short_circuits() -> bool {
+ true && false && true && false && true && false && true
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn lots_of_short_circuits2() -> bool {
+ true || false || true || false || true || false || true
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn baa() {
+ let x = || match 99 {
+ 0 => 0,
+ 1 => 1,
+ 2 => 2,
+ 4 => 4,
+ 6 => 6,
+ 9 => 9,
+ _ => 42,
+ };
+ if x() == 42 {
+ println!("x");
+ } else {
+ println!("not x");
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn bar() {
+ match 99 {
+ 0 => println!("hi"),
+ _ => println!("bye"),
+ }
+}
+
+#[test]
+#[clippy::cognitive_complexity = "1"]
+/// Tests are usually complex but simple at the same time. `clippy::cognitive_complexity` used to
+/// give lots of false-positives in tests.
+fn dont_warn_on_tests() {
+ match 99 {
+ 0 => println!("hi"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barr() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barr2() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barrr() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => panic!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barrr2() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => panic!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+ match 99 {
+ 0 => println!("hi"),
+ 1 => panic!("bla"),
+ 2 | 3 => println!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barrrr() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => panic!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn barrrr2() {
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => panic!("blub"),
+ _ => println!("bye"),
+ }
+ match 99 {
+ 0 => println!("hi"),
+ 1 => println!("bla"),
+ 2 | 3 => panic!("blub"),
+ _ => println!("bye"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn cake() {
+ if 4 == 5 {
+ println!("yea");
+ } else {
+ panic!("meh");
+ }
+ println!("whee");
+}
+
+#[clippy::cognitive_complexity = "1"]
+pub fn read_file(input_path: &str) -> String {
+ use std::fs::File;
+ use std::io::{Read, Write};
+ use std::path::Path;
+ let mut file = match File::open(&Path::new(input_path)) {
+ Ok(f) => f,
+ Err(err) => {
+ panic!("Can't open {}: {}", input_path, err);
+ },
+ };
+
+ let mut bytes = Vec::new();
+
+ match file.read_to_end(&mut bytes) {
+ Ok(..) => {},
+ Err(_) => {
+ panic!("Can't read {}", input_path);
+ },
+ };
+
+ match String::from_utf8(bytes) {
+ Ok(contents) => contents,
+ Err(_) => {
+ panic!("{} is not UTF-8 encoded", input_path);
+ },
+ }
+}
+
+enum Void {}
+
+#[clippy::cognitive_complexity = "1"]
+fn void(void: Void) {
+ if true {
+ match void {}
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn mcarton_sees_all() {
+ panic!("meh");
+ panic!("möh");
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn try_() -> Result<i32, &'static str> {
+ match 5 {
+ 5 => Ok(5),
+ _ => return Err("bla"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn try_again() -> Result<i32, &'static str> {
+ let _ = Ok(42)?;
+ let _ = Ok(43)?;
+ let _ = Ok(44)?;
+ let _ = Ok(45)?;
+ let _ = Ok(46)?;
+ let _ = Ok(47)?;
+ let _ = Ok(48)?;
+ let _ = Ok(49)?;
+ match 5 {
+ 5 => Ok(5),
+ _ => return Err("bla"),
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn early() -> Result<i32, &'static str> {
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+ return Ok(5);
+}
+
+#[rustfmt::skip]
+#[clippy::cognitive_complexity = "1"]
+fn early_ret() -> i32 {
+ let a = if true { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ let a = if a < 99 { 42 } else { return 0; };
+ match 5 {
+ 5 => 5,
+ _ => return 6,
+ }
+}
+
+#[clippy::cognitive_complexity = "1"]
+fn closures() {
+ let x = |a: i32, b: i32| -> i32 {
+ if true {
+ println!("moo");
+ }
+
+ a + b
+ };
+}
+
+struct Moo;
+
+#[clippy::cognitive_complexity = "1"]
+impl Moo {
+ fn moo(&self) {
+ if true {
+ println!("moo");
+ }
+ }
+}
++
++#[clippy::cognitive_complexity = "1"]
++mod issue9300 {
++ async fn a() {
++ let a = 0;
++ if a == 0 {}
++ }
++
++ pub struct S;
++ impl S {
++ pub async fn async_method() {
++ let a = 0;
++ if a == 0 {}
++ }
++ }
++}
--- /dev/null
- error: aborting due to 17 previous errors
+error: the function has a cognitive complexity of (28/25)
+ --> $DIR/cognitive_complexity.rs:6:4
+ |
+LL | fn main() {
+ | ^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+ = note: `-D clippy::cognitive-complexity` implied by `-D warnings`
+
+error: the function has a cognitive complexity of (7/1)
+ --> $DIR/cognitive_complexity.rs:91:4
+ |
+LL | fn kaboom() {
+ | ^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:149:4
+ |
+LL | fn baa() {
+ | ^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:150:13
+ |
+LL | let x = || match 99 {
+ | ^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:167:4
+ |
+LL | fn bar() {
+ | ^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:186:4
+ |
+LL | fn barr() {
+ | ^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (3/1)
+ --> $DIR/cognitive_complexity.rs:196:4
+ |
+LL | fn barr2() {
+ | ^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:212:4
+ |
+LL | fn barrr() {
+ | ^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (3/1)
+ --> $DIR/cognitive_complexity.rs:222:4
+ |
+LL | fn barrr2() {
+ | ^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:238:4
+ |
+LL | fn barrrr() {
+ | ^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (3/1)
+ --> $DIR/cognitive_complexity.rs:248:4
+ |
+LL | fn barrrr2() {
+ | ^^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:264:4
+ |
+LL | fn cake() {
+ | ^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (4/1)
+ --> $DIR/cognitive_complexity.rs:274:8
+ |
+LL | pub fn read_file(input_path: &str) -> String {
+ | ^^^^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:305:4
+ |
+LL | fn void(void: Void) {
+ | ^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (8/1)
+ --> $DIR/cognitive_complexity.rs:356:4
+ |
+LL | fn early_ret() -> i32 {
+ | ^^^^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:377:13
+ |
+LL | let x = |a: i32, b: i32| -> i32 {
+ | ^^^^^^^^^^^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
+error: the function has a cognitive complexity of (2/1)
+ --> $DIR/cognitive_complexity.rs:390:8
+ |
+LL | fn moo(&self) {
+ | ^^^
+ |
+ = help: you could split it up into multiple smaller functions
+
++error: the function has a cognitive complexity of (2/1)
++ --> $DIR/cognitive_complexity.rs:399:14
++ |
++LL | async fn a() {
++ | ^
++ |
++ = help: you could split it up into multiple smaller functions
++
++error: the function has a cognitive complexity of (2/1)
++ --> $DIR/cognitive_complexity.rs:406:22
++ |
++LL | pub async fn async_method() {
++ | ^^^^^^^^^^^^
++ |
++ = help: you could split it up into multiple smaller functions
++
++error: aborting due to 19 previous errors
+
--- /dev/null
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a
+ --> $DIR/ice-2774.rs:15:1
+ |
+LL | pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::needless-lifetimes` implied by `-D warnings`
+
+error: aborting due to previous error
+
--- /dev/null
--- /dev/null
++//! <https://github.com/rust-lang/rust-clippy/issues/9746#issuecomment-1297132880>
++
++trait Trait {}
++
++struct Struct<'a> {
++ _inner: &'a Struct<'a>,
++}
++
++impl Trait for Struct<'_> {}
++
++fn example<'a>(s: &'a Struct) -> Box<Box<dyn Trait + 'a>> {
++ Box::new(Box::new(Struct { _inner: s }))
++}
++
++fn main() {}
--- /dev/null
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a
+ --> $DIR/needless_lifetimes_impl_trait.rs:15:5
+ |
+LL | fn baz<'a>(&'a self) -> impl Foo + 'a {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/needless_lifetimes_impl_trait.rs:1:9
+ |
+LL | #![deny(clippy::needless_lifetimes)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
- // ignore-windows
+// compile-flags: -Clink-arg=-nostartfiles
+// ignore-macos
+
+#![feature(lang_items, start, libc)]
+#![no_std]
+
+use core::panic::PanicInfo;
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+static N: AtomicUsize = AtomicUsize::new(0);
+
+#[warn(clippy::main_recursion)]
+#[start]
+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);
+ }
+
+ 0
+}
+
+#[allow(clippy::empty_loop)]
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ loop {}
+}
+
+#[lang = "eh_personality"]
+extern "C" fn eh_personality() {}
--- /dev/null
- LL | / pub fn pub_fn_missing_errors_header() -> Result<(), ()> {
- LL | | unimplemented!();
- LL | | }
- | |_^
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:7:1
+ |
- LL | / pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> {
- LL | | unimplemented!();
- LL | | }
- | |_^
++LL | pub fn pub_fn_missing_errors_header() -> Result<(), ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::missing-errors-doc` implied by `-D warnings`
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:11:1
+ |
- LL | / pub fn pub_fn_returning_io_result() -> io::Result<()> {
- LL | | unimplemented!();
- LL | | }
- | |_^
++LL | pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:16:1
+ |
- LL | / pub async fn async_pub_fn_returning_io_result() -> io::Result<()> {
- LL | | unimplemented!();
- LL | | }
- | |_^
++LL | pub fn pub_fn_returning_io_result() -> io::Result<()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:21:1
+ |
- LL | / pub fn pub_method_missing_errors_header() -> Result<(), ()> {
- LL | | unimplemented!();
- LL | | }
- | |_____^
++LL | pub async fn async_pub_fn_returning_io_result() -> io::Result<()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:51:5
+ |
- LL | / pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> {
- LL | | unimplemented!();
- LL | | }
- | |_____^
++LL | pub fn pub_method_missing_errors_header() -> Result<(), ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:56:5
+ |
++LL | pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function returning `Result` missing `# Errors` section
+ --> $DIR/doc_errors.rs:85:5
+ |
+LL | fn trait_method_missing_errors_header() -> Result<(), ()>;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 7 previous errors
+
--- /dev/null
--- /dev/null
++// aux-build:doc_unsafe_macros.rs
++
++#![allow(clippy::let_unit_value)]
++
++#[macro_use]
++extern crate doc_unsafe_macros;
++
++/// This is has no safety section, and does not need one either
++pub fn destroy_the_planet() {
++ unimplemented!();
++}
++
++/// This one does not need a `Safety` section
++///
++/// # Safety
++///
++/// This function shouldn't be called unless the horsemen are ready
++pub fn apocalypse(universe: &mut ()) {
++ unimplemented!();
++}
++
++/// This is a private function, skip to match behavior with `missing_safety_doc`.
++///
++/// # Safety
++///
++/// Boo!
++fn you_dont_see_me() {
++ unimplemented!();
++}
++
++mod private_mod {
++ /// This is public but unexported function, skip to match behavior with `missing_safety_doc`.
++ ///
++ /// # Safety
++ ///
++ /// Very safe!
++ pub fn only_crate_wide_accessible() {
++ unimplemented!();
++ }
++
++ /// # Safety
++ ///
++ /// Unnecessary safety!
++ pub fn republished() {
++ unimplemented!();
++ }
++}
++
++pub use private_mod::republished;
++
++pub trait SafeTraitSafeMethods {
++ fn woefully_underdocumented(self);
++
++ /// # Safety
++ ///
++ /// Unnecessary!
++ fn documented(self);
++}
++
++pub trait SafeTrait {
++ fn method();
++}
++
++/// # Safety
++///
++/// Unnecessary!
++pub trait DocumentedSafeTrait {
++ fn method2();
++}
++
++pub struct Struct;
++
++impl SafeTraitSafeMethods for Struct {
++ fn woefully_underdocumented(self) {
++ // all is well
++ }
++
++ fn documented(self) {
++ // all is still well
++ }
++}
++
++impl SafeTrait for Struct {
++ fn method() {}
++}
++
++impl DocumentedSafeTrait for Struct {
++ fn method2() {}
++}
++
++impl Struct {
++ /// # Safety
++ ///
++ /// Unnecessary!
++ pub fn documented() -> Self {
++ unimplemented!();
++ }
++
++ pub fn undocumented(&self) {
++ unimplemented!();
++ }
++
++ /// Private, fine again to stay consistent with `missing_safety_doc`.
++ ///
++ /// # Safety
++ ///
++ /// Unnecessary!
++ fn private(&self) {
++ unimplemented!();
++ }
++}
++
++macro_rules! very_safe {
++ () => {
++ pub fn whee() {
++ unimplemented!()
++ }
++
++ /// # Safety
++ ///
++ /// Driving is very safe already!
++ pub fn drive() {
++ whee()
++ }
++ };
++}
++
++very_safe!();
++
++// we don't lint code from external macros
++undocd_safe!();
++
++fn main() {}
++
++// do not lint if any parent has `#[doc(hidden)]` attribute
++// see #7347
++#[doc(hidden)]
++pub mod __macro {
++ pub struct T;
++ impl T {
++ pub unsafe fn f() {}
++ }
++}
++
++/// # Implementation safety
++pub trait DocumentedSafeTraitWithImplementationHeader {
++ fn method();
++}
--- /dev/null
--- /dev/null
++error: safe function's docs have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:18:1
++ |
++LL | pub fn apocalypse(universe: &mut ()) {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::unnecessary-safety-doc` implied by `-D warnings`
++
++error: safe function's docs have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:44:5
++ |
++LL | pub fn republished() {
++ | ^^^^^^^^^^^^^^^^^^^^
++
++error: safe function's docs have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:57:5
++ |
++LL | fn documented(self);
++ | ^^^^^^^^^^^^^^^^^^^^
++
++error: docs for safe trait have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:67:1
++ |
++LL | pub trait DocumentedSafeTrait {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: safe function's docs have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:95:5
++ |
++LL | pub fn documented() -> Self {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: safe function's docs have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:122:9
++ |
++LL | pub fn drive() {
++ | ^^^^^^^^^^^^^^
++...
++LL | very_safe!();
++ | ------------ in this macro invocation
++ |
++ = note: this error originates in the macro `very_safe` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: docs for safe trait have unnecessary `# Safety` section
++ --> $DIR/doc_unnecessary_unsafe.rs:146:1
++ |
++LL | pub trait DocumentedSafeTraitWithImplementationHeader {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 7 previous errors
++
--- /dev/null
- LL | / pub unsafe fn destroy_the_planet() {
- LL | | unimplemented!();
- LL | | }
- | |_^
+error: unsafe function's docs miss `# Safety` section
+ --> $DIR/doc_unsafe.rs:9:1
+ |
- LL | / pub unsafe fn republished() {
- LL | | unimplemented!();
- LL | | }
- | |_____^
++LL | pub unsafe fn destroy_the_planet() {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::missing-safety-doc` implied by `-D warnings`
+
+error: unsafe function's docs miss `# Safety` section
+ --> $DIR/doc_unsafe.rs:32:5
+ |
- LL | / pub unsafe trait UnsafeTrait {
- LL | | fn method();
- LL | | }
- | |_^
++LL | pub unsafe fn republished() {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unsafe function's docs miss `# Safety` section
+ --> $DIR/doc_unsafe.rs:40:5
+ |
+LL | unsafe fn woefully_underdocumented(self);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for unsafe trait missing `# Safety` section
+ --> $DIR/doc_unsafe.rs:46:1
+ |
- LL | / pub unsafe fn more_undocumented_unsafe() -> Self {
- LL | | unimplemented!();
- LL | | }
- | |_____^
++LL | pub unsafe trait UnsafeTrait {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unsafe function's docs miss `# Safety` section
+ --> $DIR/doc_unsafe.rs:76:5
+ |
- LL | / pub unsafe fn whee() {
- LL | | unimplemented!()
- LL | | }
- | |_________^
++LL | pub unsafe fn more_undocumented_unsafe() -> Self {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unsafe function's docs miss `# Safety` section
+ --> $DIR/doc_unsafe.rs:92:9
+ |
- LL | very_unsafe!();
- | -------------- in this macro invocation
++LL | pub unsafe fn whee() {
++ | ^^^^^^^^^^^^^^^^^^^^
+...
++LL | very_unsafe!();
++ | -------------- in this macro invocation
+ |
+ = note: this error originates in the macro `very_unsafe` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 6 previous errors
+
--- /dev/null
+// compile-flags: --test
+
+#![warn(clippy::eq_op)]
+#![allow(clippy::double_parens, clippy::identity_op, clippy::nonminimal_bool)]
++#![allow(clippy::suspicious_xor_used_as_pow)]
+
+fn main() {
+ // simple values and comparisons
+ let _ = 1 == 1;
+ let _ = "no" == "no";
+ // even though I agree that no means no ;-)
+ let _ = false != false;
+ let _ = 1.5 < 1.5;
+ let _ = 1u64 >= 1u64;
+
+ // casts, methods, parentheses
+ let _ = (1u32 as u64) & (1u32 as u64);
+ #[rustfmt::skip]
+ {
+ let _ = 1 ^ ((((((1))))));
+ };
+
+ // unary and binary operators
+ let _ = (-(2) < -(2));
+ let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
+ let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
+
+ // various other things
+ let _ = ([1] != [1]);
+ let _ = ((1, 2) != (1, 2));
+ let _ = vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros
+
+ // const folding
+ let _ = 1 + 1 == 2;
+ let _ = 1 - 1 == 0;
+
+ let _ = 1 - 1;
+ let _ = 1 / 1;
+ let _ = true && true;
+
+ let _ = true || true;
+
+ let a: u32 = 0;
+ let b: u32 = 0;
+
+ let _ = a == b && b == a;
+ let _ = a != b && b != a;
+ let _ = a < b && b > a;
+ let _ = a <= b && b >= a;
+
+ let mut a = vec![1];
+ let _ = a == a;
+ let _ = 2 * a.len() == 2 * a.len(); // ok, functions
+ let _ = a.pop() == a.pop(); // ok, functions
+
+ check_ignore_macro();
+
+ // named constants
+ const A: u32 = 10;
+ const B: u32 = 10;
+ const C: u32 = A / B; // ok, different named constants
+ const D: u32 = A / A;
+}
+
+macro_rules! check_if_named_foo {
+ ($expression:expr) => {
+ if stringify!($expression) == "foo" {
+ println!("foo!");
+ } else {
+ println!("not foo.");
+ }
+ };
+}
+
+macro_rules! bool_macro {
+ ($expression:expr) => {
+ true
+ };
+}
+
+fn check_ignore_macro() {
+ check_if_named_foo!(foo);
+ // checks if the lint ignores macros with `!` operator
+ let _ = !bool_macro!(1) && !bool_macro!("");
+}
+
+struct Nested {
+ inner: ((i32,), (i32,), (i32,)),
+}
+
+fn check_nested(n1: &Nested, n2: &Nested) -> bool {
+ // `n2.inner.0.0` mistyped as `n1.inner.0.0`
+ (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
+}
+
+#[test]
+fn eq_op_shouldnt_trigger_in_tests() {
+ let a = 1;
+ let result = a + 1 == 1 + a;
+ assert!(result);
+}
+
+#[test]
+fn eq_op_macros_shouldnt_trigger_in_tests() {
+ let a = 1;
+ let b = 2;
+ assert_eq!(a, a);
+ assert_eq!(a + b, b + a);
+}
--- /dev/null
- --> $DIR/eq_op.rs:8:13
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:9:13
++ --> $DIR/eq_op.rs:9:13
+ |
+LL | let _ = 1 == 1;
+ | ^^^^^^
+ |
+ = note: `-D clippy::eq-op` implied by `-D warnings`
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:11:13
++ --> $DIR/eq_op.rs:10:13
+ |
+LL | let _ = "no" == "no";
+ | ^^^^^^^^^^^^
+
+error: equal expressions as operands to `!=`
- --> $DIR/eq_op.rs:12:13
++ --> $DIR/eq_op.rs:12:13
+ |
+LL | let _ = false != false;
+ | ^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `<`
- --> $DIR/eq_op.rs:13:13
++ --> $DIR/eq_op.rs:13:13
+ |
+LL | let _ = 1.5 < 1.5;
+ | ^^^^^^^^^
+
+error: equal expressions as operands to `>=`
- --> $DIR/eq_op.rs:16:13
++ --> $DIR/eq_op.rs:14:13
+ |
+LL | let _ = 1u64 >= 1u64;
+ | ^^^^^^^^^^^^
+
+error: equal expressions as operands to `&`
- --> $DIR/eq_op.rs:19:17
++ --> $DIR/eq_op.rs:17:13
+ |
+LL | let _ = (1u32 as u64) & (1u32 as u64);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `^`
- --> $DIR/eq_op.rs:23:13
++ --> $DIR/eq_op.rs:20:17
+ |
+LL | let _ = 1 ^ ((((((1))))));
+ | ^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `<`
- --> $DIR/eq_op.rs:24:13
++ --> $DIR/eq_op.rs:24:13
+ |
+LL | let _ = (-(2) < -(2));
+ | ^^^^^^^^^^^^^
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:24:14
++ --> $DIR/eq_op.rs:25:13
+ |
+LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `&`
- --> $DIR/eq_op.rs:24:35
++ --> $DIR/eq_op.rs:25:14
+ |
+LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
+ | ^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `&`
- --> $DIR/eq_op.rs:25:13
++ --> $DIR/eq_op.rs:25:35
+ |
+LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
+ | ^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:28:13
++ --> $DIR/eq_op.rs:26:13
+ |
+LL | let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `!=`
- --> $DIR/eq_op.rs:29:13
++ --> $DIR/eq_op.rs:29:13
+ |
+LL | let _ = ([1] != [1]);
+ | ^^^^^^^^^^^^
+
+error: equal expressions as operands to `!=`
- --> $DIR/eq_op.rs:33:13
++ --> $DIR/eq_op.rs:30:13
+ |
+LL | let _ = ((1, 2) != (1, 2));
+ | ^^^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:34:13
++ --> $DIR/eq_op.rs:34:13
+ |
+LL | let _ = 1 + 1 == 2;
+ | ^^^^^^^^^^
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:34:13
++ --> $DIR/eq_op.rs:35:13
+ |
+LL | let _ = 1 - 1 == 0;
+ | ^^^^^^^^^^
+
+error: equal expressions as operands to `-`
- --> $DIR/eq_op.rs:36:13
++ --> $DIR/eq_op.rs:35:13
+ |
+LL | let _ = 1 - 1 == 0;
+ | ^^^^^
+
+error: equal expressions as operands to `-`
- --> $DIR/eq_op.rs:37:13
++ --> $DIR/eq_op.rs:37:13
+ |
+LL | let _ = 1 - 1;
+ | ^^^^^
+
+error: equal expressions as operands to `/`
- --> $DIR/eq_op.rs:38:13
++ --> $DIR/eq_op.rs:38:13
+ |
+LL | let _ = 1 / 1;
+ | ^^^^^
+
+error: equal expressions as operands to `&&`
- --> $DIR/eq_op.rs:40:13
++ --> $DIR/eq_op.rs:39:13
+ |
+LL | let _ = true && true;
+ | ^^^^^^^^^^^^
+
+error: equal expressions as operands to `||`
- --> $DIR/eq_op.rs:45:13
++ --> $DIR/eq_op.rs:41:13
+ |
+LL | let _ = true || true;
+ | ^^^^^^^^^^^^
+
+error: equal expressions as operands to `&&`
- --> $DIR/eq_op.rs:46:13
++ --> $DIR/eq_op.rs:46:13
+ |
+LL | let _ = a == b && b == a;
+ | ^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `&&`
- --> $DIR/eq_op.rs:47:13
++ --> $DIR/eq_op.rs:47:13
+ |
+LL | let _ = a != b && b != a;
+ | ^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `&&`
- --> $DIR/eq_op.rs:48:13
++ --> $DIR/eq_op.rs:48:13
+ |
+LL | let _ = a < b && b > a;
+ | ^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `&&`
- --> $DIR/eq_op.rs:51:13
++ --> $DIR/eq_op.rs:49:13
+ |
+LL | let _ = a <= b && b >= a;
+ | ^^^^^^^^^^^^^^^^
+
+error: equal expressions as operands to `==`
- --> $DIR/eq_op.rs:61:20
++ --> $DIR/eq_op.rs:52:13
+ |
+LL | let _ = a == a;
+ | ^^^^^^
+
+error: equal expressions as operands to `/`
- --> $DIR/eq_op.rs:92:5
++ --> $DIR/eq_op.rs:62:20
+ |
+LL | const D: u32 = A / A;
+ | ^^^^^
+
+error: equal expressions as operands to `==`
++ --> $DIR/eq_op.rs:93:5
+ |
+LL | (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 28 previous errors
+
--- /dev/null
- if let NotPartialEq::A = f {}
+// run-rustfix
+// aux-build:macro_rules.rs
+
+#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
+#![warn(clippy::equatable_if_let)]
+
+#[macro_use]
+extern crate macro_rules;
+
+use std::cmp::Ordering;
+
+#[derive(PartialEq)]
+enum Enum {
+ TupleVariant(i32, u64),
+ RecordVariant { a: i64, b: u32 },
+ UnitVariant,
+ Recursive(Struct),
+}
+
+#[derive(PartialEq)]
+struct Struct {
+ a: i32,
+ b: bool,
+}
+
++struct NoPartialEqStruct {
++ a: i32,
++ b: bool,
++}
++
+enum NotPartialEq {
+ A,
+ B,
+}
+
+enum NotStructuralEq {
+ A,
+ B,
+}
+
+impl PartialEq for NotStructuralEq {
+ fn eq(&self, _: &NotStructuralEq) -> bool {
+ false
+ }
+}
+
+fn main() {
+ let a = 2;
+ let b = 3;
+ let c = Some(2);
+ let d = Struct { a: 2, b: false };
+ let e = Enum::UnitVariant;
+ let f = NotPartialEq::A;
+ let g = NotStructuralEq::A;
++ let h = NoPartialEqStruct { a: 2, b: false };
+
+ // true
+
+ if a == 2 {}
+ if a.cmp(&b) == Ordering::Greater {}
+ if c == Some(2) {}
+ if d == (Struct { a: 2, b: false }) {}
+ if e == Enum::TupleVariant(32, 64) {}
+ if e == (Enum::RecordVariant { a: 64, b: 32 }) {}
+ if e == Enum::UnitVariant {}
+ if (e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false }) {}
+
+ // false
+
+ if let 2 | 3 = a {}
+ if let x @ 2 = a {}
+ if let Some(3 | 4) = c {}
+ if let Struct { a, b: false } = d {}
+ if let Struct { a: 2, b: x } = d {}
- if let Some(NotPartialEq::A) = Some(f) {}
++ if matches!(f, NotPartialEq::A) {}
+ if g == NotStructuralEq::A {}
++ if matches!(Some(f), Some(NotPartialEq::A)) {}
+ if Some(g) == Some(NotStructuralEq::A) {}
++ if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}
+
+ macro_rules! m1 {
+ (x) => {
+ "abc"
+ };
+ }
+ if "abc" == m1!(x) {
+ println!("OK");
+ }
+
+ equatable_if_let!(a);
+}
--- /dev/null
+// run-rustfix
+// aux-build:macro_rules.rs
+
+#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
+#![warn(clippy::equatable_if_let)]
+
+#[macro_use]
+extern crate macro_rules;
+
+use std::cmp::Ordering;
+
+#[derive(PartialEq)]
+enum Enum {
+ TupleVariant(i32, u64),
+ RecordVariant { a: i64, b: u32 },
+ UnitVariant,
+ Recursive(Struct),
+}
+
+#[derive(PartialEq)]
+struct Struct {
+ a: i32,
+ b: bool,
+}
+
++struct NoPartialEqStruct {
++ a: i32,
++ b: bool,
++}
++
+enum NotPartialEq {
+ A,
+ B,
+}
+
+enum NotStructuralEq {
+ A,
+ B,
+}
+
+impl PartialEq for NotStructuralEq {
+ fn eq(&self, _: &NotStructuralEq) -> bool {
+ false
+ }
+}
+
+fn main() {
+ let a = 2;
+ let b = 3;
+ let c = Some(2);
+ let d = Struct { a: 2, b: false };
+ let e = Enum::UnitVariant;
+ let f = NotPartialEq::A;
+ let g = NotStructuralEq::A;
++ let h = NoPartialEqStruct { a: 2, b: false };
+
+ // true
+
+ if let 2 = a {}
+ if let Ordering::Greater = a.cmp(&b) {}
+ if let Some(2) = c {}
+ if let Struct { a: 2, b: false } = d {}
+ if let Enum::TupleVariant(32, 64) = e {}
+ if let Enum::RecordVariant { a: 64, b: 32 } = e {}
+ if let Enum::UnitVariant = e {}
+ if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {}
+
+ // false
+
+ if let 2 | 3 = a {}
+ if let x @ 2 = a {}
+ if let Some(3 | 4) = c {}
+ if let Struct { a, b: false } = d {}
+ if let Struct { a: 2, b: x } = d {}
+ if let NotPartialEq::A = f {}
+ if let NotStructuralEq::A = g {}
+ if let Some(NotPartialEq::A) = Some(f) {}
+ if let Some(NotStructuralEq::A) = Some(g) {}
++ if let NoPartialEqStruct { a: 2, b: false } = h {}
+
+ macro_rules! m1 {
+ (x) => {
+ "abc"
+ };
+ }
+ if let m1!(x) = "abc" {
+ println!("OK");
+ }
+
+ equatable_if_let!(a);
+}
--- /dev/null
- --> $DIR/equatable_if_let.rs:53:8
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:54:8
++ --> $DIR/equatable_if_let.rs:59:8
+ |
+LL | if let 2 = a {}
+ | ^^^^^^^^^ help: try: `a == 2`
+ |
+ = note: `-D clippy::equatable-if-let` implied by `-D warnings`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:55:8
++ --> $DIR/equatable_if_let.rs:60:8
+ |
+LL | if let Ordering::Greater = a.cmp(&b) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.cmp(&b) == Ordering::Greater`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:56:8
++ --> $DIR/equatable_if_let.rs:61:8
+ |
+LL | if let Some(2) = c {}
+ | ^^^^^^^^^^^^^^^ help: try: `c == Some(2)`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:57:8
++ --> $DIR/equatable_if_let.rs:62:8
+ |
+LL | if let Struct { a: 2, b: false } = d {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `d == (Struct { a: 2, b: false })`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:58:8
++ --> $DIR/equatable_if_let.rs:63:8
+ |
+LL | if let Enum::TupleVariant(32, 64) = e {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::TupleVariant(32, 64)`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:59:8
++ --> $DIR/equatable_if_let.rs:64:8
+ |
+LL | if let Enum::RecordVariant { a: 64, b: 32 } = e {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == (Enum::RecordVariant { a: 64, b: 32 })`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:60:8
++ --> $DIR/equatable_if_let.rs:65:8
+ |
+LL | if let Enum::UnitVariant = e {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::UnitVariant`
+
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:70:8
++ --> $DIR/equatable_if_let.rs:66:8
+ |
+LL | if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false })`
+
++error: this pattern matching can be expressed using `matches!`
++ --> $DIR/equatable_if_let.rs:75:8
++ |
++LL | if let NotPartialEq::A = f {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(f, NotPartialEq::A)`
++
+error: this pattern matching can be expressed using equality
- --> $DIR/equatable_if_let.rs:72:8
++ --> $DIR/equatable_if_let.rs:76:8
+ |
+LL | if let NotStructuralEq::A = g {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `g == NotStructuralEq::A`
+
++error: this pattern matching can be expressed using `matches!`
++ --> $DIR/equatable_if_let.rs:77:8
++ |
++LL | if let Some(NotPartialEq::A) = Some(f) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(Some(f), Some(NotPartialEq::A))`
++
+error: this pattern matching can be expressed using equality
- error: this pattern matching can be expressed using equality
++ --> $DIR/equatable_if_let.rs:78:8
+ |
+LL | if let Some(NotStructuralEq::A) = Some(g) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(g) == Some(NotStructuralEq::A)`
+
- error: aborting due to 11 previous errors
++error: this pattern matching can be expressed using `matches!`
+ --> $DIR/equatable_if_let.rs:79:8
+ |
++LL | if let NoPartialEqStruct { a: 2, b: false } = h {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(h, NoPartialEqStruct { a: 2, b: false })`
++
++error: this pattern matching can be expressed using equality
++ --> $DIR/equatable_if_let.rs:86:8
++ |
+LL | if let m1!(x) = "abc" {
+ | ^^^^^^^^^^^^^^^^^^ help: try: `"abc" == m1!(x)`
+
++error: aborting due to 14 previous errors
+
--- /dev/null
- error: used `expect()` on `an Option` value
++error: used `expect()` on an `Option` value
+ --> $DIR/expect.rs:5:13
+ |
+LL | let _ = opt.expect("");
+ | ^^^^^^^^^^^^^^
+ |
+ = help: if this value is `None`, it will panic
+ = note: `-D clippy::expect-used` implied by `-D warnings`
+
- error: used `expect()` on `a Result` value
++error: used `expect()` on a `Result` value
+ --> $DIR/expect.rs:10:13
+ |
+LL | let _ = res.expect("");
+ | ^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Err`, it will panic
+
- error: used `expect_err()` on `a Result` value
++error: used `expect_err()` on a `Result` value
+ --> $DIR/expect.rs:11:13
+ |
+LL | let _ = res.expect_err("");
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Ok`, it will panic
+
+error: aborting due to 3 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![feature(closure_lifetime_binder)]
+#![warn(clippy::explicit_auto_deref)]
+#![allow(
+ dead_code,
+ unused_braces,
+ clippy::borrowed_box,
+ clippy::needless_borrow,
+ clippy::needless_return,
+ clippy::ptr_arg,
+ clippy::redundant_field_names,
+ clippy::too_many_arguments,
+ clippy::borrow_deref_ref,
+ clippy::let_unit_value
+)]
+
+trait CallableStr {
+ type T: Fn(&str);
+ fn callable_str(&self) -> Self::T;
+}
+impl CallableStr for () {
+ type T = fn(&str);
+ fn callable_str(&self) -> Self::T {
+ fn f(_: &str) {}
+ f
+ }
+}
+impl CallableStr for i32 {
+ type T = <() as CallableStr>::T;
+ fn callable_str(&self) -> Self::T {
+ ().callable_str()
+ }
+}
+
+trait CallableT<U: ?Sized> {
+ type T: Fn(&U);
+ fn callable_t(&self) -> Self::T;
+}
+impl<U: ?Sized> CallableT<U> for () {
+ type T = fn(&U);
+ fn callable_t(&self) -> Self::T {
+ fn f<U: ?Sized>(_: &U) {}
+ f::<U>
+ }
+}
+impl<U: ?Sized> CallableT<U> for i32 {
+ type T = <() as CallableT<U>>::T;
+ fn callable_t(&self) -> Self::T {
+ ().callable_t()
+ }
+}
+
+fn f_str(_: &str) {}
+fn f_string(_: &String) {}
+fn f_t<T>(_: T) {}
+fn f_ref_t<T: ?Sized>(_: &T) {}
+
+fn f_str_t<T>(_: &str, _: T) {}
+
+fn f_box_t<T>(_: &Box<T>) {}
+
+extern "C" {
+ fn var(_: u32, ...);
+}
+
+fn main() {
+ let s = String::new();
+
+ let _: &str = &s;
+ let _: &str = &{ String::new() };
+ let _: &str = &mut { String::new() };
+ let _ = &*s; // Don't lint. Inferred type would change.
+ let _: &_ = &*s; // Don't lint. Inferred type would change.
+
+ f_str(&s);
+ f_t(&*s); // Don't lint. Inferred type would change.
+ f_ref_t(&*s); // Don't lint. Inferred type would change.
+
+ f_str_t(&s, &*s); // Don't lint second param.
+
+ let b = Box::new(Box::new(Box::new(5)));
+ let _: &Box<i32> = &b;
+ let _: &Box<_> = &**b; // Don't lint. Inferred type would change.
+
+ f_box_t(&**b); // Don't lint. Inferred type would change.
+
+ let c = |_x: &str| ();
+ c(&s);
+
+ let c = |_x| ();
+ c(&*s); // Don't lint. Inferred type would change.
+
+ fn _f(x: &String) -> &str {
+ x
+ }
+
+ fn _f1(x: &String) -> &str {
+ { x }
+ }
+
+ fn _f2(x: &String) -> &str {
+ { x }
+ }
+
+ fn _f3(x: &Box<Box<Box<i32>>>) -> &Box<i32> {
+ x
+ }
+
+ fn _f4(
+ x: String,
+ f1: impl Fn(&str),
+ f2: &dyn Fn(&str),
+ f3: fn(&str),
+ f4: impl CallableStr,
+ f5: <() as CallableStr>::T,
+ f6: <i32 as CallableStr>::T,
+ f7: &dyn CallableStr<T = fn(&str)>,
+ f8: impl CallableT<str>,
+ f9: <() as CallableT<str>>::T,
+ f10: <i32 as CallableT<str>>::T,
+ f11: &dyn CallableT<str, T = fn(&str)>,
+ ) {
+ f1(&x);
+ f2(&x);
+ f3(&x);
+ f4.callable_str()(&x);
+ f5(&x);
+ f6(&x);
+ f7.callable_str()(&x);
+ f8.callable_t()(&x);
+ f9(&x);
+ f10(&x);
+ f11.callable_t()(&x);
+ }
+
+ struct S1<'a>(&'a str);
+ let _ = S1(&s);
+
+ struct S2<'a> {
+ s: &'a str,
+ }
+ let _ = S2 { s: &s };
+
+ struct S3<'a, T: ?Sized>(&'a T);
+ let _ = S3(&*s); // Don't lint. Inferred type would change.
+
+ struct S4<'a, T: ?Sized> {
+ s: &'a T,
+ }
+ let _ = S4 { s: &*s }; // Don't lint. Inferred type would change.
+
+ enum E1<'a> {
+ S1(&'a str),
+ S2 { s: &'a str },
+ }
+ impl<'a> E1<'a> {
+ fn m1(s: &'a String) {
+ let _ = Self::S1(s);
+ let _ = Self::S2 { s: s };
+ }
+ }
+ let _ = E1::S1(&s);
+ let _ = E1::S2 { s: &s };
+
+ enum E2<'a, T: ?Sized> {
+ S1(&'a T),
+ S2 { s: &'a T },
+ }
+ let _ = E2::S1(&*s); // Don't lint. Inferred type would change.
+ let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change.
+
+ let ref_s = &s;
+ let _: &String = &*ref_s; // Don't lint reborrow.
+ f_string(&*ref_s); // Don't lint reborrow.
+
+ struct S5 {
+ foo: u32,
+ }
+ let b = Box::new(Box::new(S5 { foo: 5 }));
+ let _ = b.foo;
+ let _ = b.foo;
+ let _ = b.foo;
+
+ struct S6 {
+ foo: S5,
+ }
+ impl core::ops::Deref for S6 {
+ type Target = S5;
+ fn deref(&self) -> &Self::Target {
+ &self.foo
+ }
+ }
+ let s6 = S6 { foo: S5 { foo: 5 } };
+ let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo`
+
+ let ref_str = &"foo";
+ let _ = f_str(ref_str);
+ let ref_ref_str = &ref_str;
+ let _ = f_str(ref_ref_str);
+
+ fn _f5(x: &u32) -> u32 {
+ if true {
+ *x
+ } else {
+ return *x;
+ }
+ }
+
+ f_str(&&ref_str); // `needless_borrow` will suggest removing both references
+ f_str(&ref_str); // `needless_borrow` will suggest removing only one reference
+
+ let x = &&40;
+ unsafe {
+ var(0, &**x);
+ }
+
+ let s = &"str";
+ let _ = || return *s;
+ let _ = || -> &'static str { return s };
+
+ struct X;
+ struct Y(X);
+ impl core::ops::Deref for Y {
+ type Target = X;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ let _: &X = &*{ Y(X) };
+ let _: &X = &*match 0 {
+ #[rustfmt::skip]
+ 0 => { Y(X) },
+ _ => panic!(),
+ };
+ let _: &X = &*if true { Y(X) } else { panic!() };
+
+ fn deref_to_u<U, T: core::ops::Deref<Target = U>>(x: &T) -> &U {
+ x
+ }
+
+ let _ = |x: &'static Box<dyn Iterator<Item = u32>>| -> &'static dyn Iterator<Item = u32> { &**x };
+ fn ret_any(x: &Box<dyn std::any::Any>) -> &dyn std::any::Any {
+ &**x
+ }
+
+ let x = String::new();
+ let _: *const str = &*x;
+
+ struct S7([u32; 1]);
+ impl core::ops::Deref for S7 {
+ type Target = [u32; 1];
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ let x = S7([0]);
+ let _: &[u32] = &*x;
+
+ let c1 = |_: &Vec<&u32>| {};
+ let x = &&vec![&1u32];
+ c1(x);
+ let _ = for<'a, 'b> |x: &'a &'a Vec<&'b u32>, b: bool| -> &'a Vec<&'b u32> {
+ if b {
+ return x;
+ }
+ x
+ };
++
++ trait WithAssoc {
++ type Assoc: ?Sized;
++ }
++ impl WithAssoc for String {
++ type Assoc = str;
++ }
++ fn takes_assoc<T: WithAssoc>(_: &T::Assoc) -> T {
++ unimplemented!()
++ }
++ let _: String = takes_assoc(&*String::new());
+}
--- /dev/null
+// run-rustfix
+
+#![feature(closure_lifetime_binder)]
+#![warn(clippy::explicit_auto_deref)]
+#![allow(
+ dead_code,
+ unused_braces,
+ clippy::borrowed_box,
+ clippy::needless_borrow,
+ clippy::needless_return,
+ clippy::ptr_arg,
+ clippy::redundant_field_names,
+ clippy::too_many_arguments,
+ clippy::borrow_deref_ref,
+ clippy::let_unit_value
+)]
+
+trait CallableStr {
+ type T: Fn(&str);
+ fn callable_str(&self) -> Self::T;
+}
+impl CallableStr for () {
+ type T = fn(&str);
+ fn callable_str(&self) -> Self::T {
+ fn f(_: &str) {}
+ f
+ }
+}
+impl CallableStr for i32 {
+ type T = <() as CallableStr>::T;
+ fn callable_str(&self) -> Self::T {
+ ().callable_str()
+ }
+}
+
+trait CallableT<U: ?Sized> {
+ type T: Fn(&U);
+ fn callable_t(&self) -> Self::T;
+}
+impl<U: ?Sized> CallableT<U> for () {
+ type T = fn(&U);
+ fn callable_t(&self) -> Self::T {
+ fn f<U: ?Sized>(_: &U) {}
+ f::<U>
+ }
+}
+impl<U: ?Sized> CallableT<U> for i32 {
+ type T = <() as CallableT<U>>::T;
+ fn callable_t(&self) -> Self::T {
+ ().callable_t()
+ }
+}
+
+fn f_str(_: &str) {}
+fn f_string(_: &String) {}
+fn f_t<T>(_: T) {}
+fn f_ref_t<T: ?Sized>(_: &T) {}
+
+fn f_str_t<T>(_: &str, _: T) {}
+
+fn f_box_t<T>(_: &Box<T>) {}
+
+extern "C" {
+ fn var(_: u32, ...);
+}
+
+fn main() {
+ let s = String::new();
+
+ let _: &str = &*s;
+ let _: &str = &*{ String::new() };
+ let _: &str = &mut *{ String::new() };
+ let _ = &*s; // Don't lint. Inferred type would change.
+ let _: &_ = &*s; // Don't lint. Inferred type would change.
+
+ f_str(&*s);
+ f_t(&*s); // Don't lint. Inferred type would change.
+ f_ref_t(&*s); // Don't lint. Inferred type would change.
+
+ f_str_t(&*s, &*s); // Don't lint second param.
+
+ let b = Box::new(Box::new(Box::new(5)));
+ let _: &Box<i32> = &**b;
+ let _: &Box<_> = &**b; // Don't lint. Inferred type would change.
+
+ f_box_t(&**b); // Don't lint. Inferred type would change.
+
+ let c = |_x: &str| ();
+ c(&*s);
+
+ let c = |_x| ();
+ c(&*s); // Don't lint. Inferred type would change.
+
+ fn _f(x: &String) -> &str {
+ &**x
+ }
+
+ fn _f1(x: &String) -> &str {
+ { &**x }
+ }
+
+ fn _f2(x: &String) -> &str {
+ &**{ x }
+ }
+
+ fn _f3(x: &Box<Box<Box<i32>>>) -> &Box<i32> {
+ &***x
+ }
+
+ fn _f4(
+ x: String,
+ f1: impl Fn(&str),
+ f2: &dyn Fn(&str),
+ f3: fn(&str),
+ f4: impl CallableStr,
+ f5: <() as CallableStr>::T,
+ f6: <i32 as CallableStr>::T,
+ f7: &dyn CallableStr<T = fn(&str)>,
+ f8: impl CallableT<str>,
+ f9: <() as CallableT<str>>::T,
+ f10: <i32 as CallableT<str>>::T,
+ f11: &dyn CallableT<str, T = fn(&str)>,
+ ) {
+ f1(&*x);
+ f2(&*x);
+ f3(&*x);
+ f4.callable_str()(&*x);
+ f5(&*x);
+ f6(&*x);
+ f7.callable_str()(&*x);
+ f8.callable_t()(&*x);
+ f9(&*x);
+ f10(&*x);
+ f11.callable_t()(&*x);
+ }
+
+ struct S1<'a>(&'a str);
+ let _ = S1(&*s);
+
+ struct S2<'a> {
+ s: &'a str,
+ }
+ let _ = S2 { s: &*s };
+
+ struct S3<'a, T: ?Sized>(&'a T);
+ let _ = S3(&*s); // Don't lint. Inferred type would change.
+
+ struct S4<'a, T: ?Sized> {
+ s: &'a T,
+ }
+ let _ = S4 { s: &*s }; // Don't lint. Inferred type would change.
+
+ enum E1<'a> {
+ S1(&'a str),
+ S2 { s: &'a str },
+ }
+ impl<'a> E1<'a> {
+ fn m1(s: &'a String) {
+ let _ = Self::S1(&**s);
+ let _ = Self::S2 { s: &**s };
+ }
+ }
+ let _ = E1::S1(&*s);
+ let _ = E1::S2 { s: &*s };
+
+ enum E2<'a, T: ?Sized> {
+ S1(&'a T),
+ S2 { s: &'a T },
+ }
+ let _ = E2::S1(&*s); // Don't lint. Inferred type would change.
+ let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change.
+
+ let ref_s = &s;
+ let _: &String = &*ref_s; // Don't lint reborrow.
+ f_string(&*ref_s); // Don't lint reborrow.
+
+ struct S5 {
+ foo: u32,
+ }
+ let b = Box::new(Box::new(S5 { foo: 5 }));
+ let _ = b.foo;
+ let _ = (*b).foo;
+ let _ = (**b).foo;
+
+ struct S6 {
+ foo: S5,
+ }
+ impl core::ops::Deref for S6 {
+ type Target = S5;
+ fn deref(&self) -> &Self::Target {
+ &self.foo
+ }
+ }
+ let s6 = S6 { foo: S5 { foo: 5 } };
+ let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo`
+
+ let ref_str = &"foo";
+ let _ = f_str(*ref_str);
+ let ref_ref_str = &ref_str;
+ let _ = f_str(**ref_ref_str);
+
+ fn _f5(x: &u32) -> u32 {
+ if true {
+ *x
+ } else {
+ return *x;
+ }
+ }
+
+ f_str(&&*ref_str); // `needless_borrow` will suggest removing both references
+ f_str(&&**ref_str); // `needless_borrow` will suggest removing only one reference
+
+ let x = &&40;
+ unsafe {
+ var(0, &**x);
+ }
+
+ let s = &"str";
+ let _ = || return *s;
+ let _ = || -> &'static str { return *s };
+
+ struct X;
+ struct Y(X);
+ impl core::ops::Deref for Y {
+ type Target = X;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ let _: &X = &*{ Y(X) };
+ let _: &X = &*match 0 {
+ #[rustfmt::skip]
+ 0 => { Y(X) },
+ _ => panic!(),
+ };
+ let _: &X = &*if true { Y(X) } else { panic!() };
+
+ fn deref_to_u<U, T: core::ops::Deref<Target = U>>(x: &T) -> &U {
+ &**x
+ }
+
+ let _ = |x: &'static Box<dyn Iterator<Item = u32>>| -> &'static dyn Iterator<Item = u32> { &**x };
+ fn ret_any(x: &Box<dyn std::any::Any>) -> &dyn std::any::Any {
+ &**x
+ }
+
+ let x = String::new();
+ let _: *const str = &*x;
+
+ struct S7([u32; 1]);
+ impl core::ops::Deref for S7 {
+ type Target = [u32; 1];
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ let x = S7([0]);
+ let _: &[u32] = &*x;
+
+ let c1 = |_: &Vec<&u32>| {};
+ let x = &&vec![&1u32];
+ c1(*x);
+ let _ = for<'a, 'b> |x: &'a &'a Vec<&'b u32>, b: bool| -> &'a Vec<&'b u32> {
+ if b {
+ return *x;
+ }
+ *x
+ };
++
++ trait WithAssoc {
++ type Assoc: ?Sized;
++ }
++ impl WithAssoc for String {
++ type Assoc = str;
++ }
++ fn takes_assoc<T: WithAssoc>(_: &T::Assoc) -> T {
++ unimplemented!()
++ }
++ let _: String = takes_assoc(&*String::new());
+}
--- /dev/null
+#![warn(clippy::fn_params_excessive_bools)]
+#![allow(clippy::too_many_arguments)]
+
+extern "C" {
++ // Should not lint, most of the time users have no control over extern function signatures
+ fn f(_: bool, _: bool, _: bool, _: bool);
+}
+
+macro_rules! foo {
+ () => {
+ fn fff(_: bool, _: bool, _: bool, _: bool) {}
+ };
+}
+
+foo!();
+
+#[no_mangle]
+extern "C" fn k(_: bool, _: bool, _: bool, _: bool) {}
+fn g(_: bool, _: bool, _: bool, _: bool) {}
+fn h(_: bool, _: bool, _: bool) {}
+fn e(_: S, _: S, _: Box<S>, _: Vec<u32>) {}
+fn t(_: S, _: S, _: Box<S>, _: Vec<u32>, _: bool, _: bool, _: bool, _: bool) {}
+
+struct S;
+trait Trait {
++ // should warn for trait functions with and without body
+ fn f(_: bool, _: bool, _: bool, _: bool);
+ fn g(_: bool, _: bool, _: bool, _: Vec<u32>);
++ #[allow(clippy::fn_params_excessive_bools)]
++ fn h(_: bool, _: bool, _: bool, _: bool, _: bool, _: bool);
++ fn i(_: bool, _: bool, _: bool, _: bool) {}
+}
+
+impl S {
+ fn f(&self, _: bool, _: bool, _: bool, _: bool) {}
+ fn g(&self, _: bool, _: bool, _: bool) {}
+ #[no_mangle]
+ extern "C" fn h(_: bool, _: bool, _: bool, _: bool) {}
+}
+
+impl Trait for S {
++ // Should not lint because the trait might not be changeable by the user
++ // We only lint in the trait definition
+ fn f(_: bool, _: bool, _: bool, _: bool) {}
+ fn g(_: bool, _: bool, _: bool, _: Vec<u32>) {}
++ fn h(_: bool, _: bool, _: bool, _: bool, _: bool, _: bool) {}
+}
+
+fn main() {
+ fn n(_: bool, _: u32, _: bool, _: Box<u32>, _: bool, _: bool) {
+ fn nn(_: bool, _: bool, _: bool, _: bool) {}
+ }
+}
--- /dev/null
- --> $DIR/fn_params_excessive_bools.rs:18:1
+error: more than 3 bools in function parameters
- --> $DIR/fn_params_excessive_bools.rs:21:1
++ --> $DIR/fn_params_excessive_bools.rs:19:1
+ |
+LL | fn g(_: bool, _: bool, _: bool, _: bool) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider refactoring bools into two-variant enums
+ = note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings`
+
+error: more than 3 bools in function parameters
- --> $DIR/fn_params_excessive_bools.rs:25:5
++ --> $DIR/fn_params_excessive_bools.rs:22:1
+ |
+LL | fn t(_: S, _: S, _: Box<S>, _: Vec<u32>, _: bool, _: bool, _: bool, _: bool) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider refactoring bools into two-variant enums
+
+error: more than 3 bools in function parameters
- --> $DIR/fn_params_excessive_bools.rs:30:5
++ --> $DIR/fn_params_excessive_bools.rs:27:5
+ |
+LL | fn f(_: bool, _: bool, _: bool, _: bool);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider refactoring bools into two-variant enums
+
+error: more than 3 bools in function parameters
- --> $DIR/fn_params_excessive_bools.rs:42:5
++ --> $DIR/fn_params_excessive_bools.rs:31:5
++ |
++LL | fn i(_: bool, _: bool, _: bool, _: bool) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider refactoring bools into two-variant enums
++
++error: more than 3 bools in function parameters
++ --> $DIR/fn_params_excessive_bools.rs:35:5
+ |
+LL | fn f(&self, _: bool, _: bool, _: bool, _: bool) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider refactoring bools into two-variant enums
+
+error: more than 3 bools in function parameters
- --> $DIR/fn_params_excessive_bools.rs:43:9
++ --> $DIR/fn_params_excessive_bools.rs:50:5
+ |
+LL | / fn n(_: bool, _: u32, _: bool, _: Box<u32>, _: bool, _: bool) {
+LL | | fn nn(_: bool, _: bool, _: bool, _: bool) {}
+LL | | }
+ | |_____^
+ |
+ = help: consider refactoring bools into two-variant enums
+
+error: more than 3 bools in function parameters
- error: aborting due to 6 previous errors
++ --> $DIR/fn_params_excessive_bools.rs:51:9
+ |
+LL | fn nn(_: bool, _: bool, _: bool, _: bool) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider refactoring bools into two-variant enums
+
++error: aborting due to 7 previous errors
+
--- /dev/null
--- /dev/null
++#![warn(clippy::from_raw_with_void_ptr)]
++
++use std::ffi::c_void;
++use std::rc::Rc;
++use std::sync::Arc;
++
++fn main() {
++ // must lint
++ let ptr = Box::into_raw(Box::new(42usize)) as *mut c_void;
++ let _ = unsafe { Box::from_raw(ptr) };
++
++ // shouldn't be linted
++ let _ = unsafe { Box::from_raw(ptr as *mut usize) };
++
++ // shouldn't be linted
++ let should_not_lint_ptr = Box::into_raw(Box::new(12u8)) as *mut u8;
++ let _ = unsafe { Box::from_raw(should_not_lint_ptr as *mut u8) };
++
++ // must lint
++ let ptr = Rc::into_raw(Rc::new(42usize)) as *mut c_void;
++ let _ = unsafe { Rc::from_raw(ptr) };
++
++ // must lint
++ let ptr = Arc::into_raw(Arc::new(42usize)) as *mut c_void;
++ let _ = unsafe { Arc::from_raw(ptr) };
++
++ // must lint
++ let ptr = std::rc::Weak::into_raw(Rc::downgrade(&Rc::new(42usize))) as *mut c_void;
++ let _ = unsafe { std::rc::Weak::from_raw(ptr) };
++
++ // must lint
++ let ptr = std::sync::Weak::into_raw(Arc::downgrade(&Arc::new(42usize))) as *mut c_void;
++ let _ = unsafe { std::sync::Weak::from_raw(ptr) };
++}
--- /dev/null
--- /dev/null
++error: creating a `Box` from a void raw pointer
++ --> $DIR/from_raw_with_void_ptr.rs:10:22
++ |
++LL | let _ = unsafe { Box::from_raw(ptr) };
++ | ^^^^^^^^^^^^^^^^^^
++ |
++help: cast this to a pointer of the appropriate type
++ --> $DIR/from_raw_with_void_ptr.rs:10:36
++ |
++LL | let _ = unsafe { Box::from_raw(ptr) };
++ | ^^^
++ = note: `-D clippy::from-raw-with-void-ptr` implied by `-D warnings`
++
++error: creating a `Rc` from a void raw pointer
++ --> $DIR/from_raw_with_void_ptr.rs:21:22
++ |
++LL | let _ = unsafe { Rc::from_raw(ptr) };
++ | ^^^^^^^^^^^^^^^^^
++ |
++help: cast this to a pointer of the appropriate type
++ --> $DIR/from_raw_with_void_ptr.rs:21:35
++ |
++LL | let _ = unsafe { Rc::from_raw(ptr) };
++ | ^^^
++
++error: creating a `Arc` from a void raw pointer
++ --> $DIR/from_raw_with_void_ptr.rs:25:22
++ |
++LL | let _ = unsafe { Arc::from_raw(ptr) };
++ | ^^^^^^^^^^^^^^^^^^
++ |
++help: cast this to a pointer of the appropriate type
++ --> $DIR/from_raw_with_void_ptr.rs:25:36
++ |
++LL | let _ = unsafe { Arc::from_raw(ptr) };
++ | ^^^
++
++error: creating a `Weak` from a void raw pointer
++ --> $DIR/from_raw_with_void_ptr.rs:29:22
++ |
++LL | let _ = unsafe { std::rc::Weak::from_raw(ptr) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: cast this to a pointer of the appropriate type
++ --> $DIR/from_raw_with_void_ptr.rs:29:46
++ |
++LL | let _ = unsafe { std::rc::Weak::from_raw(ptr) };
++ | ^^^
++
++error: creating a `Weak` from a void raw pointer
++ --> $DIR/from_raw_with_void_ptr.rs:33:22
++ |
++LL | let _ = unsafe { std::sync::Weak::from_raw(ptr) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: cast this to a pointer of the appropriate type
++ --> $DIR/from_raw_with_void_ptr.rs:33:48
++ |
++LL | let _ = unsafe { std::sync::Weak::from_raw(ptr) };
++ | ^^^
++
++error: aborting due to 5 previous errors
++
--- /dev/null
- error: used `unwrap()` on `an Option` value
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
+ |
+note: the lint level is defined here
+ --> $DIR/get_unwrap.rs:5:9
+ |
+LL | #![deny(clippy::get_unwrap)]
+ | ^^^^^^^^^^^^^^^^^^
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+ = note: `-D clippy::unwrap-used` implied by `-D warnings`
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:44:21
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:44:22
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:49:9
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:49:10
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:50:9
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:50:10
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:51:9
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:51:10
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:52:9
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:52:10
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
+ --> $DIR/get_unwrap.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
++error: used `unwrap()` on an `Option` value
+ --> $DIR/get_unwrap.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: aborting due to 26 previous errors
+
--- /dev/null
+// run-rustfix
+#![feature(exhaustive_patterns, never_type)]
+#![allow(dead_code, unreachable_code, unused_variables)]
+#![allow(clippy::let_and_return)]
+
+enum SingleVariantEnum {
+ Variant(i32),
+}
+
+struct TupleStruct(i32);
+
++struct NonCopy;
++struct TupleStructWithNonCopy(NonCopy);
++
+enum EmptyEnum {}
+
+macro_rules! match_enum {
+ ($param:expr) => {
+ let data = match $param {
+ SingleVariantEnum::Variant(i) => i,
+ };
+ };
+}
+
+fn infallible_destructuring_match_enum() {
+ let wrapper = SingleVariantEnum::Variant(0);
+
+ // This should lint!
+ let SingleVariantEnum::Variant(data) = wrapper;
+
+ // This shouldn't (inside macro)
+ match_enum!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ SingleVariantEnum::Variant(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ SingleVariantEnum::Variant(i) => -1,
+ };
+
+ let SingleVariantEnum::Variant(data) = wrapper;
+}
+
+macro_rules! match_struct {
+ ($param:expr) => {
+ let data = match $param {
+ TupleStruct(i) => i,
+ };
+ };
+}
+
+fn infallible_destructuring_match_struct() {
+ let wrapper = TupleStruct(0);
+
+ // This should lint!
+ let TupleStruct(data) = wrapper;
+
+ // This shouldn't (inside macro)
+ match_struct!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ TupleStruct(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ TupleStruct(i) => -1,
+ };
+
+ let TupleStruct(data) = wrapper;
+}
+
++fn infallible_destructuring_match_struct_with_noncopy() {
++ let wrapper = TupleStructWithNonCopy(NonCopy);
++
++ // This should lint! (keeping `ref` in the suggestion)
++ let TupleStructWithNonCopy(ref data) = wrapper;
++
++ let TupleStructWithNonCopy(ref data) = wrapper;
++}
++
+macro_rules! match_never_enum {
+ ($param:expr) => {
+ let data = match $param {
+ Ok(i) => i,
+ };
+ };
+}
+
+fn never_enum() {
+ let wrapper: Result<i32, !> = Ok(23);
+
+ // This should lint!
+ let Ok(data) = wrapper;
+
+ // This shouldn't (inside macro)
+ match_never_enum!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ Ok(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ Ok(i) => -1,
+ };
+
+ let Ok(data) = wrapper;
+}
+
+impl EmptyEnum {
+ fn match_on(&self) -> ! {
+ // The lint shouldn't pick this up, as `let` won't work here!
+ let data = match *self {};
+ data
+ }
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+#![feature(exhaustive_patterns, never_type)]
+#![allow(dead_code, unreachable_code, unused_variables)]
+#![allow(clippy::let_and_return)]
+
+enum SingleVariantEnum {
+ Variant(i32),
+}
+
+struct TupleStruct(i32);
+
++struct NonCopy;
++struct TupleStructWithNonCopy(NonCopy);
++
+enum EmptyEnum {}
+
+macro_rules! match_enum {
+ ($param:expr) => {
+ let data = match $param {
+ SingleVariantEnum::Variant(i) => i,
+ };
+ };
+}
+
+fn infallible_destructuring_match_enum() {
+ let wrapper = SingleVariantEnum::Variant(0);
+
+ // This should lint!
+ let data = match wrapper {
+ SingleVariantEnum::Variant(i) => i,
+ };
+
+ // This shouldn't (inside macro)
+ match_enum!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ SingleVariantEnum::Variant(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ SingleVariantEnum::Variant(i) => -1,
+ };
+
+ let SingleVariantEnum::Variant(data) = wrapper;
+}
+
+macro_rules! match_struct {
+ ($param:expr) => {
+ let data = match $param {
+ TupleStruct(i) => i,
+ };
+ };
+}
+
+fn infallible_destructuring_match_struct() {
+ let wrapper = TupleStruct(0);
+
+ // This should lint!
+ let data = match wrapper {
+ TupleStruct(i) => i,
+ };
+
+ // This shouldn't (inside macro)
+ match_struct!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ TupleStruct(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ TupleStruct(i) => -1,
+ };
+
+ let TupleStruct(data) = wrapper;
+}
+
++fn infallible_destructuring_match_struct_with_noncopy() {
++ let wrapper = TupleStructWithNonCopy(NonCopy);
++
++ // This should lint! (keeping `ref` in the suggestion)
++ let data = match wrapper {
++ TupleStructWithNonCopy(ref n) => n,
++ };
++
++ let TupleStructWithNonCopy(ref data) = wrapper;
++}
++
+macro_rules! match_never_enum {
+ ($param:expr) => {
+ let data = match $param {
+ Ok(i) => i,
+ };
+ };
+}
+
+fn never_enum() {
+ let wrapper: Result<i32, !> = Ok(23);
+
+ // This should lint!
+ let data = match wrapper {
+ Ok(i) => i,
+ };
+
+ // This shouldn't (inside macro)
+ match_never_enum!(wrapper);
+
+ // This shouldn't!
+ let data = match wrapper {
+ Ok(_) => -1,
+ };
+
+ // Neither should this!
+ let data = match wrapper {
+ Ok(i) => -1,
+ };
+
+ let Ok(data) = wrapper;
+}
+
+impl EmptyEnum {
+ fn match_on(&self) -> ! {
+ // The lint shouldn't pick this up, as `let` won't work here!
+ let data = match *self {};
+ data
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/infallible_destructuring_match.rs:26:5
+error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let`
- --> $DIR/infallible_destructuring_match.rs:58:5
++ --> $DIR/infallible_destructuring_match.rs:29:5
+ |
+LL | / let data = match wrapper {
+LL | | SingleVariantEnum::Variant(i) => i,
+LL | | };
+ | |______^ help: try this: `let SingleVariantEnum::Variant(data) = wrapper;`
+ |
+ = note: `-D clippy::infallible-destructuring-match` implied by `-D warnings`
+
+error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let`
- --> $DIR/infallible_destructuring_match.rs:90:5
++ --> $DIR/infallible_destructuring_match.rs:61:5
+ |
+LL | / let data = match wrapper {
+LL | | TupleStruct(i) => i,
+LL | | };
+ | |______^ help: try this: `let TupleStruct(data) = wrapper;`
+
+error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let`
- error: aborting due to 3 previous errors
++ --> $DIR/infallible_destructuring_match.rs:85:5
++ |
++LL | / let data = match wrapper {
++LL | | TupleStructWithNonCopy(ref n) => n,
++LL | | };
++ | |______^ help: try this: `let TupleStructWithNonCopy(ref data) = wrapper;`
++
++error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let`
++ --> $DIR/infallible_destructuring_match.rs:104:5
+ |
+LL | / let data = match wrapper {
+LL | | Ok(i) => i,
+LL | | };
+ | |______^ help: try this: `let Ok(data) = wrapper;`
+
++error: aborting due to 4 previous errors
+
--- /dev/null
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a
+ --> $DIR/issue_4266.rs:4:1
+ |
+LL | async fn sink1<'a>(_: &'a str) {} // lint
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::needless-lifetimes` implied by `-D warnings`
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a
+ --> $DIR/issue_4266.rs:8:1
+ |
+LL | async fn one_to_one<'a>(s: &'a str) -> &'a str {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: methods called `new` usually take no `self`
+ --> $DIR/issue_4266.rs:28:22
+ |
+LL | pub async fn new(&mut self) -> Self {
+ | ^^^^^^^^^
+ |
+ = help: consider choosing a less ambiguous name
+ = note: `-D clippy::wrong-self-convention` implied by `-D warnings`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
--- /dev/null
++use std::future::Future;
++
++async fn some_async_fn() {}
++
++fn sync_side_effects() {}
++fn custom() -> impl Future<Output = ()> {
++ sync_side_effects();
++ async {}
++}
++
++fn do_something_to_future(future: &mut impl Future<Output = ()>) {}
++
++fn main() {
++ let _ = some_async_fn();
++ let _ = custom();
++
++ let mut future = some_async_fn();
++ do_something_to_future(&mut future);
++ let _ = future;
++}
--- /dev/null
--- /dev/null
++error: non-binding `let` on a future
++ --> $DIR/let_underscore_future.rs:14:5
++ |
++LL | let _ = some_async_fn();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider awaiting the future or dropping explicitly with `std::mem::drop`
++ = note: `-D clippy::let-underscore-future` implied by `-D warnings`
++
++error: non-binding `let` on a future
++ --> $DIR/let_underscore_future.rs:15:5
++ |
++LL | let _ = custom();
++ | ^^^^^^^^^^^^^^^^^
++ |
++ = help: consider awaiting the future or dropping explicitly with `std::mem::drop`
++
++error: non-binding `let` on a future
++ --> $DIR/let_underscore_future.rs:19:5
++ |
++LL | let _ = future;
++ | ^^^^^^^^^^^^^^^
++ |
++ = help: consider awaiting the future or dropping explicitly with `std::mem::drop`
++
++error: aborting due to 3 previous errors
++
--- /dev/null
- let m = std::sync::Mutex::new(());
- let rw = std::sync::RwLock::new(());
-
- let _ = m.lock();
- let _ = rw.read();
- let _ = rw.write();
- let _ = m.try_lock();
- let _ = rw.try_read();
- let _ = rw.try_write();
-
- // These shouldn't throw an error.
- let _ = m;
- let _ = rw;
-
+#![warn(clippy::let_underscore_lock)]
+
+extern crate parking_lot;
+
+fn main() {
+ use parking_lot::{lock_api::RawMutex, Mutex, RwLock};
+
+ let p_m: Mutex<()> = Mutex::const_new(RawMutex::INIT, ());
+ let _ = p_m.lock();
+
+ let p_m1 = Mutex::new(0);
+ let _ = p_m1.lock();
+
+ let p_rw = RwLock::new(0);
+ let _ = p_rw.read();
+ let _ = p_rw.write();
+
+ // These shouldn't throw an error.
+ let _ = p_m;
+ let _ = p_m1;
+ let _ = p_rw;
+}
++
++fn uplifted() {
++ // shouldn't lint std locks as they were uplifted as rustc's `let_underscore_lock`
++
++ let m = std::sync::Mutex::new(());
++ let rw = std::sync::RwLock::new(());
++
++ let _ = m.lock();
++ let _ = rw.read();
++ let _ = rw.write();
++ let _ = m.try_lock();
++ let _ = rw.try_read();
++ let _ = rw.try_write();
++
++ let _ = m;
++ let _ = rw;
++}
--- /dev/null
- error: non-binding let on a synchronization lock
++error: non-binding `let` on a synchronization lock
+ --> $DIR/let_underscore_lock.rs:9:5
+ |
- LL | let _ = m.lock();
- | ^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
- = note: `-D clippy::let-underscore-lock` implied by `-D warnings`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:10:5
- |
- LL | let _ = rw.read();
- | ^^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:11:5
- |
- LL | let _ = rw.write();
- | ^^^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:12:5
- |
- LL | let _ = m.try_lock();
- | ^^^^^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:13:5
- |
- LL | let _ = rw.try_read();
- | ^^^^^^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:14:5
- |
- LL | let _ = rw.try_write();
- | ^^^^^^^^^^^^^^^^^^^^^^^
- |
- = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
-
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:23:5
- |
+LL | let _ = p_m.lock();
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
++ = note: `-D clippy::let-underscore-lock` implied by `-D warnings`
+
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:26:5
++error: non-binding `let` on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:12:5
+ |
+LL | let _ = p_m1.lock();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:29:5
++error: non-binding `let` on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:15:5
+ |
+LL | let _ = p_rw.read();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
- error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:30:5
++error: non-binding `let` on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:16:5
+ |
+LL | let _ = p_rw.write();
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
- error: aborting due to 10 previous errors
++error: aborting due to 4 previous errors
+
--- /dev/null
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:67:5
+ |
+LL | let _ = f();
+ | ^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+ = note: `-D clippy::let-underscore-must-use` implied by `-D warnings`
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:68:5
+ |
+LL | let _ = g();
+ | ^^^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:70:5
+ |
+LL | let _ = l(0_u32);
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:74:5
+ |
+LL | let _ = s.f();
+ | ^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:75:5
+ |
+LL | let _ = s.g();
+ | ^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:78:5
+ |
+LL | let _ = S::h();
+ | ^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:79:5
+ |
+LL | let _ = S::p();
+ | ^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:81:5
+ |
+LL | let _ = S::a();
+ | ^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:83:5
+ |
+LL | let _ = if true { Ok(()) } else { Err(()) };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
- error: non-binding let on a result of a `#[must_use]` function
++error: non-binding `let` on a result of a `#[must_use]` function
+ --> $DIR/let_underscore_must_use.rs:87:5
+ |
+LL | let _ = a.is_ok();
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using function result
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:89:5
+ |
+LL | let _ = a.map(|_| ());
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
- error: non-binding let on an expression with `#[must_use]` type
++error: non-binding `let` on an expression with `#[must_use]` type
+ --> $DIR/let_underscore_must_use.rs:91:5
+ |
+LL | let _ = a;
+ | ^^^^^^^^^^
+ |
+ = help: consider explicitly using expression value
+
+error: aborting due to 12 previous errors
+
--- /dev/null
- // Test for loop over implicitly implicitly adjusted `Iterator` with `if let` statement
+#![warn(clippy::manual_flatten)]
+#![allow(clippy::useless_vec, clippy::uninlined_format_args)]
+
+fn main() {
+ // Test for loop over implicitly adjusted `Iterator` with `if let` expression
+ let x = vec![Some(1), Some(2), Some(3)];
+ for n in x {
+ if let Some(y) = n {
+ println!("{}", y);
+ }
+ }
+
++ // Test for loop over implicitly adjusted `Iterator` with `if let` statement
+ let y: Vec<Result<i32, i32>> = vec![];
+ for n in y.clone() {
+ if let Ok(n) = n {
+ println!("{}", n);
+ };
+ }
+
+ // Test for loop over by reference
+ for n in &y {
+ if let Ok(n) = n {
+ println!("{}", n);
+ }
+ }
+
+ // Test for loop over an implicit reference
+ let z = &y;
+ for n in z {
+ if let Ok(n) = n {
+ println!("{}", n);
+ }
+ }
+
+ // Test for loop over `Iterator` with `if let` expression
+ let z = vec![Some(1), Some(2), Some(3)];
+ let z = z.iter();
+ for n in z {
+ if let Some(m) = n {
+ println!("{}", m);
+ }
+ }
+
+ // Using the `None` variant should not trigger the lint
+ // Note: for an autofixable suggestion, the binding in the for loop has to take the
+ // name of the binding in the `if let`
+ let z = vec![Some(1), Some(2), Some(3)];
+ for n in z {
+ if n.is_none() {
+ println!("Nada.");
+ }
+ }
+
+ // Using the `Err` variant should not trigger the lint
+ for n in y.clone() {
+ if let Err(e) = n {
+ println!("Oops: {}!", e);
+ }
+ }
+
+ // Having an else clause should not trigger the lint
+ for n in y.clone() {
+ if let Ok(n) = n {
+ println!("{}", n);
+ } else {
+ println!("Oops!");
+ }
+ }
+
+ let vec_of_ref = vec![&Some(1)];
+ for n in &vec_of_ref {
+ if let Some(n) = n {
+ println!("{:?}", n);
+ }
+ }
+
+ let vec_of_ref = &vec_of_ref;
+ for n in vec_of_ref {
+ if let Some(n) = n {
+ println!("{:?}", n);
+ }
+ }
+
+ let slice_of_ref = &[&Some(1)];
+ for n in slice_of_ref {
+ if let Some(n) = n {
+ println!("{:?}", n);
+ }
+ }
+
+ struct Test {
+ a: usize,
+ }
+
+ let mut vec_of_struct = [Some(Test { a: 1 }), None];
+
+ // Usage of `if let` expression should not trigger lint
+ for n in vec_of_struct.iter_mut() {
+ if let Some(z) = n {
+ *n = None;
+ }
+ }
+
+ // Using manual flatten should not trigger the lint
+ for n in vec![Some(1), Some(2), Some(3)].iter().flatten() {
+ println!("{}", n);
+ }
+
+ run_unformatted_tests();
+}
+
+#[rustfmt::skip]
+fn run_unformatted_tests() {
+ // Skip rustfmt here on purpose so the suggestion does not fit in one line
+ for n in vec![
+ Some(1),
+ Some(2),
+ Some(3)
+ ].iter() {
+ if let Some(n) = n {
+ println!("{:?}", n);
+ }
+ }
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::manual_instant_elapsed)]
+#![allow(clippy::unnecessary_operation)]
++#![allow(clippy::unchecked_duration_subtraction)]
+#![allow(unused_variables)]
+#![allow(unused_must_use)]
+
+use std::time::Instant;
+
+fn main() {
+ let prev_instant = Instant::now();
+
+ {
+ // don't influence
+ let another_instant = Instant::now();
+ }
+
+ let duration = prev_instant.elapsed();
+
+ // don't catch
+ let duration = prev_instant.elapsed();
+
+ Instant::now() - duration;
+
+ let ref_to_instant = &Instant::now();
+
+ (*ref_to_instant).elapsed(); // to ensure parens are added correctly
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::manual_instant_elapsed)]
+#![allow(clippy::unnecessary_operation)]
++#![allow(clippy::unchecked_duration_subtraction)]
+#![allow(unused_variables)]
+#![allow(unused_must_use)]
+
+use std::time::Instant;
+
+fn main() {
+ let prev_instant = Instant::now();
+
+ {
+ // don't influence
+ let another_instant = Instant::now();
+ }
+
+ let duration = Instant::now() - prev_instant;
+
+ // don't catch
+ let duration = prev_instant.elapsed();
+
+ Instant::now() - duration;
+
+ let ref_to_instant = &Instant::now();
+
+ Instant::now() - *ref_to_instant; // to ensure parens are added correctly
+}
--- /dev/null
- --> $DIR/manual_instant_elapsed.rs:17:20
+error: manual implementation of `Instant::elapsed`
- --> $DIR/manual_instant_elapsed.rs:26:5
++ --> $DIR/manual_instant_elapsed.rs:18:20
+ |
+LL | let duration = Instant::now() - prev_instant;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `prev_instant.elapsed()`
+ |
+ = note: `-D clippy::manual-instant-elapsed` implied by `-D warnings`
+
+error: manual implementation of `Instant::elapsed`
++ --> $DIR/manual_instant_elapsed.rs:27:5
+ |
+LL | Instant::now() - *ref_to_instant; // to ensure parens are added correctly
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*ref_to_instant).elapsed()`
+
+error: aborting due to 2 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![feature(custom_inner_attributes)]
++#![allow(unused, dead_code)]
++#![warn(clippy::manual_is_ascii_check)]
++
++fn main() {
++ assert!('x'.is_ascii_lowercase());
++ assert!('X'.is_ascii_uppercase());
++ assert!(b'x'.is_ascii_lowercase());
++ assert!(b'X'.is_ascii_uppercase());
++
++ let num = '2';
++ assert!(num.is_ascii_digit());
++ assert!(b'1'.is_ascii_digit());
++ assert!('x'.is_ascii_alphabetic());
++
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
++}
++
++fn msrv_1_23() {
++ #![clippy::msrv = "1.23"]
++
++ assert!(matches!(b'1', b'0'..=b'9'));
++ assert!(matches!('X', 'A'..='Z'));
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++}
++
++fn msrv_1_24() {
++ #![clippy::msrv = "1.24"]
++
++ assert!(b'1'.is_ascii_digit());
++ assert!('X'.is_ascii_uppercase());
++ assert!('x'.is_ascii_alphabetic());
++}
++
++fn msrv_1_46() {
++ #![clippy::msrv = "1.46"]
++ const FOO: bool = matches!('x', '0'..='9');
++}
++
++fn msrv_1_47() {
++ #![clippy::msrv = "1.47"]
++ const FOO: bool = 'x'.is_ascii_digit();
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![feature(custom_inner_attributes)]
++#![allow(unused, dead_code)]
++#![warn(clippy::manual_is_ascii_check)]
++
++fn main() {
++ assert!(matches!('x', 'a'..='z'));
++ assert!(matches!('X', 'A'..='Z'));
++ assert!(matches!(b'x', b'a'..=b'z'));
++ assert!(matches!(b'X', b'A'..=b'Z'));
++
++ let num = '2';
++ assert!(matches!(num, '0'..='9'));
++ assert!(matches!(b'1', b'0'..=b'9'));
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z' | '_'));
++}
++
++fn msrv_1_23() {
++ #![clippy::msrv = "1.23"]
++
++ assert!(matches!(b'1', b'0'..=b'9'));
++ assert!(matches!('X', 'A'..='Z'));
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++}
++
++fn msrv_1_24() {
++ #![clippy::msrv = "1.24"]
++
++ assert!(matches!(b'1', b'0'..=b'9'));
++ assert!(matches!('X', 'A'..='Z'));
++ assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++}
++
++fn msrv_1_46() {
++ #![clippy::msrv = "1.46"]
++ const FOO: bool = matches!('x', '0'..='9');
++}
++
++fn msrv_1_47() {
++ #![clippy::msrv = "1.47"]
++ const FOO: bool = matches!('x', '0'..='9');
++}
--- /dev/null
--- /dev/null
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:8:13
++ |
++LL | assert!(matches!('x', 'a'..='z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_lowercase()`
++ |
++ = note: `-D clippy::manual-is-ascii-check` implied by `-D warnings`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:9:13
++ |
++LL | assert!(matches!('X', 'A'..='Z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'X'.is_ascii_uppercase()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:10:13
++ |
++LL | assert!(matches!(b'x', b'a'..=b'z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'x'.is_ascii_lowercase()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:11:13
++ |
++LL | assert!(matches!(b'X', b'A'..=b'Z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'X'.is_ascii_uppercase()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:14:13
++ |
++LL | assert!(matches!(num, '0'..='9'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.is_ascii_digit()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:15:13
++ |
++LL | assert!(matches!(b'1', b'0'..=b'9'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'1'.is_ascii_digit()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:16:13
++ |
++LL | assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:32:13
++ |
++LL | assert!(matches!(b'1', b'0'..=b'9'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'1'.is_ascii_digit()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:33:13
++ |
++LL | assert!(matches!('X', 'A'..='Z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'X'.is_ascii_uppercase()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:34:13
++ |
++LL | assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
++
++error: manual check for common ascii range
++ --> $DIR/manual_is_ascii_check.rs:44:23
++ |
++LL | const FOO: bool = matches!('x', '0'..='9');
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_digit()`
++
++error: aborting due to 11 previous errors
++
--- /dev/null
--- /dev/null
++#![allow(unused_braces, unused_variables, dead_code)]
++#![allow(
++ clippy::collapsible_else_if,
++ clippy::unused_unit,
++ clippy::let_unit_value,
++ clippy::match_single_binding,
++ clippy::never_loop
++)]
++#![warn(clippy::manual_let_else)]
++
++fn g() -> Option<()> {
++ None
++}
++
++fn main() {}
++
++fn fire() {
++ let v = if let Some(v_some) = g() { v_some } else { return };
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ return;
++ };
++
++ let v = if let Some(v) = g() {
++ // Blocks around the identity should have no impact
++ {
++ { v }
++ }
++ } else {
++ // Some computation should still make it fire
++ g();
++ return;
++ };
++
++ // continue and break diverge
++ loop {
++ let v = if let Some(v_some) = g() { v_some } else { continue };
++ let v = if let Some(v_some) = g() { v_some } else { break };
++ }
++
++ // panic also diverges
++ let v = if let Some(v_some) = g() { v_some } else { panic!() };
++
++ // abort also diverges
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ std::process::abort()
++ };
++
++ // If whose two branches diverge also diverges
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ if true { return } else { panic!() }
++ };
++
++ // Diverging after an if still makes the block diverge:
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ if true {}
++ panic!();
++ };
++
++ // A match diverges if all branches diverge:
++ // Note: the corresponding let-else requires a ; at the end of the match
++ // as otherwise the type checker does not turn it into a ! type.
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ match () {
++ _ if panic!() => {},
++ _ => panic!(),
++ }
++ };
++
++ // An if's expression can cause divergence:
++ let v = if let Some(v_some) = g() { v_some } else { if panic!() {} };
++
++ // An expression of a match can cause divergence:
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ match panic!() {
++ _ => {},
++ }
++ };
++
++ // Top level else if
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else if true {
++ return;
++ } else {
++ panic!("diverge");
++ };
++
++ // All match arms diverge
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ match (g(), g()) {
++ (Some(_), None) => return,
++ (None, Some(_)) => {
++ if true {
++ return;
++ } else {
++ panic!();
++ }
++ },
++ _ => return,
++ }
++ };
++
++ // Tuples supported for the declared variables
++ let (v, w) = if let Some(v_some) = g().map(|v| (v, 42)) {
++ v_some
++ } else {
++ return;
++ };
++
++ // Tuples supported for the identity block and pattern
++ let v = if let (Some(v_some), w_some) = (g(), 0) {
++ (w_some, v_some)
++ } else {
++ return;
++ };
++
++ // entirely inside macro lints
++ macro_rules! create_binding_if_some {
++ ($n:ident, $e:expr) => {
++ let $n = if let Some(v) = $e { v } else { return };
++ };
++ }
++ create_binding_if_some!(w, g());
++}
++
++fn not_fire() {
++ let v = if let Some(v_some) = g() {
++ // Nothing returned. Should not fire.
++ } else {
++ return;
++ };
++
++ let w = 0;
++ let v = if let Some(v_some) = g() {
++ // Different variable than v_some. Should not fire.
++ w
++ } else {
++ return;
++ };
++
++ let v = if let Some(v_some) = g() {
++ // Computation in then clause. Should not fire.
++ g();
++ v_some
++ } else {
++ return;
++ };
++
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ if false {
++ return;
++ }
++ // This doesn't diverge. Should not fire.
++ ()
++ };
++
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ // There is one match arm that doesn't diverge. Should not fire.
++ match (g(), g()) {
++ (Some(_), None) => return,
++ (None, Some(_)) => return,
++ (Some(_), Some(_)) => (),
++ _ => return,
++ }
++ };
++
++ let v = if let Some(v_some) = g() {
++ v_some
++ } else {
++ // loop with a break statement inside does not diverge.
++ loop {
++ break;
++ }
++ };
++
++ enum Uninhabited {}
++ fn un() -> Uninhabited {
++ panic!()
++ }
++ let v = if let Some(v_some) = None {
++ v_some
++ } else {
++ // Don't lint if the type is uninhabited but not !
++ un()
++ };
++
++ fn question_mark() -> Option<()> {
++ let v = if let Some(v) = g() {
++ v
++ } else {
++ // Question mark does not diverge
++ g()?
++ };
++ Some(v)
++ }
++
++ // Macro boundary inside let
++ macro_rules! some_or_return {
++ ($e:expr) => {
++ if let Some(v) = $e { v } else { return }
++ };
++ }
++ let v = some_or_return!(g());
++
++ // Also macro boundary inside let, but inside a macro
++ macro_rules! create_binding_if_some_nf {
++ ($n:ident, $e:expr) => {
++ let $n = some_or_return!($e);
++ };
++ }
++ create_binding_if_some_nf!(v, g());
++
++ // Already a let-else
++ let Some(a) = (if let Some(b) = Some(Some(())) { b } else { return }) else { panic!() };
++
++ // If a type annotation is present, don't lint as
++ // expressing the type might be too hard
++ let v: () = if let Some(v_some) = g() { v_some } else { panic!() };
++}
--- /dev/null
--- /dev/null
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:18:5
++ |
++LL | let v = if let Some(v_some) = g() { v_some } else { return };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v_some) = g() else { return };`
++ |
++ = note: `-D clippy::manual-let-else` implied by `-D warnings`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:19:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | return;
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + return;
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:25:5
++ |
++LL | / let v = if let Some(v) = g() {
++LL | | // Blocks around the identity should have no impact
++LL | | {
++LL | | { v }
++... |
++LL | | return;
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v) = g() else {
++LL + // Some computation should still make it fire
++LL + g();
++LL + return;
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:38:9
++ |
++LL | let v = if let Some(v_some) = g() { v_some } else { continue };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v_some) = g() else { continue };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:39:9
++ |
++LL | let v = if let Some(v_some) = g() { v_some } else { break };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v_some) = g() else { break };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:43:5
++ |
++LL | let v = if let Some(v_some) = g() { v_some } else { panic!() };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v_some) = g() else { panic!() };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:46:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | std::process::abort()
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + std::process::abort()
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:53:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | if true { return } else { panic!() }
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + if true { return } else { panic!() }
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:60:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | if true {}
++LL | | panic!();
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + if true {}
++LL + panic!();
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:70:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | match () {
++... |
++LL | | }
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + match () {
++LL + _ if panic!() => {},
++LL + _ => panic!(),
++LL + }
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:80:5
++ |
++LL | let v = if let Some(v_some) = g() { v_some } else { if panic!() {} };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v_some) = g() else { if panic!() {} };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:83:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | match panic!() {
++LL | | _ => {},
++LL | | }
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + match panic!() {
++LL + _ => {},
++LL + }
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:92:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else if true {
++LL | | return;
++LL | | } else {
++LL | | panic!("diverge");
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else { if true {
++LL + return;
++LL + } else {
++LL + panic!("diverge");
++LL + } };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:101:5
++ |
++LL | / let v = if let Some(v_some) = g() {
++LL | | v_some
++LL | | } else {
++LL | | match (g(), g()) {
++... |
++LL | | }
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g() else {
++LL + match (g(), g()) {
++LL + (Some(_), None) => return,
++LL + (None, Some(_)) => {
++LL + if true {
++LL + return;
++LL + } else {
++LL + panic!();
++LL + }
++LL + },
++LL + _ => return,
++LL + }
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:118:5
++ |
++LL | / let (v, w) = if let Some(v_some) = g().map(|v| (v, 42)) {
++LL | | v_some
++LL | | } else {
++LL | | return;
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let Some(v_some) = g().map(|v| (v, 42)) else {
++LL + return;
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:125:5
++ |
++LL | / let v = if let (Some(v_some), w_some) = (g(), 0) {
++LL | | (w_some, v_some)
++LL | | } else {
++LL | | return;
++LL | | };
++ | |______^
++ |
++help: consider writing
++ |
++LL ~ let (Some(v_some), w_some) = (g(), 0) else {
++LL + return;
++LL + };
++ |
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else.rs:134:13
++ |
++LL | let $n = if let Some(v) = $e { v } else { return };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { return };`
++...
++LL | create_binding_if_some!(w, g());
++ | ------------------------------- in this macro invocation
++ |
++ = note: this error originates in the macro `create_binding_if_some` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: aborting due to 17 previous errors
++
--- /dev/null
--- /dev/null
++#![allow(unused_braces, unused_variables, dead_code)]
++#![allow(clippy::collapsible_else_if, clippy::let_unit_value)]
++#![warn(clippy::manual_let_else)]
++// Ensure that we don't conflict with match -> if let lints
++#![warn(clippy::single_match_else, clippy::single_match)]
++
++fn f() -> Result<u32, u32> {
++ Ok(0)
++}
++
++fn g() -> Option<()> {
++ None
++}
++
++fn h() -> (Option<()>, Option<()>) {
++ (None, None)
++}
++
++enum Variant {
++ Foo,
++ Bar(u32),
++ Baz(u32),
++}
++
++fn build_enum() -> Variant {
++ Variant::Foo
++}
++
++fn main() {}
++
++fn fire() {
++ let v = match g() {
++ Some(v_some) => v_some,
++ None => return,
++ };
++
++ let v = match g() {
++ Some(v_some) => v_some,
++ _ => return,
++ };
++
++ loop {
++ // More complex pattern for the identity arm and diverging arm
++ let v = match h() {
++ (Some(_), Some(_)) | (None, None) => continue,
++ (Some(v), None) | (None, Some(v)) => v,
++ };
++ // Custom enums are supported as long as the "else" arm is a simple _
++ let v = match build_enum() {
++ _ => continue,
++ Variant::Bar(v) | Variant::Baz(v) => v,
++ };
++ }
++
++ // There is a _ in the diverging arm
++ // TODO also support unused bindings aka _v
++ let v = match f() {
++ Ok(v) => v,
++ Err(_) => return,
++ };
++
++ // Err(()) is an allowed pattern
++ let v = match f().map_err(|_| ()) {
++ Ok(v) => v,
++ Err(()) => return,
++ };
++}
++
++fn not_fire() {
++ // Multiple diverging arms
++ let v = match h() {
++ _ => panic!(),
++ (None, Some(_v)) => return,
++ (Some(v), None) => v,
++ };
++
++ // Multiple identity arms
++ let v = match h() {
++ _ => panic!(),
++ (None, Some(v)) => v,
++ (Some(v), None) => v,
++ };
++
++ // No diverging arm at all, only identity arms.
++ // This is no case for let else, but destructuring assignment.
++ let v = match f() {
++ Ok(v) => v,
++ Err(e) => e,
++ };
++
++ // The identity arm has a guard
++ let v = match g() {
++ Some(v) if g().is_none() => v,
++ _ => return,
++ };
++
++ // The diverging arm has a guard
++ let v = match f() {
++ Err(v) if v > 0 => panic!(),
++ Ok(v) | Err(v) => v,
++ };
++
++ // The diverging arm creates a binding
++ let v = match f() {
++ Ok(v) => v,
++ Err(e) => panic!("error: {e}"),
++ };
++
++ // Custom enum where the diverging arm
++ // explicitly mentions the variant
++ let v = match build_enum() {
++ Variant::Foo => return,
++ Variant::Bar(v) | Variant::Baz(v) => v,
++ };
++
++ // The custom enum is surrounded by an Err()
++ let v = match Err(build_enum()) {
++ Ok(v) | Err(Variant::Bar(v) | Variant::Baz(v)) => v,
++ Err(Variant::Foo) => return,
++ };
++}
--- /dev/null
--- /dev/null
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:32:5
++ |
++LL | / let v = match g() {
++LL | | Some(v_some) => v_some,
++LL | | None => return,
++LL | | };
++ | |______^ help: consider writing: `let Some(v_some) = g() else { return };`
++ |
++ = note: `-D clippy::manual-let-else` implied by `-D warnings`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:37:5
++ |
++LL | / let v = match g() {
++LL | | Some(v_some) => v_some,
++LL | | _ => return,
++LL | | };
++ | |______^ help: consider writing: `let Some(v_some) = g() else { return };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:44:9
++ |
++LL | / let v = match h() {
++LL | | (Some(_), Some(_)) | (None, None) => continue,
++LL | | (Some(v), None) | (None, Some(v)) => v,
++LL | | };
++ | |__________^ help: consider writing: `let (Some(v), None) | (None, Some(v)) = h() else { continue };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:49:9
++ |
++LL | / let v = match build_enum() {
++LL | | _ => continue,
++LL | | Variant::Bar(v) | Variant::Baz(v) => v,
++LL | | };
++ | |__________^ help: consider writing: `let Variant::Bar(v) | Variant::Baz(v) = build_enum() else { continue };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:57:5
++ |
++LL | / let v = match f() {
++LL | | Ok(v) => v,
++LL | | Err(_) => return,
++LL | | };
++ | |______^ help: consider writing: `let Ok(v) = f() else { return };`
++
++error: this could be rewritten as `let...else`
++ --> $DIR/manual_let_else_match.rs:63:5
++ |
++LL | / let v = match f().map_err(|_| ()) {
++LL | | Ok(v) => v,
++LL | | Err(()) => return,
++LL | | };
++ | |______^ help: consider writing: `let Ok(v) = f().map_err(|_| ()) else { return };`
++
++error: aborting due to 6 previous errors
++
--- /dev/null
+// run-rustfix
+#![warn(clippy::manual_ok_or)]
++#![allow(clippy::or_fun_call)]
+#![allow(clippy::disallowed_names)]
+#![allow(clippy::redundant_closure)]
+#![allow(dead_code)]
+#![allow(unused_must_use)]
+
+fn main() {
+ // basic case
+ let foo: Option<i32> = None;
+ foo.ok_or("error");
+
+ // eta expansion case
+ foo.ok_or("error");
+
+ // turbo fish syntax
+ None::<i32>.ok_or("error");
+
+ // multiline case
+ #[rustfmt::skip]
+ foo.ok_or(&format!(
+ "{}{}{}{}{}{}{}",
+ "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
+
+ // not applicable, closure isn't direct `Ok` wrapping
+ foo.map_or(Err("error"), |v| Ok(v + 1));
+
+ // not applicable, or side isn't `Result::Err`
+ foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
+
+ // not applicable, expr is not a `Result` value
+ foo.map_or(42, |v| v);
+
+ // TODO patterns not covered yet
+ match foo {
+ Some(v) => Ok(v),
+ None => Err("error"),
+ };
+ foo.map_or_else(|| Err("error"), |v| Ok(v));
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::manual_ok_or)]
++#![allow(clippy::or_fun_call)]
+#![allow(clippy::disallowed_names)]
+#![allow(clippy::redundant_closure)]
+#![allow(dead_code)]
+#![allow(unused_must_use)]
+
+fn main() {
+ // basic case
+ let foo: Option<i32> = None;
+ foo.map_or(Err("error"), |v| Ok(v));
+
+ // eta expansion case
+ foo.map_or(Err("error"), Ok);
+
+ // turbo fish syntax
+ None::<i32>.map_or(Err("error"), |v| Ok(v));
+
+ // multiline case
+ #[rustfmt::skip]
+ foo.map_or(Err::<i32, &str>(
+ &format!(
+ "{}{}{}{}{}{}{}",
+ "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
+ ),
+ |v| Ok(v),
+ );
+
+ // not applicable, closure isn't direct `Ok` wrapping
+ foo.map_or(Err("error"), |v| Ok(v + 1));
+
+ // not applicable, or side isn't `Result::Err`
+ foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
+
+ // not applicable, expr is not a `Result` value
+ foo.map_or(42, |v| v);
+
+ // TODO patterns not covered yet
+ match foo {
+ Some(v) => Ok(v),
+ None => Err("error"),
+ };
+ foo.map_or_else(|| Err("error"), |v| Ok(v));
+}
--- /dev/null
- --> $DIR/manual_ok_or.rs:11:5
+error: this pattern reimplements `Option::ok_or`
- --> $DIR/manual_ok_or.rs:14:5
++ --> $DIR/manual_ok_or.rs:12:5
+ |
+LL | foo.map_or(Err("error"), |v| Ok(v));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
+ |
+ = note: `-D clippy::manual-ok-or` implied by `-D warnings`
+
+error: this pattern reimplements `Option::ok_or`
- --> $DIR/manual_ok_or.rs:17:5
++ --> $DIR/manual_ok_or.rs:15:5
+ |
+LL | foo.map_or(Err("error"), Ok);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
+
+error: this pattern reimplements `Option::ok_or`
- --> $DIR/manual_ok_or.rs:21:5
++ --> $DIR/manual_ok_or.rs:18:5
+ |
+LL | None::<i32>.map_or(Err("error"), |v| Ok(v));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::<i32>.ok_or("error")`
+
+error: this pattern reimplements `Option::ok_or`
++ --> $DIR/manual_ok_or.rs:22:5
+ |
+LL | / foo.map_or(Err::<i32, &str>(
+LL | | &format!(
+LL | | "{}{}{}{}{}{}{}",
+LL | | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
+LL | | ),
+LL | | |v| Ok(v),
+LL | | );
+ | |_____^
+ |
+help: replace with
+ |
+LL ~ foo.ok_or(&format!(
+LL + "{}{}{}{}{}{}{}",
+LL ~ "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
+ |
+
+error: aborting due to 4 previous errors
+
--- /dev/null
- #![allow(clippy::let_underscore_drop)]
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![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);
+
+ issue8734();
+ issue8878();
+}
+
+fn issue8734() {
+ let _ = [0u8, 1, 2, 3]
+ .into_iter()
+ .flat_map(|n| match n {
+ 1 => [n
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)],
+ n => [n],
+ });
+}
+
+#[allow(clippy::bind_instead_of_map)] // map + flatten will be suggested to `and_then`, but afterwards `map` is suggested again
+#[rustfmt::skip] // whitespace is important for this one
+fn issue8878() {
+ std::collections::HashMap::<u32, u32>::new()
+ .get(&0)
+ .and_then(|_| {
+// we need some newlines
+// so that the span is big enough
+// for a split output of the diagnostic
+ Some("")
+ // whitespace beforehand is important as well
+ });
+}
--- /dev/null
- #![allow(clippy::let_underscore_drop)]
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![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();
+
+ issue8734();
+ issue8878();
+}
+
+fn issue8734() {
+ let _ = [0u8, 1, 2, 3]
+ .into_iter()
+ .map(|n| match n {
+ 1 => [n
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)
+ .saturating_add(1)],
+ n => [n],
+ })
+ .flatten();
+}
+
+#[allow(clippy::bind_instead_of_map)] // map + flatten will be suggested to `and_then`, but afterwards `map` is suggested again
+#[rustfmt::skip] // whitespace is important for this one
+fn issue8878() {
+ std::collections::HashMap::<u32, u32>::new()
+ .get(&0)
+ .map(|_| {
+// we need some newlines
+// so that the span is big enough
+// for a split output of the diagnostic
+ Some("")
+ // whitespace beforehand is important as well
+ })
+ .flatten();
+}
--- /dev/null
- --> $DIR/map_flatten_fixable.rs:18:47
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:19:47
++ --> $DIR/map_flatten_fixable.rs:17:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id)`
+ |
+ = note: `-D clippy::map-flatten` implied by `-D warnings`
+
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:20:47
++ --> $DIR/map_flatten_fixable.rs:18: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()`: `filter_map(option_id_ref)`
+
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:21:47
++ --> $DIR/map_flatten_fixable.rs:19: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()`: `filter_map(option_id_closure)`
+
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:24:47
++ --> $DIR/map_flatten_fixable.rs:20: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()`: `filter_map(|x| x.checked_add(1))`
+
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:27:40
++ --> $DIR/map_flatten_fixable.rs:23: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()`: `flat_map(|x| 0..x)`
+
+error: called `map(..).flatten()` on `Option`
- --> $DIR/map_flatten_fixable.rs:30:42
++ --> $DIR/map_flatten_fixable.rs:26:40
+ |
+LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+ | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)`
+
+error: called `map(..).flatten()` on `Result`
- --> $DIR/map_flatten_fixable.rs:39:10
++ --> $DIR/map_flatten_fixable.rs:29:42
+ |
+LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+ | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)`
+
+error: called `map(..).flatten()` on `Iterator`
- --> $DIR/map_flatten_fixable.rs:59:10
++ --> $DIR/map_flatten_fixable.rs:38:10
+ |
+LL | .map(|n| match n {
+ | __________^
+LL | | 1 => [n
+LL | | .saturating_add(1)
+LL | | .saturating_add(1)
+... |
+LL | | })
+LL | | .flatten();
+ | |__________________^
+ |
+help: try replacing `map` with `flat_map` and remove the `.flatten()`
+ |
+LL ~ .flat_map(|n| match n {
+LL + 1 => [n
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)
+LL + .saturating_add(1)],
+LL + n => [n],
+LL ~ });
+ |
+
+error: called `map(..).flatten()` on `Option`
++ --> $DIR/map_flatten_fixable.rs:58:10
+ |
+LL | .map(|_| {
+ | __________^
+LL | | // we need some newlines
+LL | | // so that the span is big enough
+LL | | // for a split output of the diagnostic
+... |
+LL | | })
+LL | | .flatten();
+ | |__________________^
+ |
+help: try replacing `map` with `and_then` and remove the `.flatten()`
+ |
+LL ~ .and_then(|_| {
+LL + // we need some newlines
+LL + // so that the span is big enough
+LL + // for a split output of the diagnostic
+LL + Some("")
+LL + // whitespace beforehand is important as well
+LL ~ });
+ |
+
+error: aborting due to 9 previous errors
+
--- /dev/null
- #![allow(unreachable_patterns, dead_code, clippy::equatable_if_let)]
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::match_like_matches_macro)]
++#![allow(
++ unreachable_patterns,
++ dead_code,
++ clippy::equatable_if_let,
++ clippy::needless_borrowed_reference
++)]
+
+fn main() {
+ let x = Some(5);
+
+ // Lint
+ let _y = matches!(x, Some(0));
+
+ // Lint
+ let _w = matches!(x, Some(_));
+
+ // Turn into is_none
+ let _z = x.is_none();
+
+ // Lint
+ let _zz = !matches!(x, Some(r) if r == 0);
+
+ // Lint
+ let _zzz = matches!(x, Some(5));
+
+ // No lint
+ let _a = match x {
+ Some(_) => false,
+ _ => false,
+ };
+
+ // No lint
+ let _ab = match x {
+ Some(0) => false,
+ _ => true,
+ None => false,
+ };
+
+ enum E {
+ A(u32),
+ B(i32),
+ C,
+ D,
+ }
+ let x = E::A(2);
+ {
+ // lint
+ let _ans = matches!(x, E::A(_) | E::B(_));
+ }
+ {
+ // lint
+ // skip rustfmt to prevent removing block for first pattern
+ #[rustfmt::skip]
+ let _ans = matches!(x, E::A(_) | E::B(_));
+ }
+ {
+ // lint
+ let _ans = !matches!(x, E::B(_) | E::C);
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(_) => false,
+ E::C => true,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => true,
+ E::B(_) => false,
+ E::C => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(a) if a < 10 => false,
+ E::B(a) if a < 10 => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(a) if a < 10 => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(a) => a == 10,
+ E::B(_) => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(_) => true,
+ _ => false,
+ };
+ }
+
+ {
+ // should print "z" in suggestion (#6503)
+ let z = &Some(3);
+ let _z = matches!(z, Some(3));
+ }
+
+ {
+ // this could also print "z" in suggestion..?
+ let z = Some(3);
+ let _z = matches!(&z, Some(3));
+ }
+
+ {
+ enum AnEnum {
+ X,
+ Y,
+ }
+
+ fn foo(_x: AnEnum) {}
+
+ fn main() {
+ let z = AnEnum::X;
+ // we can't remove the reference here!
+ let _ = matches!(&z, AnEnum::X);
+ foo(z);
+ }
+ }
+
+ {
+ struct S(i32);
+
+ fn fun(_val: Option<S>) {}
+ let val = Some(S(42));
+ // we need the reference here because later val is consumed by fun()
+ let _res = matches!(&val, &Some(ref _a));
+ fun(val);
+ }
+
+ {
+ struct S(i32);
+
+ fn fun(_val: Option<S>) {}
+ let val = Some(S(42));
+ let _res = matches!(&val, &Some(ref _a));
+ fun(val);
+ }
+
+ {
+ enum E {
+ A,
+ B,
+ C,
+ }
+
+ let _ = match E::A {
+ E::B => true,
+ #[cfg(feature = "foo")]
+ E::A => true,
+ _ => false,
+ };
+ }
+
+ let x = ' ';
+ // ignore if match block contains comment
+ let _line_comments = match x {
+ // numbers are bad!
+ '1' | '2' | '3' => true,
+ // spaces are very important to be true.
+ ' ' => true,
+ // as are dots
+ '.' => true,
+ _ => false,
+ };
+
+ let _block_comments = match x {
+ /* numbers are bad!
+ */
+ '1' | '2' | '3' => true,
+ /* spaces are very important to be true.
+ */
+ ' ' => true,
+ /* as are dots
+ */
+ '.' => true,
+ _ => false,
+ };
+}
+
+fn msrv_1_41() {
+ #![clippy::msrv = "1.41"]
+
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
+
+fn msrv_1_42() {
+ #![clippy::msrv = "1.42"]
+
+ let _y = matches!(Some(5), Some(0));
+}
--- /dev/null
- #![allow(unreachable_patterns, dead_code, clippy::equatable_if_let)]
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::match_like_matches_macro)]
++#![allow(
++ unreachable_patterns,
++ dead_code,
++ clippy::equatable_if_let,
++ clippy::needless_borrowed_reference
++)]
+
+fn main() {
+ let x = Some(5);
+
+ // Lint
+ let _y = match x {
+ Some(0) => true,
+ _ => false,
+ };
+
+ // Lint
+ let _w = match x {
+ Some(_) => true,
+ _ => false,
+ };
+
+ // Turn into is_none
+ let _z = match x {
+ Some(_) => false,
+ None => true,
+ };
+
+ // Lint
+ let _zz = match x {
+ Some(r) if r == 0 => false,
+ _ => true,
+ };
+
+ // Lint
+ let _zzz = if let Some(5) = x { true } else { false };
+
+ // No lint
+ let _a = match x {
+ Some(_) => false,
+ _ => false,
+ };
+
+ // No lint
+ let _ab = match x {
+ Some(0) => false,
+ _ => true,
+ None => false,
+ };
+
+ enum E {
+ A(u32),
+ B(i32),
+ C,
+ D,
+ }
+ let x = E::A(2);
+ {
+ // lint
+ let _ans = match x {
+ E::A(_) => true,
+ E::B(_) => true,
+ _ => false,
+ };
+ }
+ {
+ // lint
+ // skip rustfmt to prevent removing block for first pattern
+ #[rustfmt::skip]
+ let _ans = match x {
+ E::A(_) => {
+ true
+ }
+ E::B(_) => true,
+ _ => false,
+ };
+ }
+ {
+ // lint
+ let _ans = match x {
+ E::B(_) => false,
+ E::C => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(_) => false,
+ E::C => true,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => true,
+ E::B(_) => false,
+ E::C => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(a) if a < 10 => false,
+ E::B(a) if a < 10 => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(a) if a < 10 => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(a) => a == 10,
+ E::B(_) => false,
+ _ => true,
+ };
+ }
+ {
+ // no lint
+ let _ans = match x {
+ E::A(_) => false,
+ E::B(_) => true,
+ _ => false,
+ };
+ }
+
+ {
+ // should print "z" in suggestion (#6503)
+ let z = &Some(3);
+ let _z = match &z {
+ Some(3) => true,
+ _ => false,
+ };
+ }
+
+ {
+ // this could also print "z" in suggestion..?
+ let z = Some(3);
+ let _z = match &z {
+ Some(3) => true,
+ _ => false,
+ };
+ }
+
+ {
+ enum AnEnum {
+ X,
+ Y,
+ }
+
+ fn foo(_x: AnEnum) {}
+
+ fn main() {
+ let z = AnEnum::X;
+ // we can't remove the reference here!
+ let _ = match &z {
+ AnEnum::X => true,
+ _ => false,
+ };
+ foo(z);
+ }
+ }
+
+ {
+ struct S(i32);
+
+ fn fun(_val: Option<S>) {}
+ let val = Some(S(42));
+ // we need the reference here because later val is consumed by fun()
+ let _res = match &val {
+ &Some(ref _a) => true,
+ _ => false,
+ };
+ fun(val);
+ }
+
+ {
+ struct S(i32);
+
+ fn fun(_val: Option<S>) {}
+ let val = Some(S(42));
+ let _res = match &val {
+ &Some(ref _a) => true,
+ _ => false,
+ };
+ fun(val);
+ }
+
+ {
+ enum E {
+ A,
+ B,
+ C,
+ }
+
+ let _ = match E::A {
+ E::B => true,
+ #[cfg(feature = "foo")]
+ E::A => true,
+ _ => false,
+ };
+ }
+
+ let x = ' ';
+ // ignore if match block contains comment
+ let _line_comments = match x {
+ // numbers are bad!
+ '1' | '2' | '3' => true,
+ // spaces are very important to be true.
+ ' ' => true,
+ // as are dots
+ '.' => true,
+ _ => false,
+ };
+
+ let _block_comments = match x {
+ /* numbers are bad!
+ */
+ '1' | '2' | '3' => true,
+ /* spaces are very important to be true.
+ */
+ ' ' => true,
+ /* as are dots
+ */
+ '.' => true,
+ _ => false,
+ };
+}
+
+fn msrv_1_41() {
+ #![clippy::msrv = "1.41"]
+
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
+
+fn msrv_1_42() {
+ #![clippy::msrv = "1.42"]
+
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
--- /dev/null
- --> $DIR/match_expr_like_matches_macro.rs:11:14
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:17:14
++ --> $DIR/match_expr_like_matches_macro.rs:16:14
+ |
+LL | let _y = match x {
+ | ______________^
+LL | | Some(0) => true,
+LL | | _ => false,
+LL | | };
+ | |_____^ help: try this: `matches!(x, Some(0))`
+ |
+ = note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:23:14
++ --> $DIR/match_expr_like_matches_macro.rs:22:14
+ |
+LL | let _w = match x {
+ | ______________^
+LL | | Some(_) => true,
+LL | | _ => false,
+LL | | };
+ | |_____^ help: try this: `matches!(x, Some(_))`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/match_expr_like_matches_macro.rs:29:15
++ --> $DIR/match_expr_like_matches_macro.rs:28:14
+ |
+LL | let _z = match x {
+ | ______________^
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `x.is_none()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:35:16
++ --> $DIR/match_expr_like_matches_macro.rs:34:15
+ |
+LL | let _zz = match x {
+ | _______________^
+LL | | Some(r) if r == 0 => false,
+LL | | _ => true,
+LL | | };
+ | |_____^ help: try this: `!matches!(x, Some(r) if r == 0)`
+
+error: if let .. else expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:59:20
++ --> $DIR/match_expr_like_matches_macro.rs:40:16
+ |
+LL | let _zzz = if let Some(5) = x { true } else { false };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `matches!(x, Some(5))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:69:20
++ --> $DIR/match_expr_like_matches_macro.rs:64:20
+ |
+LL | let _ans = match x {
+ | ____________________^
+LL | | E::A(_) => true,
+LL | | E::B(_) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(x, E::A(_) | E::B(_))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:79:20
++ --> $DIR/match_expr_like_matches_macro.rs:74:20
+ |
+LL | let _ans = match x {
+ | ____________________^
+LL | | E::A(_) => {
+LL | | true
+LL | | }
+LL | | E::B(_) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(x, E::A(_) | E::B(_))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:139:18
++ --> $DIR/match_expr_like_matches_macro.rs:84:20
+ |
+LL | let _ans = match x {
+ | ____________________^
+LL | | E::B(_) => false,
+LL | | E::C => false,
+LL | | _ => true,
+LL | | };
+ | |_________^ help: try this: `!matches!(x, E::B(_) | E::C)`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:148:18
++ --> $DIR/match_expr_like_matches_macro.rs:144:18
+ |
+LL | let _z = match &z {
+ | __________________^
+LL | | Some(3) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(z, Some(3))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:165:21
++ --> $DIR/match_expr_like_matches_macro.rs:153:18
+ |
+LL | let _z = match &z {
+ | __________________^
+LL | | Some(3) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(&z, Some(3))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:179:20
++ --> $DIR/match_expr_like_matches_macro.rs:170:21
+ |
+LL | let _ = match &z {
+ | _____________________^
+LL | | AnEnum::X => true,
+LL | | _ => false,
+LL | | };
+ | |_____________^ help: try this: `matches!(&z, AnEnum::X)`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:191:20
++ --> $DIR/match_expr_like_matches_macro.rs:184:20
+ |
+LL | let _res = match &val {
+ | ____________________^
+LL | | &Some(ref _a) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(&val, &Some(ref _a))`
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_expr_like_matches_macro.rs:251:14
++ --> $DIR/match_expr_like_matches_macro.rs:196:20
+ |
+LL | let _res = match &val {
+ | ____________________^
+LL | | &Some(ref _a) => true,
+LL | | _ => false,
+LL | | };
+ | |_________^ help: try this: `matches!(&val, &Some(ref _a))`
+
+error: match expression looks like `matches!` macro
++ --> $DIR/match_expr_like_matches_macro.rs:256:14
+ |
+LL | let _y = match Some(5) {
+ | ______________^
+LL | | Some(0) => true,
+LL | | _ => false,
+LL | | };
+ | |_____^ help: try this: `matches!(Some(5), Some(0))`
+
+error: aborting due to 14 previous errors
+
--- /dev/null
- LL | / pub fn unwrap() {
- LL | | let result = Err("Hi");
- LL | | result.unwrap()
- LL | | }
- | |_^
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:6:1
+ |
- LL | / pub fn panic() {
- LL | | panic!("This function panics")
- LL | | }
- | |_^
++LL | pub fn unwrap() {
++ | ^^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:8:5
+ |
+LL | result.unwrap()
+ | ^^^^^^^^^^^^^^^
+ = note: `-D clippy::missing-panics-doc` implied by `-D warnings`
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:12:1
+ |
- LL | / pub fn todo() {
- LL | | todo!()
- LL | | }
- | |_^
++LL | pub fn panic() {
++ | ^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:13:5
+ |
+LL | panic!("This function panics")
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:17:1
+ |
- LL | / pub fn inner_body(opt: Option<u32>) {
- LL | | opt.map(|x| {
- LL | | if x == 10 {
- LL | | panic!()
- LL | | }
- LL | | });
- LL | | }
- | |_^
++LL | pub fn todo() {
++ | ^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:18:5
+ |
+LL | todo!()
+ | ^^^^^^^
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:22:1
+ |
- LL | / pub fn unreachable_and_panic() {
- LL | | if true { unreachable!() } else { panic!() }
- LL | | }
- | |_^
++LL | pub fn inner_body(opt: Option<u32>) {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:25:13
+ |
+LL | panic!()
+ | ^^^^^^^^
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:31:1
+ |
- LL | / pub fn assert_eq() {
- LL | | let x = 0;
- LL | | assert_eq!(x, 0);
- LL | | }
- | |_^
++LL | pub fn unreachable_and_panic() {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:32:39
+ |
+LL | if true { unreachable!() } else { panic!() }
+ | ^^^^^^^^
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:36:1
+ |
- LL | / pub fn assert_ne() {
- LL | | let x = 0;
- LL | | assert_ne!(x, 0);
- LL | | }
- | |_^
++LL | pub fn assert_eq() {
++ | ^^^^^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:38:5
+ |
+LL | assert_eq!(x, 0);
+ | ^^^^^^^^^^^^^^^^
+
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/missing_panics_doc.rs:42:1
+ |
++LL | pub fn assert_ne() {
++ | ^^^^^^^^^^^^^^^^^^
+ |
+note: first possible panic found here
+ --> $DIR/missing_panics_doc.rs:44:5
+ |
+LL | assert_ne!(x, 0);
+ | ^^^^^^^^^^^^^^^^
+
+error: aborting due to 7 previous errors
+
--- /dev/null
- #![allow(unused)]
++#![allow(unused, clippy::needless_lifetimes)]
+#![warn(clippy::mut_from_ref)]
+
+struct Foo;
+
+impl Foo {
+ fn this_wont_hurt_a_bit(&self) -> &mut Foo {
+ unsafe { unimplemented!() }
+ }
+}
+
+trait Ouch {
+ fn ouch(x: &Foo) -> &mut Foo;
+}
+
+impl Ouch for Foo {
+ fn ouch(x: &Foo) -> &mut Foo {
+ unsafe { unimplemented!() }
+ }
+}
+
+fn fail(x: &u32) -> &mut u16 {
+ unsafe { unimplemented!() }
+}
+
+fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 {
+ unsafe { unimplemented!() }
+}
+
+fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
+ unsafe { unimplemented!() }
+}
+
+// this is OK, because the result borrows y
+fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 {
+ unsafe { unimplemented!() }
+}
+
+// this is also OK, because the result could borrow y
+fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 {
+ unsafe { unimplemented!() }
+}
+
+unsafe fn also_broken(x: &u32) -> &mut u32 {
+ unimplemented!()
+}
+
+fn without_unsafe(x: &u32) -> &mut u32 {
+ unimplemented!()
+}
+
+fn main() {
+ //TODO
+}
--- /dev/null
+// aux-build:macro_rules.rs
+#![warn(clippy::mut_mut)]
+#![allow(unused)]
+#![allow(clippy::no_effect, clippy::uninlined_format_args, clippy::unnecessary_operation)]
+
+#[macro_use]
+extern crate macro_rules;
+
+fn fun(x: &mut &mut u32) -> bool {
+ **x > 0
+}
+
+fn less_fun(x: *mut *mut u32) {
+ let y = x;
+}
+
+macro_rules! mut_ptr {
+ ($p:expr) => {
+ &mut $p
+ };
+}
+
+#[allow(unused_mut, unused_variables)]
+fn main() {
+ let mut x = &mut &mut 1u32;
+ {
+ let mut y = &mut x;
+ }
+
+ if fun(x) {
+ let y: &mut &mut u32 = &mut &mut 2;
+ **y + **x;
+ }
+
+ if fun(x) {
+ let y: &mut &mut &mut u32 = &mut &mut &mut 2;
+ ***y + **x;
+ }
+
+ let mut z = mut_ptr!(&mut 3u32);
+}
+
+fn issue939() {
+ let array = [5, 6, 7, 8, 9];
+ let mut args = array.iter().skip(2);
+ for &arg in &mut args {
+ println!("{}", arg);
+ }
+
+ let args = &mut args;
+ for arg in args {
+ println!(":{}", arg);
+ }
+}
+
+fn issue6922() {
+ // do not lint from an external macro
+ mut_mut!();
+}
++
++mod issue9035 {
++ use std::fmt::Display;
++
++ struct Foo<'a> {
++ inner: &'a mut dyn Display,
++ }
++
++ impl Foo<'_> {
++ fn foo(&mut self) {
++ let hlp = &mut self.inner;
++ bar(hlp);
++ }
++ }
++
++ fn bar(_: &mut impl Display) {}
++}
--- /dev/null
- n = 1; // FIXME: warning because is is not immediately followed by break
+#![allow(unused)]
+
+fn main() {}
+
+fn mut_range_bound_upper() {
+ let mut m = 4;
+ for i in 0..m {
+ m = 5;
+ } // warning
+}
+
+fn mut_range_bound_lower() {
+ let mut m = 4;
+ for i in m..10 {
+ m *= 2;
+ } // warning
+}
+
+fn mut_range_bound_both() {
+ let mut m = 4;
+ let mut n = 6;
+ for i in m..n {
+ m = 5;
+ n = 7;
+ } // warning (1 for each mutated bound)
+}
+
+fn mut_range_bound_no_mutation() {
+ let mut m = 4;
+ for i in 0..m {
+ continue;
+ } // no warning
+}
+
+fn mut_borrow_range_bound() {
+ let mut m = 4;
+ for i in 0..m {
+ let n = &mut m; // warning
+ *n += 1;
+ }
+}
+
+fn immut_borrow_range_bound() {
+ let mut m = 4;
+ for i in 0..m {
+ let n = &m; // should be no warning?
+ }
+}
+
+fn immut_range_bound() {
+ let m = 4;
+ for i in 0..m {
+ continue;
+ } // no warning
+}
+
+fn mut_range_bound_break() {
+ let mut m = 4;
+ for i in 0..m {
+ if m == 4 {
+ m = 5; // no warning because of immediate break
+ break;
+ }
+ }
+}
+
+fn mut_range_bound_no_immediate_break() {
+ let mut m = 4;
+ for i in 0..m {
+ m = 2; // warning because it is not immediately followed by break
+ if m == 4 {
+ break;
+ }
+ }
+
+ let mut n = 3;
+ for i in n..10 {
+ if n == 4 {
++ n = 1; // FIXME: warning because it is not immediately followed by break
+ let _ = 2;
+ break;
+ }
+ }
+}
--- /dev/null
- LL | n = 1; // FIXME: warning because is is not immediately followed by break
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:8:9
+ |
+LL | m = 5;
+ | ^
+ |
+ = note: the range of the loop is unchanged
+ = note: `-D clippy::mut-range-bound` implied by `-D warnings`
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:15:9
+ |
+LL | m *= 2;
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:23:9
+ |
+LL | m = 5;
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:24:9
+ |
+LL | n = 7;
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:38:22
+ |
+LL | let n = &mut m; // warning
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:70:9
+ |
+LL | m = 2; // warning because it is not immediately followed by break
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: attempt to mutate range bound within loop
+ --> $DIR/mut_range_bound.rs:79:13
+ |
++LL | n = 1; // FIXME: warning because it is not immediately followed by break
+ | ^
+ |
+ = note: the range of the loop is unchanged
+
+error: aborting due to 7 previous errors
+
--- /dev/null
+// run-rustfix
+#![feature(custom_inner_attributes, lint_reasons)]
+
+#[warn(clippy::all, clippy::needless_borrow)]
+#[allow(unused_variables)]
+#[allow(
+ clippy::uninlined_format_args,
+ clippy::unnecessary_mut_passed,
+ clippy::unnecessary_to_owned
+)]
+fn main() {
+ let a = 5;
+ let ref_a = &a;
+ let _ = x(&a); // no warning
+ let _ = x(&a); // warn
+
+ let mut b = 5;
+ mut_ref(&mut b); // no warning
+ mut_ref(&mut b); // warn
+
+ let s = &String::from("hi");
+ let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not
+ let g_val = g(&Vec::new()); // should not error, because `&Vec<T>` derefs to `&[T]`
+ let vec = Vec::new();
+ let vec_val = g(&vec); // should not error, because `&Vec<T>` derefs to `&[T]`
+ h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait`
+ let garbl = match 42 {
+ 44 => &a,
+ 45 => {
+ println!("foo");
+ &a
+ },
+ 46 => &a,
+ 47 => {
+ println!("foo");
+ loop {
+ println!("{}", a);
+ if a == 25 {
+ break ref_a;
+ }
+ }
+ },
+ _ => panic!(),
+ };
+
+ let _ = x(&a);
+ let _ = x(&a);
+ let _ = x(&mut b);
+ let _ = x(ref_a);
+ {
+ let b = &mut b;
+ x(b);
+ }
+
+ // Issue #8191
+ let mut x = 5;
+ let mut x = &mut x;
+
+ mut_ref(x);
+ mut_ref(x);
+ let y: &mut i32 = x;
+ let y: &mut i32 = x;
+
+ let y = match 0 {
+ // Don't lint. Removing the borrow would move 'x'
+ 0 => &mut x,
+ _ => &mut *x,
+ };
+ let y: &mut i32 = match 0 {
+ // Lint here. The type given above triggers auto-borrow.
+ 0 => x,
+ _ => &mut *x,
+ };
+ fn ref_mut_i32(_: &mut i32) {}
+ ref_mut_i32(match 0 {
+ // Lint here. The type given above triggers auto-borrow.
+ 0 => x,
+ _ => &mut *x,
+ });
+ // use 'x' after to make sure it's still usable in the fixed code.
+ *x = 5;
+
+ let s = String::new();
+ // let _ = (&s).len();
+ // let _ = (&s).capacity();
+ // let _ = (&&s).capacity();
+
+ let x = (1, 2);
+ let _ = x.0;
+ let x = &x as *const (i32, i32);
+ let _ = unsafe { (*x).0 };
+
+ // Issue #8367
+ trait Foo {
+ fn foo(self);
+ }
+ impl Foo for &'_ () {
+ fn foo(self) {}
+ }
+ (&()).foo(); // Don't lint. `()` doesn't implement `Foo`
+ (&()).foo();
+
+ impl Foo for i32 {
+ fn foo(self) {}
+ }
+ impl Foo for &'_ i32 {
+ fn foo(self) {}
+ }
+ (&5).foo(); // Don't lint. `5` will call `<i32 as Foo>::foo`
+ (&5).foo();
+
+ trait FooRef {
+ fn foo_ref(&self);
+ }
+ impl FooRef for () {
+ fn foo_ref(&self) {}
+ }
+ impl FooRef for &'_ () {
+ fn foo_ref(&self) {}
+ }
+ (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref`
+
+ struct S;
+ impl From<S> for u32 {
+ fn from(s: S) -> Self {
+ (&s).into()
+ }
+ }
+ impl From<&S> for u32 {
+ fn from(s: &S) -> Self {
+ 0
+ }
+ }
+
+ let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap();
+ let _ = std::path::Path::new(".").join(".");
+ deref_target_is_x(X);
+ multiple_constraints([[""]]);
+ multiple_constraints_normalizes_to_same(X, X);
+ let _ = Some("").unwrap_or("");
+ let _ = std::fs::write("x", "".to_string());
+
+ only_sized(&""); // Don't lint. `Sized` is only bound
+ let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
+ let _ = Box::new(&""); // Don't lint. Type parameter appears in return type
+ ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter
+ refs_only(&()); // Don't lint. `&T` implements trait, but `T` doesn't
+ multiple_constraints_normalizes_to_different(&[[""]], &[""]); // Don't lint. Projected type appears in arguments
+}
+
+#[allow(clippy::needless_borrowed_reference)]
+fn x(y: &i32) -> i32 {
+ *y
+}
+
+fn mut_ref(y: &mut i32) {
+ *y = 5;
+}
+
+fn f<T: Copy>(y: &T) -> T {
+ *y
+}
+
+fn g(y: &[u8]) -> u8 {
+ y[0]
+}
+
+trait Trait {}
+
+impl<'a> Trait for &'a str {}
+
+fn h(_: &dyn Trait) {}
+
+#[allow(dead_code)]
+fn check_expect_suppression() {
+ let a = 5;
+ #[expect(clippy::needless_borrow)]
+ let _ = x(&&a);
+}
+
+#[allow(dead_code)]
+mod issue9160 {
+ pub struct S<F> {
+ f: F,
+ }
+
+ impl<T, F> S<F>
+ where
+ F: Fn() -> T,
+ {
+ fn calls_field(&self) -> T {
+ (self.f)()
+ }
+ }
+
+ impl<T, F> S<F>
+ where
+ F: FnMut() -> T,
+ {
+ fn calls_mut_field(&mut self) -> T {
+ (self.f)()
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+struct X;
+
+impl std::ops::Deref for X {
+ type Target = X;
+ fn deref(&self) -> &Self::Target {
+ self
+ }
+}
+
+fn deref_target_is_x<T>(_: T)
+where
+ T: std::ops::Deref<Target = X>,
+{
+}
+
+fn multiple_constraints<T, U, V, X, Y>(_: T)
+where
+ T: IntoIterator<Item = U> + IntoIterator<Item = X>,
+ U: IntoIterator<Item = V>,
+ V: AsRef<str>,
+ X: IntoIterator<Item = Y>,
+ Y: AsRef<std::ffi::OsStr>,
+{
+}
+
+fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V)
+where
+ T: std::ops::Deref<Target = U>,
+ U: std::ops::Deref<Target = V>,
+{
+}
+
+fn only_sized<T>(_: T) {}
+
+fn ref_as_ref_path<T: 'static>(_: &'static T)
+where
+ &'static T: AsRef<std::path::Path>,
+{
+}
+
+trait RefsOnly {
+ type Referent;
+}
+
+impl<T> RefsOnly for &T {
+ type Referent = T;
+}
+
+fn refs_only<T, U>(_: T)
+where
+ T: RefsOnly<Referent = U>,
+{
+}
+
+fn multiple_constraints_normalizes_to_different<T, U, V>(_: T, _: U)
+where
+ T: IntoIterator<Item = U>,
+ U: IntoIterator<Item = V>,
+ V: AsRef<str>,
+{
+}
+
+// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
+#[allow(dead_code)]
+mod copyable_iterator {
+ #[derive(Clone, Copy)]
+ struct Iter;
+ impl Iterator for Iter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+ }
+ fn takes_iter(_: impl Iterator) {}
+ fn dont_warn(mut x: Iter) {
+ takes_iter(&mut x);
+ }
+ #[allow(unused_mut)]
+ fn warn(mut x: &mut Iter) {
+ takes_iter(x)
+ }
+}
+
+mod under_msrv {
+ #![allow(dead_code)]
+ #![clippy::msrv = "1.52.0"]
+
+ fn foo() {
+ let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ }
+}
+
+mod meets_msrv {
+ #![allow(dead_code)]
+ #![clippy::msrv = "1.53.0"]
+
+ fn foo() {
+ let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap();
+ }
+}
+
+#[allow(unused)]
+fn issue9383() {
+ // Should not lint because unions need explicit deref when accessing field
+ use std::mem::ManuallyDrop;
+
+ union Coral {
+ crab: ManuallyDrop<Vec<i32>>,
+ }
+
+ union Ocean {
+ coral: ManuallyDrop<Coral>,
+ }
+
+ let mut ocean = Ocean {
+ coral: ManuallyDrop::new(Coral {
+ crab: ManuallyDrop::new(vec![1, 2, 3]),
+ }),
+ };
+
+ unsafe {
+ ManuallyDrop::drop(&mut (&mut ocean.coral).crab);
+
+ (*ocean.coral).crab = ManuallyDrop::new(vec![4, 5, 6]);
+ ManuallyDrop::drop(&mut (*ocean.coral).crab);
+
+ ManuallyDrop::drop(&mut ocean.coral);
+ }
+}
+
+#[allow(dead_code)]
+fn closure_test() {
+ let env = "env".to_owned();
+ let arg = "arg".to_owned();
+ let f = |arg| {
+ let loc = "loc".to_owned();
+ let _ = std::fs::write("x", &env); // Don't lint. In environment
+ let _ = std::fs::write("x", arg);
+ let _ = std::fs::write("x", loc);
+ };
+ let _ = std::fs::write("x", &env); // Don't lint. Borrowed by `f`
+ f(arg);
+}
+
+#[allow(dead_code)]
+mod significant_drop {
+ #[derive(Debug)]
+ struct X;
+
+ #[derive(Debug)]
+ struct Y;
+
+ impl Drop for Y {
+ fn drop(&mut self) {}
+ }
+
+ fn foo(x: X, y: Y) {
+ debug(x);
+ debug(&y); // Don't lint. Has significant drop
+ }
+
+ fn debug(_: impl std::fmt::Debug) {}
+}
+
+#[allow(dead_code)]
+mod used_exactly_once {
+ fn foo(x: String) {
+ use_x(x);
+ }
+ fn use_x(_: impl AsRef<str>) {}
+}
+
+#[allow(dead_code)]
+mod used_more_than_once {
+ fn foo(x: String) {
+ use_x(&x);
+ use_x_again(&x);
+ }
+ fn use_x(_: impl AsRef<str>) {}
+ fn use_x_again(_: impl AsRef<str>) {}
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
++#[allow(dead_code)]
++mod issue_9111 {
++ struct A;
++
++ impl Extend<u8> for A {
++ fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
++ unimplemented!()
++ }
++ }
++
++ impl<'a> Extend<&'a u8> for A {
++ fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
++ unimplemented!()
++ }
++ }
++
++ fn main() {
++ let mut a = A;
++ a.extend(&[]); // vs a.extend([]);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9710 {
++ fn main() {
++ let string = String::new();
++ for _i in 0..10 {
++ f(&string);
++ }
++ }
++
++ fn f<T: AsRef<str>>(_: T) {}
++}
++
++#[allow(dead_code)]
++mod issue_9739 {
++ fn foo<D: std::fmt::Display>(_it: impl IntoIterator<Item = D>) {}
++
++ fn main() {
++ foo(if std::env::var_os("HI").is_some() {
++ &[0]
++ } else {
++ &[] as &[u32]
++ });
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9739_method_variant {
++ struct S;
++
++ impl S {
++ fn foo<D: std::fmt::Display>(&self, _it: impl IntoIterator<Item = D>) {}
++ }
++
++ fn main() {
++ S.foo(if std::env::var_os("HI").is_some() {
++ &[0]
++ } else {
++ &[] as &[u32]
++ });
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782 {
++ fn foo<T: AsRef<[u8]>>(t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ // 100
++ foo::<[u8; 100]>(a);
++ foo(a);
++
++ // 16
++ foo::<&[u8]>(&a);
++ foo(a.as_slice());
++
++ // 8
++ foo::<&[u8; 100]>(&a);
++ foo(a);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782_type_relative_variant {
++ struct S;
++
++ impl S {
++ fn foo<T: AsRef<[u8]>>(t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ S::foo::<&[u8; 100]>(&a);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782_method_variant {
++ struct S;
++
++ impl S {
++ fn foo<T: AsRef<[u8]>>(&self, t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ S.foo::<&[u8; 100]>(&a);
++ }
++}
--- /dev/null
+// run-rustfix
+#![feature(custom_inner_attributes, lint_reasons)]
+
+#[warn(clippy::all, clippy::needless_borrow)]
+#[allow(unused_variables)]
+#[allow(
+ clippy::uninlined_format_args,
+ clippy::unnecessary_mut_passed,
+ clippy::unnecessary_to_owned
+)]
+fn main() {
+ let a = 5;
+ let ref_a = &a;
+ let _ = x(&a); // no warning
+ let _ = x(&&a); // warn
+
+ let mut b = 5;
+ mut_ref(&mut b); // no warning
+ mut_ref(&mut &mut b); // warn
+
+ let s = &String::from("hi");
+ let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not
+ let g_val = g(&Vec::new()); // should not error, because `&Vec<T>` derefs to `&[T]`
+ let vec = Vec::new();
+ let vec_val = g(&vec); // should not error, because `&Vec<T>` derefs to `&[T]`
+ h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait`
+ let garbl = match 42 {
+ 44 => &a,
+ 45 => {
+ println!("foo");
+ &&a
+ },
+ 46 => &&a,
+ 47 => {
+ println!("foo");
+ loop {
+ println!("{}", a);
+ if a == 25 {
+ break &ref_a;
+ }
+ }
+ },
+ _ => panic!(),
+ };
+
+ let _ = x(&&&a);
+ let _ = x(&mut &&a);
+ let _ = x(&&&mut b);
+ let _ = x(&&ref_a);
+ {
+ let b = &mut b;
+ x(&b);
+ }
+
+ // Issue #8191
+ let mut x = 5;
+ let mut x = &mut x;
+
+ mut_ref(&mut x);
+ mut_ref(&mut &mut x);
+ let y: &mut i32 = &mut x;
+ let y: &mut i32 = &mut &mut x;
+
+ let y = match 0 {
+ // Don't lint. Removing the borrow would move 'x'
+ 0 => &mut x,
+ _ => &mut *x,
+ };
+ let y: &mut i32 = match 0 {
+ // Lint here. The type given above triggers auto-borrow.
+ 0 => &mut x,
+ _ => &mut *x,
+ };
+ fn ref_mut_i32(_: &mut i32) {}
+ ref_mut_i32(match 0 {
+ // Lint here. The type given above triggers auto-borrow.
+ 0 => &mut x,
+ _ => &mut *x,
+ });
+ // use 'x' after to make sure it's still usable in the fixed code.
+ *x = 5;
+
+ let s = String::new();
+ // let _ = (&s).len();
+ // let _ = (&s).capacity();
+ // let _ = (&&s).capacity();
+
+ let x = (1, 2);
+ let _ = (&x).0;
+ let x = &x as *const (i32, i32);
+ let _ = unsafe { (&*x).0 };
+
+ // Issue #8367
+ trait Foo {
+ fn foo(self);
+ }
+ impl Foo for &'_ () {
+ fn foo(self) {}
+ }
+ (&()).foo(); // Don't lint. `()` doesn't implement `Foo`
+ (&&()).foo();
+
+ impl Foo for i32 {
+ fn foo(self) {}
+ }
+ impl Foo for &'_ i32 {
+ fn foo(self) {}
+ }
+ (&5).foo(); // Don't lint. `5` will call `<i32 as Foo>::foo`
+ (&&5).foo();
+
+ trait FooRef {
+ fn foo_ref(&self);
+ }
+ impl FooRef for () {
+ fn foo_ref(&self) {}
+ }
+ impl FooRef for &'_ () {
+ fn foo_ref(&self) {}
+ }
+ (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref`
+
+ struct S;
+ impl From<S> for u32 {
+ fn from(s: S) -> Self {
+ (&s).into()
+ }
+ }
+ impl From<&S> for u32 {
+ fn from(s: &S) -> Self {
+ 0
+ }
+ }
+
+ let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ let _ = std::path::Path::new(".").join(&&".");
+ deref_target_is_x(&X);
+ multiple_constraints(&[[""]]);
+ multiple_constraints_normalizes_to_same(&X, X);
+ let _ = Some("").unwrap_or(&"");
+ let _ = std::fs::write("x", &"".to_string());
+
+ only_sized(&""); // Don't lint. `Sized` is only bound
+ let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
+ let _ = Box::new(&""); // Don't lint. Type parameter appears in return type
+ ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter
+ refs_only(&()); // Don't lint. `&T` implements trait, but `T` doesn't
+ multiple_constraints_normalizes_to_different(&[[""]], &[""]); // Don't lint. Projected type appears in arguments
+}
+
+#[allow(clippy::needless_borrowed_reference)]
+fn x(y: &i32) -> i32 {
+ *y
+}
+
+fn mut_ref(y: &mut i32) {
+ *y = 5;
+}
+
+fn f<T: Copy>(y: &T) -> T {
+ *y
+}
+
+fn g(y: &[u8]) -> u8 {
+ y[0]
+}
+
+trait Trait {}
+
+impl<'a> Trait for &'a str {}
+
+fn h(_: &dyn Trait) {}
+
+#[allow(dead_code)]
+fn check_expect_suppression() {
+ let a = 5;
+ #[expect(clippy::needless_borrow)]
+ let _ = x(&&a);
+}
+
+#[allow(dead_code)]
+mod issue9160 {
+ pub struct S<F> {
+ f: F,
+ }
+
+ impl<T, F> S<F>
+ where
+ F: Fn() -> T,
+ {
+ fn calls_field(&self) -> T {
+ (&self.f)()
+ }
+ }
+
+ impl<T, F> S<F>
+ where
+ F: FnMut() -> T,
+ {
+ fn calls_mut_field(&mut self) -> T {
+ (&mut self.f)()
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+struct X;
+
+impl std::ops::Deref for X {
+ type Target = X;
+ fn deref(&self) -> &Self::Target {
+ self
+ }
+}
+
+fn deref_target_is_x<T>(_: T)
+where
+ T: std::ops::Deref<Target = X>,
+{
+}
+
+fn multiple_constraints<T, U, V, X, Y>(_: T)
+where
+ T: IntoIterator<Item = U> + IntoIterator<Item = X>,
+ U: IntoIterator<Item = V>,
+ V: AsRef<str>,
+ X: IntoIterator<Item = Y>,
+ Y: AsRef<std::ffi::OsStr>,
+{
+}
+
+fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V)
+where
+ T: std::ops::Deref<Target = U>,
+ U: std::ops::Deref<Target = V>,
+{
+}
+
+fn only_sized<T>(_: T) {}
+
+fn ref_as_ref_path<T: 'static>(_: &'static T)
+where
+ &'static T: AsRef<std::path::Path>,
+{
+}
+
+trait RefsOnly {
+ type Referent;
+}
+
+impl<T> RefsOnly for &T {
+ type Referent = T;
+}
+
+fn refs_only<T, U>(_: T)
+where
+ T: RefsOnly<Referent = U>,
+{
+}
+
+fn multiple_constraints_normalizes_to_different<T, U, V>(_: T, _: U)
+where
+ T: IntoIterator<Item = U>,
+ U: IntoIterator<Item = V>,
+ V: AsRef<str>,
+{
+}
+
+// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
+#[allow(dead_code)]
+mod copyable_iterator {
+ #[derive(Clone, Copy)]
+ struct Iter;
+ impl Iterator for Iter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+ }
+ fn takes_iter(_: impl Iterator) {}
+ fn dont_warn(mut x: Iter) {
+ takes_iter(&mut x);
+ }
+ #[allow(unused_mut)]
+ fn warn(mut x: &mut Iter) {
+ takes_iter(&mut x)
+ }
+}
+
+mod under_msrv {
+ #![allow(dead_code)]
+ #![clippy::msrv = "1.52.0"]
+
+ fn foo() {
+ let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ }
+}
+
+mod meets_msrv {
+ #![allow(dead_code)]
+ #![clippy::msrv = "1.53.0"]
+
+ fn foo() {
+ let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ }
+}
+
+#[allow(unused)]
+fn issue9383() {
+ // Should not lint because unions need explicit deref when accessing field
+ use std::mem::ManuallyDrop;
+
+ union Coral {
+ crab: ManuallyDrop<Vec<i32>>,
+ }
+
+ union Ocean {
+ coral: ManuallyDrop<Coral>,
+ }
+
+ let mut ocean = Ocean {
+ coral: ManuallyDrop::new(Coral {
+ crab: ManuallyDrop::new(vec![1, 2, 3]),
+ }),
+ };
+
+ unsafe {
+ ManuallyDrop::drop(&mut (&mut ocean.coral).crab);
+
+ (*ocean.coral).crab = ManuallyDrop::new(vec![4, 5, 6]);
+ ManuallyDrop::drop(&mut (*ocean.coral).crab);
+
+ ManuallyDrop::drop(&mut ocean.coral);
+ }
+}
+
+#[allow(dead_code)]
+fn closure_test() {
+ let env = "env".to_owned();
+ let arg = "arg".to_owned();
+ let f = |arg| {
+ let loc = "loc".to_owned();
+ let _ = std::fs::write("x", &env); // Don't lint. In environment
+ let _ = std::fs::write("x", &arg);
+ let _ = std::fs::write("x", &loc);
+ };
+ let _ = std::fs::write("x", &env); // Don't lint. Borrowed by `f`
+ f(arg);
+}
+
+#[allow(dead_code)]
+mod significant_drop {
+ #[derive(Debug)]
+ struct X;
+
+ #[derive(Debug)]
+ struct Y;
+
+ impl Drop for Y {
+ fn drop(&mut self) {}
+ }
+
+ fn foo(x: X, y: Y) {
+ debug(&x);
+ debug(&y); // Don't lint. Has significant drop
+ }
+
+ fn debug(_: impl std::fmt::Debug) {}
+}
+
+#[allow(dead_code)]
+mod used_exactly_once {
+ fn foo(x: String) {
+ use_x(&x);
+ }
+ fn use_x(_: impl AsRef<str>) {}
+}
+
+#[allow(dead_code)]
+mod used_more_than_once {
+ fn foo(x: String) {
+ use_x(&x);
+ use_x_again(&x);
+ }
+ fn use_x(_: impl AsRef<str>) {}
+ fn use_x_again(_: impl AsRef<str>) {}
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
++#[allow(dead_code)]
++mod issue_9111 {
++ struct A;
++
++ impl Extend<u8> for A {
++ fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
++ unimplemented!()
++ }
++ }
++
++ impl<'a> Extend<&'a u8> for A {
++ fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
++ unimplemented!()
++ }
++ }
++
++ fn main() {
++ let mut a = A;
++ a.extend(&[]); // vs a.extend([]);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9710 {
++ fn main() {
++ let string = String::new();
++ for _i in 0..10 {
++ f(&string);
++ }
++ }
++
++ fn f<T: AsRef<str>>(_: T) {}
++}
++
++#[allow(dead_code)]
++mod issue_9739 {
++ fn foo<D: std::fmt::Display>(_it: impl IntoIterator<Item = D>) {}
++
++ fn main() {
++ foo(if std::env::var_os("HI").is_some() {
++ &[0]
++ } else {
++ &[] as &[u32]
++ });
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9739_method_variant {
++ struct S;
++
++ impl S {
++ fn foo<D: std::fmt::Display>(&self, _it: impl IntoIterator<Item = D>) {}
++ }
++
++ fn main() {
++ S.foo(if std::env::var_os("HI").is_some() {
++ &[0]
++ } else {
++ &[] as &[u32]
++ });
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782 {
++ fn foo<T: AsRef<[u8]>>(t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ // 100
++ foo::<[u8; 100]>(a);
++ foo(a);
++
++ // 16
++ foo::<&[u8]>(&a);
++ foo(a.as_slice());
++
++ // 8
++ foo::<&[u8; 100]>(&a);
++ foo(&a);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782_type_relative_variant {
++ struct S;
++
++ impl S {
++ fn foo<T: AsRef<[u8]>>(t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ S::foo::<&[u8; 100]>(&a);
++ }
++}
++
++#[allow(dead_code)]
++mod issue_9782_method_variant {
++ struct S;
++
++ impl S {
++ fn foo<T: AsRef<[u8]>>(&self, t: T) {
++ println!("{}", std::mem::size_of::<T>());
++ let _t: &[u8] = t.as_ref();
++ }
++ }
++
++ fn main() {
++ let a: [u8; 100] = [0u8; 100];
++
++ S.foo::<&[u8; 100]>(&a);
++ }
++}
--- /dev/null
- error: aborting due to 35 previous errors
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:15:15
+ |
+LL | let _ = x(&&a); // warn
+ | ^^^ help: change this to: `&a`
+ |
+ = note: `-D clippy::needless-borrow` implied by `-D warnings`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:19:13
+ |
+LL | mut_ref(&mut &mut b); // warn
+ | ^^^^^^^^^^^ help: change this to: `&mut b`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:31:13
+ |
+LL | &&a
+ | ^^^ help: change this to: `&a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:33:15
+ |
+LL | 46 => &&a,
+ | ^^^ help: change this to: `&a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:39:27
+ |
+LL | break &ref_a;
+ | ^^^^^^ help: change this to: `ref_a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:46:15
+ |
+LL | let _ = x(&&&a);
+ | ^^^^ help: change this to: `&a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:47:15
+ |
+LL | let _ = x(&mut &&a);
+ | ^^^^^^^^ help: change this to: `&a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:48:15
+ |
+LL | let _ = x(&&&mut b);
+ | ^^^^^^^^ help: change this to: `&mut b`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:49:15
+ |
+LL | let _ = x(&&ref_a);
+ | ^^^^^^^ help: change this to: `ref_a`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:52:11
+ |
+LL | x(&b);
+ | ^^ help: change this to: `b`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:59:13
+ |
+LL | mut_ref(&mut x);
+ | ^^^^^^ help: change this to: `x`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:60:13
+ |
+LL | mut_ref(&mut &mut x);
+ | ^^^^^^^^^^^ help: change this to: `x`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:61:23
+ |
+LL | let y: &mut i32 = &mut x;
+ | ^^^^^^ help: change this to: `x`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:62:23
+ |
+LL | let y: &mut i32 = &mut &mut x;
+ | ^^^^^^^^^^^ help: change this to: `x`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:71:14
+ |
+LL | 0 => &mut x,
+ | ^^^^^^ help: change this to: `x`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:77:14
+ |
+LL | 0 => &mut x,
+ | ^^^^^^ help: change this to: `x`
+
+error: this expression borrows a value the compiler would automatically borrow
+ --> $DIR/needless_borrow.rs:89:13
+ |
+LL | let _ = (&x).0;
+ | ^^^^ help: change this to: `x`
+
+error: this expression borrows a value the compiler would automatically borrow
+ --> $DIR/needless_borrow.rs:91:22
+ |
+LL | let _ = unsafe { (&*x).0 };
+ | ^^^^^ help: change this to: `(*x)`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:101:5
+ |
+LL | (&&()).foo();
+ | ^^^^^^ help: change this to: `(&())`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:110:5
+ |
+LL | (&&5).foo();
+ | ^^^^^ help: change this to: `(&5)`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:135:51
+ |
+LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:136:44
+ |
+LL | let _ = std::path::Path::new(".").join(&&".");
+ | ^^^^^ help: change this to: `"."`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:137:23
+ |
+LL | deref_target_is_x(&X);
+ | ^^ help: change this to: `X`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:138:26
+ |
+LL | multiple_constraints(&[[""]]);
+ | ^^^^^^^ help: change this to: `[[""]]`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:139:45
+ |
+LL | multiple_constraints_normalizes_to_same(&X, X);
+ | ^^ help: change this to: `X`
+
+error: this expression creates a reference which is immediately dereferenced by the compiler
+ --> $DIR/needless_borrow.rs:140:32
+ |
+LL | let _ = Some("").unwrap_or(&"");
+ | ^^^ help: change this to: `""`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:141:33
+ |
+LL | let _ = std::fs::write("x", &"".to_string());
+ | ^^^^^^^^^^^^^^^ help: change this to: `"".to_string()`
+
+error: this expression borrows a value the compiler would automatically borrow
+ --> $DIR/needless_borrow.rs:192:13
+ |
+LL | (&self.f)()
+ | ^^^^^^^^^ help: change this to: `(self.f)`
+
+error: this expression borrows a value the compiler would automatically borrow
+ --> $DIR/needless_borrow.rs:201:13
+ |
+LL | (&mut self.f)()
+ | ^^^^^^^^^^^^^ help: change this to: `(self.f)`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:286:20
+ |
+LL | takes_iter(&mut x)
+ | ^^^^^^ help: change this to: `x`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:304:55
+ |
+LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
+ | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:344:37
+ |
+LL | let _ = std::fs::write("x", &arg);
+ | ^^^^ help: change this to: `arg`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:345:37
+ |
+LL | let _ = std::fs::write("x", &loc);
+ | ^^^^ help: change this to: `loc`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:364:15
+ |
+LL | debug(&x);
+ | ^^ help: change this to: `x`
+
+error: the borrowed expression implements the required traits
+ --> $DIR/needless_borrow.rs:374:15
+ |
+LL | use_x(&x);
+ | ^^ help: change this to: `x`
+
++error: the borrowed expression implements the required traits
++ --> $DIR/needless_borrow.rs:474:13
++ |
++LL | foo(&a);
++ | ^^ help: change this to: `a`
++
++error: aborting due to 36 previous errors
+
--- /dev/null
- #![allow(unused, clippy::needless_borrow)]
+// run-rustfix
+
+#![warn(clippy::needless_borrowed_reference)]
- fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++#![allow(
++ unused,
++ irrefutable_let_patterns,
++ non_shorthand_field_patterns,
++ clippy::needless_borrow
++)]
+
+fn main() {}
+
- fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++struct Struct {
++ a: usize,
++ b: usize,
++ c: usize,
++}
++
++struct TupleStruct(u8, u8, u8);
++
++fn should_lint(
++ array: [u8; 4],
++ slice: &[u8],
++ slice_of_refs: &[&u8],
++ vec: Vec<u8>,
++ tuple: (u8, u8, u8),
++ tuple_struct: TupleStruct,
++ s: Struct,
++) {
+ let mut v = Vec::<String>::new();
+ let _ = v.iter_mut().filter(|a| a.is_empty());
+
+ let var = 3;
+ let thingy = Some(&var);
+ if let Some(v) = thingy {}
+
+ if let &[a, ref b] = slice_of_refs {}
+
+ let [a, ..] = &array;
+ let [a, b, ..] = &array;
+
+ if let [a, b] = slice {}
+ if let [a, b] = &vec[..] {}
+
+ if let [a, b, ..] = slice {}
+ if let [a, .., b] = slice {}
+ if let [.., a, b] = slice {}
++
++ if let [a, _] = slice {}
++
++ if let (a, b, c) = &tuple {}
++ if let (a, _, c) = &tuple {}
++ if let (a, ..) = &tuple {}
++
++ if let TupleStruct(a, ..) = &tuple_struct {}
++
++ if let Struct {
++ a,
++ b: b,
++ c: renamed,
++ } = &s
++ {}
++
++ if let Struct { a, b: _, .. } = &s {}
+}
+
- (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
++fn should_not_lint(
++ array: [u8; 4],
++ slice: &[u8],
++ slice_of_refs: &[&u8],
++ vec: Vec<u8>,
++ tuple: (u8, u8, u8),
++ tuple_struct: TupleStruct,
++ s: Struct,
++) {
+ if let [ref a] = slice {}
+ if let &[ref a, b] = slice {}
+ if let &[ref a, .., b] = slice {}
+
++ if let &(ref a, b, ..) = &tuple {}
++ if let &TupleStruct(ref a, b, ..) = &tuple_struct {}
++ if let &Struct { ref a, b, .. } = &s {}
++
+ // must not be removed as variables must be bound consistently across | patterns
+ if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
+
++ // the `&`s here technically could be removed, but it'd be noisy and without a `ref` doesn't match
++ // the lint name
++ if let &[] = slice {}
++ if let &[_] = slice {}
++ if let &[..] = slice {}
++ if let &(..) = &tuple {}
++ if let &TupleStruct(..) = &tuple_struct {}
++ if let &Struct { .. } = &s {}
++
+ let mut var2 = 5;
+ let thingy2 = Some(&mut var2);
+ if let Some(&mut ref mut v) = thingy2 {
+ // ^ should **not** be linted
+ // v is borrowed as mutable.
+ *v = 10;
+ }
+ if let Some(&mut ref v) = thingy2 {
+ // ^ should **not** be linted
+ // here, v is borrowed as immutable.
+ // can't do that:
+ //*v = 15;
+ }
+}
+
+enum Animal {
+ Cat(u64),
+ Dog(u64),
+}
+
+fn foo(a: &Animal, b: &Animal) {
+ match (a, b) {
+ // lifetime mismatch error if there is no '&ref' before `feature(nll)` stabilization in 1.63
+ (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (),
+ // ^ and ^ should **not** be linted
++ (Animal::Dog(a), &Animal::Dog(_)) => (),
+ }
+}
--- /dev/null
- #![allow(unused, clippy::needless_borrow)]
+// run-rustfix
+
+#![warn(clippy::needless_borrowed_reference)]
- fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++#![allow(
++ unused,
++ irrefutable_let_patterns,
++ non_shorthand_field_patterns,
++ clippy::needless_borrow
++)]
+
+fn main() {}
+
- fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++struct Struct {
++ a: usize,
++ b: usize,
++ c: usize,
++}
++
++struct TupleStruct(u8, u8, u8);
++
++fn should_lint(
++ array: [u8; 4],
++ slice: &[u8],
++ slice_of_refs: &[&u8],
++ vec: Vec<u8>,
++ tuple: (u8, u8, u8),
++ tuple_struct: TupleStruct,
++ s: Struct,
++) {
+ let mut v = Vec::<String>::new();
+ let _ = v.iter_mut().filter(|&ref a| a.is_empty());
+
+ let var = 3;
+ let thingy = Some(&var);
+ if let Some(&ref v) = thingy {}
+
+ if let &[&ref a, ref b] = slice_of_refs {}
+
+ let &[ref a, ..] = &array;
+ let &[ref a, ref b, ..] = &array;
+
+ if let &[ref a, ref b] = slice {}
+ if let &[ref a, ref b] = &vec[..] {}
+
+ if let &[ref a, ref b, ..] = slice {}
+ if let &[ref a, .., ref b] = slice {}
+ if let &[.., ref a, ref b] = slice {}
++
++ if let &[ref a, _] = slice {}
++
++ if let &(ref a, ref b, ref c) = &tuple {}
++ if let &(ref a, _, ref c) = &tuple {}
++ if let &(ref a, ..) = &tuple {}
++
++ if let &TupleStruct(ref a, ..) = &tuple_struct {}
++
++ if let &Struct {
++ ref a,
++ b: ref b,
++ c: ref renamed,
++ } = &s
++ {}
++
++ if let &Struct { ref a, b: _, .. } = &s {}
+}
+
- (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
++fn should_not_lint(
++ array: [u8; 4],
++ slice: &[u8],
++ slice_of_refs: &[&u8],
++ vec: Vec<u8>,
++ tuple: (u8, u8, u8),
++ tuple_struct: TupleStruct,
++ s: Struct,
++) {
+ if let [ref a] = slice {}
+ if let &[ref a, b] = slice {}
+ if let &[ref a, .., b] = slice {}
+
++ if let &(ref a, b, ..) = &tuple {}
++ if let &TupleStruct(ref a, b, ..) = &tuple_struct {}
++ if let &Struct { ref a, b, .. } = &s {}
++
+ // must not be removed as variables must be bound consistently across | patterns
+ if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
+
++ // the `&`s here technically could be removed, but it'd be noisy and without a `ref` doesn't match
++ // the lint name
++ if let &[] = slice {}
++ if let &[_] = slice {}
++ if let &[..] = slice {}
++ if let &(..) = &tuple {}
++ if let &TupleStruct(..) = &tuple_struct {}
++ if let &Struct { .. } = &s {}
++
+ let mut var2 = 5;
+ let thingy2 = Some(&mut var2);
+ if let Some(&mut ref mut v) = thingy2 {
+ // ^ should **not** be linted
+ // v is borrowed as mutable.
+ *v = 10;
+ }
+ if let Some(&mut ref v) = thingy2 {
+ // ^ should **not** be linted
+ // here, v is borrowed as immutable.
+ // can't do that:
+ //*v = 15;
+ }
+}
+
+enum Animal {
+ Cat(u64),
+ Dog(u64),
+}
+
+fn foo(a: &Animal, b: &Animal) {
+ match (a, b) {
+ // lifetime mismatch error if there is no '&ref' before `feature(nll)` stabilization in 1.63
+ (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (),
+ // ^ and ^ should **not** be linted
++ (Animal::Dog(a), &Animal::Dog(_)) => (),
+ }
+}
--- /dev/null
- --> $DIR/needless_borrowed_ref.rs:10:34
+error: this pattern takes a reference on something that is being dereferenced
- --> $DIR/needless_borrowed_ref.rs:14:17
++ --> $DIR/needless_borrowed_ref.rs:31:34
+ |
+LL | let _ = v.iter_mut().filter(|&ref a| a.is_empty());
+ | ^^^^^^
+ |
+ = note: `-D clippy::needless-borrowed-reference` implied by `-D warnings`
+help: try removing the `&ref` part
+ |
+LL - let _ = v.iter_mut().filter(|&ref a| a.is_empty());
+LL + let _ = v.iter_mut().filter(|a| a.is_empty());
+ |
+
+error: this pattern takes a reference on something that is being dereferenced
- --> $DIR/needless_borrowed_ref.rs:16:14
++ --> $DIR/needless_borrowed_ref.rs:35:17
+ |
+LL | if let Some(&ref v) = thingy {}
+ | ^^^^^^
+ |
+help: try removing the `&ref` part
+ |
+LL - if let Some(&ref v) = thingy {}
+LL + if let Some(v) = thingy {}
+ |
+
+error: this pattern takes a reference on something that is being dereferenced
- --> $DIR/needless_borrowed_ref.rs:18:9
++ --> $DIR/needless_borrowed_ref.rs:37:14
+ |
+LL | if let &[&ref a, ref b] = slice_of_refs {}
+ | ^^^^^^
+ |
+help: try removing the `&ref` part
+ |
+LL - if let &[&ref a, ref b] = slice_of_refs {}
+LL + if let &[a, ref b] = slice_of_refs {}
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:19:9
++ --> $DIR/needless_borrowed_ref.rs:39:9
+ |
+LL | let &[ref a, ..] = &array;
+ | ^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - let &[ref a, ..] = &array;
+LL + let [a, ..] = &array;
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:21:12
++ --> $DIR/needless_borrowed_ref.rs:40:9
+ |
+LL | let &[ref a, ref b, ..] = &array;
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - let &[ref a, ref b, ..] = &array;
+LL + let [a, b, ..] = &array;
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:22:12
++ --> $DIR/needless_borrowed_ref.rs:42:12
+ |
+LL | if let &[ref a, ref b] = slice {}
+ | ^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - if let &[ref a, ref b] = slice {}
+LL + if let [a, b] = slice {}
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:24:12
++ --> $DIR/needless_borrowed_ref.rs:43:12
+ |
+LL | if let &[ref a, ref b] = &vec[..] {}
+ | ^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - if let &[ref a, ref b] = &vec[..] {}
+LL + if let [a, b] = &vec[..] {}
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:25:12
++ --> $DIR/needless_borrowed_ref.rs:45:12
+ |
+LL | if let &[ref a, ref b, ..] = slice {}
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - if let &[ref a, ref b, ..] = slice {}
+LL + if let [a, b, ..] = slice {}
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- --> $DIR/needless_borrowed_ref.rs:26:12
++ --> $DIR/needless_borrowed_ref.rs:46:12
+ |
+LL | if let &[ref a, .., ref b] = slice {}
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - if let &[ref a, .., ref b] = slice {}
+LL + if let [a, .., b] = slice {}
+ |
+
+error: dereferencing a slice pattern where every element takes a reference
- error: aborting due to 10 previous errors
++ --> $DIR/needless_borrowed_ref.rs:47:12
+ |
+LL | if let &[.., ref a, ref b] = slice {}
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: try removing the `&` and `ref` parts
+ |
+LL - if let &[.., ref a, ref b] = slice {}
+LL + if let [.., a, b] = slice {}
+ |
+
++error: dereferencing a slice pattern where every element takes a reference
++ --> $DIR/needless_borrowed_ref.rs:49:12
++ |
++LL | if let &[ref a, _] = slice {}
++ | ^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &[ref a, _] = slice {}
++LL + if let [a, _] = slice {}
++ |
++
++error: dereferencing a tuple pattern where every element takes a reference
++ --> $DIR/needless_borrowed_ref.rs:51:12
++ |
++LL | if let &(ref a, ref b, ref c) = &tuple {}
++ | ^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &(ref a, ref b, ref c) = &tuple {}
++LL + if let (a, b, c) = &tuple {}
++ |
++
++error: dereferencing a tuple pattern where every element takes a reference
++ --> $DIR/needless_borrowed_ref.rs:52:12
++ |
++LL | if let &(ref a, _, ref c) = &tuple {}
++ | ^^^^^^^^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &(ref a, _, ref c) = &tuple {}
++LL + if let (a, _, c) = &tuple {}
++ |
++
++error: dereferencing a tuple pattern where every element takes a reference
++ --> $DIR/needless_borrowed_ref.rs:53:12
++ |
++LL | if let &(ref a, ..) = &tuple {}
++ | ^^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &(ref a, ..) = &tuple {}
++LL + if let (a, ..) = &tuple {}
++ |
++
++error: dereferencing a tuple pattern where every element takes a reference
++ --> $DIR/needless_borrowed_ref.rs:55:12
++ |
++LL | if let &TupleStruct(ref a, ..) = &tuple_struct {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &TupleStruct(ref a, ..) = &tuple_struct {}
++LL + if let TupleStruct(a, ..) = &tuple_struct {}
++ |
++
++error: dereferencing a struct pattern where every field's pattern takes a reference
++ --> $DIR/needless_borrowed_ref.rs:57:12
++ |
++LL | if let &Struct {
++ | ____________^
++LL | | ref a,
++LL | | b: ref b,
++LL | | c: ref renamed,
++LL | | } = &s
++ | |_____^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL ~ if let Struct {
++LL ~ a,
++LL ~ b: b,
++LL ~ c: renamed,
++ |
++
++error: dereferencing a struct pattern where every field's pattern takes a reference
++ --> $DIR/needless_borrowed_ref.rs:64:12
++ |
++LL | if let &Struct { ref a, b: _, .. } = &s {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try removing the `&` and `ref` parts
++ |
++LL - if let &Struct { ref a, b: _, .. } = &s {}
++LL + if let Struct { a, b: _, .. } = &s {}
++ |
++
++error: aborting due to 17 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![allow(unused, clippy::suspicious_map, clippy::iter_count)]
+
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList};
+
+#[warn(clippy::needless_collect)]
+#[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)]
+fn main() {
+ let sample = [1; 5];
+ let len = sample.iter().count();
+ if sample.iter().next().is_none() {
+ // Empty
+ }
+ sample.iter().cloned().any(|x| x == 1);
+ // #7164 HashMap's and BTreeMap's `len` usage should not be linted
+ sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
+ sample.iter().map(|x| (x, x)).collect::<BTreeMap<_, _>>().len();
+
+ sample.iter().map(|x| (x, x)).next().is_none();
+ sample.iter().map(|x| (x, x)).next().is_none();
+
+ // Notice the `HashSet`--this should not be linted
+ sample.iter().collect::<HashSet<_>>().len();
+ // Neither should this
+ sample.iter().collect::<BTreeSet<_>>().len();
+
+ sample.iter().count();
+ sample.iter().next().is_none();
+ sample.iter().cloned().any(|x| x == 1);
+ sample.iter().any(|x| x == &1);
+
+ // `BinaryHeap` doesn't have `contains` method
+ sample.iter().count();
+ sample.iter().next().is_none();
++
++ // Don't lint string from str
++ let _ = ["", ""].into_iter().collect::<String>().is_empty();
++
++ let _ = sample.iter().next().is_none();
++ let _ = sample.iter().any(|x| x == &0);
++
++ struct VecWrapper<T>(Vec<T>);
++ impl<T> core::ops::Deref for VecWrapper<T> {
++ type Target = Vec<T>;
++ fn deref(&self) -> &Self::Target {
++ &self.0
++ }
++ }
++ impl<T> IntoIterator for VecWrapper<T> {
++ type IntoIter = <Vec<T> as IntoIterator>::IntoIter;
++ type Item = <Vec<T> as IntoIterator>::Item;
++ fn into_iter(self) -> Self::IntoIter {
++ self.0.into_iter()
++ }
++ }
++ impl<T> FromIterator<T> for VecWrapper<T> {
++ fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
++ Self(Vec::from_iter(iter))
++ }
++ }
++
++ let _ = sample.iter().next().is_none();
++ let _ = sample.iter().any(|x| x == &0);
+}
--- /dev/null
+// run-rustfix
+
+#![allow(unused, clippy::suspicious_map, clippy::iter_count)]
+
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList};
+
+#[warn(clippy::needless_collect)]
+#[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)]
+fn main() {
+ let sample = [1; 5];
+ let len = sample.iter().collect::<Vec<_>>().len();
+ if sample.iter().collect::<Vec<_>>().is_empty() {
+ // Empty
+ }
+ sample.iter().cloned().collect::<Vec<_>>().contains(&1);
+ // #7164 HashMap's and BTreeMap's `len` usage should not be linted
+ sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
+ sample.iter().map(|x| (x, x)).collect::<BTreeMap<_, _>>().len();
+
+ sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().is_empty();
+ sample.iter().map(|x| (x, x)).collect::<BTreeMap<_, _>>().is_empty();
+
+ // Notice the `HashSet`--this should not be linted
+ sample.iter().collect::<HashSet<_>>().len();
+ // Neither should this
+ sample.iter().collect::<BTreeSet<_>>().len();
+
+ sample.iter().collect::<LinkedList<_>>().len();
+ sample.iter().collect::<LinkedList<_>>().is_empty();
+ sample.iter().cloned().collect::<LinkedList<_>>().contains(&1);
+ sample.iter().collect::<LinkedList<_>>().contains(&&1);
+
+ // `BinaryHeap` doesn't have `contains` method
+ sample.iter().collect::<BinaryHeap<_>>().len();
+ sample.iter().collect::<BinaryHeap<_>>().is_empty();
++
++ // Don't lint string from str
++ let _ = ["", ""].into_iter().collect::<String>().is_empty();
++
++ let _ = sample.iter().collect::<HashSet<_>>().is_empty();
++ let _ = sample.iter().collect::<HashSet<_>>().contains(&&0);
++
++ struct VecWrapper<T>(Vec<T>);
++ impl<T> core::ops::Deref for VecWrapper<T> {
++ type Target = Vec<T>;
++ fn deref(&self) -> &Self::Target {
++ &self.0
++ }
++ }
++ impl<T> IntoIterator for VecWrapper<T> {
++ type IntoIter = <Vec<T> as IntoIterator>::IntoIter;
++ type Item = <Vec<T> as IntoIterator>::Item;
++ fn into_iter(self) -> Self::IntoIter {
++ self.0.into_iter()
++ }
++ }
++ impl<T> FromIterator<T> for VecWrapper<T> {
++ fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
++ Self(Vec::from_iter(iter))
++ }
++ }
++
++ let _ = sample.iter().collect::<VecWrapper<_>>().is_empty();
++ let _ = sample.iter().collect::<VecWrapper<_>>().contains(&&0);
+}
--- /dev/null
- error: aborting due to 11 previous errors
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:11:29
+ |
+LL | let len = sample.iter().collect::<Vec<_>>().len();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
+ |
+ = note: `-D clippy::needless-collect` implied by `-D warnings`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:12:22
+ |
+LL | if sample.iter().collect::<Vec<_>>().is_empty() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:15:28
+ |
+LL | sample.iter().cloned().collect::<Vec<_>>().contains(&1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:20:35
+ |
+LL | sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().is_empty();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:21:35
+ |
+LL | sample.iter().map(|x| (x, x)).collect::<BTreeMap<_, _>>().is_empty();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:28:19
+ |
+LL | sample.iter().collect::<LinkedList<_>>().len();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:29:19
+ |
+LL | sample.iter().collect::<LinkedList<_>>().is_empty();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:30:28
+ |
+LL | sample.iter().cloned().collect::<LinkedList<_>>().contains(&1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:31:19
+ |
+LL | sample.iter().collect::<LinkedList<_>>().contains(&&1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:34:19
+ |
+LL | sample.iter().collect::<BinaryHeap<_>>().len();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect.rs:35:19
+ |
+LL | sample.iter().collect::<BinaryHeap<_>>().is_empty();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
+
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect.rs:40:27
++ |
++LL | let _ = sample.iter().collect::<HashSet<_>>().is_empty();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect.rs:41:27
++ |
++LL | let _ = sample.iter().collect::<HashSet<_>>().contains(&&0);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)`
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect.rs:63:27
++ |
++LL | let _ = sample.iter().collect::<VecWrapper<_>>().is_empty();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()`
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect.rs:64:27
++ |
++LL | let _ = sample.iter().collect::<VecWrapper<_>>().contains(&&0);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)`
++
++error: aborting due to 15 previous errors
+
--- /dev/null
+#![allow(clippy::uninlined_format_args)]
++#![warn(clippy::needless_collect)]
+
+use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
+
+fn main() {
+ let sample = [1; 5];
+ let indirect_iter = sample.iter().collect::<Vec<_>>();
+ indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ let indirect_len = sample.iter().collect::<VecDeque<_>>();
+ indirect_len.len();
+ let indirect_empty = sample.iter().collect::<VecDeque<_>>();
+ indirect_empty.is_empty();
+ let indirect_contains = sample.iter().collect::<VecDeque<_>>();
+ indirect_contains.contains(&&5);
+ let indirect_negative = sample.iter().collect::<Vec<_>>();
+ indirect_negative.len();
+ indirect_negative
+ .into_iter()
+ .map(|x| (*x, *x + 1))
+ .collect::<HashMap<_, _>>();
+
+ // #6202
+ let a = "a".to_string();
+ let sample = vec![a.clone(), "b".to_string(), "c".to_string()];
+ let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
+ non_copy_contains.contains(&a);
+
+ // Fix #5991
+ let vec_a = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ let vec_b = vec_a.iter().collect::<Vec<_>>();
+ if vec_b.len() > 3 {}
+ let other_vec = vec![1, 3, 12, 4, 16, 2];
+ let we_got_the_same_numbers = other_vec.iter().filter(|item| vec_b.contains(item)).collect::<Vec<_>>();
+
+ // Fix #6297
+ let sample = [1; 5];
+ let multiple_indirect = sample.iter().collect::<Vec<_>>();
+ let sample2 = vec![2, 3];
+ if multiple_indirect.is_empty() {
+ // do something
+ } else {
+ let found = sample2
+ .iter()
+ .filter(|i| multiple_indirect.iter().any(|s| **s % **i == 0))
+ .collect::<Vec<_>>();
+ }
+}
+
+mod issue7110 {
+ // #7110 - lint for type annotation cases
+ use super::*;
+
+ fn lint_vec(string: &str) -> usize {
+ let buffer: Vec<&str> = string.split('/').collect();
+ buffer.len()
+ }
+ fn lint_vec_deque() -> usize {
+ let sample = [1; 5];
+ let indirect_len: VecDeque<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn lint_linked_list() -> usize {
+ let sample = [1; 5];
+ let indirect_len: LinkedList<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn lint_binary_heap() -> usize {
+ let sample = [1; 5];
+ let indirect_len: BinaryHeap<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn dont_lint(string: &str) -> usize {
+ let buffer: Vec<&str> = string.split('/').collect();
+ for buff in &buffer {
+ println!("{}", buff);
+ }
+ buffer.len()
+ }
+}
+
+mod issue7975 {
+ use super::*;
+
+ fn direct_mapping_with_used_mutable_reference() -> Vec<()> {
+ let test_vec: Vec<()> = vec![];
+ let mut vec_2: Vec<()> = vec![];
+ let mut_ref = &mut vec_2;
+ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
+ collected_vec.into_iter().map(|_| mut_ref.push(())).collect()
+ }
+
+ fn indirectly_mapping_with_used_mutable_reference() -> Vec<()> {
+ let test_vec: Vec<()> = vec![];
+ let mut vec_2: Vec<()> = vec![];
+ let mut_ref = &mut vec_2;
+ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
+ let iter = collected_vec.into_iter();
+ iter.map(|_| mut_ref.push(())).collect()
+ }
+
+ fn indirect_collect_after_indirect_mapping_with_used_mutable_reference() -> Vec<()> {
+ let test_vec: Vec<()> = vec![];
+ let mut vec_2: Vec<()> = vec![];
+ let mut_ref = &mut vec_2;
+ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
+ let iter = collected_vec.into_iter();
+ let mapped_iter = iter.map(|_| mut_ref.push(()));
+ mapped_iter.collect()
+ }
+}
+
+fn allow_test() {
+ #[allow(clippy::needless_collect)]
+ let v = [1].iter().collect::<Vec<_>>();
+ v.into_iter().collect::<HashSet<_>>();
+}
+
+mod issue_8553 {
+ fn test_for() {
+ let vec = vec![1, 2];
+ let w: Vec<usize> = vec.iter().map(|i| i * i).collect();
+
+ for i in 0..2 {
+ // Do not lint, because this method call is in the loop
+ w.contains(&i);
+ }
+
+ for i in 0..2 {
+ let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ // Do lint
+ y.contains(&i);
+ for j in 0..2 {
+ // Do not lint, because this method call is in the loop
+ z.contains(&j);
+ }
+ }
+
+ // Do not lint, because this variable is used.
+ w.contains(&0);
+ }
+
+ fn test_while() {
+ let vec = vec![1, 2];
+ let x: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut n = 0;
+ while n > 1 {
+ // Do not lint, because this method call is in the loop
+ x.contains(&n);
+ n += 1;
+ }
+
+ while n > 2 {
+ let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ // Do lint
+ y.contains(&n);
+ n += 1;
+ while n > 4 {
+ // Do not lint, because this method call is in the loop
+ z.contains(&n);
+ n += 1;
+ }
+ }
+ }
+
+ fn test_loop() {
+ let vec = vec![1, 2];
+ let x: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut n = 0;
+ loop {
+ if n < 1 {
+ // Do not lint, because this method call is in the loop
+ x.contains(&n);
+ n += 1;
+ } else {
+ break;
+ }
+ }
+
+ loop {
+ if n < 2 {
+ let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ // Do lint
+ y.contains(&n);
+ n += 1;
+ loop {
+ if n < 4 {
+ // Do not lint, because this method call is in the loop
+ z.contains(&n);
+ n += 1;
+ } else {
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ fn test_while_let() {
+ let vec = vec![1, 2];
+ let x: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let optional = Some(0);
+ let mut n = 0;
+ while let Some(value) = optional {
+ if n < 1 {
+ // Do not lint, because this method call is in the loop
+ x.contains(&n);
+ n += 1;
+ } else {
+ break;
+ }
+ }
+
+ while let Some(value) = optional {
+ let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ if n < 2 {
+ // Do lint
+ y.contains(&n);
+ n += 1;
+ } else {
+ break;
+ }
+
+ while let Some(value) = optional {
+ if n < 4 {
+ // Do not lint, because this method call is in the loop
+ z.contains(&n);
+ n += 1;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ fn test_if_cond() {
+ let vec = vec![1, 2];
+ let v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let w = v.iter().collect::<Vec<_>>();
+ // Do lint
+ for _ in 0..w.len() {
+ todo!();
+ }
+ }
+
+ fn test_if_cond_false_case() {
+ let vec = vec![1, 2];
+ let v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let w = v.iter().collect::<Vec<_>>();
+ // Do not lint, because w is used.
+ for _ in 0..w.len() {
+ todo!();
+ }
+
+ w.len();
+ }
+
+ fn test_while_cond() {
+ let mut vec = vec![1, 2];
+ let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut w = v.iter().collect::<Vec<_>>();
+ // Do lint
+ while 1 == w.len() {
+ todo!();
+ }
+ }
+
+ fn test_while_cond_false_case() {
+ let mut vec = vec![1, 2];
+ let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut w = v.iter().collect::<Vec<_>>();
+ // Do not lint, because w is used.
+ while 1 == w.len() {
+ todo!();
+ }
+
+ w.len();
+ }
+
+ fn test_while_let_cond() {
+ let mut vec = vec![1, 2];
+ let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut w = v.iter().collect::<Vec<_>>();
+ // Do lint
+ while let Some(i) = Some(w.len()) {
+ todo!();
+ }
+ }
+
+ fn test_while_let_cond_false_case() {
+ let mut vec = vec![1, 2];
+ let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect();
+ let mut w = v.iter().collect::<Vec<_>>();
+ // Do not lint, because w is used.
+ while let Some(i) = Some(w.len()) {
+ todo!();
+ }
+ w.len();
+ }
+}
--- /dev/null
- --> $DIR/needless_collect_indirect.rs:7:39
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:9:38
++ --> $DIR/needless_collect_indirect.rs:8:39
+ |
+LL | let indirect_iter = sample.iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ | ------------------------- the iterator could be used here instead
+ |
+ = note: `-D clippy::needless-collect` implied by `-D warnings`
+help: use the original Iterator instead of collecting it and then producing a new one
+ |
+LL ~
+LL ~ sample.iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:11:40
++ --> $DIR/needless_collect_indirect.rs:10:38
+ |
+LL | let indirect_len = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_len.len();
+ | ------------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL ~ sample.iter().count();
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:13:43
++ --> $DIR/needless_collect_indirect.rs:12:40
+ |
+LL | let indirect_empty = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_empty.is_empty();
+ | ------------------------- the iterator could be used here instead
+ |
+help: check if the original Iterator has anything instead of collecting it and seeing if it's empty
+ |
+LL ~
+LL ~ sample.iter().next().is_none();
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:25:48
++ --> $DIR/needless_collect_indirect.rs:14:43
+ |
+LL | let indirect_contains = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_contains.contains(&&5);
+ | ------------------------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL ~ sample.iter().any(|x| x == &5);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:54:51
++ --> $DIR/needless_collect_indirect.rs:26:48
+ |
+LL | let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | non_copy_contains.contains(&a);
+ | ------------------------------ the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL ~ sample.into_iter().any(|x| x == a);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:59:55
++ --> $DIR/needless_collect_indirect.rs:55:51
+ |
+LL | let buffer: Vec<&str> = string.split('/').collect();
+ | ^^^^^^^
+LL | buffer.len()
+ | ------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL ~ string.split('/').count()
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:64:57
++ --> $DIR/needless_collect_indirect.rs:60:55
+ |
+LL | let indirect_len: VecDeque<_> = sample.iter().collect();
+ | ^^^^^^^
+LL | indirect_len.len()
+ | ------------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL ~ sample.iter().count()
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:69:57
++ --> $DIR/needless_collect_indirect.rs:65:57
+ |
+LL | let indirect_len: LinkedList<_> = sample.iter().collect();
+ | ^^^^^^^
+LL | indirect_len.len()
+ | ------------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL ~ sample.iter().count()
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:129:59
++ --> $DIR/needless_collect_indirect.rs:70:57
+ |
+LL | let indirect_len: BinaryHeap<_> = sample.iter().collect();
+ | ^^^^^^^
+LL | indirect_len.len()
+ | ------------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL ~ sample.iter().count()
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:154:59
++ --> $DIR/needless_collect_indirect.rs:130:59
+ |
+LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ | ^^^^^^^
+...
+LL | y.contains(&i);
+ | -------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+LL | // Do lint
+LL ~ vec.iter().map(|k| k * k).any(|x| x == i);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:183:63
++ --> $DIR/needless_collect_indirect.rs:155:59
+ |
+LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ | ^^^^^^^
+...
+LL | y.contains(&n);
+ | -------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+LL | // Do lint
+LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:219:59
++ --> $DIR/needless_collect_indirect.rs:184:63
+ |
+LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ | ^^^^^^^
+...
+LL | y.contains(&n);
+ | -------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+LL | // Do lint
+LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:244:26
++ --> $DIR/needless_collect_indirect.rs:220:59
+ |
+LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
+ | ^^^^^^^
+...
+LL | y.contains(&n);
+ | -------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL ~
+LL | let z: Vec<usize> = vec.iter().map(|k| k * k).collect();
+LL | if n < 2 {
+LL | // Do lint
+LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:266:30
++ --> $DIR/needless_collect_indirect.rs:245:26
+ |
+LL | let w = v.iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | // Do lint
+LL | for _ in 0..w.len() {
+ | ------- the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL | // Do lint
+LL ~ for _ in 0..v.iter().count() {
+ |
+
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:288:30
++ --> $DIR/needless_collect_indirect.rs:267:30
+ |
+LL | let mut w = v.iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | // Do lint
+LL | while 1 == w.len() {
+ | ------- the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL | // Do lint
+LL ~ while 1 == v.iter().count() {
+ |
+
+error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect_indirect.rs:289:30
+ |
+LL | let mut w = v.iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | // Do lint
+LL | while let Some(i) = Some(w.len()) {
+ | ------- the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL ~
+LL | // Do lint
+LL ~ while let Some(i) = Some(v.iter().count()) {
+ |
+
+error: aborting due to 16 previous errors
+
--- /dev/null
- // No error; multiple input refs.
- fn multiple_in_and_out_2<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 {
+#![warn(clippy::needless_lifetimes)]
+#![allow(
+ dead_code,
+ clippy::boxed_local,
+ clippy::needless_pass_by_value,
+ clippy::unnecessary_wraps,
+ dyn_drop,
+ clippy::get_first
+)]
+
+fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {}
+
+fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {}
+
+// No error; same lifetime on two params.
+fn same_lifetime_on_input<'a>(_x: &'a u8, _y: &'a u8) {}
+
+// No error; static involved.
+fn only_static_on_input(_x: &u8, _y: &u8, _z: &'static u8) {}
+
+fn mut_and_static_input(_x: &mut u8, _y: &'static str) {}
+
+fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 {
+ x
+}
+
+// No error; multiple input refs.
+fn multiple_in_and_out_1<'a>(x: &'a u8, _y: &'a u8) -> &'a u8 {
+ x
+}
+
- // No error.
- fn deep_reference_1<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> {
++// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++// fn multiple_in_and_out_2a<'a>(x: &'a u8, _y: &u8) -> &'a u8
++// ^^^
++fn multiple_in_and_out_2a<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 {
+ x
+}
+
++// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++// fn multiple_in_and_out_2b<'b>(_x: &u8, y: &'b u8) -> &'b u8
++// ^^^
++fn multiple_in_and_out_2b<'a, 'b>(_x: &'a u8, y: &'b u8) -> &'b u8 {
++ y
++}
++
+// No error; multiple input refs
+async fn func<'a>(args: &[&'a str]) -> Option<&'a str> {
+ args.get(0).cloned()
+}
+
+// No error; static involved.
+fn in_static_and_out<'a>(x: &'a u8, _y: &'static u8) -> &'a u8 {
+ x
+}
+
- // No error; multiple input refs.
- fn self_and_in_out<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 {
++// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++// fn deep_reference_1a<'a>(x: &'a u8, _y: &u8) -> Result<&'a u8, ()>
++// ^^^
++fn deep_reference_1a<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> {
+ Ok(x)
+}
+
++// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++// fn deep_reference_1b<'b>(_x: &u8, y: &'b u8) -> Result<&'b u8, ()>
++// ^^^
++fn deep_reference_1b<'a, 'b>(_x: &'a u8, y: &'b u8) -> Result<&'b u8, ()> {
++ Ok(y)
++}
++
+// No error; two input refs.
+fn deep_reference_2<'a>(x: Result<&'a u8, &'a u8>) -> &'a u8 {
+ x.unwrap()
+}
+
+fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> {
+ Ok(x)
+}
+
+// Where-clause, but without lifetimes.
+fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()>
+where
+ T: Copy,
+{
+ Ok(x)
+}
+
+type Ref<'r> = &'r u8;
+
+// No error; same lifetime on two params.
+fn lifetime_param_1<'a>(_x: Ref<'a>, _y: &'a u8) {}
+
+fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {}
+
+// No error; bounded lifetime.
+fn lifetime_param_3<'a, 'b: 'a>(_x: Ref<'a>, _y: &'b u8) {}
+
+// No error; bounded lifetime.
+fn lifetime_param_4<'a, 'b>(_x: Ref<'a>, _y: &'b u8)
+where
+ 'b: 'a,
+{
+}
+
+struct Lt<'a, I: 'static> {
+ x: &'a I,
+}
+
+// No error; fn bound references `'a`.
+fn fn_bound<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I>
+where
+ F: Fn(Lt<'a, I>) -> Lt<'a, I>,
+{
+ unreachable!()
+}
+
+fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I>
+where
+ for<'x> F: Fn(Lt<'x, I>) -> Lt<'x, I>,
+{
+ unreachable!()
+}
+
+// No error; see below.
+fn fn_bound_3<'a, F: FnOnce(&'a i32)>(x: &'a i32, f: F) {
+ f(x);
+}
+
+fn fn_bound_3_cannot_elide() {
+ let x = 42;
+ let p = &x;
+ let mut q = &x;
+ // This will fail if we elide lifetimes of `fn_bound_3`.
+ fn_bound_3(p, |y| q = y);
+}
+
+// No error; multiple input refs.
+fn fn_bound_4<'a, F: FnOnce() -> &'a ()>(cond: bool, x: &'a (), f: F) -> &'a () {
+ if cond { x } else { f() }
+}
+
+struct X {
+ x: u8,
+}
+
+impl X {
+ fn self_and_out<'s>(&'s self) -> &'s u8 {
+ &self.x
+ }
+
- // No warning; two input lifetimes.
- fn struct_with_lt4<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str {
++ // Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++ // fn self_and_in_out_1<'s>(&'s self, _x: &u8) -> &'s u8
++ // ^^^
++ fn self_and_in_out_1<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 {
+ &self.x
+ }
+
++ // Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid:
++ // fn self_and_in_out_2<'t>(&self, x: &'t u8) -> &'t u8
++ // ^^^^^
++ fn self_and_in_out_2<'s, 't>(&'s self, x: &'t u8) -> &'t u8 {
++ x
++ }
++
+ fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {}
+
+ // No error; same lifetimes on two params.
+ fn self_and_same_in<'s>(&'s self, _x: &'s u8) {}
+}
+
+struct Foo<'a>(&'a u8);
+
+impl<'a> Foo<'a> {
+ // No error; lifetime `'a` not defined in method.
+ fn self_shared_lifetime(&self, _: &'a u8) {}
+ // No error; bounds exist.
+ fn self_bound_lifetime<'b: 'a>(&self, _: &'b u8) {}
+}
+
+fn already_elided<'a>(_: &u8, _: &'a u8) -> &'a u8 {
+ unimplemented!()
+}
+
+fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str {
+ unimplemented!()
+}
+
+// No warning; two input lifetimes (named on the reference, anonymous on `Foo`).
+fn struct_with_lt2<'a>(_foo: &'a Foo) -> &'a str {
+ unimplemented!()
+}
+
+// No warning; two input lifetimes (anonymous on the reference, named on `Foo`).
+fn struct_with_lt3<'a>(_foo: &Foo<'a>) -> &'a str {
+ unimplemented!()
+}
+
- // No warning; two input lifetimes.
- fn alias_with_lt4<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str {
++// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is
++// valid:
++// fn struct_with_lt4a<'a>(_foo: &'a Foo<'_>) -> &'a str
++// ^^
++fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str {
++ unimplemented!()
++}
++
++// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is
++// valid:
++// fn struct_with_lt4b<'b>(_foo: &Foo<'b>) -> &'b str
++// ^^^^
++fn struct_with_lt4b<'a, 'b>(_foo: &'a Foo<'b>) -> &'b str {
+ unimplemented!()
+}
+
+trait WithLifetime<'a> {}
+
+type WithLifetimeAlias<'a> = dyn WithLifetime<'a>;
+
+// Should not warn because it won't build without the lifetime.
+fn trait_obj_elided<'a>(_arg: &'a dyn WithLifetime) -> &'a str {
+ unimplemented!()
+}
+
+// Should warn because there is no lifetime on `Drop`, so this would be
+// unambiguous if we elided the lifetime.
+fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str {
+ unimplemented!()
+}
+
+type FooAlias<'a> = Foo<'a>;
+
+fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str {
+ unimplemented!()
+}
+
+// No warning; two input lifetimes (named on the reference, anonymous on `FooAlias`).
+fn alias_with_lt2<'a>(_foo: &'a FooAlias) -> &'a str {
+ unimplemented!()
+}
+
+// No warning; two input lifetimes (anonymous on the reference, named on `FooAlias`).
+fn alias_with_lt3<'a>(_foo: &FooAlias<'a>) -> &'a str {
+ unimplemented!()
+}
+
++// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is
++// valid:
++// fn alias_with_lt4a<'a>(_foo: &'a FooAlias<'_>) -> &'a str
++// ^^
++fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str {
++ unimplemented!()
++}
++
++// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is
++// valid:
++// fn alias_with_lt4b<'b>(_foo: &FooAlias<'b>) -> &'b str
++// ^^^^^^^^^
++fn alias_with_lt4b<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'b str {
+ unimplemented!()
+}
+
+fn named_input_elided_output<'a>(_arg: &'a str) -> &str {
+ unimplemented!()
+}
+
+fn elided_input_named_output<'a>(_arg: &str) -> &'a str {
+ unimplemented!()
+}
+
+fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) {
+ unimplemented!()
+}
+fn trait_bound<'a, T: WithLifetime<'a>>(_: &'a u8, _: T) {
+ unimplemented!()
+}
+
+// Don't warn on these; see issue #292.
+fn trait_bound_bug<'a, T: WithLifetime<'a>>() {
+ unimplemented!()
+}
+
+// See issue #740.
+struct Test {
+ vec: Vec<usize>,
+}
+
+impl Test {
+ fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = usize> + 'a> {
+ unimplemented!()
+ }
+}
+
+trait LintContext<'a> {}
+
+fn f<'a, T: LintContext<'a>>(_: &T) {}
+
+fn test<'a>(x: &'a [u8]) -> u8 {
+ let y: &'a u8 = &x[5];
+ *y
+}
+
+// Issue #3284: give hint regarding lifetime in return type.
+struct Cow<'a> {
+ x: &'a str,
+}
+fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> {
+ unimplemented!()
+}
+
+// Make sure we still warn on implementations
+mod issue4291 {
+ trait BadTrait {
+ fn needless_lt<'a>(x: &'a u8) {}
+ }
+
+ impl BadTrait for () {
+ fn needless_lt<'a>(_x: &'a u8) {}
+ }
+}
+
+mod issue2944 {
+ trait Foo {}
+ struct Bar;
+ struct Baz<'a> {
+ bar: &'a Bar,
+ }
+
+ impl<'a> Foo for Baz<'a> {}
+ impl Bar {
+ fn baz<'a>(&'a self) -> impl Foo + 'a {
+ Baz { bar: self }
+ }
+ }
+}
+
+mod nested_elision_sites {
+ // issue #issue2944
+
+ // closure trait bounds subject to nested elision
+ // don't lint because they refer to outer lifetimes
+ fn trait_fn<'a>(i: &'a i32) -> impl Fn() -> &'a i32 {
+ move || i
+ }
+ fn trait_fn_mut<'a>(i: &'a i32) -> impl FnMut() -> &'a i32 {
+ move || i
+ }
+ fn trait_fn_once<'a>(i: &'a i32) -> impl FnOnce() -> &'a i32 {
+ move || i
+ }
+
+ // don't lint
+ fn impl_trait_in_input_position<'a>(f: impl Fn() -> &'a i32) -> &'a i32 {
+ f()
+ }
+ fn impl_trait_in_output_position<'a>(i: &'a i32) -> impl Fn() -> &'a i32 {
+ move || i
+ }
+ // lint
+ fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 {
+ f(i)
+ }
+ fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 {
+ f(i)
+ }
+
+ // don't lint
+ fn generics_not_elidable<'a, T: Fn() -> &'a i32>(f: T) -> &'a i32 {
+ f()
+ }
+ // lint
+ fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 {
+ f(i)
+ }
+
+ // don't lint
+ fn where_clause_not_elidable<'a, T>(f: T) -> &'a i32
+ where
+ T: Fn() -> &'a i32,
+ {
+ f()
+ }
+ // lint
+ fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32
+ where
+ T: Fn(&i32) -> &i32,
+ {
+ f(i)
+ }
+
+ // don't lint
+ fn pointer_fn_in_input_position<'a>(f: fn(&'a i32) -> &'a i32, i: &'a i32) -> &'a i32 {
+ f(i)
+ }
+ fn pointer_fn_in_output_position<'a>(_: &'a i32) -> fn(&'a i32) -> &'a i32 {
+ |i| i
+ }
+ // lint
+ fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 {
+ f(i)
+ }
+
+ // don't lint
+ fn nested_fn_pointer_1<'a>(_: &'a i32) -> fn(fn(&'a i32) -> &'a i32) -> i32 {
+ |f| 42
+ }
+ fn nested_fn_pointer_2<'a>(_: &'a i32) -> impl Fn(fn(&'a i32)) {
+ |f| ()
+ }
+
+ // lint
+ fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 {
+ |f| 42
+ }
+ fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) {
+ |f| ()
+ }
+}
+
+mod issue6159 {
+ use std::ops::Deref;
+ pub fn apply_deref<'a, T, F, R>(x: &'a T, f: F) -> R
+ where
+ T: Deref,
+ F: FnOnce(&'a T::Target) -> R,
+ {
+ f(x.deref())
+ }
+}
+
+mod issue7296 {
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ struct Foo;
+ impl Foo {
+ fn implicit<'a>(&'a self) -> &'a () {
+ &()
+ }
+ fn implicit_mut<'a>(&'a mut self) -> &'a () {
+ &()
+ }
+
+ fn explicit<'a>(self: &'a Arc<Self>) -> &'a () {
+ &()
+ }
+ fn explicit_mut<'a>(self: &'a mut Rc<Self>) -> &'a () {
+ &()
+ }
+
+ fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a () {
+ &()
+ }
+ }
+
+ trait Bar {
+ fn implicit<'a>(&'a self) -> &'a ();
+ fn implicit_provided<'a>(&'a self) -> &'a () {
+ &()
+ }
+
+ fn explicit<'a>(self: &'a Arc<Self>) -> &'a ();
+ fn explicit_provided<'a>(self: &'a Arc<Self>) -> &'a () {
+ &()
+ }
+
+ fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a ();
+ fn lifetime_elsewhere_provided<'a>(self: Box<Self>, here: &'a ()) -> &'a () {
+ &()
+ }
+ }
+}
+
++mod pr_9743_false_negative_fix {
++ #![allow(unused)]
++
++ fn foo<'a>(x: &'a u8, y: &'_ u8) {}
++
++ fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {}
++}
++
++mod pr_9743_output_lifetime_checks {
++ #![allow(unused)]
++
++ // lint: only one input
++ fn one_input<'a>(x: &'a u8) -> &'a u8 {
++ unimplemented!()
++ }
++
++ // lint: multiple inputs, output would not be elided
++ fn multiple_inputs_output_not_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'b u8 {
++ unimplemented!()
++ }
++
++ // don't lint: multiple inputs, output would be elided (which would create an ambiguity)
++ fn multiple_inputs_output_would_be_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'a u8 {
++ unimplemented!()
++ }
++}
++
+fn main() {}
--- /dev/null
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a, 'b
+ --> $DIR/needless_lifetimes.rs:11:1
+ |
+LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::needless-lifetimes` implied by `-D warnings`
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a, 'b
+ --> $DIR/needless_lifetimes.rs:13:1
+ |
+LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
++error: the following explicit lifetimes could be elided: 'a
+ --> $DIR/needless_lifetimes.rs:23:1
+ |
+LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:57:1
++error: the following explicit lifetimes could be elided: 'b
++ --> $DIR/needless_lifetimes.rs:35:1
++ |
++LL | fn multiple_in_and_out_2a<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:42:1
++ |
++LL | fn multiple_in_and_out_2b<'a, 'b>(_x: &'a u8, y: &'b u8) -> &'b u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'b
++ --> $DIR/needless_lifetimes.rs:59:1
++ |
++LL | fn deep_reference_1a<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:66:1
++ |
++LL | fn deep_reference_1b<'a, 'b>(_x: &'a u8, y: &'b u8) -> Result<&'b u8, ()> {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:75:1
+ |
+LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:62:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:80:1
+ |
+LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()>
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:74:1
++error: the following explicit lifetimes could be elided: 'a, 'b
++ --> $DIR/needless_lifetimes.rs:92:1
+ |
+LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:92:37
++ |
++LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {}
++ | ^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:98:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:116:1
+ |
+LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I>
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:116:32
++ |
++LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I>
++ | ^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:128:5
++error: the following explicit lifetimes could be elided: 's
++ --> $DIR/needless_lifetimes.rs:146:5
+ |
+LL | fn self_and_out<'s>(&'s self) -> &'s u8 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:137:5
++error: the following explicit lifetimes could be elided: 't
++ --> $DIR/needless_lifetimes.rs:153:5
++ |
++LL | fn self_and_in_out_1<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 's
++ --> $DIR/needless_lifetimes.rs:160:5
++ |
++LL | fn self_and_in_out_2<'s, 't>(&'s self, x: &'t u8) -> &'t u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 's, 't
++ --> $DIR/needless_lifetimes.rs:164:5
+ |
+LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:156:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:183:1
+ |
+LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:183:33
++ |
++LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str {
++ | ^^
++
++error: the following explicit lifetimes could be elided: 'b
++ --> $DIR/needless_lifetimes.rs:201:1
++ |
++LL | fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:201:43
++ |
++LL | fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str {
++ | ^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:209:1
++ |
++LL | fn struct_with_lt4b<'a, 'b>(_foo: &'a Foo<'b>) -> &'b str {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:186:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:224:1
+ |
+LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:192:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:230:1
+ |
+LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:230:37
++ |
++LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str {
++ | ^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:211:1
++error: the following explicit lifetimes could be elided: 'b
++ --> $DIR/needless_lifetimes.rs:248:1
++ |
++LL | fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:248:47
++ |
++LL | fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str {
++ | ^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:256:1
++ |
++LL | fn alias_with_lt4b<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'b str {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:260:1
+ |
+LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:219:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:268:1
+ |
+LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:255:1
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:304:1
+ |
+LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: replace with `'_` in generic arguments such as here
++ --> $DIR/needless_lifetimes.rs:304:47
++ |
++LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> {
++ | ^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:262:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:311:9
+ |
+LL | fn needless_lt<'a>(x: &'a u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:266:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:315:9
+ |
+LL | fn needless_lt<'a>(_x: &'a u8) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:279:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:328:9
+ |
+LL | fn baz<'a>(&'a self) -> impl Foo + 'a {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:311:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:360:5
+ |
+LL | fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:320:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:369:5
+ |
+LL | fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:332:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:381:5
+ |
+LL | fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:347:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:396:5
+ |
+LL | fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:360:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:409:5
+ |
+LL | fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:363:5
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:412:5
+ |
+LL | fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:385:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:434:9
+ |
+LL | fn implicit<'a>(&'a self) -> &'a () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:388:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:437:9
+ |
+LL | fn implicit_mut<'a>(&'a mut self) -> &'a () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:399:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:448:9
+ |
+LL | fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:405:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:454:9
+ |
+LL | fn implicit<'a>(&'a self) -> &'a ();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:406:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:455:9
+ |
+LL | fn implicit_provided<'a>(&'a self) -> &'a () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:415:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:464:9
+ |
+LL | fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a ();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/needless_lifetimes.rs:416:9
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:465:9
+ |
+LL | fn lifetime_elsewhere_provided<'a>(self: Box<Self>, here: &'a ()) -> &'a () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
- error: aborting due to 31 previous errors
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:474:5
++ |
++LL | fn foo<'a>(x: &'a u8, y: &'_ u8) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:476:5
++ |
++LL | fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:483:5
++ |
++LL | fn one_input<'a>(x: &'a u8) -> &'a u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: the following explicit lifetimes could be elided: 'a
++ --> $DIR/needless_lifetimes.rs:488:5
++ |
++LL | fn multiple_inputs_output_not_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'b u8 {
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 45 previous errors
+
--- /dev/null
+#![allow(
+ clippy::single_match,
+ unused_assignments,
+ unused_variables,
+ clippy::while_immutable_condition
+)]
+
+fn test1() {
+ let mut x = 0;
+ loop {
+ // clippy::never_loop
+ x += 1;
+ if x == 1 {
+ return;
+ }
+ break;
+ }
+}
+
+fn test2() {
+ let mut x = 0;
+ loop {
+ x += 1;
+ if x == 1 {
+ break;
+ }
+ }
+}
+
+fn test3() {
+ let mut x = 0;
+ loop {
+ // never loops
+ x += 1;
+ break;
+ }
+}
+
+fn test4() {
+ let mut x = 1;
+ loop {
+ x += 1;
+ match x {
+ 5 => return,
+ _ => (),
+ }
+ }
+}
+
+fn test5() {
+ let i = 0;
+ loop {
+ // never loops
+ while i == 0 {
+ // never loops
+ break;
+ }
+ return;
+ }
+}
+
+fn test6() {
+ let mut x = 0;
+ 'outer: loop {
+ x += 1;
+ loop {
+ // never loops
+ if x == 5 {
+ break;
+ }
+ continue 'outer;
+ }
+ return;
+ }
+}
+
+fn test7() {
+ let mut x = 0;
+ loop {
+ x += 1;
+ match x {
+ 1 => continue,
+ _ => (),
+ }
+ return;
+ }
+}
+
+fn test8() {
+ let mut x = 0;
+ loop {
+ x += 1;
+ match x {
+ 5 => return,
+ _ => continue,
+ }
+ }
+}
+
+fn test9() {
+ let x = Some(1);
+ while let Some(y) = x {
+ // never loops
+ return;
+ }
+}
+
+fn test10() {
+ for x in 0..10 {
+ // never loops
+ match x {
+ 1 => break,
+ _ => return,
+ }
+ }
+}
+
+fn test11<F: FnMut() -> i32>(mut f: F) {
+ loop {
+ return match f() {
+ 1 => continue,
+ _ => (),
+ };
+ }
+}
+
+pub fn test12(a: bool, b: bool) {
+ 'label: loop {
+ loop {
+ if a {
+ continue 'label;
+ }
+ if b {
+ break;
+ }
+ }
+ break;
+ }
+}
+
+pub fn test13() {
+ let mut a = true;
+ loop {
+ // infinite loop
+ while a {
+ if true {
+ a = false;
+ continue;
+ }
+ return;
+ }
+ }
+}
+
+pub fn test14() {
+ let mut a = true;
+ 'outer: while a {
+ // never loops
+ while a {
+ if a {
+ a = false;
+ continue;
+ }
+ }
+ break 'outer;
+ }
+}
+
+// Issue #1991: the outer loop should not warn.
+pub fn test15() {
+ 'label: loop {
+ while false {
+ break 'label;
+ }
+ }
+}
+
+// Issue #4058: `continue` in `break` expression
+pub fn test16() {
+ let mut n = 1;
+ loop {
+ break if n != 5 {
+ n += 1;
+ continue;
+ };
+ }
+}
+
+// Issue #9001: `continue` in struct expression fields
+pub fn test17() {
+ struct Foo {
+ f: (),
+ }
+
+ let mut n = 0;
+ let _ = loop {
+ break Foo {
+ f: if n < 5 {
+ n += 1;
+ continue;
+ },
+ };
+ };
+}
+
+// Issue #9356: `continue` in else branch of let..else
+pub fn test18() {
+ let x = Some(0);
+ let y = 0;
+ // might loop
+ let _ = loop {
+ let Some(x) = x else {
+ if y > 0 {
+ continue;
+ } else {
+ return;
+ }
+ };
+
+ break x;
+ };
+ // never loops
+ let _ = loop {
+ let Some(x) = x else {
+ return;
+ };
+
+ break x;
+ };
+}
+
++// Issue #9831: unconditional break to internal labeled block
++pub fn test19() {
++ fn thing(iter: impl Iterator) {
++ for _ in iter {
++ 'b: {
++ break 'b;
++ }
++ }
++ }
++}
++
++pub fn test20() {
++ 'a: loop {
++ 'b: {
++ break 'b 'c: {
++ break 'a;
++ };
++ }
++ }
++}
++
+fn main() {
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ test7();
+ test8();
+ test9();
+ test10();
+ test11(|| 0);
+ test12(true, false);
+ test13();
+ test14();
+}
--- /dev/null
- error: aborting due to 10 previous errors
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:10:5
+ |
+LL | / loop {
+LL | | // clippy::never_loop
+LL | | x += 1;
+LL | | if x == 1 {
+... |
+LL | | break;
+LL | | }
+ | |_____^
+ |
+ = note: `#[deny(clippy::never_loop)]` on by default
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:32:5
+ |
+LL | / loop {
+LL | | // never loops
+LL | | x += 1;
+LL | | break;
+LL | | }
+ | |_____^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:52:5
+ |
+LL | / loop {
+LL | | // never loops
+LL | | while i == 0 {
+LL | | // never loops
+... |
+LL | | return;
+LL | | }
+ | |_____^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:54:9
+ |
+LL | / while i == 0 {
+LL | | // never loops
+LL | | break;
+LL | | }
+ | |_________^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:66:9
+ |
+LL | / loop {
+LL | | // never loops
+LL | | if x == 5 {
+LL | | break;
+LL | | }
+LL | | continue 'outer;
+LL | | }
+ | |_________^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:102:5
+ |
+LL | / while let Some(y) = x {
+LL | | // never loops
+LL | | return;
+LL | | }
+ | |_____^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:109:5
+ |
+LL | / for x in 0..10 {
+LL | | // never loops
+LL | | match x {
+LL | | 1 => break,
+LL | | _ => return,
+LL | | }
+LL | | }
+ | |_____^
+ |
+help: if you need the first element of the iterator, try writing
+ |
+LL | if let Some(x) = (0..10).next() {
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:157:5
+ |
+LL | / 'outer: while a {
+LL | | // never loops
+LL | | while a {
+LL | | if a {
+... |
+LL | | break 'outer;
+LL | | }
+ | |_____^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:172:9
+ |
+LL | / while false {
+LL | | break 'label;
+LL | | }
+ | |_________^
+
+error: this loop never actually loops
+ --> $DIR/never_loop.rs:223:13
+ |
+LL | let _ = loop {
+ | _____________^
+LL | | let Some(x) = x else {
+LL | | return;
+LL | | };
+LL | |
+LL | | break x;
+LL | | };
+ | |_____^
+
++error: this loop never actually loops
++ --> $DIR/never_loop.rs:244:5
++ |
++LL | / 'a: loop {
++LL | | 'b: {
++LL | | break 'b 'c: {
++LL | | break 'a;
++LL | | };
++LL | | }
++LL | | }
++ | |_____^
++
++error: aborting due to 11 previous errors
+
--- /dev/null
+#![warn(clippy::new_ret_no_self)]
+#![allow(dead_code)]
+
+fn main() {}
+
+trait R {
+ type Item;
+}
+
+trait Q {
+ type Item;
+ type Item2;
+}
+
+struct S;
+
+impl R for S {
+ type Item = Self;
+}
+
+impl S {
+ // should not trigger the lint
+ pub fn new() -> impl R<Item = Self> {
+ S
+ }
+}
+
+struct S2;
+
+impl R for S2 {
+ type Item = Self;
+}
+
+impl S2 {
+ // should not trigger the lint
+ pub fn new(_: String) -> impl R<Item = Self> {
+ S2
+ }
+}
+
+struct S3;
+
+impl R for S3 {
+ type Item = u32;
+}
+
+impl S3 {
+ // should trigger the lint
+ pub fn new(_: String) -> impl R<Item = u32> {
+ S3
+ }
+}
+
+struct S4;
+
+impl Q for S4 {
+ type Item = u32;
+ type Item2 = Self;
+}
+
+impl S4 {
+ // should not trigger the lint
+ pub fn new(_: String) -> impl Q<Item = u32, Item2 = Self> {
+ S4
+ }
+}
+
+struct T;
+
+impl T {
+ // should not trigger lint
+ pub fn new() -> Self {
+ unimplemented!();
+ }
+}
+
+struct U;
+
+impl U {
+ // should trigger lint
+ pub fn new() -> u32 {
+ unimplemented!();
+ }
+}
+
+struct V;
+
+impl V {
+ // should trigger lint
+ pub fn new(_: String) -> u32 {
+ unimplemented!();
+ }
+}
+
+struct TupleReturnerOk;
+
+impl TupleReturnerOk {
+ // should not trigger lint
+ pub fn new() -> (Self, u32) {
+ unimplemented!();
+ }
+}
+
+struct TupleReturnerOk2;
+
+impl TupleReturnerOk2 {
+ // should not trigger lint (it doesn't matter which element in the tuple is Self)
+ pub fn new() -> (u32, Self) {
+ unimplemented!();
+ }
+}
+
+struct TupleReturnerOk3;
+
+impl TupleReturnerOk3 {
+ // should not trigger lint (tuple can contain multiple Self)
+ pub fn new() -> (Self, Self) {
+ unimplemented!();
+ }
+}
+
+struct TupleReturnerBad;
+
+impl TupleReturnerBad {
+ // should trigger lint
+ pub fn new() -> (u32, u32) {
+ unimplemented!();
+ }
+}
+
+struct MutPointerReturnerOk;
+
+impl MutPointerReturnerOk {
+ // should not trigger lint
+ pub fn new() -> *mut Self {
+ unimplemented!();
+ }
+}
+
+struct ConstPointerReturnerOk2;
+
+impl ConstPointerReturnerOk2 {
+ // should not trigger lint
+ pub fn new() -> *const Self {
+ unimplemented!();
+ }
+}
+
+struct MutPointerReturnerBad;
+
+impl MutPointerReturnerBad {
+ // should trigger lint
+ pub fn new() -> *mut V {
+ unimplemented!();
+ }
+}
+
+struct GenericReturnerOk;
+
+impl GenericReturnerOk {
+ // should not trigger lint
+ pub fn new() -> Option<Self> {
+ unimplemented!();
+ }
+}
+
+struct GenericReturnerBad;
+
+impl GenericReturnerBad {
+ // should trigger lint
+ pub fn new() -> Option<u32> {
+ unimplemented!();
+ }
+}
+
+struct NestedReturnerOk;
+
+impl NestedReturnerOk {
+ // should not trigger lint
+ pub fn new() -> (Option<Self>, u32) {
+ unimplemented!();
+ }
+}
+
+struct NestedReturnerOk2;
+
+impl NestedReturnerOk2 {
+ // should not trigger lint
+ pub fn new() -> ((Self, u32), u32) {
+ unimplemented!();
+ }
+}
+
+struct NestedReturnerOk3;
+
+impl NestedReturnerOk3 {
+ // should not trigger lint
+ pub fn new() -> Option<(Self, u32)> {
+ unimplemented!();
+ }
+}
+
+struct WithLifetime<'a> {
+ cat: &'a str,
+}
+
+impl<'a> WithLifetime<'a> {
+ // should not trigger the lint, because the lifetimes are different
+ pub fn new<'b: 'a>(s: &'b str) -> WithLifetime<'b> {
+ unimplemented!();
+ }
+}
+
+mod issue5435 {
+ struct V;
+
+ pub trait TraitRetSelf {
+ // should not trigger lint
+ fn new() -> Self;
+ }
+
+ pub trait TraitRet {
+ // should trigger lint as we are in trait definition
+ fn new() -> String;
+ }
+ pub struct StructRet;
+ impl TraitRet for StructRet {
+ // should not trigger lint as we are in the impl block
+ fn new() -> String {
+ unimplemented!();
+ }
+ }
+
+ pub trait TraitRet2 {
+ // should trigger lint
+ fn new(_: String) -> String;
+ }
+
+ trait TupleReturnerOk {
+ // should not trigger lint
+ fn new() -> (Self, u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerOk2 {
+ // should not trigger lint (it doesn't matter which element in the tuple is Self)
+ fn new() -> (u32, Self)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerOk3 {
+ // should not trigger lint (tuple can contain multiple Self)
+ fn new() -> (Self, Self)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerBad {
+ // should trigger lint
+ fn new() -> (u32, u32) {
+ unimplemented!();
+ }
+ }
+
+ trait MutPointerReturnerOk {
+ // should not trigger lint
+ fn new() -> *mut Self
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait ConstPointerReturnerOk2 {
+ // should not trigger lint
+ fn new() -> *const Self
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait MutPointerReturnerBad {
+ // should trigger lint
+ fn new() -> *mut V {
+ unimplemented!();
+ }
+ }
+
+ trait GenericReturnerOk {
+ // should not trigger lint
+ fn new() -> Option<Self>
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk {
+ // should not trigger lint
+ fn new() -> (Option<Self>, u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk2 {
+ // should not trigger lint
+ fn new() -> ((Self, u32), u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk3 {
+ // should not trigger lint
+ fn new() -> Option<(Self, u32)>
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+}
+
+// issue #1724
+struct RetOtherSelf<T>(T);
+struct RetOtherSelfWrapper<T>(T);
+
+impl RetOtherSelf<T> {
+ fn new(t: T) -> RetOtherSelf<RetOtherSelfWrapper<T>> {
+ RetOtherSelf(RetOtherSelfWrapper(t))
+ }
+}
++
++mod issue7344 {
++ struct RetImplTraitSelf<T>(T);
++
++ impl<T> RetImplTraitSelf<T> {
++ // should not trigger lint
++ fn new(t: T) -> impl Into<Self> {
++ Self(t)
++ }
++ }
++
++ struct RetImplTraitNoSelf<T>(T);
++
++ impl<T> RetImplTraitNoSelf<T> {
++ // should trigger lint
++ fn new(t: T) -> impl Into<i32> {
++ 1
++ }
++ }
++
++ trait Trait2<T, U> {}
++ impl<T, U> Trait2<T, U> for () {}
++
++ struct RetImplTraitSelf2<T>(T);
++
++ impl<T> RetImplTraitSelf2<T> {
++ // should not trigger lint
++ fn new(t: T) -> impl Trait2<(), Self> {
++ unimplemented!()
++ }
++ }
++
++ struct RetImplTraitNoSelf2<T>(T);
++
++ impl<T> RetImplTraitNoSelf2<T> {
++ // should trigger lint
++ fn new(t: T) -> impl Trait2<(), i32> {
++ unimplemented!()
++ }
++ }
++
++ struct RetImplTraitSelfAdt<'a>(&'a str);
++
++ impl<'a> RetImplTraitSelfAdt<'a> {
++ // should not trigger lint
++ fn new<'b: 'a>(s: &'b str) -> impl Into<RetImplTraitSelfAdt<'b>> {
++ RetImplTraitSelfAdt(s)
++ }
++ }
++}
--- /dev/null
- error: aborting due to 10 previous errors
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:49:5
+ |
+LL | / pub fn new(_: String) -> impl R<Item = u32> {
+LL | | S3
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::new-ret-no-self` implied by `-D warnings`
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:81:5
+ |
+LL | / pub fn new() -> u32 {
+LL | | unimplemented!();
+LL | | }
+ | |_____^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:90:5
+ |
+LL | / pub fn new(_: String) -> u32 {
+LL | | unimplemented!();
+LL | | }
+ | |_____^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:126:5
+ |
+LL | / pub fn new() -> (u32, u32) {
+LL | | unimplemented!();
+LL | | }
+ | |_____^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:153:5
+ |
+LL | / pub fn new() -> *mut V {
+LL | | unimplemented!();
+LL | | }
+ | |_____^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:171:5
+ |
+LL | / pub fn new() -> Option<u32> {
+LL | | unimplemented!();
+LL | | }
+ | |_____^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:224:9
+ |
+LL | fn new() -> String;
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:236:9
+ |
+LL | fn new(_: String) -> String;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:271:9
+ |
+LL | / fn new() -> (u32, u32) {
+LL | | unimplemented!();
+LL | | }
+ | |_________^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:298:9
+ |
+LL | / fn new() -> *mut V {
+LL | | unimplemented!();
+LL | | }
+ | |_________^
+
++error: methods called `new` usually return `Self`
++ --> $DIR/new_ret_no_self.rs:368:9
++ |
++LL | / fn new(t: T) -> impl Into<i32> {
++LL | | 1
++LL | | }
++ | |_________^
++
++error: methods called `new` usually return `Self`
++ --> $DIR/new_ret_no_self.rs:389:9
++ |
++LL | / fn new(t: T) -> impl Trait2<(), i32> {
++LL | | unimplemented!()
++LL | | }
++ | |_________^
++
++error: aborting due to 12 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::option_if_let_else)]
+#![allow(
+ unused_tuple_struct_fields,
+ clippy::redundant_closure,
+ clippy::ref_option_ref,
+ clippy::equatable_if_let,
+ clippy::let_unit_value
+)]
+
+fn bad1(string: Option<&str>) -> (bool, &str) {
+ string.map_or((false, "hello"), |x| (true, x))
+}
+
+fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> {
+ if string.is_none() {
+ None
+ } else if let Some(x) = string {
+ Some((true, x))
+ } else {
+ Some((false, ""))
+ }
+}
+
+fn unop_bad(string: &Option<&str>, mut num: Option<i32>) {
+ let _ = string.map_or(0, |s| s.len());
+ let _ = num.as_ref().map_or(&0, |s| s);
+ let _ = num.as_mut().map_or(&mut 0, |s| {
+ *s += 1;
+ s
+ });
+ let _ = num.as_ref().map_or(&0, |s| s);
+ let _ = num.map_or(0, |mut s| {
+ s += 1;
+ s
+ });
+ let _ = num.as_mut().map_or(&mut 0, |s| {
+ *s += 1;
+ s
+ });
+}
+
+fn longer_body(arg: Option<u32>) -> u32 {
+ arg.map_or(13, |x| {
+ let y = x * x;
+ y * y
+ })
+}
+
+fn impure_else(arg: Option<i32>) {
+ let side_effect = || {
+ println!("return 1");
+ 1
+ };
+ let _ = arg.map_or_else(|| side_effect(), |x| x);
+}
+
+fn test_map_or_else(arg: Option<u32>) {
+ let _ = arg.map_or_else(|| {
+ let mut y = 1;
+ y = (y + 2 / y) / 2;
+ y = (y + 2 / y) / 2;
+ y
+ }, |x| x * x * x * x);
+}
+
+fn negative_tests(arg: Option<u32>) -> u32 {
+ let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
+ for _ in 0..10 {
+ let _ = if let Some(x) = arg {
+ x
+ } else {
+ continue;
+ };
+ }
+ let _ = if let Some(x) = arg {
+ return x;
+ } else {
+ 5
+ };
+ 7
+}
+
+// #7973
+fn pattern_to_vec(pattern: &str) -> Vec<String> {
+ pattern
+ .trim_matches('/')
+ .split('/')
+ .flat_map(|s| {
+ s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])
+ })
+ .collect::<Vec<_>>()
+}
+
+enum DummyEnum {
+ One(u8),
+ Two,
+}
+
+// should not warn since there is a compled complex subpat
+// see #7991
+fn complex_subpat() -> DummyEnum {
+ let x = Some(DummyEnum::One(1));
+ let _ = if let Some(_one @ DummyEnum::One(..)) = x { 1 } else { 2 };
+ DummyEnum::Two
+}
+
+fn main() {
+ let optional = Some(5);
+ let _ = optional.map_or(5, |x| x + 2);
+ let _ = bad1(None);
+ let _ = else_if_option(None);
+ unop_bad(&None, None);
+ let _ = longer_body(None);
+ test_map_or_else(None);
+ let _ = negative_tests(None);
+ let _ = impure_else(None);
+
+ let _ = Some(0).map_or(0, |x| loop {
+ if x == 0 {
+ break x;
+ }
+ });
+
+ // #7576
+ const fn _f(x: Option<u32>) -> u32 {
+ // Don't lint, `map_or` isn't const
+ if let Some(x) = x { x } else { 10 }
+ }
+
+ // #5822
+ let s = String::new();
+ // Don't lint, `Some` branch consumes `s`, but else branch uses `s`
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ s.len()
+ };
+
+ let s = String::new();
+ // Lint, both branches immutably borrow `s`.
+ let _ = Some(0).map_or(s.len(), |x| s.len() + x);
+
+ let s = String::new();
+ // Lint, `Some` branch consumes `s`, but else branch doesn't use `s`.
+ let _ = Some(0).map_or(1, |x| {
+ let s = s;
+ s.len() + x
+ });
+
+ let s = Some(String::new());
+ // Don't lint, `Some` branch borrows `s`, but else branch consumes `s`
+ let _ = if let Some(x) = &s {
+ x.len()
+ } else {
+ let _s = s;
+ 10
+ };
+
+ let mut s = Some(String::new());
+ // Don't lint, `Some` branch mutably borrows `s`, but else branch also borrows `s`
+ let _ = if let Some(x) = &mut s {
+ x.push_str("test");
+ x.len()
+ } else {
+ let _s = &s;
+ 10
+ };
+
+ async fn _f1(x: u32) -> u32 {
+ x
+ }
+
+ async fn _f2() {
+ // Don't lint. `await` can't be moved into a closure.
+ let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 };
+ }
+
+ let _ = pattern_to_vec("hello world");
+ let _ = complex_subpat();
+
+ // issue #8492
+ let _ = s.map_or(1, |string| string.len());
+ let _ = Some(10).map_or(5, |a| a + 1);
+
+ let res: Result<i32, i32> = Ok(5);
+ let _ = res.map_or(1, |a| a + 1);
+ let _ = res.map_or(1, |a| a + 1);
+ let _ = res.map_or(5, |a| a + 1);
+}
++
++#[allow(dead_code)]
++fn issue9742() -> Option<&'static str> {
++ // should not lint because of guards
++ match Some("foo ") {
++ Some(name) if name.starts_with("foo") => Some(name.trim()),
++ _ => None,
++ }
++}
--- /dev/null
+// run-rustfix
+#![warn(clippy::option_if_let_else)]
+#![allow(
+ unused_tuple_struct_fields,
+ clippy::redundant_closure,
+ clippy::ref_option_ref,
+ clippy::equatable_if_let,
+ clippy::let_unit_value
+)]
+
+fn bad1(string: Option<&str>) -> (bool, &str) {
+ if let Some(x) = string {
+ (true, x)
+ } else {
+ (false, "hello")
+ }
+}
+
+fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> {
+ if string.is_none() {
+ None
+ } else if let Some(x) = string {
+ Some((true, x))
+ } else {
+ Some((false, ""))
+ }
+}
+
+fn unop_bad(string: &Option<&str>, mut num: Option<i32>) {
+ let _ = if let Some(s) = *string { s.len() } else { 0 };
+ let _ = if let Some(s) = &num { s } else { &0 };
+ let _ = if let Some(s) = &mut num {
+ *s += 1;
+ s
+ } else {
+ &mut 0
+ };
+ let _ = if let Some(ref s) = num { s } else { &0 };
+ let _ = if let Some(mut s) = num {
+ s += 1;
+ s
+ } else {
+ 0
+ };
+ let _ = if let Some(ref mut s) = num {
+ *s += 1;
+ s
+ } else {
+ &mut 0
+ };
+}
+
+fn longer_body(arg: Option<u32>) -> u32 {
+ if let Some(x) = arg {
+ let y = x * x;
+ y * y
+ } else {
+ 13
+ }
+}
+
+fn impure_else(arg: Option<i32>) {
+ let side_effect = || {
+ println!("return 1");
+ 1
+ };
+ let _ = if let Some(x) = arg {
+ x
+ } else {
+ // map_or_else must be suggested
+ side_effect()
+ };
+}
+
+fn test_map_or_else(arg: Option<u32>) {
+ let _ = if let Some(x) = arg {
+ x * x * x * x
+ } else {
+ let mut y = 1;
+ y = (y + 2 / y) / 2;
+ y = (y + 2 / y) / 2;
+ y
+ };
+}
+
+fn negative_tests(arg: Option<u32>) -> u32 {
+ let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
+ for _ in 0..10 {
+ let _ = if let Some(x) = arg {
+ x
+ } else {
+ continue;
+ };
+ }
+ let _ = if let Some(x) = arg {
+ return x;
+ } else {
+ 5
+ };
+ 7
+}
+
+// #7973
+fn pattern_to_vec(pattern: &str) -> Vec<String> {
+ pattern
+ .trim_matches('/')
+ .split('/')
+ .flat_map(|s| {
+ if let Some(idx) = s.find('.') {
+ vec![s[..idx].to_string(), s[idx..].to_string()]
+ } else {
+ vec![s.to_string()]
+ }
+ })
+ .collect::<Vec<_>>()
+}
+
+enum DummyEnum {
+ One(u8),
+ Two,
+}
+
+// should not warn since there is a compled complex subpat
+// see #7991
+fn complex_subpat() -> DummyEnum {
+ let x = Some(DummyEnum::One(1));
+ let _ = if let Some(_one @ DummyEnum::One(..)) = x { 1 } else { 2 };
+ DummyEnum::Two
+}
+
+fn main() {
+ let optional = Some(5);
+ let _ = if let Some(x) = optional { x + 2 } else { 5 };
+ let _ = bad1(None);
+ let _ = else_if_option(None);
+ unop_bad(&None, None);
+ let _ = longer_body(None);
+ test_map_or_else(None);
+ let _ = negative_tests(None);
+ let _ = impure_else(None);
+
+ let _ = if let Some(x) = Some(0) {
+ loop {
+ if x == 0 {
+ break x;
+ }
+ }
+ } else {
+ 0
+ };
+
+ // #7576
+ const fn _f(x: Option<u32>) -> u32 {
+ // Don't lint, `map_or` isn't const
+ if let Some(x) = x { x } else { 10 }
+ }
+
+ // #5822
+ let s = String::new();
+ // Don't lint, `Some` branch consumes `s`, but else branch uses `s`
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ s.len()
+ };
+
+ let s = String::new();
+ // Lint, both branches immutably borrow `s`.
+ let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() };
+
+ let s = String::new();
+ // Lint, `Some` branch consumes `s`, but else branch doesn't use `s`.
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ 1
+ };
+
+ let s = Some(String::new());
+ // Don't lint, `Some` branch borrows `s`, but else branch consumes `s`
+ let _ = if let Some(x) = &s {
+ x.len()
+ } else {
+ let _s = s;
+ 10
+ };
+
+ let mut s = Some(String::new());
+ // Don't lint, `Some` branch mutably borrows `s`, but else branch also borrows `s`
+ let _ = if let Some(x) = &mut s {
+ x.push_str("test");
+ x.len()
+ } else {
+ let _s = &s;
+ 10
+ };
+
+ async fn _f1(x: u32) -> u32 {
+ x
+ }
+
+ async fn _f2() {
+ // Don't lint. `await` can't be moved into a closure.
+ let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 };
+ }
+
+ let _ = pattern_to_vec("hello world");
+ let _ = complex_subpat();
+
+ // issue #8492
+ let _ = match s {
+ Some(string) => string.len(),
+ None => 1,
+ };
+ let _ = match Some(10) {
+ Some(a) => a + 1,
+ None => 5,
+ };
+
+ let res: Result<i32, i32> = Ok(5);
+ let _ = match res {
+ Ok(a) => a + 1,
+ _ => 1,
+ };
+ let _ = match res {
+ Err(_) => 1,
+ Ok(a) => a + 1,
+ };
+ let _ = if let Ok(a) = res { a + 1 } else { 5 };
+}
++
++#[allow(dead_code)]
++fn issue9742() -> Option<&'static str> {
++ // should not lint because of guards
++ match Some("foo ") {
++ Some(name) if name.starts_with("foo") => Some(name.trim()),
++ _ => None,
++ }
++}
--- /dev/null
+// run-rustfix
+#![warn(clippy::or_fun_call)]
+#![allow(dead_code)]
+#![allow(clippy::borrow_as_ptr, clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::time::Duration;
+
+/// Checks implementation of the `OR_FUN_CALL` lint.
+fn or_fun_call() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo
+ }
+ }
+
+ struct FakeDefault;
+ impl FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ impl Default for FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ enum Enum {
+ A(i32),
+ }
+
+ fn make<T>() -> T {
+ unimplemented!();
+ }
+
+ let with_enum = Some(Enum::A(1));
+ with_enum.unwrap_or(Enum::A(5));
+
+ let with_const_fn = Some(Duration::from_secs(1));
+ with_const_fn.unwrap_or(Duration::from_secs(5));
+
+ let with_constructor = Some(vec![1]);
+ with_constructor.unwrap_or_else(make);
+
+ let with_new = Some(vec![1]);
+ with_new.unwrap_or_default();
+
+ let with_const_args = Some(vec![1]);
+ with_const_args.unwrap_or_else(|| Vec::with_capacity(12));
+
+ let with_err: Result<_, ()> = Ok(vec![1]);
+ with_err.unwrap_or_else(|_| make());
+
+ let with_err_args: Result<_, ()> = Ok(vec![1]);
+ with_err_args.unwrap_or_else(|_| Vec::with_capacity(12));
+
+ let with_default_trait = Some(1);
+ with_default_trait.unwrap_or_default();
+
+ let with_default_type = Some(1);
+ with_default_type.unwrap_or_default();
+
+ let self_default = None::<FakeDefault>;
+ self_default.unwrap_or_else(<FakeDefault>::default);
+
+ let real_default = None::<FakeDefault>;
+ real_default.unwrap_or_default();
+
+ let with_vec = Some(vec![1]);
+ with_vec.unwrap_or_default();
+
+ let without_default = Some(Foo);
+ without_default.unwrap_or_else(Foo::new);
+
+ let mut map = HashMap::<u64, String>::new();
+ map.entry(42).or_default();
+
+ let mut map_vec = HashMap::<u64, Vec<i32>>::new();
+ map_vec.entry(42).or_default();
+
+ let mut btree = BTreeMap::<u64, String>::new();
+ btree.entry(42).or_default();
+
+ let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
+ btree_vec.entry(42).or_default();
+
+ let stringy = Some(String::new());
+ let _ = stringy.unwrap_or_default();
+
+ let opt = Some(1);
+ let hello = "Hello";
+ let _ = opt.ok_or(format!("{} world.", hello));
+
+ // index
+ let map = HashMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or_else(|| map[&1]);
+ let map = BTreeMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or_else(|| map[&1]);
+ // don't lint index vec
+ let vec = vec![1];
+ let _ = Some(1).unwrap_or(vec[1]);
+}
+
+struct Foo(u8);
+struct Bar(String, Duration);
+#[rustfmt::skip]
+fn test_or_with_ctors() {
+ let opt = Some(1);
+ let opt_opt = Some(Some(1));
+ // we also test for const promotion, this makes sure we don't hit that
+ let two = 2;
+
+ let _ = opt_opt.unwrap_or(Some(2));
+ let _ = opt_opt.unwrap_or(Some(two));
+ let _ = opt.ok_or(Some(2));
+ let _ = opt.ok_or(Some(two));
+ let _ = opt.ok_or(Foo(2));
+ let _ = opt.ok_or(Foo(two));
+ let _ = opt.or(Some(2));
+ let _ = opt.or(Some(two));
+
+ let _ = Some("a".to_string()).or_else(|| Some("b".to_string()));
+
+ let b = "b".to_string();
+ let _ = Some(Bar("a".to_string(), Duration::from_secs(1)))
+ .or(Some(Bar(b, Duration::from_secs(2))));
+
+ let vec = vec!["foo"];
+ let _ = opt.ok_or(vec.len());
+
+ let array = ["foo"];
+ let _ = opt.ok_or(array.len());
+
+ let slice = &["foo"][..];
+ let _ = opt.ok_or(slice.len());
+
+ let string = "foo";
+ let _ = opt.ok_or(string.len());
+}
+
+// Issue 4514 - early return
+fn f() -> Option<()> {
+ let a = Some(1);
+ let b = 1i32;
+
+ let _ = a.unwrap_or(b.checked_mul(3)?.min(240));
+
+ Some(())
+}
+
+mod issue6675 {
+ unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T {
+ #[allow(unused)]
+ let x = vec![0; 1000]; // future-proofing, make this function expensive.
+ &*p
+ }
+
+ unsafe fn foo() {
+ let s = "test".to_owned();
+ let s = &s as *const _;
+ None.unwrap_or_else(|| ptr_to_ref(s));
+ }
+
+ fn bar() {
+ let s = "test".to_owned();
+ let s = &s as *const _;
+ None.unwrap_or_else(|| unsafe { ptr_to_ref(s) });
+ #[rustfmt::skip]
+ None.unwrap_or_else(|| unsafe { ptr_to_ref(s) });
+ }
+}
+
+mod issue8239 {
+ fn more_than_max_suggestion_highest_lines_0() {
+ let frames = Vec::new();
+ frames
+ .iter()
+ .map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or_default();
+ }
+
+ fn more_to_max_suggestion_highest_lines_1() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ iter.map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ let _ = "";
+ let _ = "";
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or_default();
+ }
+
+ fn equal_to_max_suggestion_highest_lines() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ iter.map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ let _ = "";
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or_default();
+ }
+
+ fn less_than_max_suggestion_highest_lines() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ let map = iter.map(|f: &String| f.to_lowercase());
+ map.reduce(|mut acc, f| {
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or_default();
+ }
+}
+
+mod issue9608 {
+ fn sig_drop() {
+ enum X {
+ X(std::fs::File),
+ Y(u32),
+ }
+
+ let _ = None.unwrap_or(X::Y(0));
+ }
+}
+
++mod issue8993 {
++ fn g() -> i32 {
++ 3
++ }
++
++ fn f(n: i32) -> i32 {
++ n
++ }
++
++ fn test_map_or() {
++ let _ = Some(4).map_or_else(g, |v| v);
++ let _ = Some(4).map_or_else(g, f);
++ let _ = Some(4).map_or(0, f);
++ }
++}
++
+fn main() {}
--- /dev/null
+// run-rustfix
+#![warn(clippy::or_fun_call)]
+#![allow(dead_code)]
+#![allow(clippy::borrow_as_ptr, clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::time::Duration;
+
+/// Checks implementation of the `OR_FUN_CALL` lint.
+fn or_fun_call() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo
+ }
+ }
+
+ struct FakeDefault;
+ impl FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ impl Default for FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ enum Enum {
+ A(i32),
+ }
+
+ fn make<T>() -> T {
+ unimplemented!();
+ }
+
+ let with_enum = Some(Enum::A(1));
+ with_enum.unwrap_or(Enum::A(5));
+
+ let with_const_fn = Some(Duration::from_secs(1));
+ with_const_fn.unwrap_or(Duration::from_secs(5));
+
+ let with_constructor = Some(vec![1]);
+ with_constructor.unwrap_or(make());
+
+ let with_new = Some(vec![1]);
+ with_new.unwrap_or(Vec::new());
+
+ let with_const_args = Some(vec![1]);
+ with_const_args.unwrap_or(Vec::with_capacity(12));
+
+ let with_err: Result<_, ()> = Ok(vec![1]);
+ with_err.unwrap_or(make());
+
+ let with_err_args: Result<_, ()> = Ok(vec![1]);
+ with_err_args.unwrap_or(Vec::with_capacity(12));
+
+ let with_default_trait = Some(1);
+ with_default_trait.unwrap_or(Default::default());
+
+ let with_default_type = Some(1);
+ with_default_type.unwrap_or(u64::default());
+
+ let self_default = None::<FakeDefault>;
+ self_default.unwrap_or(<FakeDefault>::default());
+
+ let real_default = None::<FakeDefault>;
+ real_default.unwrap_or(<FakeDefault as Default>::default());
+
+ let with_vec = Some(vec![1]);
+ with_vec.unwrap_or(vec![]);
+
+ let without_default = Some(Foo);
+ without_default.unwrap_or(Foo::new());
+
+ let mut map = HashMap::<u64, String>::new();
+ map.entry(42).or_insert(String::new());
+
+ let mut map_vec = HashMap::<u64, Vec<i32>>::new();
+ map_vec.entry(42).or_insert(vec![]);
+
+ let mut btree = BTreeMap::<u64, String>::new();
+ btree.entry(42).or_insert(String::new());
+
+ let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
+ btree_vec.entry(42).or_insert(vec![]);
+
+ let stringy = Some(String::new());
+ let _ = stringy.unwrap_or(String::new());
+
+ let opt = Some(1);
+ let hello = "Hello";
+ let _ = opt.ok_or(format!("{} world.", hello));
+
+ // index
+ let map = HashMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or(map[&1]);
+ let map = BTreeMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or(map[&1]);
+ // don't lint index vec
+ let vec = vec![1];
+ let _ = Some(1).unwrap_or(vec[1]);
+}
+
+struct Foo(u8);
+struct Bar(String, Duration);
+#[rustfmt::skip]
+fn test_or_with_ctors() {
+ let opt = Some(1);
+ let opt_opt = Some(Some(1));
+ // we also test for const promotion, this makes sure we don't hit that
+ let two = 2;
+
+ let _ = opt_opt.unwrap_or(Some(2));
+ let _ = opt_opt.unwrap_or(Some(two));
+ let _ = opt.ok_or(Some(2));
+ let _ = opt.ok_or(Some(two));
+ let _ = opt.ok_or(Foo(2));
+ let _ = opt.ok_or(Foo(two));
+ let _ = opt.or(Some(2));
+ let _ = opt.or(Some(two));
+
+ let _ = Some("a".to_string()).or(Some("b".to_string()));
+
+ let b = "b".to_string();
+ let _ = Some(Bar("a".to_string(), Duration::from_secs(1)))
+ .or(Some(Bar(b, Duration::from_secs(2))));
+
+ let vec = vec!["foo"];
+ let _ = opt.ok_or(vec.len());
+
+ let array = ["foo"];
+ let _ = opt.ok_or(array.len());
+
+ let slice = &["foo"][..];
+ let _ = opt.ok_or(slice.len());
+
+ let string = "foo";
+ let _ = opt.ok_or(string.len());
+}
+
+// Issue 4514 - early return
+fn f() -> Option<()> {
+ let a = Some(1);
+ let b = 1i32;
+
+ let _ = a.unwrap_or(b.checked_mul(3)?.min(240));
+
+ Some(())
+}
+
+mod issue6675 {
+ unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T {
+ #[allow(unused)]
+ let x = vec![0; 1000]; // future-proofing, make this function expensive.
+ &*p
+ }
+
+ unsafe fn foo() {
+ let s = "test".to_owned();
+ let s = &s as *const _;
+ None.unwrap_or(ptr_to_ref(s));
+ }
+
+ fn bar() {
+ let s = "test".to_owned();
+ let s = &s as *const _;
+ None.unwrap_or(unsafe { ptr_to_ref(s) });
+ #[rustfmt::skip]
+ None.unwrap_or( unsafe { ptr_to_ref(s) } );
+ }
+}
+
+mod issue8239 {
+ fn more_than_max_suggestion_highest_lines_0() {
+ let frames = Vec::new();
+ frames
+ .iter()
+ .map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or(String::new());
+ }
+
+ fn more_to_max_suggestion_highest_lines_1() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ iter.map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ let _ = "";
+ let _ = "";
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or(String::new());
+ }
+
+ fn equal_to_max_suggestion_highest_lines() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ iter.map(|f: &String| f.to_lowercase())
+ .reduce(|mut acc, f| {
+ let _ = "";
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or(String::new());
+ }
+
+ fn less_than_max_suggestion_highest_lines() {
+ let frames = Vec::new();
+ let iter = frames.iter();
+ let map = iter.map(|f: &String| f.to_lowercase());
+ map.reduce(|mut acc, f| {
+ acc.push_str(&f);
+ acc
+ })
+ .unwrap_or(String::new());
+ }
+}
+
+mod issue9608 {
+ fn sig_drop() {
+ enum X {
+ X(std::fs::File),
+ Y(u32),
+ }
+
+ let _ = None.unwrap_or(X::Y(0));
+ }
+}
+
++mod issue8993 {
++ fn g() -> i32 {
++ 3
++ }
++
++ fn f(n: i32) -> i32 {
++ n
++ }
++
++ fn test_map_or() {
++ let _ = Some(4).map_or(g(), |v| v);
++ let _ = Some(4).map_or(g(), f);
++ let _ = Some(4).map_or(0, f);
++ }
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 26 previous errors
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:48:22
+ |
+LL | with_constructor.unwrap_or(make());
+ | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(make)`
+ |
+ = note: `-D clippy::or-fun-call` implied by `-D warnings`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:51:14
+ |
+LL | with_new.unwrap_or(Vec::new());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:54:21
+ |
+LL | with_const_args.unwrap_or(Vec::with_capacity(12));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Vec::with_capacity(12))`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:57:14
+ |
+LL | with_err.unwrap_or(make());
+ | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| make())`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:60:19
+ |
+LL | with_err_args.unwrap_or(Vec::with_capacity(12));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| Vec::with_capacity(12))`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:63:24
+ |
+LL | with_default_trait.unwrap_or(Default::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:66:23
+ |
+LL | with_default_type.unwrap_or(u64::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:69:18
+ |
+LL | self_default.unwrap_or(<FakeDefault>::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(<FakeDefault>::default)`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:72:18
+ |
+LL | real_default.unwrap_or(<FakeDefault as Default>::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:75:14
+ |
+LL | with_vec.unwrap_or(vec![]);
+ | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:78:21
+ |
+LL | without_default.unwrap_or(Foo::new());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)`
+
+error: use of `or_insert` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:81:19
+ |
+LL | map.entry(42).or_insert(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_default()`
+
+error: use of `or_insert` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:84:23
+ |
+LL | map_vec.entry(42).or_insert(vec![]);
+ | ^^^^^^^^^^^^^^^^^ help: try this: `or_default()`
+
+error: use of `or_insert` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:87:21
+ |
+LL | btree.entry(42).or_insert(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_default()`
+
+error: use of `or_insert` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:90:25
+ |
+LL | btree_vec.entry(42).or_insert(vec![]);
+ | ^^^^^^^^^^^^^^^^^ help: try this: `or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:93:21
+ |
+LL | let _ = stringy.unwrap_or(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:101:21
+ |
+LL | let _ = Some(1).unwrap_or(map[&1]);
+ | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:103:21
+ |
+LL | let _ = Some(1).unwrap_or(map[&1]);
+ | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])`
+
+error: use of `or` followed by a function call
+ --> $DIR/or_fun_call.rs:127:35
+ |
+LL | let _ = Some("a".to_string()).or(Some("b".to_string()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:166:14
+ |
+LL | None.unwrap_or(ptr_to_ref(s));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| ptr_to_ref(s))`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:172:14
+ |
+LL | None.unwrap_or(unsafe { ptr_to_ref(s) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:174:14
+ |
+LL | None.unwrap_or( unsafe { ptr_to_ref(s) } );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:188:14
+ |
+LL | .unwrap_or(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:201:14
+ |
+LL | .unwrap_or(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:213:14
+ |
+LL | .unwrap_or(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:224:10
+ |
+LL | .unwrap_or(String::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
+
++error: use of `map_or` followed by a function call
++ --> $DIR/or_fun_call.rs:249:25
++ |
++LL | let _ = Some(4).map_or(g(), |v| v);
++ | ^^^^^^^^^^^^^^^^^^ help: try this: `map_or_else(g, |v| v)`
++
++error: use of `map_or` followed by a function call
++ --> $DIR/or_fun_call.rs:250:25
++ |
++LL | let _ = Some(4).map_or(g(), f);
++ | ^^^^^^^^^^^^^^ help: try this: `map_or_else(g, f)`
++
++error: aborting due to 28 previous errors
+
--- /dev/null
+// run-rustfix
+#![allow(unreachable_code)]
+#![allow(dead_code)]
+#![allow(clippy::unnecessary_wraps)]
+
+fn some_func(a: Option<u32>) -> Option<u32> {
+ a?;
+
+ a
+}
+
+fn some_other_func(a: Option<u32>) -> Option<u32> {
+ if a.is_none() {
+ return None;
+ } else {
+ return Some(0);
+ }
+ unreachable!()
+}
+
+pub enum SeemsOption<T> {
+ Some(T),
+ None,
+}
+
+impl<T> SeemsOption<T> {
+ pub fn is_none(&self) -> bool {
+ match *self {
+ SeemsOption::None => true,
+ SeemsOption::Some(_) => false,
+ }
+ }
+}
+
+fn returns_something_similar_to_option(a: SeemsOption<u32>) -> SeemsOption<u32> {
+ if a.is_none() {
+ return SeemsOption::None;
+ }
+
+ a
+}
+
+pub struct CopyStruct {
+ pub opt: Option<u32>,
+}
+
+impl CopyStruct {
+ #[rustfmt::skip]
+ pub fn func(&self) -> Option<u32> {
+ (self.opt)?;
+
+ self.opt?;
+
+ let _ = Some(self.opt?);
+
+ let _ = self.opt?;
+
+ self.opt
+ }
+}
+
+#[derive(Clone)]
+pub struct MoveStruct {
+ pub opt: Option<Vec<u32>>,
+}
+
+impl MoveStruct {
+ pub fn ref_func(&self) -> Option<Vec<u32>> {
+ self.opt.as_ref()?;
+
+ self.opt.clone()
+ }
+
+ pub fn mov_func_reuse(self) -> Option<Vec<u32>> {
+ self.opt.as_ref()?;
+
+ self.opt
+ }
+
+ pub fn mov_func_no_use(self) -> Option<Vec<u32>> {
+ self.opt.as_ref()?;
+ Some(Vec::new())
+ }
+
+ pub fn if_let_ref_func(self) -> Option<Vec<u32>> {
+ let v: &Vec<_> = self.opt.as_ref()?;
+
+ Some(v.clone())
+ }
+
+ pub fn if_let_mov_func(self) -> Option<Vec<u32>> {
+ let v = self.opt?;
+
+ Some(v)
+ }
+}
+
+fn func() -> Option<i32> {
+ fn f() -> Option<String> {
+ Some(String::new())
+ }
+
+ f()?;
+
+ Some(0)
+}
+
+fn func_returning_result() -> Result<i32, i32> {
+ Ok(1)
+}
+
+fn result_func(x: Result<i32, i32>) -> Result<i32, i32> {
+ let _ = x?;
+
+ x?;
+
+ // No warning
+ let y = if let Ok(x) = x {
+ x
+ } else {
+ return Err(0);
+ };
+
+ // issue #7859
+ // no warning
+ let _ = if let Ok(x) = func_returning_result() {
+ x
+ } else {
+ return Err(0);
+ };
+
+ // no warning
+ if func_returning_result().is_err() {
+ return func_returning_result();
+ }
+
++ // no warning
++ let _ = if let Err(e) = x { Err(e) } else { Ok(0) };
++
+ Ok(y)
+}
+
+// see issue #8019
+pub enum NotOption {
+ None,
+ First,
+ AfterFirst,
+}
+
+fn obj(_: i32) -> Result<(), NotOption> {
+ Err(NotOption::First)
+}
+
+fn f() -> NotOption {
+ if obj(2).is_err() {
+ return NotOption::None;
+ }
+ NotOption::First
+}
+
+fn do_something() {}
+
+fn err_immediate_return() -> Result<i32, i32> {
+ func_returning_result()?;
+ Ok(1)
+}
+
+fn err_immediate_return_and_do_something() -> Result<i32, i32> {
+ func_returning_result()?;
+ do_something();
+ Ok(1)
+}
+
+// No warning
+fn no_immediate_return() -> Result<i32, i32> {
+ if let Err(err) = func_returning_result() {
+ do_something();
+ return Err(err);
+ }
+ Ok(1)
+}
+
+// No warning
+fn mixed_result_and_option() -> Option<i32> {
+ if let Err(err) = func_returning_result() {
+ return Some(err);
+ }
+ None
+}
+
+// No warning
+fn else_if_check() -> Result<i32, i32> {
+ if true {
+ Ok(1)
+ } else if let Err(e) = func_returning_result() {
+ Err(e)
+ } else {
+ Err(-1)
+ }
+}
+
+// No warning
+#[allow(clippy::manual_map)]
+#[rustfmt::skip]
+fn option_map() -> Option<bool> {
+ if let Some(a) = Some(false) {
+ Some(!a)
+ } else {
+ None
+ }
+}
+
+pub struct PatternedError {
+ flag: bool,
+}
+
+// No warning
+fn pattern() -> Result<(), PatternedError> {
+ let res = Ok(());
+
+ if let Err(err @ PatternedError { flag: true }) = res {
+ return Err(err);
+ }
+
+ res
+}
+
+fn main() {}
+
+// should not lint, `?` operator not available in const context
+const fn issue9175(option: Option<()>) -> Option<()> {
+ if option.is_none() {
+ return None;
+ }
+ //stuff
+ Some(())
+}
--- /dev/null
+// run-rustfix
+#![allow(unreachable_code)]
+#![allow(dead_code)]
+#![allow(clippy::unnecessary_wraps)]
+
+fn some_func(a: Option<u32>) -> Option<u32> {
+ if a.is_none() {
+ return None;
+ }
+
+ a
+}
+
+fn some_other_func(a: Option<u32>) -> Option<u32> {
+ if a.is_none() {
+ return None;
+ } else {
+ return Some(0);
+ }
+ unreachable!()
+}
+
+pub enum SeemsOption<T> {
+ Some(T),
+ None,
+}
+
+impl<T> SeemsOption<T> {
+ pub fn is_none(&self) -> bool {
+ match *self {
+ SeemsOption::None => true,
+ SeemsOption::Some(_) => false,
+ }
+ }
+}
+
+fn returns_something_similar_to_option(a: SeemsOption<u32>) -> SeemsOption<u32> {
+ if a.is_none() {
+ return SeemsOption::None;
+ }
+
+ a
+}
+
+pub struct CopyStruct {
+ pub opt: Option<u32>,
+}
+
+impl CopyStruct {
+ #[rustfmt::skip]
+ pub fn func(&self) -> Option<u32> {
+ if (self.opt).is_none() {
+ return None;
+ }
+
+ if self.opt.is_none() {
+ return None
+ }
+
+ let _ = if self.opt.is_none() {
+ return None;
+ } else {
+ self.opt
+ };
+
+ let _ = if let Some(x) = self.opt {
+ x
+ } else {
+ return None;
+ };
+
+ self.opt
+ }
+}
+
+#[derive(Clone)]
+pub struct MoveStruct {
+ pub opt: Option<Vec<u32>>,
+}
+
+impl MoveStruct {
+ pub fn ref_func(&self) -> Option<Vec<u32>> {
+ if self.opt.is_none() {
+ return None;
+ }
+
+ self.opt.clone()
+ }
+
+ pub fn mov_func_reuse(self) -> Option<Vec<u32>> {
+ if self.opt.is_none() {
+ return None;
+ }
+
+ self.opt
+ }
+
+ pub fn mov_func_no_use(self) -> Option<Vec<u32>> {
+ if self.opt.is_none() {
+ return None;
+ }
+ Some(Vec::new())
+ }
+
+ pub fn if_let_ref_func(self) -> Option<Vec<u32>> {
+ let v: &Vec<_> = if let Some(ref v) = self.opt {
+ v
+ } else {
+ return None;
+ };
+
+ Some(v.clone())
+ }
+
+ pub fn if_let_mov_func(self) -> Option<Vec<u32>> {
+ let v = if let Some(v) = self.opt {
+ v
+ } else {
+ return None;
+ };
+
+ Some(v)
+ }
+}
+
+fn func() -> Option<i32> {
+ fn f() -> Option<String> {
+ Some(String::new())
+ }
+
+ if f().is_none() {
+ return None;
+ }
+
+ Some(0)
+}
+
+fn func_returning_result() -> Result<i32, i32> {
+ Ok(1)
+}
+
+fn result_func(x: Result<i32, i32>) -> Result<i32, i32> {
+ let _ = if let Ok(x) = x { x } else { return x };
+
+ if x.is_err() {
+ return x;
+ }
+
+ // No warning
+ let y = if let Ok(x) = x {
+ x
+ } else {
+ return Err(0);
+ };
+
+ // issue #7859
+ // no warning
+ let _ = if let Ok(x) = func_returning_result() {
+ x
+ } else {
+ return Err(0);
+ };
+
+ // no warning
+ if func_returning_result().is_err() {
+ return func_returning_result();
+ }
+
++ // no warning
++ let _ = if let Err(e) = x { Err(e) } else { Ok(0) };
++
+ Ok(y)
+}
+
+// see issue #8019
+pub enum NotOption {
+ None,
+ First,
+ AfterFirst,
+}
+
+fn obj(_: i32) -> Result<(), NotOption> {
+ Err(NotOption::First)
+}
+
+fn f() -> NotOption {
+ if obj(2).is_err() {
+ return NotOption::None;
+ }
+ NotOption::First
+}
+
+fn do_something() {}
+
+fn err_immediate_return() -> Result<i32, i32> {
+ if let Err(err) = func_returning_result() {
+ return Err(err);
+ }
+ Ok(1)
+}
+
+fn err_immediate_return_and_do_something() -> Result<i32, i32> {
+ if let Err(err) = func_returning_result() {
+ return Err(err);
+ }
+ do_something();
+ Ok(1)
+}
+
+// No warning
+fn no_immediate_return() -> Result<i32, i32> {
+ if let Err(err) = func_returning_result() {
+ do_something();
+ return Err(err);
+ }
+ Ok(1)
+}
+
+// No warning
+fn mixed_result_and_option() -> Option<i32> {
+ if let Err(err) = func_returning_result() {
+ return Some(err);
+ }
+ None
+}
+
+// No warning
+fn else_if_check() -> Result<i32, i32> {
+ if true {
+ Ok(1)
+ } else if let Err(e) = func_returning_result() {
+ Err(e)
+ } else {
+ Err(-1)
+ }
+}
+
+// No warning
+#[allow(clippy::manual_map)]
+#[rustfmt::skip]
+fn option_map() -> Option<bool> {
+ if let Some(a) = Some(false) {
+ Some(!a)
+ } else {
+ None
+ }
+}
+
+pub struct PatternedError {
+ flag: bool,
+}
+
+// No warning
+fn pattern() -> Result<(), PatternedError> {
+ let res = Ok(());
+
+ if let Err(err @ PatternedError { flag: true }) = res {
+ return Err(err);
+ }
+
+ res
+}
+
+fn main() {}
+
+// should not lint, `?` operator not available in const context
+const fn issue9175(option: Option<()>) -> Option<()> {
+ if option.is_none() {
+ return None;
+ }
+ //stuff
+ Some(())
+}
--- /dev/null
- --> $DIR/question_mark.rs:193:5
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:7:5
+ |
+LL | / if a.is_none() {
+LL | | return None;
+LL | | }
+ | |_____^ help: replace it with: `a?;`
+ |
+ = note: `-D clippy::question-mark` implied by `-D warnings`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:52:9
+ |
+LL | / if (self.opt).is_none() {
+LL | | return None;
+LL | | }
+ | |_________^ help: replace it with: `(self.opt)?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:56:9
+ |
+LL | / if self.opt.is_none() {
+LL | | return None
+LL | | }
+ | |_________^ help: replace it with: `self.opt?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:60:17
+ |
+LL | let _ = if self.opt.is_none() {
+ | _________________^
+LL | | return None;
+LL | | } else {
+LL | | self.opt
+LL | | };
+ | |_________^ help: replace it with: `Some(self.opt?)`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:66:17
+ |
+LL | let _ = if let Some(x) = self.opt {
+ | _________________^
+LL | | x
+LL | | } else {
+LL | | return None;
+LL | | };
+ | |_________^ help: replace it with: `self.opt?`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:83:9
+ |
+LL | / if self.opt.is_none() {
+LL | | return None;
+LL | | }
+ | |_________^ help: replace it with: `self.opt.as_ref()?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:91:9
+ |
+LL | / if self.opt.is_none() {
+LL | | return None;
+LL | | }
+ | |_________^ help: replace it with: `self.opt.as_ref()?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:99:9
+ |
+LL | / if self.opt.is_none() {
+LL | | return None;
+LL | | }
+ | |_________^ help: replace it with: `self.opt.as_ref()?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:106:26
+ |
+LL | let v: &Vec<_> = if let Some(ref v) = self.opt {
+ | __________________________^
+LL | | v
+LL | | } else {
+LL | | return None;
+LL | | };
+ | |_________^ help: replace it with: `self.opt.as_ref()?`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:116:17
+ |
+LL | let v = if let Some(v) = self.opt {
+ | _________________^
+LL | | v
+LL | | } else {
+LL | | return None;
+LL | | };
+ | |_________^ help: replace it with: `self.opt?`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:131:5
+ |
+LL | / if f().is_none() {
+LL | | return None;
+LL | | }
+ | |_____^ help: replace it with: `f()?;`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:143:13
+ |
+LL | let _ = if let Ok(x) = x { x } else { return x };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?`
+
+error: this block may be rewritten with the `?` operator
+ --> $DIR/question_mark.rs:145:5
+ |
+LL | / if x.is_err() {
+LL | | return x;
+LL | | }
+ | |_____^ help: replace it with: `x?;`
+
+error: this block may be rewritten with the `?` operator
- --> $DIR/question_mark.rs:200:5
++ --> $DIR/question_mark.rs:196:5
+ |
+LL | / if let Err(err) = func_returning_result() {
+LL | | return Err(err);
+LL | | }
+ | |_____^ help: replace it with: `func_returning_result()?;`
+
+error: this block may be rewritten with the `?` operator
++ --> $DIR/question_mark.rs:203:5
+ |
+LL | / if let Err(err) = func_returning_result() {
+LL | | return Err(err);
+LL | | }
+ | |_____^ help: replace it with: `func_returning_result()?;`
+
+error: aborting due to 15 previous errors
+
--- /dev/null
- #![allow(for_loops_over_fallibles)]
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+// run-rustfix
+
+#![allow(clippy::disallowed_names)]
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::disallowed_methods)]
+#![allow(clippy::disallowed_types)]
+#![allow(clippy::mixed_read_write_in_expression)]
- #![warn(for_loops_over_fallibles)]
- #![warn(for_loops_over_fallibles)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::match_result_ok)]
+#![allow(clippy::overly_complex_bool_expr)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::needless_borrow)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::recursive_format_impl)]
+#![allow(clippy::invisible_characters)]
+#![allow(drop_bounds)]
++#![allow(for_loops_over_fallibles)]
+#![allow(array_into_iter)]
+#![allow(invalid_atomic_ordering)]
+#![allow(invalid_value)]
++#![allow(let_underscore_drop)]
+#![allow(enum_intrinsics_non_enums)]
+#![allow(non_fmt_panics)]
+#![allow(named_arguments_used_positionally)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(unknown_lints)]
+#![allow(unused_labels)]
+#![warn(clippy::disallowed_names)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::box_collection)]
+#![warn(clippy::redundant_static_lifetimes)]
+#![warn(clippy::cognitive_complexity)]
+#![warn(clippy::disallowed_methods)]
+#![warn(clippy::disallowed_types)]
+#![warn(clippy::mixed_read_write_in_expression)]
+#![warn(clippy::useless_conversion)]
+#![warn(clippy::match_result_ok)]
+#![warn(clippy::overly_complex_bool_expr)]
+#![warn(clippy::new_without_default)]
+#![warn(clippy::bind_instead_of_map)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::needless_borrow)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::single_char_add_str)]
+#![warn(clippy::module_name_repetitions)]
+#![warn(clippy::recursive_format_impl)]
+#![warn(clippy::invisible_characters)]
+#![warn(drop_bounds)]
+#![warn(for_loops_over_fallibles)]
++#![warn(for_loops_over_fallibles)]
++#![warn(for_loops_over_fallibles)]
+#![warn(array_into_iter)]
+#![warn(invalid_atomic_ordering)]
+#![warn(invalid_value)]
++#![warn(let_underscore_drop)]
+#![warn(enum_intrinsics_non_enums)]
+#![warn(non_fmt_panics)]
+#![warn(named_arguments_used_positionally)]
+#![warn(temporary_cstring_as_ptr)]
+#![warn(unknown_lints)]
+#![warn(unused_labels)]
+
+fn main() {}
--- /dev/null
- #![allow(for_loops_over_fallibles)]
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+// run-rustfix
+
+#![allow(clippy::disallowed_names)]
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::disallowed_methods)]
+#![allow(clippy::disallowed_types)]
+#![allow(clippy::mixed_read_write_in_expression)]
- #![warn(clippy::for_loop_over_option)]
- #![warn(clippy::for_loop_over_result)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::match_result_ok)]
+#![allow(clippy::overly_complex_bool_expr)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::needless_borrow)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::recursive_format_impl)]
+#![allow(clippy::invisible_characters)]
+#![allow(drop_bounds)]
++#![allow(for_loops_over_fallibles)]
+#![allow(array_into_iter)]
+#![allow(invalid_atomic_ordering)]
+#![allow(invalid_value)]
++#![allow(let_underscore_drop)]
+#![allow(enum_intrinsics_non_enums)]
+#![allow(non_fmt_panics)]
+#![allow(named_arguments_used_positionally)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(unknown_lints)]
+#![allow(unused_labels)]
+#![warn(clippy::blacklisted_name)]
+#![warn(clippy::block_in_if_condition_expr)]
+#![warn(clippy::block_in_if_condition_stmt)]
+#![warn(clippy::box_vec)]
+#![warn(clippy::const_static_lifetime)]
+#![warn(clippy::cyclomatic_complexity)]
+#![warn(clippy::disallowed_method)]
+#![warn(clippy::disallowed_type)]
+#![warn(clippy::eval_order_dependence)]
+#![warn(clippy::identity_conversion)]
+#![warn(clippy::if_let_some_result)]
+#![warn(clippy::logic_bug)]
+#![warn(clippy::new_without_default_derive)]
+#![warn(clippy::option_and_then_some)]
+#![warn(clippy::option_expect_used)]
+#![warn(clippy::option_map_unwrap_or)]
+#![warn(clippy::option_map_unwrap_or_else)]
+#![warn(clippy::option_unwrap_used)]
+#![warn(clippy::ref_in_deref)]
+#![warn(clippy::result_expect_used)]
+#![warn(clippy::result_map_unwrap_or_else)]
+#![warn(clippy::result_unwrap_used)]
+#![warn(clippy::single_char_push_str)]
+#![warn(clippy::stutter)]
+#![warn(clippy::to_string_in_display)]
+#![warn(clippy::zero_width_space)]
+#![warn(clippy::drop_bounds)]
++#![warn(clippy::for_loop_over_option)]
++#![warn(clippy::for_loop_over_result)]
+#![warn(clippy::for_loops_over_fallibles)]
+#![warn(clippy::into_iter_on_array)]
+#![warn(clippy::invalid_atomic_ordering)]
+#![warn(clippy::invalid_ref)]
++#![warn(clippy::let_underscore_drop)]
+#![warn(clippy::mem_discriminant_non_enum)]
+#![warn(clippy::panic_params)]
+#![warn(clippy::positional_named_format_parameters)]
+#![warn(clippy::temporary_cstring_as_ptr)]
+#![warn(clippy::unknown_clippy_lints)]
+#![warn(clippy::unused_label)]
+
+fn main() {}
--- /dev/null
- --> $DIR/rename.rs:39:9
+error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names`
- --> $DIR/rename.rs:40:9
++ --> $DIR/rename.rs:40:9
+ |
+LL | #![warn(clippy::blacklisted_name)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names`
+ |
+ = note: `-D renamed-and-removed-lints` implied by `-D warnings`
+
+error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:41:9
++ --> $DIR/rename.rs:41:9
+ |
+LL | #![warn(clippy::block_in_if_condition_expr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+
+error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:42:9
++ --> $DIR/rename.rs:42:9
+ |
+LL | #![warn(clippy::block_in_if_condition_stmt)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+
+error: lint `clippy::box_vec` has been renamed to `clippy::box_collection`
- --> $DIR/rename.rs:43:9
++ --> $DIR/rename.rs:43:9
+ |
+LL | #![warn(clippy::box_vec)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection`
+
+error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes`
- --> $DIR/rename.rs:44:9
++ --> $DIR/rename.rs:44:9
+ |
+LL | #![warn(clippy::const_static_lifetime)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes`
+
+error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity`
- --> $DIR/rename.rs:45:9
++ --> $DIR/rename.rs:45:9
+ |
+LL | #![warn(clippy::cyclomatic_complexity)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity`
+
+error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods`
- --> $DIR/rename.rs:46:9
++ --> $DIR/rename.rs:46:9
+ |
+LL | #![warn(clippy::disallowed_method)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods`
+
+error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types`
- --> $DIR/rename.rs:47:9
++ --> $DIR/rename.rs:47:9
+ |
+LL | #![warn(clippy::disallowed_type)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types`
+
+error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression`
- error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles`
- --> $DIR/rename.rs:48:9
- |
- LL | #![warn(clippy::for_loop_over_option)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles`
-
- error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles`
- --> $DIR/rename.rs:49:9
- |
- LL | #![warn(clippy::for_loop_over_result)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles`
-
++ --> $DIR/rename.rs:48:9
+ |
+LL | #![warn(clippy::eval_order_dependence)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression`
+
- --> $DIR/rename.rs:50:9
+error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion`
- --> $DIR/rename.rs:51:9
++ --> $DIR/rename.rs:49:9
+ |
+LL | #![warn(clippy::identity_conversion)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion`
+
+error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok`
- --> $DIR/rename.rs:52:9
++ --> $DIR/rename.rs:50:9
+ |
+LL | #![warn(clippy::if_let_some_result)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok`
+
+error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr`
- --> $DIR/rename.rs:53:9
++ --> $DIR/rename.rs:51:9
+ |
+LL | #![warn(clippy::logic_bug)]
+ | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr`
+
+error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default`
- --> $DIR/rename.rs:54:9
++ --> $DIR/rename.rs:52:9
+ |
+LL | #![warn(clippy::new_without_default_derive)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default`
+
+error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map`
- --> $DIR/rename.rs:55:9
++ --> $DIR/rename.rs:53:9
+ |
+LL | #![warn(clippy::option_and_then_some)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map`
+
+error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:56:9
++ --> $DIR/rename.rs:54:9
+ |
+LL | #![warn(clippy::option_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:57:9
++ --> $DIR/rename.rs:55:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:58:9
++ --> $DIR/rename.rs:56:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:59:9
++ --> $DIR/rename.rs:57:9
+ |
+LL | #![warn(clippy::option_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow`
- --> $DIR/rename.rs:60:9
++ --> $DIR/rename.rs:58:9
+ |
+LL | #![warn(clippy::ref_in_deref)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow`
+
+error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:61:9
++ --> $DIR/rename.rs:59:9
+ |
+LL | #![warn(clippy::result_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:62:9
++ --> $DIR/rename.rs:60:9
+ |
+LL | #![warn(clippy::result_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:63:9
++ --> $DIR/rename.rs:61:9
+ |
+LL | #![warn(clippy::result_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str`
- --> $DIR/rename.rs:64:9
++ --> $DIR/rename.rs:62:9
+ |
+LL | #![warn(clippy::single_char_push_str)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str`
+
+error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions`
- --> $DIR/rename.rs:65:9
++ --> $DIR/rename.rs:63:9
+ |
+LL | #![warn(clippy::stutter)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions`
+
+error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl`
- --> $DIR/rename.rs:66:9
++ --> $DIR/rename.rs:64:9
+ |
+LL | #![warn(clippy::to_string_in_display)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl`
+
+error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters`
- --> $DIR/rename.rs:67:9
++ --> $DIR/rename.rs:65:9
+ |
+LL | #![warn(clippy::zero_width_space)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters`
+
+error: lint `clippy::drop_bounds` has been renamed to `drop_bounds`
- error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles`
++ --> $DIR/rename.rs:66:9
+ |
+LL | #![warn(clippy::drop_bounds)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds`
+
- --> $DIR/rename.rs:69:9
++error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles`
++ --> $DIR/rename.rs:67:9
++ |
++LL | #![warn(clippy::for_loop_over_option)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles`
++
++error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles`
+ --> $DIR/rename.rs:68:9
+ |
++LL | #![warn(clippy::for_loop_over_result)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles`
++
++error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles`
++ --> $DIR/rename.rs:69:9
++ |
+LL | #![warn(clippy::for_loops_over_fallibles)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles`
+
+error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter`
- --> $DIR/rename.rs:70:9
++ --> $DIR/rename.rs:70:9
+ |
+LL | #![warn(clippy::into_iter_on_array)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter`
+
+error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering`
- --> $DIR/rename.rs:71:9
++ --> $DIR/rename.rs:71:9
+ |
+LL | #![warn(clippy::invalid_atomic_ordering)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering`
+
+error: lint `clippy::invalid_ref` has been renamed to `invalid_value`
- --> $DIR/rename.rs:72:9
++ --> $DIR/rename.rs:72:9
+ |
+LL | #![warn(clippy::invalid_ref)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value`
+
++error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop`
++ --> $DIR/rename.rs:73:9
++ |
++LL | #![warn(clippy::let_underscore_drop)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop`
++
+error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums`
- --> $DIR/rename.rs:73:9
++ --> $DIR/rename.rs:74:9
+ |
+LL | #![warn(clippy::mem_discriminant_non_enum)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums`
+
+error: lint `clippy::panic_params` has been renamed to `non_fmt_panics`
- --> $DIR/rename.rs:74:9
++ --> $DIR/rename.rs:75:9
+ |
+LL | #![warn(clippy::panic_params)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics`
+
+error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally`
- --> $DIR/rename.rs:75:9
++ --> $DIR/rename.rs:76:9
+ |
+LL | #![warn(clippy::positional_named_format_parameters)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally`
+
+error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr`
- --> $DIR/rename.rs:76:9
++ --> $DIR/rename.rs:77:9
+ |
+LL | #![warn(clippy::temporary_cstring_as_ptr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr`
+
+error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints`
- --> $DIR/rename.rs:77:9
++ --> $DIR/rename.rs:78:9
+ |
+LL | #![warn(clippy::unknown_clippy_lints)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints`
+
+error: lint `clippy::unused_label` has been renamed to `unused_labels`
- error: aborting due to 39 previous errors
++ --> $DIR/rename.rs:79:9
+ |
+LL | #![warn(clippy::unused_label)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels`
+
++error: aborting due to 40 previous errors
+
--- /dev/null
+#![warn(clippy::result_large_err)]
+#![allow(clippy::large_enum_variant)]
+
+pub fn small_err() -> Result<(), u128> {
+ Ok(())
+}
+
+pub fn large_err() -> Result<(), [u8; 512]> {
+ Ok(())
+}
+
+pub struct FullyDefinedLargeError {
+ _foo: u128,
+ _bar: [u8; 100],
+ _foobar: [u8; 120],
+}
+
+impl FullyDefinedLargeError {
+ pub fn ret() -> Result<(), Self> {
+ Ok(())
+ }
+}
+
+pub fn struct_error() -> Result<(), FullyDefinedLargeError> {
+ Ok(())
+}
+
+type Fdlr<T> = std::result::Result<T, FullyDefinedLargeError>;
+pub fn large_err_via_type_alias<T>(x: T) -> Fdlr<T> {
+ Ok(x)
+}
+
+pub fn param_small_error<R>() -> Result<(), (R, u128)> {
+ Ok(())
+}
+
+pub fn param_large_error<R>() -> Result<(), (u128, R, FullyDefinedLargeError)> {
+ Ok(())
+}
+
+pub enum LargeErrorVariants<T> {
+ _Small(u8),
+ _Omg([u8; 512]),
+ _Param(T),
+}
+
+impl LargeErrorVariants<()> {
+ pub fn large_enum_error() -> Result<(), Self> {
+ Ok(())
+ }
+}
+
++enum MultipleLargeVariants {
++ _Biggest([u8; 1024]),
++ _AlsoBig([u8; 512]),
++ _Ok(usize),
++}
++
++impl MultipleLargeVariants {
++ fn large_enum_error() -> Result<(), Self> {
++ Ok(())
++ }
++}
++
+trait TraitForcesLargeError {
+ fn large_error() -> Result<(), [u8; 512]> {
+ Ok(())
+ }
+}
+
+struct TraitImpl;
+
+impl TraitForcesLargeError for TraitImpl {
+ // Should not lint
+ fn large_error() -> Result<(), [u8; 512]> {
+ Ok(())
+ }
+}
+
+pub union FullyDefinedUnionError {
+ _maybe: u8,
+ _or_even: [[u8; 16]; 32],
+}
+
+pub fn large_union_err() -> Result<(), FullyDefinedUnionError> {
+ Ok(())
+}
+
+pub union UnionError<T: Copy> {
+ _maybe: T,
+ _or_perhaps_even: (T, [u8; 512]),
+}
+
+pub fn param_large_union<T: Copy>() -> Result<(), UnionError<T>> {
+ Ok(())
+}
+
+pub struct ArrayError<T, U> {
+ _large_array: [T; 32],
+ _other_stuff: U,
+}
+
+pub fn array_error_subst<U>() -> Result<(), ArrayError<i32, U>> {
+ Ok(())
+}
+
+pub fn array_error<T, U>() -> Result<(), ArrayError<(i32, T), U>> {
+ Ok(())
+}
+
+fn main() {}
--- /dev/null
- | ^^^^^^^^^^^^^^^^ the `Err`-variant is at least 513 bytes
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:8:23
+ |
+LL | pub fn large_err() -> Result<(), [u8; 512]> {
+ | ^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes
+ |
+ = help: try reducing the size of `[u8; 512]`, for example by boxing large elements or replacing it with `Box<[u8; 512]>`
+ = note: `-D clippy::result-large-err` implied by `-D warnings`
+
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:19:21
+ |
+LL | pub fn ret() -> Result<(), Self> {
+ | ^^^^^^^^^^^^^^^^ the `Err`-variant is at least 240 bytes
+ |
+ = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>`
+
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:24:26
+ |
+LL | pub fn struct_error() -> Result<(), FullyDefinedLargeError> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 240 bytes
+ |
+ = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>`
+
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:29:45
+ |
+LL | pub fn large_err_via_type_alias<T>(x: T) -> Fdlr<T> {
+ | ^^^^^^^ the `Err`-variant is at least 240 bytes
+ |
+ = help: try reducing the size of `FullyDefinedLargeError`, for example by boxing large elements or replacing it with `Box<FullyDefinedLargeError>`
+
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:37:34
+ |
+LL | pub fn param_large_error<R>() -> Result<(), (u128, R, FullyDefinedLargeError)> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 256 bytes
+ |
+ = help: try reducing the size of `(u128, R, FullyDefinedLargeError)`, for example by boxing large elements or replacing it with `Box<(u128, R, FullyDefinedLargeError)>`
+
+error: the `Err`-variant returned from this function is very large
+ --> $DIR/result_large_err.rs:48:34
+ |
++LL | _Omg([u8; 512]),
++ | --------------- the largest variant contains at least 512 bytes
++...
+LL | pub fn large_enum_error() -> Result<(), Self> {
- --> $DIR/result_large_err.rs:54:25
++ | ^^^^^^^^^^^^^^^^
+ |
+ = help: try reducing the size of `LargeErrorVariants<()>`, for example by boxing large elements or replacing it with `Box<LargeErrorVariants<()>>`
+
+error: the `Err`-variant returned from this function is very large
- --> $DIR/result_large_err.rs:73:29
++ --> $DIR/result_large_err.rs:60:30
++ |
++LL | _Biggest([u8; 1024]),
++ | -------------------- the largest variant contains at least 1024 bytes
++LL | _AlsoBig([u8; 512]),
++ | ------------------- the variant `_AlsoBig` contains at least 512 bytes
++...
++LL | fn large_enum_error() -> Result<(), Self> {
++ | ^^^^^^^^^^^^^^^^
++ |
++ = help: try reducing the size of `MultipleLargeVariants`, for example by boxing large elements or replacing it with `Box<MultipleLargeVariants>`
++
++error: the `Err`-variant returned from this function is very large
++ --> $DIR/result_large_err.rs:66:25
+ |
+LL | fn large_error() -> Result<(), [u8; 512]> {
+ | ^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes
+ |
+ = help: try reducing the size of `[u8; 512]`, for example by boxing large elements or replacing it with `Box<[u8; 512]>`
+
+error: the `Err`-variant returned from this function is very large
- --> $DIR/result_large_err.rs:82:40
++ --> $DIR/result_large_err.rs:85:29
+ |
+LL | pub fn large_union_err() -> Result<(), FullyDefinedUnionError> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes
+ |
+ = help: try reducing the size of `FullyDefinedUnionError`, for example by boxing large elements or replacing it with `Box<FullyDefinedUnionError>`
+
+error: the `Err`-variant returned from this function is very large
- --> $DIR/result_large_err.rs:91:34
++ --> $DIR/result_large_err.rs:94:40
+ |
+LL | pub fn param_large_union<T: Copy>() -> Result<(), UnionError<T>> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes
+ |
+ = help: try reducing the size of `UnionError<T>`, for example by boxing large elements or replacing it with `Box<UnionError<T>>`
+
+error: the `Err`-variant returned from this function is very large
- --> $DIR/result_large_err.rs:95:31
++ --> $DIR/result_large_err.rs:103:34
+ |
+LL | pub fn array_error_subst<U>() -> Result<(), ArrayError<i32, U>> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 128 bytes
+ |
+ = help: try reducing the size of `ArrayError<i32, U>`, for example by boxing large elements or replacing it with `Box<ArrayError<i32, U>>`
+
+error: the `Err`-variant returned from this function is very large
- error: aborting due to 11 previous errors
++ --> $DIR/result_large_err.rs:107:31
+ |
+LL | pub fn array_error<T, U>() -> Result<(), ArrayError<(i32, T), U>> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 128 bytes
+ |
+ = help: try reducing the size of `ArrayError<(i32, T), U>`, for example by boxing large elements or replacing it with `Box<ArrayError<(i32, T), U>>`
+
++error: aborting due to 12 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::seek_from_current)]
++#![feature(custom_inner_attributes)]
++
++use std::fs::File;
++use std::io::{self, Seek, SeekFrom, Write};
++
++fn _msrv_1_50() -> io::Result<()> {
++ #![clippy::msrv = "1.50"]
++ let mut f = File::create("foo.txt")?;
++ f.write_all(b"Hi!")?;
++ f.seek(SeekFrom::Current(0))?;
++ f.seek(SeekFrom::Current(1))?;
++ Ok(())
++}
++
++fn _msrv_1_51() -> io::Result<()> {
++ #![clippy::msrv = "1.51"]
++ let mut f = File::create("foo.txt")?;
++ f.write_all(b"Hi!")?;
++ f.stream_position()?;
++ f.seek(SeekFrom::Current(1))?;
++ Ok(())
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::seek_from_current)]
++#![feature(custom_inner_attributes)]
++
++use std::fs::File;
++use std::io::{self, Seek, SeekFrom, Write};
++
++fn _msrv_1_50() -> io::Result<()> {
++ #![clippy::msrv = "1.50"]
++ let mut f = File::create("foo.txt")?;
++ f.write_all(b"Hi!")?;
++ f.seek(SeekFrom::Current(0))?;
++ f.seek(SeekFrom::Current(1))?;
++ Ok(())
++}
++
++fn _msrv_1_51() -> io::Result<()> {
++ #![clippy::msrv = "1.51"]
++ let mut f = File::create("foo.txt")?;
++ f.write_all(b"Hi!")?;
++ f.seek(SeekFrom::Current(0))?;
++ f.seek(SeekFrom::Current(1))?;
++ Ok(())
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: using `SeekFrom::Current` to start from current position
++ --> $DIR/seek_from_current.rs:21:5
++ |
++LL | f.seek(SeekFrom::Current(0))?;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `f.stream_position()`
++ |
++ = note: `-D clippy::seek-from-current` implied by `-D warnings`
++
++error: aborting due to previous error
++
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(unused)]
++#![feature(custom_inner_attributes)]
++#![warn(clippy::seek_to_start_instead_of_rewind)]
++
++use std::fs::OpenOptions;
++use std::io::{Read, Seek, SeekFrom, Write};
++
++struct StructWithSeekMethod {}
++
++impl StructWithSeekMethod {
++ fn seek(&mut self, from: SeekFrom) {}
++}
++
++trait MySeekTrait {
++ fn seek(&mut self, from: SeekFrom) {}
++}
++
++struct StructWithSeekTrait {}
++impl MySeekTrait for StructWithSeekTrait {}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_method(t: &mut StructWithSeekMethod) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait_bound<T: MySeekTrait>(t: &mut T) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should trigger clippy warning
++fn seek_to_start<T: Seek>(t: &mut T) {
++ t.rewind();
++}
++
++// This should trigger clippy warning
++fn owned_seek_to_start<T: Seek>(mut t: T) {
++ t.rewind();
++}
++
++// This should NOT trigger clippy warning because
++// it does not seek to start
++fn seek_to_5<T: Seek>(t: &mut T) {
++ t.seek(SeekFrom::Start(5));
++}
++
++// This should NOT trigger clippy warning because
++// it does not seek to start
++fn seek_to_end<T: Seek>(t: &mut T) {
++ t.seek(SeekFrom::End(0));
++}
++
++fn main() {
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let mut my_struct_trait = StructWithSeekTrait {};
++ seek_to_start_false_trait_bound(&mut my_struct_trait);
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++ seek_to_5(&mut f);
++ seek_to_end(&mut f);
++ seek_to_start(&mut f);
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
++
++fn msrv_1_54() {
++ #![clippy::msrv = "1.54"]
++
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++
++ f.seek(SeekFrom::Start(0));
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
++
++fn msrv_1_55() {
++ #![clippy::msrv = "1.55"]
++
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++
++ f.rewind();
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(unused)]
++#![feature(custom_inner_attributes)]
++#![warn(clippy::seek_to_start_instead_of_rewind)]
++
++use std::fs::OpenOptions;
++use std::io::{Read, Seek, SeekFrom, Write};
++
++struct StructWithSeekMethod {}
++
++impl StructWithSeekMethod {
++ fn seek(&mut self, from: SeekFrom) {}
++}
++
++trait MySeekTrait {
++ fn seek(&mut self, from: SeekFrom) {}
++}
++
++struct StructWithSeekTrait {}
++impl MySeekTrait for StructWithSeekTrait {}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_method(t: &mut StructWithSeekMethod) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// StructWithSeekMethod does not implement std::io::Seek;
++fn seek_to_start_false_trait_bound<T: MySeekTrait>(t: &mut T) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should trigger clippy warning
++fn seek_to_start<T: Seek>(t: &mut T) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should trigger clippy warning
++fn owned_seek_to_start<T: Seek>(mut t: T) {
++ t.seek(SeekFrom::Start(0));
++}
++
++// This should NOT trigger clippy warning because
++// it does not seek to start
++fn seek_to_5<T: Seek>(t: &mut T) {
++ t.seek(SeekFrom::Start(5));
++}
++
++// This should NOT trigger clippy warning because
++// it does not seek to start
++fn seek_to_end<T: Seek>(t: &mut T) {
++ t.seek(SeekFrom::End(0));
++}
++
++fn main() {
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let mut my_struct_trait = StructWithSeekTrait {};
++ seek_to_start_false_trait_bound(&mut my_struct_trait);
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++ seek_to_5(&mut f);
++ seek_to_end(&mut f);
++ seek_to_start(&mut f);
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
++
++fn msrv_1_54() {
++ #![clippy::msrv = "1.54"]
++
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++
++ f.seek(SeekFrom::Start(0));
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
++
++fn msrv_1_55() {
++ #![clippy::msrv = "1.55"]
++
++ let mut f = OpenOptions::new()
++ .write(true)
++ .read(true)
++ .create(true)
++ .open("foo.txt")
++ .unwrap();
++
++ let hello = "Hello!\n";
++ write!(f, "{hello}").unwrap();
++
++ f.seek(SeekFrom::Start(0));
++
++ let mut buf = String::new();
++ f.read_to_string(&mut buf).unwrap();
++
++ assert_eq!(&buf, hello);
++}
--- /dev/null
--- /dev/null
++error: used `seek` to go to the start of the stream
++ --> $DIR/seek_to_start_instead_of_rewind.rs:54:7
++ |
++LL | t.seek(SeekFrom::Start(0));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
++ |
++ = note: `-D clippy::seek-to-start-instead-of-rewind` implied by `-D warnings`
++
++error: used `seek` to go to the start of the stream
++ --> $DIR/seek_to_start_instead_of_rewind.rs:59:7
++ |
++LL | t.seek(SeekFrom::Start(0));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
++
++error: used `seek` to go to the start of the stream
++ --> $DIR/seek_to_start_instead_of_rewind.rs:131:7
++ |
++LL | f.seek(SeekFrom::Start(0));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
++
++error: aborting due to 3 previous errors
++
--- /dev/null
- --> $DIR/single_component_path_imports.rs:23:5
+error: this import is redundant
- LL | use regex;
- | ^^^^^^^^^^ help: remove it entirely
++ --> $DIR/single_component_path_imports.rs:5:1
+ |
- --> $DIR/single_component_path_imports.rs:5:1
++LL | use regex;
++ | ^^^^^^^^^^ help: remove it entirely
+ |
+ = note: `-D clippy::single-component-path-imports` implied by `-D warnings`
+
+error: this import is redundant
- LL | use regex;
- | ^^^^^^^^^^ help: remove it entirely
++ --> $DIR/single_component_path_imports.rs:23:5
+ |
++LL | use regex;
++ | ^^^^^^^^^^ help: remove it entirely
+
+error: aborting due to 2 previous errors
+
--- /dev/null
- = note: `-D clippy::single-component-path-imports` implied by `-D warnings`
++error: this import is redundant
++ --> $DIR/single_component_path_imports_nested_first.rs:4:1
++ |
++LL | use regex;
++ | ^^^^^^^^^^ help: remove it entirely
++ |
++ = note: `-D clippy::single-component-path-imports` implied by `-D warnings`
++
+error: this import is redundant
+ --> $DIR/single_component_path_imports_nested_first.rs:13:10
+ |
+LL | use {regex, serde};
+ | ^^^^^
+ |
+ = help: remove this import
- error: this import is redundant
- --> $DIR/single_component_path_imports_nested_first.rs:4:1
- |
- LL | use regex;
- | ^^^^^^^^^^ help: remove it entirely
-
+
+error: this import is redundant
+ --> $DIR/single_component_path_imports_nested_first.rs:13:17
+ |
+LL | use {regex, serde};
+ | ^^^^^
+ |
+ = help: remove this import
+
+error: aborting due to 3 previous errors
+
--- /dev/null
+// run-rustfix
+
+#[derive(Copy, Clone)]
+struct HasChars;
+
+impl HasChars {
+ fn chars(self) -> std::str::Chars<'static> {
+ "HasChars".chars()
+ }
+}
+
+fn main() {
+ let abc = "abc";
+ let def = String::from("def");
+ let mut s = String::new();
+
+ s.push_str(abc);
+ s.push_str(abc);
+
+ s.push_str("abc");
+ s.push_str("abc");
+
+ s.push_str(&def);
+ s.push_str(&def);
+
+ s.extend(abc.chars().skip(1));
+ s.extend("abc".chars().skip(1));
+ s.extend(['a', 'b', 'c'].iter());
+
+ let f = HasChars;
+ s.extend(f.chars());
++
++ // issue #9735
++ s.push_str(&abc[0..2]);
+}
--- /dev/null
+// run-rustfix
+
+#[derive(Copy, Clone)]
+struct HasChars;
+
+impl HasChars {
+ fn chars(self) -> std::str::Chars<'static> {
+ "HasChars".chars()
+ }
+}
+
+fn main() {
+ let abc = "abc";
+ let def = String::from("def");
+ let mut s = String::new();
+
+ s.push_str(abc);
+ s.extend(abc.chars());
+
+ s.push_str("abc");
+ s.extend("abc".chars());
+
+ s.push_str(&def);
+ s.extend(def.chars());
+
+ s.extend(abc.chars().skip(1));
+ s.extend("abc".chars().skip(1));
+ s.extend(['a', 'b', 'c'].iter());
+
+ let f = HasChars;
+ s.extend(f.chars());
++
++ // issue #9735
++ s.extend(abc[0..2].chars());
+}
--- /dev/null
- error: aborting due to 3 previous errors
+error: calling `.extend(_.chars())`
+ --> $DIR/string_extend.rs:18:5
+ |
+LL | s.extend(abc.chars());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(abc)`
+ |
+ = note: `-D clippy::string-extend-chars` implied by `-D warnings`
+
+error: calling `.extend(_.chars())`
+ --> $DIR/string_extend.rs:21:5
+ |
+LL | s.extend("abc".chars());
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str("abc")`
+
+error: calling `.extend(_.chars())`
+ --> $DIR/string_extend.rs:24:5
+ |
+LL | s.extend(def.chars());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(&def)`
+
++error: calling `.extend(_.chars())`
++ --> $DIR/string_extend.rs:34:5
++ |
++LL | s.extend(abc[0..2].chars());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(&abc[0..2])`
++
++error: aborting due to 4 previous errors
+
--- /dev/null
--- /dev/null
++#![allow(unused)]
++#![warn(clippy::suspicious_xor_used_as_pow)]
++#![allow(clippy::eq_op)]
++
++macro_rules! macro_test {
++ () => {
++ 13
++ };
++}
++
++macro_rules! macro_test_inside {
++ () => {
++ 1 ^ 2 // should warn even if inside macro
++ };
++}
++
++fn main() {
++ // Should warn:
++ let _ = 2 ^ 5;
++ let _ = 2i32 ^ 9i32;
++ let _ = 2i32 ^ 2i32;
++ let _ = 50i32 ^ 3i32;
++ let _ = 5i32 ^ 8i32;
++ let _ = 2i32 ^ 32i32;
++ macro_test_inside!();
++
++ // Should not warn:
++ let x = 0x02;
++ let _ = x ^ 2;
++ let _ = 2 ^ x;
++ let _ = x ^ 5;
++ let _ = 10 ^ 0b0101;
++ let _ = 2i32 ^ macro_test!();
++}
--- /dev/null
--- /dev/null
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:19:13
++ |
++LL | let _ = 2 ^ 5;
++ | ^^^^^ help: did you mean to write: `2.pow(5)`
++ |
++ = note: `-D clippy::suspicious-xor-used-as-pow` implied by `-D warnings`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:20:13
++ |
++LL | let _ = 2i32 ^ 9i32;
++ | ^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(9_i32)`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:21:13
++ |
++LL | let _ = 2i32 ^ 2i32;
++ | ^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(2_i32)`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:22:13
++ |
++LL | let _ = 50i32 ^ 3i32;
++ | ^^^^^^^^^^^^ help: did you mean to write: `50_i32.pow(3_i32)`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:23:13
++ |
++LL | let _ = 5i32 ^ 8i32;
++ | ^^^^^^^^^^^ help: did you mean to write: `5_i32.pow(8_i32)`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:24:13
++ |
++LL | let _ = 2i32 ^ 32i32;
++ | ^^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(32_i32)`
++
++error: `^` is not the exponentiation operator
++ --> $DIR/suspicious_xor_used_as_pow.rs:13:9
++ |
++LL | 1 ^ 2 // should warn even if inside macro
++ | ^^^^^ help: did you mean to write: `1.pow(2)`
++...
++LL | macro_test_inside!();
++ | -------------------- in this macro invocation
++ |
++ = note: this error originates in the macro `macro_test_inside` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: aborting due to 7 previous errors
++
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all)]
+#![allow(
+ clippy::disallowed_names,
+ clippy::no_effect,
+ clippy::redundant_clone,
+ redundant_semicolons,
+ dead_code,
+ unused_assignments
+)]
+
+struct Foo(u32);
+
+#[derive(Clone)]
+struct Bar {
+ a: u32,
+ b: u32,
+}
+
+fn field() {
+ let mut bar = Bar { a: 1, b: 2 };
+
+ std::mem::swap(&mut bar.a, &mut bar.b);
+
+ let mut baz = vec![bar.clone(), bar.clone()];
+ let temp = baz[0].a;
+ baz[0].a = baz[1].a;
+ baz[1].a = temp;
+}
+
+fn array() {
+ let mut foo = [1, 2];
+ foo.swap(0, 1);
+
+ foo.swap(0, 1);
+}
+
+fn slice() {
+ let foo = &mut [1, 2];
+ foo.swap(0, 1);
+
+ foo.swap(0, 1);
+}
+
+fn unswappable_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ let temp = foo[0][1];
+ foo[0][1] = foo[1][0];
+ foo[1][0] = temp;
+
+ // swap(foo[0][1], foo[1][0]) would fail
+ // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn vec() {
+ let mut foo = vec![1, 2];
+ foo.swap(0, 1);
+
+ foo.swap(0, 1);
+}
+
+fn xor_swap_locals() {
+ // This is an xor-based swap of local variables.
+ let mut a = 0;
+ let mut b = 1;
+ std::mem::swap(&mut a, &mut b)
+}
+
+fn xor_field_swap() {
+ // This is an xor-based swap of fields in a struct.
+ let mut bar = Bar { a: 0, b: 1 };
+ std::mem::swap(&mut bar.a, &mut bar.b)
+}
+
+fn xor_slice_swap() {
+ // This is an xor-based swap of a slice
+ let foo = &mut [1, 2];
+ foo.swap(0, 1)
+}
+
+fn xor_no_swap() {
+ // This is a sequence of xor-assignment statements that doesn't result in a swap.
+ let mut a = 0;
+ let mut b = 1;
+ let mut c = 2;
+ a ^= b;
+ b ^= c;
+ a ^= c;
+ c ^= a;
+}
+
+fn xor_unswappable_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ foo[0][1] ^= foo[1][0];
+ foo[1][0] ^= foo[0][0];
+ foo[0][1] ^= foo[1][0];
+
+ // swap(foo[0][1], foo[1][0]) would fail
+ // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn distinct_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ let bar = &mut [vec![1, 2], vec![3, 4]];
+ std::mem::swap(&mut foo[0][1], &mut bar[1][0]);
+}
+
+#[rustfmt::skip]
+fn main() {
+
+ let mut a = 42;
+ let mut b = 1337;
+
+ std::mem::swap(&mut a, &mut b);
+
+ ; std::mem::swap(&mut a, &mut b);
+
+ let mut c = Foo(42);
+
+ std::mem::swap(&mut c.0, &mut a);
+
+ ; std::mem::swap(&mut c.0, &mut a);
+}
+
+fn issue_8154() {
+ struct S1 {
+ x: i32,
+ y: i32,
+ }
+ struct S2(S1);
+ struct S3<'a, 'b>(&'a mut &'b mut S1);
+
+ impl std::ops::Deref for S2 {
+ type Target = S1;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ impl std::ops::DerefMut for S2 {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+ }
+
+ // Don't lint. `s.0` is mutably borrowed by `s.x` and `s.y` via the deref impl.
+ let mut s = S2(S1 { x: 0, y: 0 });
+ let t = s.x;
+ s.x = s.y;
+ s.y = t;
+
+ // Accessing through a mutable reference is fine
+ let mut s = S1 { x: 0, y: 0 };
+ let mut s = &mut s;
+ let s = S3(&mut s);
+ std::mem::swap(&mut s.0.x, &mut s.0.y);
+}
++
++const fn issue_9864(mut u: u32) -> u32 {
++ let mut v = 10;
++
++ let temp = u;
++ u = v;
++ v = temp;
++ u + v
++}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all)]
+#![allow(
+ clippy::disallowed_names,
+ clippy::no_effect,
+ clippy::redundant_clone,
+ redundant_semicolons,
+ dead_code,
+ unused_assignments
+)]
+
+struct Foo(u32);
+
+#[derive(Clone)]
+struct Bar {
+ a: u32,
+ b: u32,
+}
+
+fn field() {
+ let mut bar = Bar { a: 1, b: 2 };
+
+ let temp = bar.a;
+ bar.a = bar.b;
+ bar.b = temp;
+
+ let mut baz = vec![bar.clone(), bar.clone()];
+ let temp = baz[0].a;
+ baz[0].a = baz[1].a;
+ baz[1].a = temp;
+}
+
+fn array() {
+ let mut foo = [1, 2];
+ let temp = foo[0];
+ foo[0] = foo[1];
+ foo[1] = temp;
+
+ foo.swap(0, 1);
+}
+
+fn slice() {
+ let foo = &mut [1, 2];
+ let temp = foo[0];
+ foo[0] = foo[1];
+ foo[1] = temp;
+
+ foo.swap(0, 1);
+}
+
+fn unswappable_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ let temp = foo[0][1];
+ foo[0][1] = foo[1][0];
+ foo[1][0] = temp;
+
+ // swap(foo[0][1], foo[1][0]) would fail
+ // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn vec() {
+ let mut foo = vec![1, 2];
+ let temp = foo[0];
+ foo[0] = foo[1];
+ foo[1] = temp;
+
+ foo.swap(0, 1);
+}
+
+fn xor_swap_locals() {
+ // This is an xor-based swap of local variables.
+ let mut a = 0;
+ let mut b = 1;
+ a ^= b;
+ b ^= a;
+ a ^= b;
+}
+
+fn xor_field_swap() {
+ // This is an xor-based swap of fields in a struct.
+ let mut bar = Bar { a: 0, b: 1 };
+ bar.a ^= bar.b;
+ bar.b ^= bar.a;
+ bar.a ^= bar.b;
+}
+
+fn xor_slice_swap() {
+ // This is an xor-based swap of a slice
+ let foo = &mut [1, 2];
+ foo[0] ^= foo[1];
+ foo[1] ^= foo[0];
+ foo[0] ^= foo[1];
+}
+
+fn xor_no_swap() {
+ // This is a sequence of xor-assignment statements that doesn't result in a swap.
+ let mut a = 0;
+ let mut b = 1;
+ let mut c = 2;
+ a ^= b;
+ b ^= c;
+ a ^= c;
+ c ^= a;
+}
+
+fn xor_unswappable_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ foo[0][1] ^= foo[1][0];
+ foo[1][0] ^= foo[0][0];
+ foo[0][1] ^= foo[1][0];
+
+ // swap(foo[0][1], foo[1][0]) would fail
+ // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn distinct_slice() {
+ let foo = &mut [vec![1, 2], vec![3, 4]];
+ let bar = &mut [vec![1, 2], vec![3, 4]];
+ let temp = foo[0][1];
+ foo[0][1] = bar[1][0];
+ bar[1][0] = temp;
+}
+
+#[rustfmt::skip]
+fn main() {
+
+ let mut a = 42;
+ let mut b = 1337;
+
+ a = b;
+ b = a;
+
+ ; let t = a;
+ a = b;
+ b = t;
+
+ let mut c = Foo(42);
+
+ c.0 = a;
+ a = c.0;
+
+ ; let t = c.0;
+ c.0 = a;
+ a = t;
+}
+
+fn issue_8154() {
+ struct S1 {
+ x: i32,
+ y: i32,
+ }
+ struct S2(S1);
+ struct S3<'a, 'b>(&'a mut &'b mut S1);
+
+ impl std::ops::Deref for S2 {
+ type Target = S1;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ impl std::ops::DerefMut for S2 {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+ }
+
+ // Don't lint. `s.0` is mutably borrowed by `s.x` and `s.y` via the deref impl.
+ let mut s = S2(S1 { x: 0, y: 0 });
+ let t = s.x;
+ s.x = s.y;
+ s.y = t;
+
+ // Accessing through a mutable reference is fine
+ let mut s = S1 { x: 0, y: 0 };
+ let mut s = &mut s;
+ let s = S3(&mut s);
+ let t = s.0.x;
+ s.0.x = s.0.y;
+ s.0.y = t;
+}
++
++const fn issue_9864(mut u: u32) -> u32 {
++ let mut v = 10;
++
++ let temp = u;
++ u = v;
++ v = temp;
++ u + v
++}
--- /dev/null
- #![allow(dead_code, clippy::borrow_as_ptr)]
++#![allow(dead_code, clippy::borrow_as_ptr, clippy::needless_lifetimes)]
+
+extern crate core;
+
+use std::mem::transmute as my_transmute;
+use std::vec::Vec as MyVec;
+
+fn my_int() -> Usize {
+ Usize(42)
+}
+
+fn my_vec() -> MyVec<i32> {
+ vec![]
+}
+
+#[allow(clippy::needless_lifetimes, clippy::transmute_ptr_to_ptr)]
+#[warn(clippy::useless_transmute)]
+unsafe fn _generic<'a, T, U: 'a>(t: &'a T) {
+ // FIXME: should lint
+ // let _: &'a T = core::intrinsics::transmute(t);
+
+ let _: &'a U = core::intrinsics::transmute(t);
+
+ let _: *const T = core::intrinsics::transmute(t);
+
+ let _: *mut T = core::intrinsics::transmute(t);
+
+ let _: *const U = core::intrinsics::transmute(t);
+}
+
+#[warn(clippy::useless_transmute)]
+fn useless() {
+ unsafe {
+ let _: Vec<i32> = core::intrinsics::transmute(my_vec());
+
+ let _: Vec<i32> = core::mem::transmute(my_vec());
+
+ let _: Vec<i32> = std::intrinsics::transmute(my_vec());
+
+ let _: Vec<i32> = std::mem::transmute(my_vec());
+
+ let _: Vec<i32> = my_transmute(my_vec());
+
+ let _: *const usize = std::mem::transmute(5_isize);
+
+ let _ = 5_isize as *const usize;
+
+ let _: *const usize = std::mem::transmute(1 + 1usize);
+
+ let _ = (1 + 1_usize) as *const usize;
+ }
+
+ unsafe fn _f<'a, 'b>(x: &'a u32) -> &'b u32 {
+ std::mem::transmute(x)
+ }
+
+ unsafe fn _f2<'a, 'b>(x: *const (dyn Iterator<Item = u32> + 'a)) -> *const (dyn Iterator<Item = u32> + 'b) {
+ std::mem::transmute(x)
+ }
+
+ unsafe fn _f3<'a, 'b>(x: fn(&'a u32)) -> fn(&'b u32) {
+ std::mem::transmute(x)
+ }
+
+ unsafe fn _f4<'a, 'b>(x: std::borrow::Cow<'a, str>) -> std::borrow::Cow<'b, str> {
+ std::mem::transmute(x)
+ }
+}
+
+struct Usize(usize);
+
+#[warn(clippy::crosspointer_transmute)]
+fn crosspointer() {
+ let mut int: Usize = Usize(0);
+ let int_const_ptr: *const Usize = &int as *const Usize;
+ let int_mut_ptr: *mut Usize = &mut int as *mut Usize;
+
+ unsafe {
+ let _: Usize = core::intrinsics::transmute(int_const_ptr);
+
+ let _: Usize = core::intrinsics::transmute(int_mut_ptr);
+
+ let _: *const Usize = core::intrinsics::transmute(my_int());
+
+ let _: *mut Usize = core::intrinsics::transmute(my_int());
+ }
+}
+
+#[warn(clippy::transmute_int_to_char)]
+fn int_to_char() {
+ let _: char = unsafe { std::mem::transmute(0_u32) };
+ let _: char = unsafe { std::mem::transmute(0_i32) };
+
+ // These shouldn't warn
+ const _: char = unsafe { std::mem::transmute(0_u32) };
+ const _: char = unsafe { std::mem::transmute(0_i32) };
+}
+
+#[warn(clippy::transmute_int_to_bool)]
+fn int_to_bool() {
+ let _: bool = unsafe { std::mem::transmute(0_u8) };
+}
+
+#[warn(clippy::transmute_int_to_float)]
+mod int_to_float {
+ fn test() {
+ let _: f32 = unsafe { std::mem::transmute(0_u32) };
+ let _: f32 = unsafe { std::mem::transmute(0_i32) };
+ let _: f64 = unsafe { std::mem::transmute(0_u64) };
+ let _: f64 = unsafe { std::mem::transmute(0_i64) };
+ }
+
+ mod issue_5747 {
+ const VALUE32: f32 = unsafe { std::mem::transmute(0_u32) };
+ const VALUE64: f64 = unsafe { std::mem::transmute(0_i64) };
+
+ const fn from_bits_32(v: i32) -> f32 {
+ unsafe { std::mem::transmute(v) }
+ }
+
+ const fn from_bits_64(v: u64) -> f64 {
+ unsafe { std::mem::transmute(v) }
+ }
+ }
+}
+
+mod num_to_bytes {
+ fn test() {
+ unsafe {
+ let _: [u8; 1] = std::mem::transmute(0u8);
+ let _: [u8; 4] = std::mem::transmute(0u32);
+ let _: [u8; 16] = std::mem::transmute(0u128);
+ let _: [u8; 1] = std::mem::transmute(0i8);
+ let _: [u8; 4] = std::mem::transmute(0i32);
+ let _: [u8; 16] = std::mem::transmute(0i128);
+ let _: [u8; 4] = std::mem::transmute(0.0f32);
+ let _: [u8; 8] = std::mem::transmute(0.0f64);
+ }
+ }
+ const fn test_const() {
+ unsafe {
+ let _: [u8; 1] = std::mem::transmute(0u8);
+ let _: [u8; 4] = std::mem::transmute(0u32);
+ let _: [u8; 16] = std::mem::transmute(0u128);
+ let _: [u8; 1] = std::mem::transmute(0i8);
+ let _: [u8; 4] = std::mem::transmute(0i32);
+ let _: [u8; 16] = std::mem::transmute(0i128);
+ let _: [u8; 4] = std::mem::transmute(0.0f32);
+ let _: [u8; 8] = std::mem::transmute(0.0f64);
+ }
+ }
+}
+
+fn bytes_to_str(mb: &mut [u8]) {
+ const B: &[u8] = b"";
+
+ let _: &str = unsafe { std::mem::transmute(B) };
+ let _: &mut str = unsafe { std::mem::transmute(mb) };
+ const _: &str = unsafe { std::mem::transmute(B) };
+}
+
+fn main() {}
--- /dev/null
+// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)"
+// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)"
+#![deny(clippy::trivially_copy_pass_by_ref)]
+#![allow(
+ clippy::disallowed_names,
++ clippy::needless_lifetimes,
+ clippy::redundant_field_names,
+ clippy::uninlined_format_args
+)]
+
+#[derive(Copy, Clone)]
+struct Foo(u32);
+
+#[derive(Copy, Clone)]
+struct Bar([u8; 24]);
+
+#[derive(Copy, Clone)]
+pub struct Color {
+ pub r: u8,
+ pub g: u8,
+ pub b: u8,
+ pub a: u8,
+}
+
+struct FooRef<'a> {
+ foo: &'a Foo,
+}
+
+type Baz = u32;
+
+fn good(a: &mut u32, b: u32, c: &Bar) {}
+
+fn good_return_implicit_lt_ref(foo: &Foo) -> &u32 {
+ &foo.0
+}
+
+#[allow(clippy::needless_lifetimes)]
+fn good_return_explicit_lt_ref<'a>(foo: &'a Foo) -> &'a u32 {
+ &foo.0
+}
+
+fn good_return_implicit_lt_struct(foo: &Foo) -> FooRef {
+ FooRef { foo }
+}
+
+#[allow(clippy::needless_lifetimes)]
+fn good_return_explicit_lt_struct<'a>(foo: &'a Foo) -> FooRef<'a> {
+ FooRef { foo }
+}
+
+fn bad(x: &u32, y: &Foo, z: &Baz) {}
+
+impl Foo {
+ fn good(self, a: &mut u32, b: u32, c: &Bar) {}
+
+ fn good2(&mut self) {}
+
+ fn bad(&self, x: &u32, y: &Foo, z: &Baz) {}
+
+ fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+
+ fn bad_issue7518(self, other: &Self) {}
+}
+
+impl AsRef<u32> for Foo {
+ fn as_ref(&self) -> &u32 {
+ &self.0
+ }
+}
+
+impl Bar {
+ fn good(&self, a: &mut u32, b: u32, c: &Bar) {}
+
+ fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+}
+
+trait MyTrait {
+ fn trait_method(&self, _foo: &Foo);
+}
+
+pub trait MyTrait2 {
+ fn trait_method2(&self, _color: &Color);
+}
+
+impl MyTrait for Foo {
+ fn trait_method(&self, _foo: &Foo) {
+ unimplemented!()
+ }
+}
+
+#[allow(unused_variables)]
+mod issue3992 {
+ pub trait A {
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn a(b: &u16) {}
+ }
+
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ pub fn c(d: &u16) {}
+}
+
+mod issue5876 {
+ // Don't lint here as it is always inlined
+ #[inline(always)]
+ fn foo_always(x: &i32) {
+ println!("{}", x);
+ }
+
+ #[inline(never)]
+ fn foo_never(x: &i32) {
+ println!("{}", x);
+ }
+
+ #[inline]
+ fn foo(x: &i32) {
+ println!("{}", x);
+ }
+}
+
+fn _ref_to_opt_ref_implicit(x: &u32) -> Option<&u32> {
+ Some(x)
+}
+
+#[allow(clippy::needless_lifetimes)]
+fn _ref_to_opt_ref_explicit<'a>(x: &'a u32) -> Option<&'a u32> {
+ Some(x)
+}
+
+fn _with_constraint<'a, 'b: 'a>(x: &'b u32, y: &'a u32) -> &'a u32 {
+ if true { x } else { y }
+}
+
+async fn _async_implicit(x: &u32) -> &u32 {
+ x
+}
+
+#[allow(clippy::needless_lifetimes)]
+async fn _async_explicit<'a>(x: &'a u32) -> &'a u32 {
+ x
+}
+
+fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
+ y
+}
+
+fn _return_ptr(x: &u32) -> *const u32 {
+ x
+}
+
+fn _return_field_ptr(x: &(u32, u32)) -> *const u32 {
+ &x.0
+}
+
+fn _return_field_ptr_addr_of(x: &(u32, u32)) -> *const u32 {
+ core::ptr::addr_of!(x.0)
+}
+
+fn main() {
+ let (mut foo, bar) = (Foo(0), Bar([0; 24]));
+ let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0);
+ good(&mut a, b, &c);
+ good_return_implicit_lt_ref(&y);
+ good_return_explicit_lt_ref(&y);
+ bad(&x, &y, &z);
+ foo.good(&mut a, b, &c);
+ foo.good2();
+ foo.bad(&x, &y, &z);
+ Foo::bad2(&x, &y, &z);
+ bar.good(&mut a, b, &c);
+ Bar::bad2(&x, &y, &z);
+ foo.as_ref();
+}
--- /dev/null
- --> $DIR/trivially_copy_pass_by_ref.rs:50:11
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:50:20
++ --> $DIR/trivially_copy_pass_by_ref.rs:51:11
+ |
+LL | fn bad(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `u32`
+ |
+note: the lint level is defined here
+ --> $DIR/trivially_copy_pass_by_ref.rs:3:9
+ |
+LL | #![deny(clippy::trivially_copy_pass_by_ref)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:50:29
++ --> $DIR/trivially_copy_pass_by_ref.rs:51:20
+ |
+LL | fn bad(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Foo`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:57:12
++ --> $DIR/trivially_copy_pass_by_ref.rs:51:29
+ |
+LL | fn bad(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Baz`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:57:22
++ --> $DIR/trivially_copy_pass_by_ref.rs:58:12
+ |
+LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^^ help: consider passing by value instead: `self`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:57:31
++ --> $DIR/trivially_copy_pass_by_ref.rs:58:22
+ |
+LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `u32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:57:40
++ --> $DIR/trivially_copy_pass_by_ref.rs:58:31
+ |
+LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Foo`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:59:16
++ --> $DIR/trivially_copy_pass_by_ref.rs:58:40
+ |
+LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Baz`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:59:25
++ --> $DIR/trivially_copy_pass_by_ref.rs:60:16
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `u32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:59:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:60:25
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Foo`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:61:35
++ --> $DIR/trivially_copy_pass_by_ref.rs:60:34
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Baz`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:73:16
++ --> $DIR/trivially_copy_pass_by_ref.rs:62:35
+ |
+LL | fn bad_issue7518(self, other: &Self) {}
+ | ^^^^^ help: consider passing by value instead: `Self`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:73:25
++ --> $DIR/trivially_copy_pass_by_ref.rs:74:16
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `u32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:73:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:74:25
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Foo`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:77:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:74:34
+ |
+LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {}
+ | ^^^^ help: consider passing by value instead: `Baz`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:109:21
++ --> $DIR/trivially_copy_pass_by_ref.rs:78:34
+ |
+LL | fn trait_method(&self, _foo: &Foo);
+ | ^^^^ help: consider passing by value instead: `Foo`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:114:15
++ --> $DIR/trivially_copy_pass_by_ref.rs:110:21
+ |
+LL | fn foo_never(x: &i32) {
+ | ^^^^ help: consider passing by value instead: `i32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
- --> $DIR/trivially_copy_pass_by_ref.rs:141:37
++ --> $DIR/trivially_copy_pass_by_ref.rs:115:15
+ |
+LL | fn foo(x: &i32) {
+ | ^^^^ help: consider passing by value instead: `i32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
++ --> $DIR/trivially_copy_pass_by_ref.rs:142:37
+ |
+LL | fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
+ | ^^^^^^^ help: consider passing by value instead: `u32`
+
+error: aborting due to 18 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::unchecked_duration_subtraction)]
++
++use std::time::{Duration, Instant};
++
++fn main() {
++ let _first = Instant::now();
++ let second = Duration::from_secs(3);
++
++ let _ = _first.checked_sub(second).unwrap();
++
++ let _ = Instant::now().checked_sub(Duration::from_secs(5)).unwrap();
++
++ let _ = _first.checked_sub(Duration::from_secs(5)).unwrap();
++
++ let _ = Instant::now().checked_sub(second).unwrap();
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![warn(clippy::unchecked_duration_subtraction)]
++
++use std::time::{Duration, Instant};
++
++fn main() {
++ let _first = Instant::now();
++ let second = Duration::from_secs(3);
++
++ let _ = _first - second;
++
++ let _ = Instant::now() - Duration::from_secs(5);
++
++ let _ = _first - Duration::from_secs(5);
++
++ let _ = Instant::now() - second;
++}
--- /dev/null
--- /dev/null
++error: unchecked subtraction of a 'Duration' from an 'Instant'
++ --> $DIR/unchecked_duration_subtraction.rs:10:13
++ |
++LL | let _ = _first - second;
++ | ^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(second).unwrap()`
++ |
++ = note: `-D clippy::unchecked-duration-subtraction` implied by `-D warnings`
++
++error: unchecked subtraction of a 'Duration' from an 'Instant'
++ --> $DIR/unchecked_duration_subtraction.rs:12:13
++ |
++LL | let _ = Instant::now() - Duration::from_secs(5);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(Duration::from_secs(5)).unwrap()`
++
++error: unchecked subtraction of a 'Duration' from an 'Instant'
++ --> $DIR/unchecked_duration_subtraction.rs:14:13
++ |
++LL | let _ = _first - Duration::from_secs(5);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(Duration::from_secs(5)).unwrap()`
++
++error: unchecked subtraction of a 'Duration' from an 'Instant'
++ --> $DIR/unchecked_duration_subtraction.rs:16:13
++ |
++LL | let _ = Instant::now() - second;
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()`
++
++error: aborting due to 4 previous errors
++
--- /dev/null
+// aux-build:proc_macro_unsafe.rs
+
+#![warn(clippy::undocumented_unsafe_blocks)]
+#![allow(clippy::let_unit_value, clippy::missing_safety_doc)]
+
+extern crate proc_macro_unsafe;
+
+// Valid comments
+
+fn nested_local() {
+ let _ = {
+ let _ = {
+ // SAFETY:
+ let _ = unsafe {};
+ };
+ };
+}
+
+fn deep_nest() {
+ let _ = {
+ let _ = {
+ // SAFETY:
+ let _ = unsafe {};
+
+ // Safety:
+ unsafe {};
+
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ // Safety:
+ let _ = unsafe {};
+
+ // SAFETY:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+
+ // SAFETY:
+ unsafe {};
+}
+
+fn local_tuple_expression() {
+ // Safety:
+ let _ = (42, unsafe {});
+}
+
+fn line_comment() {
+ // Safety:
+ unsafe {}
+}
+
+fn line_comment_newlines() {
+ // SAFETY:
+
+ unsafe {}
+}
+
+fn line_comment_empty() {
+ // Safety:
+ //
+ //
+ //
+ unsafe {}
+}
+
+fn line_comment_with_extras() {
+ // This is a description
+ // Safety:
+ unsafe {}
+}
+
+fn block_comment() {
+ /* Safety: */
+ unsafe {}
+}
+
+fn block_comment_newlines() {
+ /* SAFETY: */
+
+ unsafe {}
+}
+
+fn block_comment_with_extras() {
+ /* This is a description
+ * SAFETY:
+ */
+ unsafe {}
+}
+
+fn block_comment_terminator_same_line() {
+ /* This is a description
+ * Safety: */
+ unsafe {}
+}
+
+fn buried_safety() {
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ // incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ // reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+ // occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ // laborum. Safety:
+ // Tellus elementum sagittis vitae et leo duis ut diam quam. Sit amet nulla facilisi
+ // morbi tempus iaculis urna. Amet luctus venenatis lectus magna. At quis risus sed vulputate odio
+ // ut. Luctus venenatis lectus magna fringilla urna. Tortor id aliquet lectus proin nibh nisl
+ // condimentum id venenatis. Vulputate dignissim suspendisse in est ante in nibh mauris cursus.
+ unsafe {}
+}
+
+fn safety_with_prepended_text() {
+ // This is a test. safety:
+ unsafe {}
+}
+
+fn local_line_comment() {
+ // Safety:
+ let _ = unsafe {};
+}
+
+fn local_block_comment() {
+ /* SAFETY: */
+ let _ = unsafe {};
+}
+
+fn comment_array() {
+ // Safety:
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn comment_tuple() {
+ // sAFETY:
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn comment_unary() {
+ // SAFETY:
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn comment_match() {
+ // SAFETY:
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn comment_addr_of() {
+ // Safety:
+ let _ = &unsafe {};
+}
+
+fn comment_repeat() {
+ // Safety:
+ let _ = [unsafe {}; 5];
+}
+
+fn comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(
+ // SAFETY:
+ unsafe {}
+ );
+}
+
+fn comment_macro_def() {
+ macro_rules! t {
+ () => {
+ // Safety:
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn non_ascii_comment() {
+ // ॐ᧻໒ SaFeTy: ௵∰
+ unsafe {};
+}
+
+fn local_commented_block() {
+ let _ =
+ // safety:
+ unsafe {};
+}
+
+fn local_nest() {
+ // safety:
+ let _ = [(42, unsafe {}, unsafe {}), (52, unsafe {}, unsafe {})];
+}
+
+fn in_fn_call(x: *const u32) {
+ fn f(x: u32) {}
+
+ // Safety: reason
+ f(unsafe { *x });
+}
+
+fn multi_in_fn_call(x: *const u32) {
+ fn f(x: u32, y: u32) {}
+
+ // Safety: reason
+ f(unsafe { *x }, unsafe { *x });
+}
+
+fn in_multiline_fn_call(x: *const u32) {
+ fn f(x: u32, y: u32) {}
+
+ f(
+ // Safety: reason
+ unsafe { *x },
+ 0,
+ );
+}
+
+fn in_macro_call(x: *const u32) {
+ // Safety: reason
+ println!("{}", unsafe { *x });
+}
+
+fn in_multiline_macro_call(x: *const u32) {
+ println!(
+ "{}",
+ // Safety: reason
+ unsafe { *x },
+ );
+}
+
+fn from_proc_macro() {
+ proc_macro_unsafe::unsafe_block!(token);
+}
+
+fn in_closure(x: *const u32) {
+ // Safety: reason
+ let _ = || unsafe { *x };
+}
+
+// Invalid comments
+
+#[rustfmt::skip]
+fn inline_block_comment() {
+ /* Safety: */ unsafe {}
+}
+
+fn no_comment() {
+ unsafe {}
+}
+
+fn no_comment_array() {
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn no_comment_tuple() {
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn no_comment_unary() {
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn no_comment_match() {
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn no_comment_addr_of() {
+ let _ = &unsafe {};
+}
+
+fn no_comment_repeat() {
+ let _ = [unsafe {}; 5];
+}
+
+fn local_no_comment() {
+ let _ = unsafe {};
+}
+
+fn no_comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(unsafe {});
+}
+
+fn no_comment_macro_def() {
+ macro_rules! t {
+ () => {
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn trailing_comment() {
+ unsafe {} // SAFETY:
+}
+
+fn internal_comment() {
+ unsafe {
+ // SAFETY:
+ }
+}
+
+fn interference() {
+ // SAFETY
+
+ let _ = 42;
+
+ unsafe {};
+}
+
+pub fn print_binary_tree() {
+ println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
+}
+
+mod unsafe_impl_smoke_test {
+ unsafe trait A {}
+
+ // error: no safety comment
+ unsafe impl A for () {}
+
+ // Safety: ok
+ unsafe impl A for (i32) {}
+
+ mod sub_mod {
+ // error:
+ unsafe impl B for (u32) {}
+ unsafe trait B {}
+ }
+
+ #[rustfmt::skip]
+ mod sub_mod2 {
+ //
+ // SAFETY: ok
+ //
+
+ unsafe impl B for (u32) {}
+ unsafe trait B {}
+ }
+}
+
+mod unsafe_impl_from_macro {
+ unsafe trait T {}
+
+ // error
+ macro_rules! no_safety_comment {
+ ($t:ty) => {
+ unsafe impl T for $t {}
+ };
+ }
+
+ // ok
+ no_safety_comment!(());
+
+ // ok
+ macro_rules! with_safety_comment {
+ ($t:ty) => {
+ // SAFETY:
+ unsafe impl T for $t {}
+ };
+ }
+
+ // ok
+ with_safety_comment!((i32));
+}
+
+mod unsafe_impl_macro_and_not_macro {
+ unsafe trait T {}
+
+ // error
+ macro_rules! no_safety_comment {
+ ($t:ty) => {
+ unsafe impl T for $t {}
+ };
+ }
+
+ // ok
+ no_safety_comment!(());
+
+ // error
+ unsafe impl T for (i32) {}
+
+ // ok
+ no_safety_comment!(u32);
+
+ // error
+ unsafe impl T for (bool) {}
+}
+
+#[rustfmt::skip]
+mod unsafe_impl_valid_comment {
+ unsafe trait SaFety {}
+ // SaFety:
+ unsafe impl SaFety for () {}
+
+ unsafe trait MultiLineComment {}
+ // The following impl is safe
+ // ...
+ // Safety: reason
+ unsafe impl MultiLineComment for () {}
+
+ unsafe trait NoAscii {}
+ // 安全 SAFETY: 以下のコードは安全です
+ unsafe impl NoAscii for () {}
+
+ unsafe trait InlineAndPrecedingComment {}
+ // SAFETY:
+ /* comment */ unsafe impl InlineAndPrecedingComment for () {}
+
+ unsafe trait BuriedSafety {}
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ // incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ // reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+ // occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ // laborum. Safety:
+ // Tellus elementum sagittis vitae et leo duis ut diam quam. Sit amet nulla facilisi
+ // morbi tempus iaculis urna. Amet luctus venenatis lectus magna. At quis risus sed vulputate odio
+ // ut. Luctus venenatis lectus magna fringilla urna. Tortor id aliquet lectus proin nibh nisl
+ // condimentum id venenatis. Vulputate dignissim suspendisse in est ante in nibh mauris cursus.
+ unsafe impl BuriedSafety for () {}
+
+ unsafe trait MultiLineBlockComment {}
+ /* This is a description
+ * Safety: */
+ unsafe impl MultiLineBlockComment for () {}
+}
+
+#[rustfmt::skip]
+mod unsafe_impl_invalid_comment {
+ unsafe trait NoComment {}
+
+ unsafe impl NoComment for () {}
+
+ unsafe trait InlineComment {}
+
+ /* SAFETY: */ unsafe impl InlineComment for () {}
+
+ unsafe trait TrailingComment {}
+
+ unsafe impl TrailingComment for () {} // SAFETY:
+
+ unsafe trait Interference {}
+ // SAFETY:
+ const BIG_NUMBER: i32 = 1000000;
+ unsafe impl Interference for () {}
+}
+
+unsafe trait ImplInFn {}
+
+fn impl_in_fn() {
+ // error
+ unsafe impl ImplInFn for () {}
+
+ // SAFETY: ok
+ unsafe impl ImplInFn for (i32) {}
+}
+
+unsafe trait CrateRoot {}
+
+// error
+unsafe impl CrateRoot for () {}
+
+// SAFETY: ok
+unsafe impl CrateRoot for (i32) {}
+
++fn issue_9142() {
++ // SAFETY: ok
++ let _ =
++ // we need this comment to avoid rustfmt putting
++ // it all on one line
++ unsafe {};
++
++ // SAFETY: this is more than one level away, so it should warn
++ let _ = {
++ if unsafe { true } {
++ todo!();
++ } else {
++ let bar = unsafe {};
++ todo!();
++ bar
++ }
++ };
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 31 previous errors
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:262:19
+ |
+LL | /* Safety: */ unsafe {}
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:266:5
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:270:14
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:270:29
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:270:48
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:274:18
+ |
+LL | let _ = (42, unsafe {}, "test", unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:274:37
+ |
+LL | let _ = (42, unsafe {}, "test", unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:278:14
+ |
+LL | let _ = *unsafe { &42 };
+ | ^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:283:19
+ |
+LL | let _ = match unsafe {} {
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:289:14
+ |
+LL | let _ = &unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:293:14
+ |
+LL | let _ = [unsafe {}; 5];
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:297:13
+ |
+LL | let _ = unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:307:8
+ |
+LL | t!(unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:313:13
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+...
+LL | t!();
+ | ---- in this macro invocation
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:321:5
+ |
+LL | unsafe {} // SAFETY:
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:325:5
+ |
+LL | unsafe {
+ | ^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:335:5
+ |
+LL | unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:339:20
+ |
+LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:346:5
+ |
+LL | unsafe impl A for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:353:9
+ |
+LL | unsafe impl B for (u32) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:374:13
+ |
+LL | unsafe impl T for $t {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | no_safety_comment!(());
+ | ---------------------- in this macro invocation
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:399:13
+ |
+LL | unsafe impl T for $t {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | no_safety_comment!(());
+ | ---------------------- in this macro invocation
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:407:5
+ |
+LL | unsafe impl T for (i32) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:399:13
+ |
+LL | unsafe impl T for $t {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | no_safety_comment!(u32);
+ | ----------------------- in this macro invocation
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:413:5
+ |
+LL | unsafe impl T for (bool) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:459:5
+ |
+LL | unsafe impl NoComment for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:463:19
+ |
+LL | /* SAFETY: */ unsafe impl InlineComment for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:467:5
+ |
+LL | unsafe impl TrailingComment for () {} // SAFETY:
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:472:5
+ |
+LL | unsafe impl Interference for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:479:5
+ |
+LL | unsafe impl ImplInFn for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe impl missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:488:1
+ |
+LL | unsafe impl CrateRoot for () {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
++error: unsafe block missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:498:9
++ |
++LL | unsafe {};
++ | ^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe block missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:502:12
++ |
++LL | if unsafe { true } {
++ | ^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe block missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:505:23
++ |
++LL | let bar = unsafe {};
++ | ^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: aborting due to 34 previous errors
+
--- /dev/null
- error: called `.collect<Vec<String>>().join("")` on an iterator
++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
++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
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::unused_rounding)]
+
+fn main() {
+ let _ = 1f32;
+ let _ = 1.0f64;
+ let _ = 1.00f32;
+ let _ = 2e-54f64.floor();
++
++ // issue9866
++ let _ = 3.3_f32.round();
++ let _ = 3.3_f64.round();
++ let _ = 3.0_f32;
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::unused_rounding)]
+
+fn main() {
+ let _ = 1f32.ceil();
+ let _ = 1.0f64.floor();
+ let _ = 1.00f32.round();
+ let _ = 2e-54f64.floor();
++
++ // issue9866
++ let _ = 3.3_f32.round();
++ let _ = 3.3_f64.round();
++ let _ = 3.0_f32.round();
+}
--- /dev/null
- error: aborting due to 3 previous errors
+error: used the `ceil` method with a whole number float
+ --> $DIR/unused_rounding.rs:5:13
+ |
+LL | let _ = 1f32.ceil();
+ | ^^^^^^^^^^^ help: remove the `ceil` method call: `1f32`
+ |
+ = note: `-D clippy::unused-rounding` implied by `-D warnings`
+
+error: used the `floor` method with a whole number float
+ --> $DIR/unused_rounding.rs:6:13
+ |
+LL | let _ = 1.0f64.floor();
+ | ^^^^^^^^^^^^^^ help: remove the `floor` method call: `1.0f64`
+
+error: used the `round` method with a whole number float
+ --> $DIR/unused_rounding.rs:7:13
+ |
+LL | let _ = 1.00f32.round();
+ | ^^^^^^^^^^^^^^^ help: remove the `round` method call: `1.00f32`
+
++error: used the `round` method with a whole number float
++ --> $DIR/unused_rounding.rs:13:13
++ |
++LL | let _ = 3.0_f32.round();
++ | ^^^^^^^^^^^^^^^ help: remove the `round` method call: `3.0_f32`
++
++error: aborting due to 4 previous errors
+
--- /dev/null
+// run-rustfix
+
+// The output for humans should just highlight the whole span without showing
+// the suggested replacement, but we also want to test that suggested
+// replacement only removes one set of parentheses, rather than naïvely
+// stripping away any starting or ending parenthesis characters—hence this
+// test of the JSON error format.
+
+#![feature(custom_inner_attributes)]
++#![feature(closure_lifetime_binder)]
+#![rustfmt::skip]
+
+#![deny(clippy::unused_unit)]
+#![allow(dead_code)]
+#![allow(clippy::from_over_into)]
+
+struct Unitter;
+impl Unitter {
+ #[allow(clippy::no_effect)]
+ pub fn get_unit<F: Fn(), G>(&self, f: F, _g: G)
+ where G: Fn() {
+ let _y: &dyn Fn() = &f;
+ (); // this should not lint, as it's not in return type position
+ }
+}
+
+impl Into<()> for Unitter {
+ #[rustfmt::skip]
+ fn into(self) {
+
+ }
+}
+
+trait Trait {
+ fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut(),
+ H: Fn();
+}
+
+impl Trait for Unitter {
+ fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut(),
+ H: Fn() {}
+}
+
+fn return_unit() { }
+
+#[allow(clippy::needless_return)]
+#[allow(clippy::never_loop)]
+#[allow(clippy::unit_cmp)]
+fn main() {
+ let u = Unitter;
+ assert_eq!(u.get_unit(|| {}, return_unit), u.into());
+ return_unit();
+ loop {
+ break;
+ }
+ return;
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4076
+fn foo() {
+ macro_rules! foo {
+ (recv($r:expr) -> $res:pat => $body:expr) => {
+ $body
+ }
+ }
+
+ foo! {
+ recv(rx) -> _x => ()
+ }
+}
+
+#[rustfmt::skip]
+fn test(){}
+
+#[rustfmt::skip]
+fn test2(){}
+
+#[rustfmt::skip]
+fn test3(){}
+
+fn macro_expr() {
+ macro_rules! e {
+ () => (());
+ }
+ e!()
+}
++
++mod issue9748 {
++ fn main() {
++ let _ = for<'a> |_: &'a u32| -> () {};
++ }
++}
--- /dev/null
+// run-rustfix
+
+// The output for humans should just highlight the whole span without showing
+// the suggested replacement, but we also want to test that suggested
+// replacement only removes one set of parentheses, rather than naïvely
+// stripping away any starting or ending parenthesis characters—hence this
+// test of the JSON error format.
+
+#![feature(custom_inner_attributes)]
++#![feature(closure_lifetime_binder)]
+#![rustfmt::skip]
+
+#![deny(clippy::unused_unit)]
+#![allow(dead_code)]
+#![allow(clippy::from_over_into)]
+
+struct Unitter;
+impl Unitter {
+ #[allow(clippy::no_effect)]
+ pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
+ where G: Fn() -> () {
+ let _y: &dyn Fn() -> () = &f;
+ (); // this should not lint, as it's not in return type position
+ }
+}
+
+impl Into<()> for Unitter {
+ #[rustfmt::skip]
+ fn into(self) -> () {
+ ()
+ }
+}
+
+trait Trait {
+ fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut() -> (),
+ H: Fn() -> ();
+}
+
+impl Trait for Unitter {
+ fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut() -> (),
+ H: Fn() -> () {}
+}
+
+fn return_unit() -> () { () }
+
+#[allow(clippy::needless_return)]
+#[allow(clippy::never_loop)]
+#[allow(clippy::unit_cmp)]
+fn main() {
+ let u = Unitter;
+ assert_eq!(u.get_unit(|| {}, return_unit), u.into());
+ return_unit();
+ loop {
+ break();
+ }
+ return();
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4076
+fn foo() {
+ macro_rules! foo {
+ (recv($r:expr) -> $res:pat => $body:expr) => {
+ $body
+ }
+ }
+
+ foo! {
+ recv(rx) -> _x => ()
+ }
+}
+
+#[rustfmt::skip]
+fn test()->(){}
+
+#[rustfmt::skip]
+fn test2() ->(){}
+
+#[rustfmt::skip]
+fn test3()-> (){}
+
+fn macro_expr() {
+ macro_rules! e {
+ () => (());
+ }
+ e!()
+}
++
++mod issue9748 {
++ fn main() {
++ let _ = for<'a> |_: &'a u32| -> () {};
++ }
++}
--- /dev/null
- --> $DIR/unused_unit.rs:19:58
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:12:9
++ --> $DIR/unused_unit.rs:20:58
+ |
+LL | pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
+ | ^^^^^^ help: remove the `-> ()`
+ |
+note: the lint level is defined here
- --> $DIR/unused_unit.rs:19:28
++ --> $DIR/unused_unit.rs:13:9
+ |
+LL | #![deny(clippy::unused_unit)]
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:20:18
++ --> $DIR/unused_unit.rs:20:28
+ |
+LL | pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:21:26
++ --> $DIR/unused_unit.rs:21:18
+ |
+LL | where G: Fn() -> () {
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:28:18
++ --> $DIR/unused_unit.rs:22:26
+ |
+LL | let _y: &dyn Fn() -> () = &f;
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:29:9
++ --> $DIR/unused_unit.rs:29:18
+ |
+LL | fn into(self) -> () {
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit expression
- --> $DIR/unused_unit.rs:34:29
++ --> $DIR/unused_unit.rs:30:9
+ |
+LL | ()
+ | ^^ help: remove the final `()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:36:19
++ --> $DIR/unused_unit.rs:35:29
+ |
+LL | fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:37:16
++ --> $DIR/unused_unit.rs:37:19
+ |
+LL | G: FnMut() -> (),
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:41:29
++ --> $DIR/unused_unit.rs:38:16
+ |
+LL | H: Fn() -> ();
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:43:19
++ --> $DIR/unused_unit.rs:42:29
+ |
+LL | fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:44:16
++ --> $DIR/unused_unit.rs:44:19
+ |
+LL | G: FnMut() -> (),
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:47:17
++ --> $DIR/unused_unit.rs:45:16
+ |
+LL | H: Fn() -> () {}
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:47:26
++ --> $DIR/unused_unit.rs:48:17
+ |
+LL | fn return_unit() -> () { () }
+ | ^^^^^^ help: remove the `-> ()`
+
+error: unneeded unit expression
- --> $DIR/unused_unit.rs:57:14
++ --> $DIR/unused_unit.rs:48:26
+ |
+LL | fn return_unit() -> () { () }
+ | ^^ help: remove the final `()`
+
+error: unneeded `()`
- --> $DIR/unused_unit.rs:59:11
++ --> $DIR/unused_unit.rs:58:14
+ |
+LL | break();
+ | ^^ help: remove the `()`
+
+error: unneeded `()`
- --> $DIR/unused_unit.rs:76:10
++ --> $DIR/unused_unit.rs:60:11
+ |
+LL | return();
+ | ^^ help: remove the `()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:79:11
++ --> $DIR/unused_unit.rs:77:10
+ |
+LL | fn test()->(){}
+ | ^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
- --> $DIR/unused_unit.rs:82:11
++ --> $DIR/unused_unit.rs:80:11
+ |
+LL | fn test2() ->(){}
+ | ^^^^^ help: remove the `-> ()`
+
+error: unneeded unit return type
++ --> $DIR/unused_unit.rs:83:11
+ |
+LL | fn test3()-> (){}
+ | ^^^^^ help: remove the `-> ()`
+
+error: aborting due to 19 previous errors
+
--- /dev/null
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap.rs:5:13
+ |
+LL | let _ = opt.unwrap();
+ | ^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+ = note: `-D clippy::unwrap-used` implied by `-D warnings`
+
- error: used `unwrap()` on `a Result` value
++error: used `unwrap()` on a `Result` value
+ --> $DIR/unwrap.rs:10:13
+ |
+LL | let _ = res.unwrap();
+ | ^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message
+
- error: used `unwrap_err()` on `a Result` value
++error: used `unwrap_err()` on a `Result` value
+ --> $DIR/unwrap.rs:11:13
+ |
+LL | let _ = res.unwrap_err();
+ | ^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `Ok` case gracefully, consider using `expect_err()` to provide a better panic message
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- error: used `unwrap()` on `an Option` value
++error: used `unwrap()` on an `Option` value
+ --> $DIR/unwrap_expect_used.rs:23:5
+ |
+LL | Some(3).unwrap();
+ | ^^^^^^^^^^^^^^^^
+ |
+ = help: if this value is `None`, it will panic
+ = note: `-D clippy::unwrap-used` implied by `-D warnings`
+
- error: used `expect()` on `an Option` value
++error: used `expect()` on an `Option` value
+ --> $DIR/unwrap_expect_used.rs:24:5
+ |
+LL | Some(3).expect("Hello world!");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if this value is `None`, it will panic
+ = note: `-D clippy::expect-used` implied by `-D warnings`
+
- error: used `unwrap()` on `a Result` value
++error: used `unwrap()` on a `Result` value
+ --> $DIR/unwrap_expect_used.rs:31:5
+ |
+LL | a.unwrap();
+ | ^^^^^^^^^^
+ |
+ = help: if this value is an `Err`, it will panic
+
- error: used `expect()` on `a Result` value
++error: used `expect()` on a `Result` value
+ --> $DIR/unwrap_expect_used.rs:32:5
+ |
+LL | a.expect("Hello world!");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Err`, it will panic
+
- error: used `unwrap_err()` on `a Result` value
++error: used `unwrap_err()` on a `Result` value
+ --> $DIR/unwrap_expect_used.rs:33:5
+ |
+LL | a.unwrap_err();
+ | ^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Ok`, it will panic
+
- error: used `expect_err()` on `a Result` value
++error: used `expect_err()` on a `Result` value
+ --> $DIR/unwrap_expect_used.rs:34:5
+ |
+LL | a.expect_err("Hello error!");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if this value is an `Ok`, it will panic
+
+error: aborting due to 6 previous errors
+
--- /dev/null
- #![warn(clippy::all)]
++#![warn(clippy::all, clippy::or_fun_call)]
+
+fn main() {
+ let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len();
+}
+
+fn new_lines() {
+ let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len();
+}
--- /dev/null
- // FIXME: applicable here
- Bad
+// run-rustfix
+
+#![warn(clippy::use_self)]
+#![allow(dead_code)]
+#![allow(clippy::should_implement_trait, clippy::boxed_local)]
+
+use std::ops::Mul;
+
+trait SelfTrait {
+ fn refs(p1: &Self) -> &Self;
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self;
+ fn mut_refs(p1: &mut Self) -> &mut Self;
+ fn nested(p1: Box<Self>, p2: (&u8, &Self));
+ fn vals(r: Self) -> Self;
+}
+
+#[derive(Default)]
+struct Bad;
+
+impl SelfTrait for Bad {
+ fn refs(p1: &Self) -> &Self {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Self) -> &mut Self {
+ p1
+ }
+
+ fn nested(_p1: Box<Self>, _p2: (&u8, &Self)) {}
+
+ fn vals(_: Self) -> Self {
+ Self::default()
+ }
+}
+
+impl Mul for Bad {
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self {
+ rhs
+ }
+}
+
+impl Clone for Bad {
+ fn clone(&self) -> Self {
++ Self
+ }
+}
+
+#[derive(Default)]
+struct Good;
+
+impl SelfTrait for Good {
+ fn refs(p1: &Self) -> &Self {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Self) -> &mut Self {
+ p1
+ }
+
+ fn nested(_p1: Box<Self>, _p2: (&u8, &Self)) {}
+
+ fn vals(_: Self) -> Self {
+ Self::default()
+ }
+}
+
+impl Mul for Good {
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self {
+ rhs
+ }
+}
+
+trait NameTrait {
+ fn refs(p1: &u8) -> &u8;
+ fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8;
+ fn mut_refs(p1: &mut u8) -> &mut u8;
+ fn nested(p1: Box<u8>, p2: (&u8, &u8));
+ fn vals(p1: u8) -> u8;
+}
+
+// Using `Self` instead of the type name is OK
+impl NameTrait for u8 {
+ fn refs(p1: &Self) -> &Self {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Self) -> &mut Self {
+ p1
+ }
+
+ fn nested(_p1: Box<Self>, _p2: (&Self, &Self)) {}
+
+ fn vals(_: Self) -> Self {
+ Self::default()
+ }
+}
+
++mod impl_in_macro {
++ macro_rules! parse_ip_impl {
++ // minimized from serde=1.0.118
++ ($ty:ty) => {
++ impl FooTrait for $ty {
++ fn new() -> Self {
++ <$ty>::bar()
++ }
++ }
++ };
++ }
++
++ struct Foo;
++
++ trait FooTrait {
++ fn new() -> Self;
++ }
++
++ impl Foo {
++ fn bar() -> Self {
++ Self
++ }
++ }
++ parse_ip_impl!(Foo); // Should not lint
++}
++
++mod full_path_replacement {
++ trait Error {
++ fn custom<T: std::fmt::Display>(_msg: T) -> Self;
++ }
++
++ impl Error for std::fmt::Error {
++ fn custom<T: std::fmt::Display>(_msg: T) -> Self {
++ Self // Should lint
++ }
++ }
++}
++
+fn main() {}
--- /dev/null
- // FIXME: applicable here
+// run-rustfix
+
+#![warn(clippy::use_self)]
+#![allow(dead_code)]
+#![allow(clippy::should_implement_trait, clippy::boxed_local)]
+
+use std::ops::Mul;
+
+trait SelfTrait {
+ fn refs(p1: &Self) -> &Self;
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self;
+ fn mut_refs(p1: &mut Self) -> &mut Self;
+ fn nested(p1: Box<Self>, p2: (&u8, &Self));
+ fn vals(r: Self) -> Self;
+}
+
+#[derive(Default)]
+struct Bad;
+
+impl SelfTrait for Bad {
+ fn refs(p1: &Bad) -> &Bad {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Bad) -> &mut Bad {
+ p1
+ }
+
+ fn nested(_p1: Box<Bad>, _p2: (&u8, &Bad)) {}
+
+ fn vals(_: Bad) -> Bad {
+ Bad::default()
+ }
+}
+
+impl Mul for Bad {
+ type Output = Bad;
+
+ fn mul(self, rhs: Bad) -> Bad {
+ rhs
+ }
+}
+
+impl Clone for Bad {
+ fn clone(&self) -> Self {
+ Bad
+ }
+}
+
+#[derive(Default)]
+struct Good;
+
+impl SelfTrait for Good {
+ fn refs(p1: &Self) -> &Self {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Self) -> &mut Self {
+ p1
+ }
+
+ fn nested(_p1: Box<Self>, _p2: (&u8, &Self)) {}
+
+ fn vals(_: Self) -> Self {
+ Self::default()
+ }
+}
+
+impl Mul for Good {
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self {
+ rhs
+ }
+}
+
+trait NameTrait {
+ fn refs(p1: &u8) -> &u8;
+ fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8;
+ fn mut_refs(p1: &mut u8) -> &mut u8;
+ fn nested(p1: Box<u8>, p2: (&u8, &u8));
+ fn vals(p1: u8) -> u8;
+}
+
+// Using `Self` instead of the type name is OK
+impl NameTrait for u8 {
+ fn refs(p1: &Self) -> &Self {
+ p1
+ }
+
+ fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self {
+ p1
+ }
+
+ fn mut_refs(p1: &mut Self) -> &mut Self {
+ p1
+ }
+
+ fn nested(_p1: Box<Self>, _p2: (&Self, &Self)) {}
+
+ fn vals(_: Self) -> Self {
+ Self::default()
+ }
+}
+
++mod impl_in_macro {
++ macro_rules! parse_ip_impl {
++ // minimized from serde=1.0.118
++ ($ty:ty) => {
++ impl FooTrait for $ty {
++ fn new() -> Self {
++ <$ty>::bar()
++ }
++ }
++ };
++ }
++
++ struct Foo;
++
++ trait FooTrait {
++ fn new() -> Self;
++ }
++
++ impl Foo {
++ fn bar() -> Self {
++ Self
++ }
++ }
++ parse_ip_impl!(Foo); // Should not lint
++}
++
++mod full_path_replacement {
++ trait Error {
++ fn custom<T: std::fmt::Display>(_msg: T) -> Self;
++ }
++
++ impl Error for std::fmt::Error {
++ fn custom<T: std::fmt::Display>(_msg: T) -> Self {
++ std::fmt::Error // Should lint
++ }
++ }
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 14 previous errors
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:21:18
+ |
+LL | fn refs(p1: &Bad) -> &Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+ |
+ = note: `-D clippy::use-self` implied by `-D warnings`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:21:27
+ |
+LL | fn refs(p1: &Bad) -> &Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:25:33
+ |
+LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:25:49
+ |
+LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:29:26
+ |
+LL | fn mut_refs(p1: &mut Bad) -> &mut Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:29:39
+ |
+LL | fn mut_refs(p1: &mut Bad) -> &mut Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:33:24
+ |
+LL | fn nested(_p1: Box<Bad>, _p2: (&u8, &Bad)) {}
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:33:42
+ |
+LL | fn nested(_p1: Box<Bad>, _p2: (&u8, &Bad)) {}
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:35:16
+ |
+LL | fn vals(_: Bad) -> Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:35:24
+ |
+LL | fn vals(_: Bad) -> Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:36:9
+ |
+LL | Bad::default()
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:41:19
+ |
+LL | type Output = Bad;
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:43:23
+ |
+LL | fn mul(self, rhs: Bad) -> Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self_trait.rs:43:31
+ |
+LL | fn mul(self, rhs: Bad) -> Bad {
+ | ^^^ help: use the applicable keyword: `Self`
+
++error: unnecessary structure name repetition
++ --> $DIR/use_self_trait.rs:50:9
++ |
++LL | Bad
++ | ^^^ help: use the applicable keyword: `Self`
++
++error: unnecessary structure name repetition
++ --> $DIR/use_self_trait.rs:147:13
++ |
++LL | std::fmt::Error // Should lint
++ | ^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self`
++
++error: aborting due to 16 previous errors
+
--- /dev/null
- fn test_indented_attr() {
- #![allow(clippy::almost_swapped)]
- use std::collections::HashSet;
-
- let _ = HashSet::<u32>::default();
+// run-rustfix
+// aux-build:proc_macro_derive.rs
+
++#![allow(unused)]
+#![warn(clippy::useless_attribute)]
+#![warn(unreachable_pub)]
+#![feature(rustc_private)]
+
+#![allow(dead_code)]
+#![cfg_attr(feature = "cargo-clippy", allow(dead_code))]
+#[rustfmt::skip]
+#[allow(unused_imports)]
+#[allow(unused_extern_crates)]
+#[macro_use]
+extern crate rustc_middle;
+
+#[macro_use]
+extern crate proc_macro_derive;
+
++fn test_indented_attr() {
++ #![allow(clippy::almost_swapped)]
++ use std::collections::HashSet;
++
++ let _ = HashSet::<u32>::default();
++}
++
+// don't lint on unused_import for `use` items
+#[allow(unused_imports)]
+use std::collections;
+
+// don't lint on unused for `use` items
+#[allow(unused)]
+use std::option;
+
+// don't lint on deprecated for `use` items
+mod foo {
+ #[deprecated]
+ pub struct Bar;
+}
+#[allow(deprecated)]
+pub use foo::Bar;
+
+// This should not trigger the lint. There's lint level definitions inside the external derive
+// that would trigger the useless_attribute lint.
+#[derive(DeriveSomething)]
+struct Baz;
+
+// don't lint on unreachable_pub for `use` items
+mod a {
+ mod b {
+ #[allow(dead_code)]
+ #[allow(unreachable_pub)]
+ pub struct C;
+ }
+
+ #[allow(unreachable_pub)]
+ pub use self::b::C;
+}
+
+// don't lint on clippy::wildcard_imports for `use` items
+#[allow(clippy::wildcard_imports)]
+pub use std::io::prelude::*;
+
+// don't lint on clippy::enum_glob_use for `use` items
+#[allow(clippy::enum_glob_use)]
+pub use std::cmp::Ordering::*;
+
+// don't lint on clippy::redundant_pub_crate
+mod c {
+ #[allow(clippy::redundant_pub_crate)]
+ pub(crate) struct S;
+}
+
++// https://github.com/rust-lang/rust-clippy/issues/7511
++pub mod split {
++ #[allow(clippy::module_name_repetitions)]
++ pub use regex::SplitN;
+}
+
++// https://github.com/rust-lang/rust-clippy/issues/8768
++#[allow(clippy::single_component_path_imports)]
++use regex;
++
+fn main() {
+ test_indented_attr();
+}
--- /dev/null
- fn test_indented_attr() {
- #[allow(clippy::almost_swapped)]
- use std::collections::HashSet;
-
- let _ = HashSet::<u32>::default();
+// run-rustfix
+// aux-build:proc_macro_derive.rs
+
++#![allow(unused)]
+#![warn(clippy::useless_attribute)]
+#![warn(unreachable_pub)]
+#![feature(rustc_private)]
+
+#[allow(dead_code)]
+#[cfg_attr(feature = "cargo-clippy", allow(dead_code))]
+#[rustfmt::skip]
+#[allow(unused_imports)]
+#[allow(unused_extern_crates)]
+#[macro_use]
+extern crate rustc_middle;
+
+#[macro_use]
+extern crate proc_macro_derive;
+
++fn test_indented_attr() {
++ #[allow(clippy::almost_swapped)]
++ use std::collections::HashSet;
++
++ let _ = HashSet::<u32>::default();
++}
++
+// don't lint on unused_import for `use` items
+#[allow(unused_imports)]
+use std::collections;
+
+// don't lint on unused for `use` items
+#[allow(unused)]
+use std::option;
+
+// don't lint on deprecated for `use` items
+mod foo {
+ #[deprecated]
+ pub struct Bar;
+}
+#[allow(deprecated)]
+pub use foo::Bar;
+
+// This should not trigger the lint. There's lint level definitions inside the external derive
+// that would trigger the useless_attribute lint.
+#[derive(DeriveSomething)]
+struct Baz;
+
+// don't lint on unreachable_pub for `use` items
+mod a {
+ mod b {
+ #[allow(dead_code)]
+ #[allow(unreachable_pub)]
+ pub struct C;
+ }
+
+ #[allow(unreachable_pub)]
+ pub use self::b::C;
+}
+
+// don't lint on clippy::wildcard_imports for `use` items
+#[allow(clippy::wildcard_imports)]
+pub use std::io::prelude::*;
+
+// don't lint on clippy::enum_glob_use for `use` items
+#[allow(clippy::enum_glob_use)]
+pub use std::cmp::Ordering::*;
+
+// don't lint on clippy::redundant_pub_crate
+mod c {
+ #[allow(clippy::redundant_pub_crate)]
+ pub(crate) struct S;
+}
+
++// https://github.com/rust-lang/rust-clippy/issues/7511
++pub mod split {
++ #[allow(clippy::module_name_repetitions)]
++ pub use regex::SplitN;
+}
+
++// https://github.com/rust-lang/rust-clippy/issues/8768
++#[allow(clippy::single_component_path_imports)]
++use regex;
++
+fn main() {
+ test_indented_attr();
+}
--- /dev/null
- --> $DIR/useless_attribute.rs:8:1
+error: useless lint attribute
- --> $DIR/useless_attribute.rs:9:1
++ --> $DIR/useless_attribute.rs:9:1
+ |
+LL | #[allow(dead_code)]
+ | ^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(dead_code)]`
+ |
+ = note: `-D clippy::useless-attribute` implied by `-D warnings`
+
+error: useless lint attribute
- --> $DIR/useless_attribute.rs:67:5
++ --> $DIR/useless_attribute.rs:10:1
+ |
+LL | #[cfg_attr(feature = "cargo-clippy", allow(dead_code))]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![cfg_attr(feature = "cargo-clippy", allow(dead_code)`
+
+error: useless lint attribute
++ --> $DIR/useless_attribute.rs:21:5
+ |
+LL | #[allow(clippy::almost_swapped)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(clippy::almost_swapped)]`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() {
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+#![allow(clippy::single_match_else)]
+
+use rustc_tools_util::VersionInfo;
+use std::fs;
+
+#[test]
- let clippy_lints_version = read_version("clippy_lints/Cargo.toml");
- let clippy_utils_version = read_version("clippy_utils/Cargo.toml");
++fn consistent_clippy_crate_versions() {
+ fn read_version(path: &str) -> String {
+ let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("error reading `{path}`: {e:?}"));
+ contents
+ .lines()
+ .filter_map(|l| l.split_once('='))
+ .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))
+ .unwrap_or_else(|| panic!("error finding version in `{path}`"))
+ .to_string()
+ }
+
+ // do not run this test inside the upstream rustc repo:
+ // https://github.com/rust-lang/rust-clippy/issues/6683
+ if option_env!("RUSTC_TEST_SUITE").is_some() {
+ return;
+ }
+
+ let clippy_version = read_version("Cargo.toml");
- assert_eq!(clippy_version, clippy_lints_version);
- assert_eq!(clippy_version, clippy_utils_version);
+
++ let paths = [
++ "declare_clippy_lint/Cargo.toml",
++ "clippy_lints/Cargo.toml",
++ "clippy_utils/Cargo.toml",
++ ];
++
++ for path in paths {
++ assert_eq!(clippy_version, read_version(path), "{path} version differs");
++ }
+}
+
+#[test]
+fn check_that_clippy_has_the_same_major_version_as_rustc() {
+ // do not run this test inside the upstream rustc repo:
+ // https://github.com/rust-lang/rust-clippy/issues/6683
+ if option_env!("RUSTC_TEST_SUITE").is_some() {
+ return;
+ }
+
+ let clippy_version = rustc_tools_util::get_version_info!();
+ let clippy_major = clippy_version.major;
+ let clippy_minor = clippy_version.minor;
+ let clippy_patch = clippy_version.patch;
+
+ // get the rustc version either from the rustc installed with the toolchain file or from
+ // `RUSTC_REAL` if Clippy is build in the Rust repo with `./x.py`.
+ let rustc = std::env::var("RUSTC_REAL").unwrap_or_else(|_| "rustc".to_string());
+ let rustc_version = String::from_utf8(
+ std::process::Command::new(rustc)
+ .arg("--version")
+ .output()
+ .expect("failed to run `rustc --version`")
+ .stdout,
+ )
+ .unwrap();
+ // extract "1 XX 0" from "rustc 1.XX.0-nightly (<commit> <date>)"
+ let vsplit: Vec<&str> = rustc_version
+ .split(' ')
+ .nth(1)
+ .unwrap()
+ .split('-')
+ .next()
+ .unwrap()
+ .split('.')
+ .collect();
+ match vsplit.as_slice() {
+ [rustc_major, rustc_minor, _rustc_patch] => {
+ // clippy 0.1.XX should correspond to rustc 1.XX.0
+ assert_eq!(clippy_major, 0); // this will probably stay the same for a long time
+ assert_eq!(
+ clippy_minor.to_string(),
+ *rustc_major,
+ "clippy minor version does not equal rustc major version"
+ );
+ assert_eq!(
+ clippy_patch.to_string(),
+ *rustc_minor,
+ "clippy patch version does not equal rustc minor version"
+ );
+ // do not check rustc_patch because when a stable-patch-release is made (like 1.50.2),
+ // we don't want our tests failing suddenly
+ },
+ _ => {
+ panic!("Failed to parse rustc version: {vsplit:?}");
+ },
+ };
+}